Terraria ModLoader  0.11.1
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 
15 namespace Terraria.ModLoader
16 {
17  public static class ModNet
18  {
19  internal class ModHeader
20  {
21  public string name;
22  public Version version;
23  public byte[] hash;
24  public bool signed;
25  public string path;
26 
27  public ModHeader(string name, Version version, byte[] hash, bool signed) {
28  this.name = name;
29  this.version = version;
30  this.hash = hash;
31  this.signed = signed;
32  path = Path.Combine(ModLoader.ModPath, name + ".tmod");
33  }
34 
35  public bool Matches(TmodFile mod) => name == mod.name && version == mod.version && hash.SequenceEqual(mod.hash);
36  public override string ToString() => name + " v" + version;
37  }
38 
39  internal class NetConfig
40  {
41  public string modname;
42  public string configname;
43  public string json;
44 
45  public NetConfig(string modname, string configname, string json) {
46  this.modname = modname;
47  this.configname = configname;
48  this.json = json;
49  }
50  }
51 
52  public static bool AllowVanillaClients { get; internal set; }
53  internal static bool downloadModsFromServers = true;
54  internal static bool onlyDownloadSignedMods = false;
55 
56  internal static bool[] isModdedClient = new bool[256];
57 
58  private static Mod[] netMods;
59 
60  public static bool IsModdedClient(int i) => isModdedClient[i];
61 
62  public static Mod GetMod(int netID) =>
63  netID >= 0 && netID < netMods.Length ? netMods[netID] : null;
64 
65  public static int NetModCount => netMods.Length;
66 
67  private static Queue<ModHeader> downloadQueue = new Queue<ModHeader>();
68  internal static List<NetConfig> pendingConfigs = new List<NetConfig>();
69 
70  internal static void AssignNetIDs() {
71  netMods = ModLoader.Mods.Where(mod => mod.Side != ModSide.Server).ToArray();
72  for (short i = 0; i < netMods.Length; i++)
73  netMods[i].netID = i;
74  }
75 
76  internal static void Unload() {
77  netMods = null;
78  if (!Main.dedServ && Main.netMode != 1) //disable vanilla client compatibility restrictions when reloading on a client
79  AllowVanillaClients = false;
80  }
81 
82  internal static void SyncMods(int clientIndex) {
83  var p = new ModPacket(MessageID.SyncMods);
84  p.Write(AllowVanillaClients);
85 
86  var syncMods = ModLoader.Mods.Where(mod => mod.Side == ModSide.Both).ToList();
87  AddNoSyncDeps(syncMods);
88 
89  p.Write(syncMods.Count);
90  foreach (var mod in syncMods) { // We only sync ServerSide configs for ModSide.Both. ModSide.Server can have
91  p.Write(mod.Name);
92  p.Write(mod.Version.ToString());
93  p.Write(mod.File.hash);
94  p.Write(mod.File.ValidModBrowserSignature);
95  SendServerConfigs(p, mod);
96  }
97 
98  p.Send(clientIndex);
99  }
100 
101  private static void AddNoSyncDeps(List<Mod> syncMods) {
102  var queue = new Queue<Mod>(syncMods.Where(m => m.Side == ModSide.Both));
103  while (queue.Count > 0) {
104  foreach (var dep in AssemblyManager.GetDependencies(queue.Dequeue())) {
105  if (dep.Side == ModSide.NoSync && !syncMods.Contains(dep)) {
106  syncMods.Add(dep);
107  queue.Enqueue(dep);
108  }
109  }
110  }
111  }
112 
113  private static void SendServerConfigs(ModPacket p, Mod mod) {
114  if (!ConfigManager.Configs.TryGetValue(mod, out var configs)) {
115  p.Write(0);
116  return;
117  }
118 
119  var serverConfigs = configs.Where(x => x.Mode == ConfigScope.ServerSide).ToArray();
120  p.Write(serverConfigs.Length);
121  foreach (var config in serverConfigs) {
122  string json = JsonConvert.SerializeObject(config, ConfigManager.serializerSettingsCompact);
123  Logging.Terraria.Info($"Sending Server Config {config.Name}: {json}");
124 
125  p.Write(config.Name);
126  p.Write(json);
127  }
128  }
129 
130  internal static void SyncClientMods(BinaryReader reader) {
131  if (!SyncClientMods(reader, out bool needsReload))
132  return; //error syncing can't connect to server
133 
134  if (downloadQueue.Count > 0)
135  DownloadNextMod();
136  else
137  OnModsDownloaded(needsReload);
138  }
139 
140  // This method is split so that the local variables aren't held by the GC when reloading
141  internal static bool SyncClientMods(BinaryReader reader, out bool needsReload) {
142  AllowVanillaClients = reader.ReadBoolean();
143  Logging.tML.Info($"Server reports AllowVanillaClients set to {AllowVanillaClients}");
144 
145  Main.statusText = Language.GetTextValue("tModLoader.MPSyncingMods");
146  var clientMods = ModLoader.Mods;
147  var modFiles = ModOrganizer.FindMods();
148  needsReload = false;
149  downloadQueue.Clear();
150  pendingConfigs.Clear();
151  var syncSet = new HashSet<string>();
152  var blockedList = new List<ModHeader>();
153 
154  int n = reader.ReadInt32();
155  for (int i = 0; i < n; i++) {
156  var header = new ModHeader(reader.ReadString(), new Version(reader.ReadString()), reader.ReadBytes(20), reader.ReadBoolean());
157  syncSet.Add(header.name);
158 
159  int configCount = reader.ReadInt32();
160  for (int c = 0; c < configCount; c++)
161  pendingConfigs.Add(new NetConfig(header.name, reader.ReadString(), reader.ReadString()));
162 
163  var clientMod = clientMods.SingleOrDefault(m => m.Name == header.name);
164  if (clientMod != null && header.Matches(clientMod.File))
165  continue;
166 
167  needsReload = true;
168 
169  var localVersions = modFiles.Where(m => m.Name == header.name).ToArray();
170  var matching = Array.Find(localVersions, mod => header.Matches(mod.modFile));
171  if (matching != null) {
172  matching.Enabled = true;
173  continue;
174  }
175 
176  // overwrite an existing version of the mod if there is one
177  if (localVersions.Length > 0)
178  header.path = localVersions[0].modFile.path;
179 
180  if (downloadModsFromServers && (header.signed || !onlyDownloadSignedMods))
181  downloadQueue.Enqueue(header);
182  else
183  blockedList.Add(header);
184  }
185 
186  foreach (var mod in clientMods)
187  if (mod.Side == ModSide.Both && !syncSet.Contains(mod.Name)) {
188  ModLoader.DisableMod(mod.Name);
189  needsReload = true;
190  }
191 
192  if (blockedList.Count > 0) {
193  var msg = Language.GetTextValue("tModLoader.MPServerModsCantDownload");
194  msg += downloadModsFromServers
195  ? Language.GetTextValue("tModLoader.MPServerModsCantDownloadReasonSigned")
196  : Language.GetTextValue("tModLoader.MPServerModsCantDownloadReasonAutomaticDownloadDisabled");
197  msg += ".\n" + Language.GetTextValue("tModLoader.MPServerModsCantDownloadChangeSettingsHint") + "\n";
198  foreach (var mod in blockedList)
199  msg += "\n " + mod;
200 
201  Logging.tML.Warn(msg);
202  Interface.errorMessage.Show(msg, 0);
203  return false;
204  }
205 
206  // ready to connect, apply configs. Config manager will apply the configs on reload automatically
207  if (!needsReload) {
208  foreach (var pendingConfig in pendingConfigs)
209  JsonConvert.PopulateObject(pendingConfig.json, ConfigManager.GetConfig(pendingConfig), ConfigManager.serializerSettingsCompact);
210 
211  if (ConfigManager.AnyModNeedsReload()) {
212  needsReload = true;
213  }
214  else {
215  foreach (var pendingConfig in pendingConfigs)
216  ConfigManager.GetConfig(pendingConfig).OnChanged();
217  }
218  }
219 
220  return true;
221  }
222 
223  // Set the next downloading mod
224  private static ModHeader downloadingHeader;
225  private static void DownloadNextMod() {
226  downloadingHeader = downloadQueue.Dequeue();
227  var p = new ModPacket(MessageID.ModFile);
228  p.Write(downloadingHeader.name);
229  p.Send();
230  }
231 
232  // Start sending the mod to the connecting client
233  // First, send the initial mod name and length of the file stream
234  // so the client knows what to expect
235  internal const int CHUNK_SIZE = 16384;
236  internal static void SendMod(BinaryReader reader, int toClient) {
237  string modName = reader.ReadString();
238 
239  var mod = ModLoader.GetMod(modName);
240  var path = mod.File.path;
241  using (var fs = File.OpenRead(path)) {
242  {
243  //send mod name and file length
244  var p = new ModPacket(MessageID.ModFile);
245  p.Write(mod.DisplayName);
246  p.Write(fs.Length);
247  p.Send(toClient);
248  }
249  // start sending the file using a buffer
250  var buf = new byte[CHUNK_SIZE];
251  int count;
252  while ((count = fs.Read(buf, 0, buf.Length)) > 0) {
253  var p = new ModPacket(MessageID.ModFile, CHUNK_SIZE + 3);
254  p.Write(buf, 0, count);
255  p.Send(toClient);
256  }
257  }
258  }
259 
260  private static StreamingDownloadRequest _currentRequest;
261 
262  // Receive a mod when connecting to server
263  internal static void ReceiveMod(BinaryReader reader) {
264 
265  // We have no request yet, but we are ready to request a mod for download
266  if (_currentRequest == null) {
267  // Create this request and enqueue it in the download manager
268  _currentRequest = new StreamingDownloadRequest(reader.ReadString(), downloadingHeader.path, reader.ReadInt64(), downloadingHeader,
269  onComplete: () => {
270  if (downloadQueue.Count > 0)
271  DownloadNextMod();
272  else
273  OnModsDownloaded(true);
274  },
275  onCancel: () => _currentRequest = null
276  );
277 
278  Interface.downloadManager.EnqueueRequest(_currentRequest);
279  Main.menuMode = Interface.downloadManagerID;
280  return;
281  }
282 
283  // Read the currently streamed mod
284  if (_currentRequest.Receive(reader)) {
285  // When we reached download completion, complete the request
286  _currentRequest.Complete();
287  _currentRequest = null;
288  }
289  }
290 
291  private static void OnModsDownloaded(bool needsReload) {
292  if (needsReload) {
293  ModLoader.OnSuccessfulLoad = NetReload();
294  ModLoader.Reload();
295  return;
296  }
297 
298  netMods = null;
299  foreach (var mod in ModLoader.Mods)
300  mod.netID = -1;
301 
302  new ModPacket(MessageID.SyncMods).Send();
303  }
304 
305  internal static Action NetReload() {
306  // Main.ActivePlayerFileData gets cleared during reload
307  var path = Main.ActivePlayerFileData.Path;
308  var isCloudSave = Main.ActivePlayerFileData.IsCloudSave;
309  return () => {
310  // re-select the current player
311  Player.GetFileData(path, isCloudSave).SetAsActive();
312  //from Netplay.ClientLoopSetup
313  Main.player[Main.myPlayer].hostile = false;
314  Main.clientPlayer = (Player)Main.player[Main.myPlayer].clientClone();
315 
316  Main.menuMode = 10;
317  OnModsDownloaded(false);
318  };
319  }
320 
321  internal static void SendNetIDs(int toClient) {
322  var p = new ModPacket(MessageID.ModPacket);
323  p.Write(netMods.Length);
324  foreach (var mod in netMods)
325  p.Write(mod.Name);
326 
327  ItemLoader.WriteNetGlobalOrder(p);
328  WorldHooks.WriteNetWorldOrder(p);
329 
330  p.Send(toClient);
331  }
332 
333  private static void ReadNetIDs(BinaryReader reader) {
334  var mods = ModLoader.Mods;
335  var list = new List<Mod>();
336  var n = reader.ReadInt32();
337  for (short i = 0; i < n; i++) {
338  var name = reader.ReadString();
339  var mod = mods.SingleOrDefault(m => m.Name == name);
340  list.Add(mod);
341  if (mod != null) //nosync mod that doesn't exist on the client
342  mod.netID = i;
343  }
344  netMods = list.ToArray();
345  SetupDiagnostics();
346 
347  ItemLoader.ReadNetGlobalOrder(reader);
348  WorldHooks.ReadNetWorldOrder(reader);
349  }
350 
351  internal static void HandleModPacket(BinaryReader reader, int whoAmI, int length) {
352  if (netMods == null) {
353  ReadNetIDs(reader);
354  return;
355  }
356 
357  var id = NetModCount < 256 ? reader.ReadByte() : reader.ReadInt16();
358  GetMod(id)?.HandlePacket(reader, whoAmI);
359 
360  if (Main.netMode == 1) {
361  rxMsgType[id]++;
362  rxDataType[id] += length;
363  }
364  }
365 
366  internal static bool HijackGetData(ref byte messageType, ref BinaryReader reader, int playerNumber) {
367  if (netMods == null) {
368  return false;
369  }
370 
371  bool hijacked = false;
372  long readerPos = reader.BaseStream.Position;
373  long biggestReaderPos = readerPos;
374  foreach (var mod in ModLoader.Mods) {
375  if (mod.HijackGetData(ref messageType, ref reader, playerNumber)) {
376  hijacked = true;
377  biggestReaderPos = Math.Max(reader.BaseStream.Position, biggestReaderPos);
378  }
379  reader.BaseStream.Position = readerPos;
380  }
381  if (hijacked) {
382  reader.BaseStream.Position = biggestReaderPos;
383  }
384  return hijacked;
385  }
386 
387  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) {
388  bool hijacked = false;
389  foreach (Mod mod in ModLoader.Mods) {
390  hijacked |= mod.HijackSendData(whoAmI, msgType, remoteClient, ignoreClient, text, number, number2, number3, number4, number5, number6, number7);
391  }
392  return hijacked;
393  }
394 
395  // Mirror of Main class network diagnostic fields, but mod specific.
396  // 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
397  // Currently we only update these on client
398  public static int[] rxMsgType;
399  public static int[] rxDataType;
400  public static int[] txMsgType;
401  public static int[] txDataType;
402 
403  private static void SetupDiagnostics() {
404  rxMsgType = new int[netMods.Length];
405  rxDataType = new int[netMods.Length];
406  txMsgType = new int[netMods.Length];
407  txDataType = new int[netMods.Length];
408  }
409 
410  internal static void ResetNetDiag() {
411  if (netMods == null || Main.netMode == 2) return;
412  for (int i = 0; i < netMods.Length; i++) {
413  rxMsgType[i] = 0;
414  rxDataType[i] = 0;
415  txMsgType[i] = 0;
416  txDataType[i] = 0;
417  }
418  }
419 
420  internal static void DrawModDiagnoseNet() {
421  if (netMods == null) return;
422  float scale = 0.7f;
423 
424  for (int j = -1; j < netMods.Length; j++) {
425  int i = j + Main.maxMsg + 2;
426  int x = 200;
427  int y = 120;
428  int xAdjust = i / 50;
429  x += xAdjust * 400;
430  y += (i - xAdjust * 50) * 13;
431  if (j == -1) {
432  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);
433  continue;
434  }
435  Main.spriteBatch.DrawString(Main.fontMouseText, netMods[j].Name, new Vector2(x, y), Color.White, 0f, default(Vector2), scale, SpriteEffects.None, 0f);
436  x += 120;
437  Main.spriteBatch.DrawString(Main.fontMouseText, rxMsgType[j].ToString(), new Vector2(x, y), Color.White, 0f, default(Vector2), scale, SpriteEffects.None, 0f);
438  x += 30;
439  Main.spriteBatch.DrawString(Main.fontMouseText, rxDataType[j].ToString(), new Vector2(x, y), Color.White, 0f, default(Vector2), scale, SpriteEffects.None, 0f);
440  x += 80;
441  Main.spriteBatch.DrawString(Main.fontMouseText, txMsgType[j].ToString(), new Vector2(x, y), Color.White, 0f, default(Vector2), scale, SpriteEffects.None, 0f);
442  x += 30;
443  Main.spriteBatch.DrawString(Main.fontMouseText, txDataType[j].ToString(), new Vector2(x, y), Color.White, 0f, default(Vector2), scale, SpriteEffects.None, 0f);
444  }
445  }
446  }
447 }
static int [] rxDataType
Definition: ModNet.cs:399
static int [] txDataType
Definition: ModNet.cs:401
static void AddNoSyncDeps(List< Mod > syncMods)
Definition: ModNet.cs:101
static void SendServerConfigs(ModPacket p, Mod mod)
Definition: ModNet.cs:113
static StreamingDownloadRequest _currentRequest
Definition: ModNet.cs:260
This serves as the central class which loads mods. It contains many static fields and methods related...
Definition: ModLoader.cs:24
This is where all ModWorld hooks are gathered and called.
Definition: WorldHooks.cs:12
static void SetupDiagnostics()
Definition: ModNet.cs:403
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:291
This class inherits from BinaryWriter. This means that you can use all of its writing functions to se...
Definition: ModPacket.cs:13
static ModHeader downloadingHeader
Definition: ModNet.cs:224
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:333
virtual void OnChanged()
This hook is called anytime new config values have been set and are ready to take effect...
Definition: ModConfig.cs:38
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:41
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:23
static Mod GetMod(string name)
Gets the instance of the Mod with the specified name.
Definition: ModLoader.cs:80
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:86
static Mod [] netMods
Definition: ModNet.cs:58
static void DownloadNextMod()
Definition: ModNet.cs:225
static int [] txMsgType
Definition: ModNet.cs:400
static int [] rxMsgType
Definition: ModNet.cs:398