Terraria ModLoader  0.11.6.2
A framework for 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 && !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  ModLoader.OnSuccessfulLoad = NetReload();
328  ModLoader.Reload();
329  return;
330  }
331 
332  downloadingMod = null;
333  netMods = null;
334  foreach (var mod in ModLoader.Mods)
335  mod.netID = -1;
336 
337  new ModPacket(MessageID.SyncMods).Send();
338  }
339 
340  internal static Action NetReload() {
341  // Main.ActivePlayerFileData gets cleared during reload
342  var path = Main.ActivePlayerFileData.Path;
343  var isCloudSave = Main.ActivePlayerFileData.IsCloudSave;
344  return () => {
345  // re-select the current player
346  Player.GetFileData(path, isCloudSave).SetAsActive();
347  //from Netplay.ClientLoopSetup
348  Main.player[Main.myPlayer].hostile = false;
349  Main.clientPlayer = (Player)Main.player[Main.myPlayer].clientClone();
350 
351  Main.menuMode = 10;
352  OnModsDownloaded(false);
353  };
354  }
355 
356  internal static void SendNetIDs(int toClient) {
357  var p = new ModPacket(MessageID.ModPacket);
358  p.Write(netMods.Length);
359  foreach (var mod in netMods)
360  p.Write(mod.Name);
361 
362  ItemLoader.WriteNetGlobalOrder(p);
363  WorldHooks.WriteNetWorldOrder(p);
364  p.Write(Player.MaxBuffs);
365 
366  p.Send(toClient);
367  }
368 
369  private static void ReadNetIDs(BinaryReader reader) {
370  var mods = ModLoader.Mods;
371  var list = new List<Mod>();
372  var n = reader.ReadInt32();
373  for (short i = 0; i < n; i++) {
374  var name = reader.ReadString();
375  var mod = mods.SingleOrDefault(m => m.Name == name);
376  list.Add(mod);
377  if (mod != null) //nosync mod that doesn't exist on the client
378  mod.netID = i;
379  }
380  netMods = list.ToArray();
381  SetupDiagnostics();
382 
383  ItemLoader.ReadNetGlobalOrder(reader);
384  WorldHooks.ReadNetWorldOrder(reader);
385  int serverMaxBuffs = reader.ReadInt32();
386  if (serverMaxBuffs != Player.MaxBuffs) {
387  Netplay.disconnect = true;
388  Main.statusText = $"The server expects Player.MaxBuffs of {serverMaxBuffs}\nbut this client reports {Player.MaxBuffs}.\nSome mod is behaving poorly.";
389  }
390  }
391 
392  // 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.
393  internal static bool ReadUnderflowBypass = false; // Remove by 0.11.7
394  internal static void HandleModPacket(BinaryReader reader, int whoAmI, int length) {
395  if (netMods == null) {
396  ReadNetIDs(reader);
397  return;
398  }
399 
400  var id = NetModCount < 256 ? reader.ReadByte() : reader.ReadInt16();
401  int start = (int)reader.BaseStream.Position;
402  int actualLength = length - 1 - (NetModCount < 256 ? 1 : 2);
403  try {
404  ReadUnderflowBypass = false;
405  GetMod(id)?.HandlePacket(reader, whoAmI);
406  if (!ReadUnderflowBypass && reader.BaseStream.Position - start != actualLength) {
407  throw new IOException($"Read underflow {reader.BaseStream.Position - start} of {actualLength} bytes caused by {GetMod(id).Name} in HandlePacket");
408  }
409  }
410  catch { }
411 
412  if (Main.netMode == 1) {
413  rxMsgType[id]++;
414  rxDataType[id] += length;
415  }
416  }
417 
418  internal static bool HijackGetData(ref byte messageType, ref BinaryReader reader, int playerNumber) {
419  if (netMods == null) {
420  return false;
421  }
422 
423  bool hijacked = false;
424  long readerPos = reader.BaseStream.Position;
425  long biggestReaderPos = readerPos;
426  foreach (var mod in ModLoader.Mods) {
427  if (mod.HijackGetData(ref messageType, ref reader, playerNumber)) {
428  hijacked = true;
429  biggestReaderPos = Math.Max(reader.BaseStream.Position, biggestReaderPos);
430  }
431  reader.BaseStream.Position = readerPos;
432  }
433  if (hijacked) {
434  reader.BaseStream.Position = biggestReaderPos;
435  }
436  return hijacked;
437  }
438 
439  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) {
440  bool hijacked = false;
441  foreach (Mod mod in ModLoader.Mods) {
442  hijacked |= mod.HijackSendData(whoAmI, msgType, remoteClient, ignoreClient, text, number, number2, number3, number4, number5, number6, number7);
443  }
444  return hijacked;
445  }
446 
447  // Mirror of Main class network diagnostic fields, but mod specific.
448  // 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
449  // Currently we only update these on client
450  public static int[] rxMsgType;
451  public static int[] rxDataType;
452  public static int[] txMsgType;
453  public static int[] txDataType;
454 
455  private static void SetupDiagnostics() {
456  rxMsgType = new int[netMods.Length];
457  rxDataType = new int[netMods.Length];
458  txMsgType = new int[netMods.Length];
459  txDataType = new int[netMods.Length];
460  }
461 
462  internal static void ResetNetDiag() {
463  if (netMods == null || Main.netMode == 2) return;
464  for (int i = 0; i < netMods.Length; i++) {
465  rxMsgType[i] = 0;
466  rxDataType[i] = 0;
467  txMsgType[i] = 0;
468  txDataType[i] = 0;
469  }
470  }
471 
472  internal static void DrawModDiagnoseNet() {
473  if (netMods == null) return;
474  float scale = 0.7f;
475 
476  for (int j = -1; j < netMods.Length; j++) {
477  int i = j + Main.maxMsg + 2;
478  int x = 200;
479  int y = 120;
480  int xAdjust = i / 50;
481  x += xAdjust * 400;
482  y += (i - xAdjust * 50) * 13;
483  if (j == -1) {
484  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);
485  continue;
486  }
487  Main.spriteBatch.DrawString(Main.fontMouseText, netMods[j].Name, new Vector2(x, y), Color.White, 0f, default(Vector2), scale, SpriteEffects.None, 0f);
488  x += 120;
489  Main.spriteBatch.DrawString(Main.fontMouseText, rxMsgType[j].ToString(), new Vector2(x, y), Color.White, 0f, default(Vector2), scale, SpriteEffects.None, 0f);
490  x += 30;
491  Main.spriteBatch.DrawString(Main.fontMouseText, rxDataType[j].ToString(), new Vector2(x, y), Color.White, 0f, default(Vector2), scale, SpriteEffects.None, 0f);
492  x += 80;
493  Main.spriteBatch.DrawString(Main.fontMouseText, txMsgType[j].ToString(), new Vector2(x, y), Color.White, 0f, default(Vector2), scale, SpriteEffects.None, 0f);
494  x += 30;
495  Main.spriteBatch.DrawString(Main.fontMouseText, txDataType[j].ToString(), new Vector2(x, y), Color.White, 0f, default(Vector2), scale, SpriteEffects.None, 0f);
496  }
497  }
498  }
499 }
static int[] rxDataType
Definition: ModNet.cs:451
static int[] txDataType
Definition: ModNet.cs:453
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:26
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:455
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:369
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:86
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:452
static long downloadingLength
Definition: ModNet.cs:72
static int[] rxMsgType
Definition: ModNet.cs:450