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