tModLoader v0.11.8.9
A mod to make and play Terraria mods
ModNet.cs
Go to the documentation of this file.
1using Newtonsoft.Json;
2using Microsoft.Xna.Framework;
3using Microsoft.Xna.Framework.Graphics;
4using ReLogic.Graphics;
5using System;
6using System.Collections.Generic;
7using System.IO;
8using System.Linq;
9using Terraria.ID;
10using Terraria.Localization;
12using Terraria.ModLoader.Core;
13using Terraria.ModLoader.UI.DownloadManager;
14using Terraria.ModLoader.UI;
15
16namespace Terraria.ModLoader
17{
18 public static class ModNet
19 {
20 internal class ModHeader
21 {
22 public string name;
23 public Version version;
24 public byte[] hash;
25 public bool signed;
26 public string path;
27
28 public ModHeader(string name, Version version, byte[] hash, bool signed) {
29 this.name = name;
30 this.version = version;
31 this.hash = hash;
32 this.signed = signed;
33 path = Path.Combine(ModLoader.ModPath, name + ".tmod");
34 }
35
36 public bool Matches(TmodFile mod) => name == mod.name && version == mod.version && hash.SequenceEqual(mod.hash);
37 public override string ToString() => name + " v" + version;
38 }
39
40 internal class NetConfig
41 {
42 public string modname;
43 public string configname;
44 public string json;
45
46 public NetConfig(string modname, string configname, string json) {
47 this.modname = modname;
48 this.configname = configname;
49 this.json = json;
50 }
51 }
52
53 public static bool AllowVanillaClients { get; internal set; }
54 internal static bool downloadModsFromServers = true;
55 internal static bool onlyDownloadSignedMods = false;
56
57 internal static bool[] isModdedClient = new bool[256];
58
59 private static Mod[] netMods;
60
61 public static bool IsModdedClient(int i) => isModdedClient[i];
62
63 public static Mod GetMod(int netID) =>
64 netID >= 0 && netID < netMods.Length ? netMods[netID] : null;
65
66 public static int NetModCount => netMods.Length;
67
68 private static Queue<ModHeader> downloadQueue = new Queue<ModHeader>();
69 internal static List<NetConfig> pendingConfigs = new List<NetConfig>();
70 private static ModHeader downloadingMod;
71 private static FileStream downloadingFile;
72 private static long downloadingLength;
73
74 internal static void AssignNetIDs() {
75 netMods = ModLoader.Mods.Where(mod => mod.Side != ModSide.Server).ToArray();
76 for (short i = 0; i < netMods.Length; i++)
77 netMods[i].netID = i;
78 }
79
80 internal static void Unload() {
81 netMods = null;
82 if (!Main.dedServ && Main.netMode != 1) //disable vanilla client compatibility restrictions when reloading on a client
83 AllowVanillaClients = false;
84 }
85
86 internal static void SyncMods(int clientIndex) {
87 var p = new ModPacket(MessageID.SyncMods);
88 p.Write(AllowVanillaClients);
89
90 var syncMods = ModLoader.Mods.Where(mod => mod.Side == ModSide.Both).ToList();
91 AddNoSyncDeps(syncMods);
92
93 p.Write(syncMods.Count);
94 foreach (var mod in syncMods) { // We only sync ServerSide configs for ModSide.Both. ModSide.Server can have
95 p.Write(mod.Name);
96 p.Write(mod.Version.ToString());
97 p.Write(mod.File.hash);
98 p.Write(mod.File.ValidModBrowserSignature);
99 SendServerConfigs(p, mod);
100 }
101
102 p.Send(clientIndex);
103 }
104
105 private static void AddNoSyncDeps(List<Mod> syncMods) {
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)) {
110 syncMods.Add(dep);
111 queue.Enqueue(dep);
112 }
113 }
114 }
115 }
116
117 private static void SendServerConfigs(ModPacket p, Mod mod) {
118 if (!ConfigManager.Configs.TryGetValue(mod, out var configs)) {
119 p.Write(0);
120 return;
121 }
122
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}");
128
129 p.Write(config.Name);
130 p.Write(json);
131 }
132 }
133
134 internal static void SyncClientMods(BinaryReader reader) {
135 if (!SyncClientMods(reader, out bool needsReload))
136 return; //error syncing can't connect to server
137
138 if (downloadQueue.Count > 0)
140 else
141 OnModsDownloaded(needsReload);
142 }
143
144 // This method is split so that the local variables aren't held by the GC when reloading
145 internal static bool SyncClientMods(BinaryReader reader, out bool needsReload) {
146 AllowVanillaClients = reader.ReadBoolean();
147 Logging.tML.Info($"Server reports AllowVanillaClients set to {AllowVanillaClients}");
148
149 Main.statusText = Language.GetTextValue("tModLoader.MPSyncingMods");
150 var clientMods = ModLoader.Mods;
151 var modFiles = ModOrganizer.FindMods();
152 needsReload = false;
153 downloadQueue.Clear();
154 pendingConfigs.Clear();
155 var syncSet = new HashSet<string>();
156 var blockedList = new List<ModHeader>();
157
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);
162
163 int configCount = reader.ReadInt32();
164 for (int c = 0; c < configCount; c++)
165 pendingConfigs.Add(new NetConfig(header.name, reader.ReadString(), reader.ReadString()));
166
167 var clientMod = clientMods.SingleOrDefault(m => m.Name == header.name);
168 if (clientMod != null && header.Matches(clientMod.File))
169 continue;
170
171 needsReload = true;
172
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;
177 continue;
178 }
179
180 // overwrite an existing version of the mod if there is one
181 if (localVersions.Length > 0)
182 header.path = localVersions[0].modFile.path;
183
184 if (downloadModsFromServers && (header.signed || !onlyDownloadSignedMods))
185 downloadQueue.Enqueue(header);
186 else
187 blockedList.Add(header);
188 }
189
190 foreach (var mod in clientMods)
191 if (mod.Side == ModSide.Both && !syncSet.Contains(mod.Name)) {
192 ModLoader.DisableMod(mod.Name);
193 needsReload = true;
194 }
195
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)
203 msg += "\n " + mod;
204
205 Logging.tML.Warn(msg);
206 Interface.errorMessage.Show(msg, 0);
207 return false;
208 }
209
210 // ready to connect, apply configs. Config manager will apply the configs on reload automatically
211 if (!needsReload) {
212 foreach (var pendingConfig in pendingConfigs)
213 JsonConvert.PopulateObject(pendingConfig.json, ConfigManager.GetConfig(pendingConfig), ConfigManager.serializerSettingsCompact);
214
215 if (ConfigManager.AnyModNeedsReload()) {
216 needsReload = true;
217 }
218 else {
219 foreach (var pendingConfig in pendingConfigs)
220 ConfigManager.GetConfig(pendingConfig).OnChanged();
221 }
222 }
223
224 return true;
225 }
226
227 private static void DownloadNextMod() {
228 downloadingMod = downloadQueue.Dequeue();
229 downloadingFile = null;
230 var p = new ModPacket(MessageID.ModFile);
231 p.Write(downloadingMod.name);
232 p.Send();
233 }
234
235 // Start sending the mod to the connecting client
236 // First, send the initial mod name and length of the file stream
237 // so the client knows what to expect
238 internal const int CHUNK_SIZE = 16384;
239 internal static void SendMod(string modName, int toClient) {
240 var mod = ModLoader.GetMod(modName);
241 var path = mod.File.path;
242 var fs = File.OpenRead(path);
243 {
244 //send file length
245 var p = new ModPacket(MessageID.ModFile);
246 p.Write(mod.DisplayName);
247 p.Write(fs.Length);
248 p.Send(toClient);
249 }
250
251 var buf = new byte[CHUNK_SIZE];
252 int count;
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);
256 p.Send(toClient);
257 }
258
259 fs.Close();
260 }
261
262 // Receive a mod when connecting to server
263 internal static void ReceiveMod(BinaryReader reader) {
264 if (downloadingMod == null)
265 return;
266
267 try {
268 if (downloadingFile == null) {
269 Interface.progress.Show(displayText: reader.ReadString(), cancel: CancelDownload);
270
272 downloadingLength = reader.ReadInt64();
273 downloadingFile = new FileStream(downloadingMod.path, FileMode.Create);
274 return;
275 }
276
277 var bytes = reader.ReadBytes((int)Math.Min(downloadingLength - downloadingFile.Position, CHUNK_SIZE));
278 downloadingFile.Write(bytes, 0, bytes.Length);
279 Interface.progress.Progress = downloadingFile.Position / (float)downloadingLength;
280
281 if (downloadingFile.Position == downloadingLength) {
282 downloadingFile.Close();
283 var mod = new TmodFile(downloadingMod.path);
284 using (mod.Open()) { }
285
286 if (!downloadingMod.Matches(mod))
287 throw new Exception(Language.GetTextValue("tModLoader.MPErrorModHashMismatch"));
288
289 if (downloadingMod.signed && onlyDownloadSignedMods && !mod.ValidModBrowserSignature)
290 throw new Exception(Language.GetTextValue("tModLoader.MPErrorModNotSigned"));
291
292 ModLoader.EnableMod(mod.name);
293 if (downloadQueue.Count > 0) DownloadNextMod();
294 else OnModsDownloaded(true);
295 }
296 }
297 catch (Exception e) {
298 try {
299 downloadingFile?.Close();
300 File.Delete(downloadingMod.path);
301 }
302 catch (Exception exc2) {
303 Logging.tML.Error("Unknown error during mod sync", exc2);
304 }
305
306 var msg = Language.GetTextValue("tModLoader.MPErrorModDownloadError", downloadingMod.name);
307 Logging.tML.Error(msg, e);
308 Interface.errorMessage.Show(msg + e, 0);
309
310 Netplay.disconnect = true;
311 downloadingMod = null;
312 }
313 }
314
315 private static void CancelDownload() {
316 try {
317 downloadingFile?.Close();
318 File.Delete(downloadingMod.path);
319 }
320 catch { }
321 downloadingMod = null;
322 Netplay.disconnect = true;
323 }
324
325 private static void OnModsDownloaded(bool needsReload) {
326 if (needsReload) {
327 Main.netMode = 0;
328 ModLoader.OnSuccessfulLoad = NetReload();
329 ModLoader.Reload();
330 return;
331 }
332
333 Main.netMode = 1;
334 downloadingMod = null;
335 netMods = null;
336 foreach (var mod in ModLoader.Mods)
337 mod.netID = -1;
338
339 new ModPacket(MessageID.SyncMods).Send();
340 }
341
342 internal static bool NetReloadActive;
343 internal static Action NetReload() {
344 // Main.ActivePlayerFileData gets cleared during reload
345 var path = Main.ActivePlayerFileData.Path;
346 var isCloudSave = Main.ActivePlayerFileData.IsCloudSave;
347 NetReloadActive = true;
348 return () => {
349 NetReloadActive = false;
350 // re-select the current player
351 Player.GetFileData(path, isCloudSave).SetAsActive();
352 //from Netplay.ClientLoopSetup
353 Main.player[Main.myPlayer].hostile = false;
354 Main.clientPlayer = (Player)Main.player[Main.myPlayer].clientClone();
355
356 Main.menuMode = 10;
357 OnModsDownloaded(false);
358 };
359 }
360
361 internal static void SendNetIDs(int toClient) {
362 var p = new ModPacket(MessageID.ModPacket);
363 p.Write(netMods.Length);
364 foreach (var mod in netMods)
365 p.Write(mod.Name);
366
367 ItemLoader.WriteNetGlobalOrder(p);
368 WorldHooks.WriteNetWorldOrder(p);
369 p.Write(Player.MaxBuffs);
370
371 p.Send(toClient);
372 }
373
374 private static void ReadNetIDs(BinaryReader reader) {
375 var mods = ModLoader.Mods;
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);
381 list.Add(mod);
382 if (mod != null) //nosync mod that doesn't exist on the client
383 mod.netID = i;
384 }
385 netMods = list.ToArray();
387
388 ItemLoader.ReadNetGlobalOrder(reader);
389 WorldHooks.ReadNetWorldOrder(reader);
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.";
394 }
395 }
396
397 // Some mods have expressed concern about read underflow exceptions conflicting with their ModPacket design, they can use reflection to set this bool as a bandaid until they fix their code.
398 internal static bool ReadUnderflowBypass = false; // Remove by 0.11.7
399 internal static void HandleModPacket(BinaryReader reader, int whoAmI, int length) {
400 if (netMods == null) {
401 ReadNetIDs(reader);
402 return;
403 }
404
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);
408 try {
409 ReadUnderflowBypass = false;
410 GetMod(id)?.HandlePacket(reader, whoAmI);
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");
413 }
414 }
415 catch { }
416
417 if (Main.netMode == 1) {
418 rxMsgType[id]++;
419 rxDataType[id] += length;
420 }
421 }
422
423 internal static bool HijackGetData(ref byte messageType, ref BinaryReader reader, int playerNumber) {
424 if (netMods == null) {
425 return false;
426 }
427
428 bool hijacked = false;
429 long readerPos = reader.BaseStream.Position;
430 long biggestReaderPos = readerPos;
431 foreach (var mod in ModLoader.Mods) {
432 if (mod.HijackGetData(ref messageType, ref reader, playerNumber)) {
433 hijacked = true;
434 biggestReaderPos = Math.Max(reader.BaseStream.Position, biggestReaderPos);
435 }
436 reader.BaseStream.Position = readerPos;
437 }
438 if (hijacked) {
439 reader.BaseStream.Position = biggestReaderPos;
440 }
441 return hijacked;
442 }
443
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;
446 foreach (Mod mod in ModLoader.Mods) {
447 hijacked |= mod.HijackSendData(whoAmI, msgType, remoteClient, ignoreClient, text, number, number2, number3, number4, number5, number6, number7);
448 }
449 return hijacked;
450 }
451
452 // Mirror of Main class network diagnostic fields, but mod specific.
453 // Potential improvements: separate page from vanilla messageIDs, track automatic/ModWorld/etc sends per class or mod, sort by most active, moving average, NetStats console command in ModLoaderMod
454 // Currently we only update these on client
455 public static int[] rxMsgType;
456 public static int[] rxDataType;
457 public static int[] txMsgType;
458 public static int[] txDataType;
459
460 private static void SetupDiagnostics() {
461 rxMsgType = new int[netMods.Length];
462 rxDataType = new int[netMods.Length];
463 txMsgType = new int[netMods.Length];
464 txDataType = new int[netMods.Length];
465 }
466
467 internal static void ResetNetDiag() {
468 if (netMods == null || Main.netMode == 2) return;
469 for (int i = 0; i < netMods.Length; i++) {
470 rxMsgType[i] = 0;
471 rxDataType[i] = 0;
472 txMsgType[i] = 0;
473 txDataType[i] = 0;
474 }
475 }
476
477 internal static void DrawModDiagnoseNet() {
478 if (netMods == null) return;
479 float scale = 0.7f;
480
481 for (int j = -1; j < netMods.Length; j++) {
482 int i = j + Main.maxMsg + 2;
483 int x = 200;
484 int y = 120;
485 int xAdjust = i / 50;
486 x += xAdjust * 400;
487 y += (i - xAdjust * 50) * 13;
488 if (j == -1) {
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);
490 continue;
491 }
492 Main.spriteBatch.DrawString(Main.fontMouseText, netMods[j].Name, new Vector2(x, y), Color.White, 0f, default(Vector2), scale, SpriteEffects.None, 0f);
493 x += 120;
494 Main.spriteBatch.DrawString(Main.fontMouseText, rxMsgType[j].ToString(), new Vector2(x, y), Color.White, 0f, default(Vector2), scale, SpriteEffects.None, 0f);
495 x += 30;
496 Main.spriteBatch.DrawString(Main.fontMouseText, rxDataType[j].ToString(), new Vector2(x, y), Color.White, 0f, default(Vector2), scale, SpriteEffects.None, 0f);
497 x += 80;
498 Main.spriteBatch.DrawString(Main.fontMouseText, txMsgType[j].ToString(), new Vector2(x, y), Color.White, 0f, default(Vector2), scale, SpriteEffects.None, 0f);
499 x += 30;
500 Main.spriteBatch.DrawString(Main.fontMouseText, txDataType[j].ToString(), new Vector2(x, y), Color.White, 0f, default(Vector2), scale, SpriteEffects.None, 0f);
501 }
502 }
503 }
504}
System.Version Version
Definition: ModLoader.cs:21
virtual void OnChanged()
This hook is called anytime new config values have been set and are ready to take effect....
Definition: ModConfig.cs:43
This serves as the central class from which item-related functions are carried out....
Definition: ItemLoader.cs:22
Mod is an abstract class that you will override. It serves as a central place from which the mod's co...
Definition: Mod.cs:25
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 ...
Definition: ModHooks.cs:44
virtual string Name
Stores the name of the mod. This name serves as the mod's identification, and also helps with saving ...
Definition: Mod.cs:42
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...
Definition: ModHooks.cs:61
virtual void Close()
Close is called before Unload, and may be called at any time when mod unloading is imminent (such as ...
Definition: Mod.cs:136
This serves as the central class which loads mods. It contains many static fields and methods related...
Definition: ModLoader.cs:29
static Mod GetMod(string name)
Gets the instance of the Mod with the specified name.
Definition: ModLoader.cs:90
static int[] rxDataType
Definition: ModNet.cs:456
static int[] rxMsgType
Definition: ModNet.cs:455
static int[] txDataType
Definition: ModNet.cs:458
static ModHeader downloadingMod
Definition: ModNet.cs:70
static Mod GetMod(int netID)
static void ReadNetIDs(BinaryReader reader)
Definition: ModNet.cs:374
static void SendServerConfigs(ModPacket p, Mod mod)
Definition: ModNet.cs:117
static void CancelDownload()
Definition: ModNet.cs:315
static int[] txMsgType
Definition: ModNet.cs:457
static void SetupDiagnostics()
Definition: ModNet.cs:460
static void AddNoSyncDeps(List< Mod > syncMods)
Definition: ModNet.cs:105
static long downloadingLength
Definition: ModNet.cs:72
static bool IsModdedClient(int i)
static void OnModsDownloaded(bool needsReload)
Definition: ModNet.cs:325
static bool AllowVanillaClients
Definition: ModNet.cs:53
static void DownloadNextMod()
Definition: ModNet.cs:227
static Mod[] netMods
Definition: ModNet.cs:59
static Queue< ModHeader > downloadQueue
Definition: ModNet.cs:68
static FileStream downloadingFile
Definition: ModNet.cs:71
static int NetModCount
Definition: ModNet.cs:66
This class inherits from BinaryWriter. This means that you can use all of its writing functions to se...
Definition: ModPacket.cs:14
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-...
Definition: ModPacket.cs:27
This is where all ModWorld hooks are gathered and called.
Definition: WorldHooks.cs:13
ConfigScope
Each ModConfig class has a different scope. Failure to use the correct mode will lead to bugs.
Definition: ModConfig.cs:92
ModSide
A ModSide enum defines how mods are synced between clients and servers. You can set your mod's ModSid...
Definition: ModSide.cs:5