2 using Microsoft.Xna.Framework;
3 using Microsoft.Xna.Framework.Graphics;
4 using ReLogic.Graphics;
6 using System.Collections.Generic;
20 internal class ModHeader
28 public ModHeader(
string name,
Version version, byte[] hash,
bool signed) {
30 this.version = version;
36 public bool Matches(TmodFile mod) => name == mod.name && version == mod.version && hash.SequenceEqual(mod.hash);
37 public override string ToString() => name +
" v" + version;
40 internal class NetConfig
42 public string modname;
43 public string configname;
46 public NetConfig(
string modname,
string configname,
string json) {
47 this.modname = modname;
48 this.configname = configname;
53 public static bool AllowVanillaClients {
get;
internal set; }
54 internal static bool downloadModsFromServers =
true;
55 internal static bool onlyDownloadSignedMods =
false;
57 internal static bool[] isModdedClient =
new bool[256];
61 public static bool IsModdedClient(
int i) => isModdedClient[i];
63 public static Mod GetMod(
int netID) =>
64 netID >= 0 && netID < netMods.Length ? netMods[netID] : null;
66 public static int NetModCount => netMods.Length;
68 private static Queue<ModHeader> downloadQueue =
new Queue<ModHeader>();
69 internal static List<NetConfig> pendingConfigs =
new List<NetConfig>();
74 internal static void AssignNetIDs() {
76 for (
short i = 0; i < netMods.Length; i++)
80 internal static void Unload() {
82 if (!Main.dedServ && Main.netMode != 1)
83 AllowVanillaClients =
false;
86 internal static void SyncMods(
int clientIndex) {
87 var p =
new ModPacket(MessageID.SyncMods);
88 p.Write(AllowVanillaClients);
91 AddNoSyncDeps(syncMods);
93 p.Write(syncMods.Count);
94 foreach (var mod
in syncMods) {
96 p.Write(mod.Version.ToString());
97 p.Write(mod.File.hash);
98 p.Write(mod.File.ValidModBrowserSignature);
99 SendServerConfigs(p, mod);
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)) {
118 if (!
ConfigManager.Configs.TryGetValue(mod, out var configs)) {
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}");
129 p.Write(config.Name);
134 internal static void SyncClientMods(
BinaryReader reader) {
135 if (!SyncClientMods(reader, out
bool needsReload))
138 if (downloadQueue.Count > 0)
141 OnModsDownloaded(needsReload);
145 internal static bool SyncClientMods(
BinaryReader reader, out
bool needsReload) {
146 AllowVanillaClients = reader.ReadBoolean();
147 Logging.tML.Info($
"Server reports AllowVanillaClients set to {AllowVanillaClients}");
149 Main.statusText = Language.GetTextValue(
"tModLoader.MPSyncingMods");
151 var modFiles = ModOrganizer.FindMods();
153 downloadQueue.Clear();
154 pendingConfigs.Clear();
155 var syncSet =
new HashSet<string>();
156 var blockedList =
new List<ModHeader>();
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);
163 int configCount = reader.ReadInt32();
164 for (
int c = 0; c < configCount; c++)
165 pendingConfigs.Add(
new NetConfig(header.name, reader.ReadString(), reader.ReadString()));
167 var clientMod = clientMods.SingleOrDefault(m => m.Name == header.name);
168 if (clientMod != null && header.Matches(clientMod.File))
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;
181 if (localVersions.Length > 0)
182 header.path = localVersions[0].modFile.path;
184 if (downloadModsFromServers && (header.signed || !onlyDownloadSignedMods))
185 downloadQueue.Enqueue(header);
187 blockedList.Add(header);
190 foreach (var mod
in clientMods)
191 if (mod.Side ==
ModSide.Both && !syncSet.Contains(mod.Name)) {
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)
206 Interface.errorMessage.Show(msg, 0);
212 foreach (var pendingConfig
in pendingConfigs)
213 JsonConvert.PopulateObject(pendingConfig.json,
ConfigManager.GetConfig(pendingConfig),
ConfigManager.serializerSettingsCompact);
219 foreach (var pendingConfig
in pendingConfigs)
228 downloadingMod = downloadQueue.Dequeue();
229 downloadingFile = null;
230 var p =
new ModPacket(MessageID.ModFile);
231 p.Write(downloadingMod.name);
238 internal const int CHUNK_SIZE = 16384;
239 internal static void SendMod(
string modName,
int toClient) {
241 var path = mod.File.path;
242 var fs = File.OpenRead(path);
245 var p =
new ModPacket(MessageID.ModFile);
246 p.Write(mod.DisplayName);
251 var buf =
new byte[CHUNK_SIZE];
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);
264 if (downloadingMod == null)
268 if (downloadingFile == null) {
269 Interface.progress.Show(displayText: reader.ReadString(), cancel: CancelDownload);
272 downloadingLength = reader.ReadInt64();
273 downloadingFile =
new FileStream(downloadingMod.path, FileMode.Create);
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;
281 if (downloadingFile.Position == downloadingLength) {
282 downloadingFile.Close();
283 var mod =
new TmodFile(downloadingMod.path);
284 using (mod.Open()) { }
286 if (!downloadingMod.Matches(mod))
287 throw new Exception(Language.GetTextValue(
"tModLoader.MPErrorModHashMismatch"));
289 if (downloadingMod.signed && onlyDownloadSignedMods && !mod.ValidModBrowserSignature)
290 throw new Exception(Language.GetTextValue(
"tModLoader.MPErrorModNotSigned"));
293 if (downloadQueue.Count > 0) DownloadNextMod();
294 else OnModsDownloaded(
true);
299 downloadingFile?.Close();
300 File.Delete(downloadingMod.path);
303 Logging.tML.Error(
"Unknown error during mod sync", exc2);
306 var msg = Language.GetTextValue(
"tModLoader.MPErrorModDownloadError", downloadingMod.name);
308 Interface.errorMessage.Show(msg + e, 0);
310 Netplay.disconnect =
true;
311 downloadingMod = null;
317 downloadingFile?.Close();
318 File.Delete(downloadingMod.path);
321 downloadingMod = null;
322 Netplay.disconnect =
true;
328 ModLoader.OnSuccessfulLoad = NetReload();
334 downloadingMod = null;
342 internal static bool NetReloadActive;
343 internal static Action NetReload() {
345 var path = Main.ActivePlayerFileData.Path;
346 var isCloudSave = Main.ActivePlayerFileData.IsCloudSave;
347 NetReloadActive =
true;
349 NetReloadActive =
false;
351 Player.GetFileData(path, isCloudSave).SetAsActive();
353 Main.player[Main.myPlayer].hostile =
false;
354 Main.clientPlayer = (Player)Main.player[Main.myPlayer].clientClone();
357 OnModsDownloaded(
false);
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)
369 p.Write(Player.MaxBuffs);
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);
385 netMods = list.ToArray();
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.";
398 internal static bool ReadUnderflowBypass =
false;
399 internal static void HandleModPacket(
BinaryReader reader,
int whoAmI,
int length) {
400 if (netMods == null) {
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);
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");
417 if (Main.netMode == 1) {
419 rxDataType[id] += length;
423 internal static bool HijackGetData(ref byte messageType, ref
BinaryReader reader,
int playerNumber) {
424 if (netMods == null) {
428 bool hijacked =
false;
429 long readerPos = reader.BaseStream.Position;
430 long biggestReaderPos = readerPos;
432 if (mod.HijackGetData(ref messageType, ref reader, playerNumber)) {
434 biggestReaderPos = Math.Max(reader.BaseStream.Position, biggestReaderPos);
436 reader.BaseStream.Position = readerPos;
439 reader.BaseStream.Position = biggestReaderPos;
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;
447 hijacked |= mod.
HijackSendData(whoAmI, msgType, remoteClient, ignoreClient, text, number, number2, number3, number4, number5, number6, number7);
461 rxMsgType =
new int[netMods.Length];
462 rxDataType =
new int[netMods.Length];
463 txMsgType =
new int[netMods.Length];
464 txDataType =
new int[netMods.Length];
467 internal static void ResetNetDiag() {
468 if (netMods == null || Main.netMode == 2)
return;
469 for (
int i = 0; i < netMods.Length; i++) {
477 internal static void DrawModDiagnoseNet() {
478 if (netMods == null)
return;
481 for (
int j = -1; j < netMods.Length; j++) {
482 int i = j + Main.maxMsg + 2;
485 int xAdjust = i / 50;
487 y += (i - xAdjust * 50) * 13;
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);
492 Main.spriteBatch.DrawString(Main.fontMouseText, netMods[j].
Name,
new Vector2(x, y), Color.White, 0f,
default(Vector2), scale, SpriteEffects.None, 0f);
494 Main.spriteBatch.DrawString(Main.fontMouseText, rxMsgType[j].ToString(),
new Vector2(x, y), Color.White, 0f,
default(Vector2), scale, SpriteEffects.None, 0f);
496 Main.spriteBatch.DrawString(Main.fontMouseText, rxDataType[j].ToString(),
new Vector2(x, y), Color.White, 0f,
default(Vector2), scale, SpriteEffects.None, 0f);
498 Main.spriteBatch.DrawString(Main.fontMouseText, txMsgType[j].ToString(),
new Vector2(x, y), Color.White, 0f,
default(Vector2), scale, SpriteEffects.None, 0f);
500 Main.spriteBatch.DrawString(Main.fontMouseText, txDataType[j].ToString(),
new Vector2(x, y), Color.White, 0f,
default(Vector2), scale, SpriteEffects.None, 0f);
static void CancelDownload()
static ModHeader downloadingMod
static void AddNoSyncDeps(List< Mod > syncMods)
static void SendServerConfigs(ModPacket p, Mod mod)
This serves as the central class which loads mods. It contains many static fields and methods related...
This is where all ModWorld hooks are gathered and called.
virtual void Close()
Close is called before Unload, and may be called at any time when mod unloading is imminent (such as ...
static void SetupDiagnostics()
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...
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-...
static void OnModsDownloaded(bool needsReload)
This class inherits from BinaryWriter. This means that you can use all of its writing functions to se...
static FileStream downloadingFile
This serves as the central class from which item-related functions are carried out. It also stores a list of mod items by ID.
static void ReadNetIDs(BinaryReader reader)
virtual void OnChanged()
This hook is called anytime new config values have been set and are ready to take effect...
virtual string Name
Stores the name of the mod. This name serves as the mod's identification, and also helps with saving ...
Mod is an abstract class that you will override. It serves as a central place from which the mod's co...
static Mod GetMod(string name)
Gets the instance of the Mod with the specified name.
ModSide
A ModSide enum defines how mods are synced between clients and servers. You can set your mod's ModSid...
ConfigScope
Each ModConfig class has a different scope. Failure to use the correct mode will lead to bugs...
static void DownloadNextMod()
static long downloadingLength