Terraria ModLoader  0.11.7.8
A mod to make and play Terraria mods
ModNet.cs
Go to the documentation of this file.
1 using Newtonsoft.Json;
2 using Microsoft.Xna.Framework;
3 using Microsoft.Xna.Framework.Graphics;
4 using ReLogic.Graphics;
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using Terraria.ID;
10 using Terraria.Localization;
12 using Terraria.ModLoader.Core;
13 using Terraria.ModLoader.UI.DownloadManager;
14 using Terraria.ModLoader.UI;
15 
16 namespace 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)
139  DownloadNextMod();
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 
271  ModLoader.GetMod(downloadingMod.name)?.Close();
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();
386  SetupDiagnostics();
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 }
static int[] rxDataType
Definition: ModNet.cs:456
static int[] txDataType
Definition: ModNet.cs:458
static void CancelDownload()
Definition: ModNet.cs:315
static ModHeader downloadingMod
Definition: ModNet.cs:70
static void AddNoSyncDeps(List< Mod > syncMods)
Definition: ModNet.cs:105
static void SendServerConfigs(ModPacket p, Mod mod)
Definition: ModNet.cs:117
This serves as the central class which loads mods. It contains many static fields and methods related...
Definition: ModLoader.cs:28
This is where all ModWorld hooks are gathered and called.
Definition: WorldHooks.cs:12
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
static void SetupDiagnostics()
Definition: ModNet.cs:460
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
void Send(int toClient=-1, int ignoreClient=-1)
Sends all the information you&#39;ve written between client and server. If the toClient parameter is non-...
Definition: ModPacket.cs:27
static void OnModsDownloaded(bool needsReload)
Definition: ModNet.cs:325
This class inherits from BinaryWriter. This means that you can use all of its writing functions to se...
Definition: ModPacket.cs:13
static FileStream downloadingFile
Definition: ModNet.cs:71
This serves as the central class from which item-related functions are carried out. It also stores a list of mod items by ID.
Definition: ItemLoader.cs:21
static void ReadNetIDs(BinaryReader reader)
Definition: ModNet.cs:374
virtual void OnChanged()
This hook is called anytime new config values have been set and are ready to take effect...
Definition: ModConfig.cs:43
virtual string Name
Stores the name of the mod. This name serves as the mod&#39;s identification, and also helps with saving ...
Definition: Mod.cs:42
Mod is an abstract class that you will override. It serves as a central place from which the mod&#39;s co...
Definition: Mod.cs:24
static Mod GetMod(string name)
Gets the instance of the Mod with the specified name.
Definition: ModLoader.cs:90
System.Version Version
Definition: ModLoader.cs:21
ModSide
A ModSide enum defines how mods are synced between clients and servers. You can set your mod&#39;s ModSid...
Definition: ModSide.cs:4
ConfigScope
Each ModConfig class has a different scope. Failure to use the correct mode will lead to bugs...
Definition: ModConfig.cs:91
static Mod[] netMods
Definition: ModNet.cs:59
static void DownloadNextMod()
Definition: ModNet.cs:227
static int[] txMsgType
Definition: ModNet.cs:457
static long downloadingLength
Definition: ModNet.cs:72
static int[] rxMsgType
Definition: ModNet.cs:455