tModLoader v0.11.8.9
A mod to make and play Terraria mods
ModLoader.cs
Go to the documentation of this file.
1using Microsoft.Xna.Framework.Audio;
2using Microsoft.Xna.Framework.Graphics;
3using ReLogic.OS;
4using System;
5using System.Collections.Generic;
6using System.Diagnostics;
7using System.Linq;
8using System.Linq.Expressions;
9using System.Reflection;
10using System.Security.Cryptography;
11using System.Threading;
12using System.Threading.Tasks;
13using System.Windows.Forms;
14using Steamworks;
15using Terraria.Localization;
17using Terraria.ModLoader.Core;
18using Terraria.ModLoader.Default;
19using Terraria.ModLoader.Engine;
20using Terraria.ModLoader.UI;
21using Version = System.Version;
22
23namespace Terraria.ModLoader
24{
28 public static class ModLoader
29 {
30 public static readonly Version version = new Version(0, 11, 8, 9);
31 // Stores the most recent version of tModLoader launched. Can be used for migration.
33 // public static bool ShowWhatsNew;
35
36 public static readonly string branchName = "";
37 // beta > 0 cannot publish to mod browser
38 public static readonly int beta = 0;
39
40 // SteamApps.GetCurrentBetaName(out string betaName, 100) ? betaName :
41 public static readonly string versionedName = $"tModLoader v{version}" +
42 (branchName.Length == 0 ? "" : $" {branchName}") +
43 (beta == 0 ? "" : $" Beta {beta}");
44
45 public static readonly string versionTag = $"v{version}" +
46 (branchName.Length == 0 ? "" : $"-{branchName.ToLower()}") +
47 (beta == 0 ? "" : $"-beta{beta}");
48
49 [Obsolete("Use Platform.IsWindows")]
50 public static readonly bool windows = Platform.IsWindows;
51 [Obsolete("Use Platform.IsLinux")]
52 public static readonly bool linux = Platform.IsLinux;
53 [Obsolete("Use Platform.IsOSX")]
54 public static readonly bool mac = Platform.IsOSX;
55
56 [Obsolete("Use CompressedPlatformRepresentation instead")]
57 public static readonly string compressedPlatformRepresentation = Platform.IsWindows ? "w" : (Platform.IsLinux ? "l" : "m");
58
59 public static string CompressedPlatformRepresentation => (Platform.IsWindows ? "w" : (Platform.IsLinux ? "l" : "m")) + (InstallVerifier.IsGoG ? "g" : "s") + (FrameworkVersion.Framework == Framework.NetFramework ? "n" : (FrameworkVersion.Framework == Framework.Mono ? "o" : "u"));
60
61 public static string ModPath => ModOrganizer.modPath;
62
63 private static readonly IDictionary<string, Mod> modsByName = new Dictionary<string, Mod>(StringComparer.OrdinalIgnoreCase);
64 private static WeakReference[] weakModReferences = new WeakReference[0];
65
66 internal static readonly string modBrowserPublicKey = "<RSAKeyValue><Modulus>oCZObovrqLjlgTXY/BKy72dRZhoaA6nWRSGuA+aAIzlvtcxkBK5uKev3DZzIj0X51dE/qgRS3OHkcrukqvrdKdsuluu0JmQXCv+m7sDYjPQ0E6rN4nYQhgfRn2kfSvKYWGefp+kqmMF9xoAq666YNGVoERPm3j99vA+6EIwKaeqLB24MrNMO/TIf9ysb0SSxoV8pC/5P/N6ViIOk3adSnrgGbXnFkNQwD0qsgOWDks8jbYyrxUFMc4rFmZ8lZKhikVR+AisQtPGUs3ruVh4EWbiZGM2NOkhOCOM4k1hsdBOyX2gUliD0yjK5tiU3LBqkxoi2t342hWAkNNb4ZxLotw==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
67 internal static string modBrowserPassphrase = "";
68
69 private static string steamID64 = "";
70 internal static string SteamID64 {
71 get => InstallVerifier.IsGoG ? steamID64 : Steamworks.SteamUser.GetSteamID().ToString();
72 set => steamID64 = value;
73 }
74
75 internal static bool autoReloadAndEnableModsLeavingModBrowser = true;
76 internal static bool dontRemindModBrowserUpdateReload;
77 internal static bool dontRemindModBrowserDownloadEnable;
78 internal static bool removeForcedMinimumZoom;
79 internal static bool showMemoryEstimates = true;
80
81 internal static bool skipLoad;
82
83 internal static Action OnSuccessfulLoad;
84
85 public static Mod[] Mods { get; private set; } = new Mod[0];
86
90 public static Mod GetMod(string name)
91 {
92 modsByName.TryGetValue(name, out Mod m);
93 return m;
94 }
95
96 public static Mod GetMod(int index) => index >= 0 && index < Mods.Length ? Mods[index] : null;
97
98 [Obsolete("Use ModLoader.Mods", true)]
99 public static Mod[] LoadedMods => Mods;
100
101 [Obsolete("Use ModLoader.Mods.Length", true)]
102 public static int ModCount => Mods.Length;
103
104 [Obsolete("Use ModLoader.Mods.Select(m => m.Name)", true)]
105 public static string[] GetLoadedMods() => Mods.Reverse().Select(m => m.Name).ToArray();
106
107 internal static void EngineInit()
108 {
110 FileAssociationSupport.UpdateFileAssociation();
111 GLCallLocker.Init();
112 HiDefGraphicsIssues.Init();
113 MonoModHooks.Initialize();
114 ZipExtractFix.Init();
115 }
116
117 internal static void BeginLoad(CancellationToken token) => Task.Run(() => Load(token));
118
119 private static bool isLoading = false;
120 private static void Load(CancellationToken token = default)
121 {
122 try {
123 if (isLoading)
124 throw new Exception("Load called twice");
125 isLoading = true;
126
127 if (!Unload())
128 return;
129
130 var modInstances = ModOrganizer.LoadMods(token);
131
132 weakModReferences = modInstances.Select(x => new WeakReference(x)).ToArray();
133 modInstances.Insert(0, new ModLoaderMod());
134 Mods = modInstances.ToArray();
135 foreach (var mod in Mods)
136 modsByName[mod.Name] = mod;
137
138 ModContent.Load(token);
139
140 if (OnSuccessfulLoad != null) {
141 OnSuccessfulLoad();
142 }
143 else {
144 Main.menuMode = 0;
145 }
146 }
147 catch when (token.IsCancellationRequested) {
148 // cancel needs to reload with ModLoaderMod and all others skipped
149 skipLoad = true;
150 OnSuccessfulLoad += () => Main.menuMode = Interface.modsMenuID;
151
152 isLoading = false;
153 Load(); // don't provide a token, loading just ModLoaderMod should be quick
154 }
155 catch (Exception e) {
156 var responsibleMods = new List<string>();
157 if (e.Data.Contains("mod"))
158 responsibleMods.Add((string)e.Data["mod"]);
159 if (e.Data.Contains("mods"))
160 responsibleMods.AddRange((IEnumerable<string>)e.Data["mods"]);
161 responsibleMods.Remove("ModLoader");
162
163 if (responsibleMods.Count == 0 && AssemblyManager.FirstModInStackTrace(new StackTrace(e), out var stackMod))
164 responsibleMods.Add(stackMod);
165
166 var msg = Language.GetTextValue("tModLoader.LoadError", string.Join(", ", responsibleMods));
167 if (responsibleMods.Count == 1) {
168 var mod = ModOrganizer.FindMods().FirstOrDefault(m => m.Name == responsibleMods[0]); //use First rather than Single, incase of "Two mods with the same name" error message from ModOrganizer (#639)
169 if (mod != null && (mod.tModLoaderVersion.Major != version.Major || mod.tModLoaderVersion.Minor != version.Minor || mod.tModLoaderVersion.Build != version.Build))
170 msg += "\n" + Language.GetTextValue("tModLoader.LoadErrorVersionMessage", mod.tModLoaderVersion, versionedName);
171 }
172 if (responsibleMods.Count > 0)
173 msg += "\n" + Language.GetTextValue("tModLoader.LoadErrorDisabled");
174 else
175 msg += "\n" + Language.GetTextValue("tModLoader.LoadErrorCulpritUnknown");
176
177 if (e is ReflectionTypeLoadException reflectionTypeLoadException)
178 msg += "\n\n" + string.Join("\n", reflectionTypeLoadException.LoaderExceptions.Select(x => x.Message));
179
180 Logging.tML.Error(msg, e);
181
182 foreach (var mod in responsibleMods)
183 DisableMod(mod);
184
185 isLoading = false; // disable loading flag, because server will just instantly retry reload
186 DisplayLoadError(msg, e, e.Data.Contains("fatal"), responsibleMods.Count == 0);
187 }
188 finally {
189 isLoading = false;
190 OnSuccessfulLoad = null;
191 skipLoad = false;
192 ModNet.NetReloadActive = false;
193 }
194 }
195
196 private static void DotNet45Check()
197 {
198 if (FrameworkVersion.Framework != Framework.NetFramework || FrameworkVersion.Version >= new Version(4, 5))
199 return;
200
201 var msg = Language.GetTextValue("tModLoader.LoadErrorDotNet45Required");
202#if CLIENT
203 Interface.MessageBoxShow(msg);
204 Process.Start("https://dotnet.microsoft.com/download/dotnet-framework");
205#else
206 Console.ForegroundColor = ConsoleColor.Red;
207 Console.WriteLine(msg);
208 Console.ResetColor();
209 Console.WriteLine("Press any key to exit...");
210 Console.ReadKey();
211#endif
212 Environment.Exit(-1);
213 }
214
215 internal static void Reload()
216 {
217 if (Main.dedServ)
218 Load();
219 else
220 Main.menuMode = Interface.loadModsID;
221 }
222
223 private static bool Unload()
224 {
225 try {
226 // have to move unload logic to a separate method so the stack frame is cleared. Otherwise unloading can capture mod instances in local variables, even with memory barriers (thanks compiler weirdness)
227 do_Unload();
229 return true;
230 }
231 catch (Exception e) {
232 var msg = Language.GetTextValue("tModLoader.UnloadError");
233
234 if (e.Data.Contains("mod"))
235 msg += "\n" + Language.GetTextValue("tModLoader.DefensiveUnload", e.Data["mod"]);
236
237 Logging.tML.Fatal(msg, e);
238 DisplayLoadError(msg, e, true);
239
240 return false;
241 }
242 }
243
244 private static void do_Unload()
245 {
246 Logging.tML.Info("Unloading mods");
247 if (Main.dedServ) {
248 Console.WriteLine("Unloading mods...");
249 }
250 else {
251 Interface.loadMods.SetLoadStage("tModLoader.MSUnloading", Mods.Length);
252 }
253
254 ModContent.UnloadModContent();
255 Mods = new Mod[0];
256 modsByName.Clear();
257 ModContent.Unload();
258
259 MemoryTracking.Clear();
260 Thread.MemoryBarrier();
261 GC.Collect();
262 }
263
264 internal static List<string> badUnloaders = new List<string>();
265 private static void WarnModsStillLoaded()
266 {
267 badUnloaders = weakModReferences.Where(r => r.IsAlive).Select(r => ((Mod)r.Target).Name).ToList();
268 foreach (var modName in badUnloaders)
269 Logging.tML.WarnFormat("{0} not fully unloaded during unload.", modName);
270 }
271
272 private static void DisplayLoadError(string msg, Exception e, bool fatal, bool continueIsRetry = false)
273 {
274 msg += "\n\n" + (e.Data.Contains("hideStackTrace") ? e.Message : e.ToString());
275
276 if (Main.dedServ) {
277 Console.ForegroundColor = ConsoleColor.Red;
278 Console.WriteLine(msg);
279 Console.ResetColor();
280
281 if (fatal) {
282 Console.WriteLine("Press any key to exit...");
283 Console.ReadKey();
284 Environment.Exit(-1);
285 }
286 else {
287 Reload();
288 }
289 }
290 else {
291 Interface.errorMessage.Show(msg,
292 gotoMenu: fatal ? -1 : Interface.reloadModsID,
293 webHelpURL: e.HelpLink,
294 continueIsRetry: continueIsRetry,
295 showSkip: !fatal);
296 }
297 }
298
299 // TODO: This doesn't work on mono for some reason. Investigate.
300 public static bool IsSignedBy(TmodFile mod, string xmlPublicKey)
301 {
302 var f = new RSAPKCS1SignatureDeformatter();
303 var v = AsymmetricAlgorithm.Create("RSA");
304 f.SetHashAlgorithm("SHA1");
305 v.FromXmlString(xmlPublicKey);
306 f.SetKey(v);
307 return f.VerifySignature(mod.hash, mod.signature);
308 }
309
310 private static bool _pauseSavingEnabledMods;
311 private static bool _needsSavingEnabledMods;
312 internal static bool PauseSavingEnabledMods {
314 set {
315 if (_pauseSavingEnabledMods == value) { return; }
316 if (!value && _needsSavingEnabledMods) {
317 ModOrganizer.SaveEnabledMods();
319 }
321 }
322 }
324 private static HashSet<string> _enabledMods;
325 internal static HashSet<string> EnabledMods => _enabledMods ?? (_enabledMods = ModOrganizer.LoadEnabledMods());
326
327 internal static bool IsEnabled(string modName) => EnabledMods.Contains(modName);
328 internal static void EnableMod(string modName) => SetModEnabled(modName, true);
329 internal static void DisableMod(string modName) => SetModEnabled(modName, false);
330 internal static void SetModEnabled(string modName, bool active)
331 {
332 if (active) {
333 EnabledMods.Add(modName);
334 Logging.tML.InfoFormat("Enabling Mod: {0}", modName);
335 }
336 else {
337 EnabledMods.Remove(modName);
338 Logging.tML.InfoFormat("Disabling Mod: {0}", modName);
339 }
340 if (PauseSavingEnabledMods) {
342 }
343 else {
344 ModOrganizer.SaveEnabledMods();
345 }
346 }
347
348 internal static void SaveConfiguration()
349 {
350 Main.Configuration.Put("ModBrowserPassphrase", modBrowserPassphrase);
351 Main.Configuration.Put("SteamID64", steamID64);
352 Main.Configuration.Put("DownloadModsFromServers", ModNet.downloadModsFromServers);
353 Main.Configuration.Put("OnlyDownloadSignedModsFromServers", ModNet.onlyDownloadSignedMods);
354 Main.Configuration.Put("AutomaticallyReloadAndEnableModsLeavingModBrowser", autoReloadAndEnableModsLeavingModBrowser);
355 Main.Configuration.Put("DontRemindModBrowserUpdateReload", dontRemindModBrowserUpdateReload);
356 Main.Configuration.Put("DontRemindModBrowserDownloadEnable", dontRemindModBrowserDownloadEnable);
357 Main.Configuration.Put("RemoveForcedMinimumZoom", removeForcedMinimumZoom);
358 Main.Configuration.Put("ShowMemoryEstimates", showMemoryEstimates);
359 Main.Configuration.Put("AvoidGithub", UI.ModBrowser.UIModBrowser.AvoidGithub);
360 Main.Configuration.Put("AvoidImgur", UI.ModBrowser.UIModBrowser.AvoidImgur);
361 Main.Configuration.Put(nameof(UI.ModBrowser.UIModBrowser.EarlyAutoUpdate), UI.ModBrowser.UIModBrowser.EarlyAutoUpdate);
362 Main.Configuration.Put("LastLaunchedTModLoaderVersion", version.ToString());
363 }
364
365 internal static void LoadConfiguration()
366 {
367 Main.Configuration.Get("ModBrowserPassphrase", ref modBrowserPassphrase);
368 Main.Configuration.Get("SteamID64", ref steamID64);
369 Main.Configuration.Get("DownloadModsFromServers", ref ModNet.downloadModsFromServers);
370 Main.Configuration.Get("OnlyDownloadSignedModsFromServers", ref ModNet.onlyDownloadSignedMods);
371 Main.Configuration.Get("AutomaticallyReloadAndEnableModsLeavingModBrowser", ref autoReloadAndEnableModsLeavingModBrowser);
372 Main.Configuration.Get("DontRemindModBrowserUpdateReload", ref dontRemindModBrowserUpdateReload);
373 Main.Configuration.Get("DontRemindModBrowserDownloadEnable", ref dontRemindModBrowserDownloadEnable);
374 Main.Configuration.Get("RemoveForcedMinimumZoom", ref removeForcedMinimumZoom);
375 Main.Configuration.Get("ShowMemoryEstimates", ref showMemoryEstimates);
376 Main.Configuration.Get("AvoidGithub", ref UI.ModBrowser.UIModBrowser.AvoidGithub);
377 Main.Configuration.Get("AvoidImgur", ref UI.ModBrowser.UIModBrowser.AvoidImgur);
378 Main.Configuration.Get(nameof(UI.ModBrowser.UIModBrowser.EarlyAutoUpdate), ref UI.ModBrowser.UIModBrowser.EarlyAutoUpdate);
379 LastLaunchedTModLoaderVersion = new Version(Main.Configuration.Get("LastLaunchedTModLoaderVersion", "0.0"));
380 }
381
382 internal static void MigrateSettings()
383 {
384 if (LastLaunchedTModLoaderVersion < new Version(0, 11, 7, 5))
385 showMemoryEstimates = true;
386 /*
387 if (LastLaunchedTModLoaderVersion < version)
388 ShowWhatsNew = true;
389 */
390 if (LastLaunchedTModLoaderVersion == new Version(0, 0))
392 }
393
397 internal static void BuildGlobalHook<T, F>(ref F[] list, IList<T> providers, Expression<Func<T, F>> expr)
398 {
399 list = BuildGlobalHook(providers, expr).Select(expr.Compile()).ToArray();
400 }
401
402 internal static T[] BuildGlobalHook<T, F>(IList<T> providers, Expression<Func<T, F>> expr)
403 {
404 return BuildGlobalHook(providers, Method(expr));
405 }
406
407 internal static T[] BuildGlobalHook<T>(IList<T> providers, MethodInfo method)
408 {
409 if (!method.IsVirtual) throw new ArgumentException("Cannot build hook for non-virtual method " + method);
410 var argTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
411 return providers.Where(p => p.GetType().GetMethod(method.Name, argTypes).DeclaringType != typeof(T)).ToArray();
412 }
413
414 internal static MethodInfo Method<T, F>(Expression<Func<T, F>> expr)
415 {
416 MethodInfo method;
417 try {
418 var convert = expr.Body as UnaryExpression;
419 var makeDelegate = convert.Operand as MethodCallExpression;
420 var methodArg = makeDelegate.Object as ConstantExpression;
421 method = methodArg.Value as MethodInfo;
422 if (method == null) throw new NullReferenceException();
423 }
424 catch (Exception e) {
425 throw new ArgumentException("Invalid hook expression " + expr, e);
426 }
427 return method;
428 }
429 /*
430 * Forwarder, deprecated, methods
431 * These are methods used likely by many modders, which may need some time to adjust to changes
432 */
433 [Obsolete("ModLoader.GetFileBytes is deprecated since v0.11, use ModContent.GetFileBytes instead.", true)]
434 public static byte[] GetFileBytes(string name) => ModContent.GetFileBytes(name);
435
436 [Obsolete("ModLoader.FileExists is deprecated since v0.11, use ModContent.FileExists instead.", true)]
437 public static bool FileExists(string name) => ModContent.FileExists(name);
438
439 [Obsolete("ModLoader.GetTexture is deprecated since v0.11, use ModContent.GetTexture instead.", true)]
440 public static Texture2D GetTexture(string name) => ModContent.GetTexture(name);
441
442 [Obsolete("ModLoader.TextureExists is deprecated since v0.11, use ModContent.TextureExists instead.", true)]
443 public static bool TextureExists(string name) => ModContent.TextureExists(name);
444
445 [Obsolete("ModLoader.GetSound is deprecated since v0.11, use ModContent.GetSound instead.", true)]
446 public static SoundEffect GetSound(string name) => ModContent.GetSound(name);
447
448 [Obsolete("ModLoader.SoundExists is deprecated since v0.1, use ModContent.SoundExists instead.", true)]
449 public static bool SoundExists(string name) => ModContent.SoundExists(name);
450
451 [Obsolete("ModLoader.GetMusic is deprecated since v0.11, use ModContent.GetMusic instead.", true)]
452 public static Music GetMusic(string name) => ModContent.GetMusic(name);
453
454 [Obsolete("ModLoader.MusicExists is deprecated since v0.11, use ModContent.MusicExists instead.", true)]
455 public static bool MusicExists(string name) => ModContent.MusicExists(name);
456 }
457}
System.Version Version
Definition: ModLoader.cs:21
static readonly Framework Framework
Manages content added by mods. Liasons between mod content and Terraria's arrays and oversees the Loa...
Definition: ModContent.cs:27
static bool TextureExists(string name)
Returns whether or not a texture with the specified name exists.
Definition: ModContent.cs:91
static bool FileExists(string name)
Returns whether or not a file with the specified name exists.
Definition: ModContent.cs:57
static SoundEffect GetSound(string name)
Gets the sound with the specified name. The name is in the same format as for texture names....
Definition: ModContent.cs:147
static Music GetMusic(string name)
Gets the music with the specified name. The name is in the same format as for texture names....
Definition: ModContent.cs:179
static Texture2D GetTexture(string name)
Gets the texture with the specified name. The name is in the format of "ModFolder/OtherFolders/FileNa...
Definition: ModContent.cs:72
static bool SoundExists(string name)
Returns whether or not a sound with the specified name exists.
Definition: ModContent.cs:164
static bool MusicExists(string name)
Returns whether or not a sound with the specified name exists.
Definition: ModContent.cs:191
static byte[] GetFileBytes(string name)
Gets the byte representation of the file with the specified name. The name is in the format of "ModFo...
Definition: ModContent.cs:43
Mod is an abstract class that you will override. It serves as a central place from which the mod's co...
Definition: Mod.cs:25
This serves as the central class which loads mods. It contains many static fields and methods related...
Definition: ModLoader.cs:29
static readonly IDictionary< string, Mod > modsByName
Definition: ModLoader.cs:63
static readonly Version version
Definition: ModLoader.cs:30
static bool SoundExists(string name)
static void Load(CancellationToken token=default)
Definition: ModLoader.cs:120
static string CompressedPlatformRepresentation
Definition: ModLoader.cs:59
static Music GetMusic(string name)
static void DisplayLoadError(string msg, Exception e, bool fatal, bool continueIsRetry=false)
Definition: ModLoader.cs:272
static readonly int beta
Definition: ModLoader.cs:38
static bool TextureExists(string name)
static SoundEffect GetSound(string name)
static readonly bool linux
Definition: ModLoader.cs:52
static Mod GetMod(int index)
static void DotNet45Check()
Definition: ModLoader.cs:196
static HashSet< string > _enabledMods
A cached list of enabled mods (not necessarily currently loaded or even installed),...
Definition: ModLoader.cs:324
static byte[] GetFileBytes(string name)
static Texture2D GetTexture(string name)
static bool _pauseSavingEnabledMods
Definition: ModLoader.cs:310
static string[] GetLoadedMods()
static readonly bool windows
Definition: ModLoader.cs:50
static readonly bool mac
Definition: ModLoader.cs:54
static readonly string compressedPlatformRepresentation
Definition: ModLoader.cs:57
static bool MusicExists(string name)
static bool _needsSavingEnabledMods
Definition: ModLoader.cs:311
static Version LastLaunchedTModLoaderVersion
Definition: ModLoader.cs:32
static readonly string versionedName
Definition: ModLoader.cs:41
static bool ShowFirstLaunchWelcomeMessage
Definition: ModLoader.cs:34
static void WarnModsStillLoaded()
Definition: ModLoader.cs:265
static WeakReference[] weakModReferences
Definition: ModLoader.cs:64
static readonly string versionTag
Definition: ModLoader.cs:45
static bool FileExists(string name)
static Mod GetMod(string name)
Gets the instance of the Mod with the specified name.
Definition: ModLoader.cs:90
static bool IsSignedBy(TmodFile mod, string xmlPublicKey)
Definition: ModLoader.cs:300
static readonly string branchName
Definition: ModLoader.cs:36
@ Environment
Sandstorm, Hell, Above surface during Eclipse, Space
@ Console
Command can be used in server console during MP.