2using Microsoft.Xna.Framework;
3using Microsoft.Xna.Framework.Graphics;
6using System.Collections.Generic;
10using Terraria.Localization;
12using Terraria.ModLoader.Core;
13using Terraria.ModLoader.UI.DownloadManager;
14using Terraria.ModLoader.UI;
20 internal class ModHeader
28 public ModHeader(
string name,
Version version,
byte[] hash,
bool signed) {
30 this.version = version;
36 public bool Matches(TmodFile mod) => name == mod.name && version == mod.version && hash.SequenceEqual(mod.hash);
37 public override string ToString() => name +
" v" + version;
40 internal class NetConfig
42 public string modname;
43 public string configname;
46 public NetConfig(
string modname,
string configname,
string json) {
47 this.modname = modname;
48 this.configname = configname;
54 internal static bool downloadModsFromServers =
true;
55 internal static bool onlyDownloadSignedMods =
false;
57 internal static bool[] isModdedClient =
new bool[256];
64 netID >= 0 && netID < netMods.Length ?
netMods[netID] :
null;
69 internal static List<NetConfig> pendingConfigs =
new List<NetConfig>();
74 internal static void AssignNetIDs() {
76 for (
short i = 0; i <
netMods.Length; i++)
80 internal static void Unload() {
82 if (!Main.dedServ && Main.netMode != 1)
86 internal static void SyncMods(
int clientIndex) {
87 var p =
new ModPacket(MessageID.SyncMods);
93 p.Write(syncMods.Count);
94 foreach (var mod
in syncMods) {
96 p.Write(mod.Version.ToString());
97 p.Write(mod.File.hash);
98 p.Write(mod.File.ValidModBrowserSignature);
106 var queue =
new Queue<Mod>(syncMods.Where(m => m.Side ==
ModSide.Both));
107 while (queue.Count > 0) {
108 foreach (var dep
in AssemblyManager.GetDependencies(queue.Dequeue())) {
109 if (dep.Side ==
ModSide.NoSync && !syncMods.Contains(dep)) {
118 if (!
ConfigManager.Configs.TryGetValue(mod, out var configs)) {
123 var serverConfigs = configs.Where(x => x.Mode ==
ConfigScope.ServerSide).ToArray();
124 p.Write(serverConfigs.Length);
125 foreach (var config
in serverConfigs) {
126 string json = JsonConvert.SerializeObject(config,
ConfigManager.serializerSettingsCompact);
127 Logging.Terraria.Info($
"Sending Server Config {config.mod.Name} {config.Name}: {json}");
129 p.Write(config.Name);
134 internal static void SyncClientMods(
BinaryReader reader) {
135 if (!SyncClientMods(reader, out
bool needsReload))
145 internal static bool SyncClientMods(
BinaryReader reader, out
bool needsReload) {
147 Logging.tML.Info($
"Server reports AllowVanillaClients set to {AllowVanillaClients}");
149 Main.statusText = Language.GetTextValue(
"tModLoader.MPSyncingMods");
151 var modFiles = ModOrganizer.FindMods();
154 pendingConfigs.Clear();
155 var syncSet =
new HashSet<string>();
156 var blockedList =
new List<ModHeader>();
158 int n = reader.ReadInt32();
159 for (
int i = 0; i < n; i++) {
160 var header =
new ModHeader(reader.ReadString(),
new Version(reader.ReadString()), reader.ReadBytes(20), reader.ReadBoolean());
161 syncSet.Add(header.name);
163 int configCount = reader.ReadInt32();
164 for (
int c = 0; c < configCount; c++)
165 pendingConfigs.Add(
new NetConfig(header.name, reader.ReadString(), reader.ReadString()));
167 var clientMod = clientMods.SingleOrDefault(m => m.Name == header.name);
168 if (clientMod !=
null && header.Matches(clientMod.File))
173 var localVersions = modFiles.Where(m => m.Name == header.name).ToArray();
174 var matching = Array.Find(localVersions, mod => header.Matches(mod.modFile));
175 if (matching !=
null) {
176 matching.Enabled =
true;
181 if (localVersions.Length > 0)
182 header.path = localVersions[0].modFile.path;
184 if (downloadModsFromServers && (header.signed || !onlyDownloadSignedMods))
187 blockedList.Add(header);
190 foreach (var mod
in clientMods)
191 if (mod.Side ==
ModSide.Both && !syncSet.Contains(mod.Name)) {
196 if (blockedList.Count > 0) {
197 var msg = Language.GetTextValue(
"tModLoader.MPServerModsCantDownload");
198 msg += downloadModsFromServers
199 ? Language.GetTextValue(
"tModLoader.MPServerModsCantDownloadReasonSigned")
200 : Language.GetTextValue(
"tModLoader.MPServerModsCantDownloadReasonAutomaticDownloadDisabled");
201 msg +=
".\n" + Language.GetTextValue(
"tModLoader.MPServerModsCantDownloadChangeSettingsHint") +
"\n";
202 foreach (var mod
in blockedList)
206 Interface.errorMessage.Show(msg, 0);
212 foreach (var pendingConfig
in pendingConfigs)
213 JsonConvert.PopulateObject(pendingConfig.json,
ConfigManager.GetConfig(pendingConfig),
ConfigManager.serializerSettingsCompact);
219 foreach (var pendingConfig
in pendingConfigs)
230 var p =
new ModPacket(MessageID.ModFile);
238 internal const int CHUNK_SIZE = 16384;
239 internal static void SendMod(
string modName,
int toClient) {
241 var path = mod.File.path;
242 var fs = File.OpenRead(path);
245 var p =
new ModPacket(MessageID.ModFile);
246 p.Write(mod.DisplayName);
251 var buf =
new byte[CHUNK_SIZE];
253 while ((count = fs.Read(buf, 0, buf.Length)) > 0) {
254 var p =
new ModPacket(MessageID.ModFile, CHUNK_SIZE + 3);
255 p.Write(buf, 0, count);
269 Interface.progress.Show(displayText: reader.ReadString(), cancel:
CancelDownload);
279 Interface.progress.Progress = downloadingFile.Position / (float)
downloadingLength;
284 using (mod.Open()) { }
287 throw new Exception(Language.GetTextValue(
"tModLoader.MPErrorModHashMismatch"));
289 if (
downloadingMod.signed && onlyDownloadSignedMods && !mod.ValidModBrowserSignature)
290 throw new Exception(Language.GetTextValue(
"tModLoader.MPErrorModNotSigned"));
303 Logging.tML.Error(
"Unknown error during mod sync", exc2);
306 var msg = Language.GetTextValue(
"tModLoader.MPErrorModDownloadError",
downloadingMod.name);
308 Interface.errorMessage.Show(msg + e, 0);
310 Netplay.disconnect =
true;
322 Netplay.disconnect =
true;
328 ModLoader.OnSuccessfulLoad = NetReload();
342 internal static bool NetReloadActive;
343 internal static Action NetReload() {
345 var path = Main.ActivePlayerFileData.Path;
346 var isCloudSave = Main.ActivePlayerFileData.IsCloudSave;
347 NetReloadActive =
true;
349 NetReloadActive =
false;
351 Player.GetFileData(path, isCloudSave).SetAsActive();
353 Main.player[Main.myPlayer].hostile =
false;
354 Main.clientPlayer = (Player)Main.player[Main.myPlayer].clientClone();
361 internal static void SendNetIDs(
int toClient) {
362 var p =
new ModPacket(MessageID.ModPacket);
369 p.Write(Player.MaxBuffs);
376 var list =
new List<Mod>();
377 var n = reader.ReadInt32();
378 for (
short i = 0; i < n; i++) {
379 var name = reader.ReadString();
380 var mod = mods.SingleOrDefault(m => m.Name == name);
390 int serverMaxBuffs = reader.ReadInt32();
391 if (serverMaxBuffs != Player.MaxBuffs) {
392 Netplay.disconnect =
true;
393 Main.statusText = $
"The server expects Player.MaxBuffs of {serverMaxBuffs}\nbut this client reports {Player.MaxBuffs}.\nSome mod is behaving poorly.";
398 internal static bool ReadUnderflowBypass =
false;
399 internal static void HandleModPacket(
BinaryReader reader,
int whoAmI,
int length) {
405 var
id =
NetModCount < 256 ? reader.ReadByte() : reader.ReadInt16();
406 int start = (int)reader.BaseStream.Position;
407 int actualLength = length - 1 - (
NetModCount < 256 ? 1 : 2);
409 ReadUnderflowBypass =
false;
411 if (!ReadUnderflowBypass && reader.BaseStream.Position - start != actualLength) {
412 throw new IOException($
"Read underflow {reader.BaseStream.Position - start} of {actualLength} bytes caused by {GetMod(id).Name} in HandlePacket");
417 if (Main.netMode == 1) {
423 internal static bool HijackGetData(ref
byte messageType, ref
BinaryReader reader,
int playerNumber) {
428 bool hijacked =
false;
429 long readerPos = reader.BaseStream.Position;
430 long biggestReaderPos = readerPos;
432 if (mod.HijackGetData(ref messageType, ref reader, playerNumber)) {
434 biggestReaderPos = Math.Max(reader.BaseStream.Position, biggestReaderPos);
436 reader.BaseStream.Position = readerPos;
439 reader.BaseStream.Position = biggestReaderPos;
444 internal static bool HijackSendData(
int whoAmI,
int msgType,
int remoteClient,
int ignoreClient, NetworkText text,
int number,
float number2,
float number3,
float number4,
int number5,
int number6,
int number7) {
445 bool hijacked =
false;
447 hijacked |= mod.
HijackSendData(whoAmI, msgType, remoteClient, ignoreClient, text, number, number2, number3, number4, number5, number6, number7);
467 internal static void ResetNetDiag() {
468 if (
netMods ==
null || Main.netMode == 2)
return;
469 for (
int i = 0; i <
netMods.Length; i++) {
477 internal static void DrawModDiagnoseNet() {
481 for (
int j = -1; j <
netMods.Length; j++) {
482 int i = j + Main.maxMsg + 2;
485 int xAdjust = i / 50;
487 y += (i - xAdjust * 50) * 13;
489 Main.spriteBatch.DrawString(Main.fontMouseText,
"Mod Received(#, Bytes) Sent(#, Bytes)",
new Vector2((
float)x, (
float)y), Color.White, 0f,
default(Vector2), scale, SpriteEffects.None, 0f);
492 Main.spriteBatch.DrawString(Main.fontMouseText,
netMods[j].
Name,
new Vector2(x, y), Color.White, 0f,
default(Vector2), scale, SpriteEffects.None, 0f);
494 Main.spriteBatch.DrawString(Main.fontMouseText,
rxMsgType[j].ToString(),
new Vector2(x, y), Color.White, 0f,
default(Vector2), scale, SpriteEffects.None, 0f);
496 Main.spriteBatch.DrawString(Main.fontMouseText,
rxDataType[j].ToString(),
new Vector2(x, y), Color.White, 0f,
default(Vector2), scale, SpriteEffects.None, 0f);
498 Main.spriteBatch.DrawString(Main.fontMouseText,
txMsgType[j].ToString(),
new Vector2(x, y), Color.White, 0f,
default(Vector2), scale, SpriteEffects.None, 0f);
500 Main.spriteBatch.DrawString(Main.fontMouseText,
txDataType[j].ToString(),
new Vector2(x, y), Color.White, 0f,
default(Vector2), scale, SpriteEffects.None, 0f);
virtual void OnChanged()
This hook is called anytime new config values have been set and are ready to take effect....
This serves as the central class from which item-related functions are carried out....
Mod is an abstract class that you will override. It serves as a central place from which the mod's co...
virtual void HandlePacket(BinaryReader reader, int whoAmI)
Called whenever a net message / packet is received from a client (if this is a server) or the server ...
virtual string Name
Stores the name of the mod. This name serves as the mod's identification, and also helps with saving ...
virtual bool HijackSendData(int whoAmI, int msgType, int remoteClient, int ignoreClient, NetworkText text, int number, float number2, float number3, float number4, int number5, int number6, int number7)
Hijacks the send data method. Only use if you absolutely know what you are doing. If any hooks return...
virtual void Close()
Close is called before Unload, and may be called at any time when mod unloading is imminent (such as ...
This serves as the central class which loads mods. It contains many static fields and methods related...
static Mod GetMod(string name)
Gets the instance of the Mod with the specified name.
static ModHeader downloadingMod
static Mod GetMod(int netID)
static void ReadNetIDs(BinaryReader reader)
static void SendServerConfigs(ModPacket p, Mod mod)
static void CancelDownload()
static void SetupDiagnostics()
static void AddNoSyncDeps(List< Mod > syncMods)
static long downloadingLength
static bool IsModdedClient(int i)
static void OnModsDownloaded(bool needsReload)
static bool AllowVanillaClients
static void DownloadNextMod()
static Queue< ModHeader > downloadQueue
static FileStream downloadingFile
This class inherits from BinaryWriter. This means that you can use all of its writing functions to se...
void Send(int toClient=-1, int ignoreClient=-1)
Sends all the information you've written between client and server. If the toClient parameter is non-...
This is where all ModWorld hooks are gathered and called.
ConfigScope
Each ModConfig class has a different scope. Failure to use the correct mode will lead to bugs.
ModSide
A ModSide enum defines how mods are synced between clients and servers. You can set your mod's ModSid...