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