Terraria ModLoader  0.11.7.5
A mod to make and play 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 Steamworks;
15 using Terraria.Localization;
17 using Terraria.ModLoader.Core;
18 using Terraria.ModLoader.Default;
19 using Terraria.ModLoader.Engine;
20 using Terraria.ModLoader.UI;
21 using Version = System.Version;
22 
23 namespace Terraria.ModLoader
24 {
28  public static class ModLoader
29  {
30  public static readonly Version version = new Version(0, 11, 7, 5);
31  // Stores the most recent version of tModLoader launched. Can be used for migration.
33  // public static bool ShowWhatsNew;
34  public static bool ShowFirstLaunchWelcomeMessage;
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  {
109  DotNet45Check();
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 != version)
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  }
193  }
194 
195  private static void DotNet45Check()
196  {
197  if (FrameworkVersion.Framework != Framework.NetFramework || FrameworkVersion.Version >= new Version(4, 5))
198  return;
199 
200  var msg = Language.GetTextValue("tModLoader.LoadErrorDotNet45Required");
201 #if CLIENT
202  Interface.MessageBoxShow(msg);
203  Process.Start("https://dotnet.microsoft.com/download/dotnet-framework");
204 #else
205  Console.ForegroundColor = ConsoleColor.Red;
206  Console.WriteLine(msg);
207  Console.ResetColor();
208  Console.WriteLine("Press any key to exit...");
209  Console.ReadKey();
210 #endif
211  Environment.Exit(-1);
212  }
213 
214  internal static void Reload()
215  {
216  if (Main.dedServ)
217  Load();
218  else
219  Main.menuMode = Interface.loadModsID;
220  }
221 
222  private static bool Unload()
223  {
224  try {
225  // 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)
226  do_Unload();
227  WarnModsStillLoaded();
228  return true;
229  }
230  catch (Exception e) {
231  var msg = Language.GetTextValue("tModLoader.UnloadError");
232 
233  if (e.Data.Contains("mod"))
234  msg += "\n" + Language.GetTextValue("tModLoader.DefensiveUnload", e.Data["mod"]);
235 
236  Logging.tML.Fatal(msg, e);
237  DisplayLoadError(msg, e, true);
238 
239  return false;
240  }
241  }
242 
243  private static void do_Unload()
244  {
245  Logging.tML.Info("Unloading mods");
246  if (Main.dedServ) {
247  Console.WriteLine("Unloading mods...");
248  }
249  else {
250  Interface.loadMods.SetLoadStage("tModLoader.MSUnloading", Mods.Length);
251  }
252 
253  ModContent.UnloadModContent();
254  Mods = new Mod[0];
255  modsByName.Clear();
256  ModContent.Unload();
257 
258  MemoryTracking.Clear();
259  Thread.MemoryBarrier();
260  GC.Collect();
261  }
262 
263  internal static List<string> badUnloaders = new List<string>();
264  private static void WarnModsStillLoaded()
265  {
266  badUnloaders = weakModReferences.Where(r => r.IsAlive).Select(r => ((Mod)r.Target).Name).ToList();
267  foreach (var modName in badUnloaders)
268  Logging.tML.WarnFormat("{0} not fully unloaded during unload.", modName);
269  }
270 
271  private static void DisplayLoadError(string msg, Exception e, bool fatal, bool continueIsRetry = false)
272  {
273  msg += "\n\n" + (e.Data.Contains("hideStackTrace") ? e.Message : e.ToString());
274 
275  if (Main.dedServ) {
276  Console.ForegroundColor = ConsoleColor.Red;
277  Console.WriteLine(msg);
278  Console.ResetColor();
279 
280  if (fatal) {
281  Console.WriteLine("Press any key to exit...");
282  Console.ReadKey();
283  Environment.Exit(-1);
284  }
285  else {
286  Reload();
287  }
288  }
289  else {
290  Interface.errorMessage.Show(msg,
291  gotoMenu: fatal ? -1 : Interface.reloadModsID,
292  webHelpURL: e.HelpLink,
293  continueIsRetry: continueIsRetry,
294  showSkip: !fatal);
295  }
296  }
297 
298  // TODO: This doesn't work on mono for some reason. Investigate.
299  public static bool IsSignedBy(TmodFile mod, string xmlPublicKey)
300  {
301  var f = new RSAPKCS1SignatureDeformatter();
302  var v = AsymmetricAlgorithm.Create("RSA");
303  f.SetHashAlgorithm("SHA1");
304  v.FromXmlString(xmlPublicKey);
305  f.SetKey(v);
306  return f.VerifySignature(mod.hash, mod.signature);
307  }
308 
309  private static bool _pauseSavingEnabledMods;
310  private static bool _needsSavingEnabledMods;
311  internal static bool PauseSavingEnabledMods {
312  get => _pauseSavingEnabledMods;
313  set {
314  if (_pauseSavingEnabledMods == value) { return; }
315  if (!value && _needsSavingEnabledMods) {
316  ModOrganizer.SaveEnabledMods();
317  _needsSavingEnabledMods = false;
318  }
319  _pauseSavingEnabledMods = value;
320  }
321  }
323  private static HashSet<string> _enabledMods;
324  internal static HashSet<string> EnabledMods => _enabledMods ?? (_enabledMods = ModOrganizer.LoadEnabledMods());
325 
326  internal static bool IsEnabled(string modName) => EnabledMods.Contains(modName);
327  internal static void EnableMod(string modName) => SetModEnabled(modName, true);
328  internal static void DisableMod(string modName) => SetModEnabled(modName, false);
329  internal static void SetModEnabled(string modName, bool active)
330  {
331  if (active) {
332  EnabledMods.Add(modName);
333  Logging.tML.InfoFormat("Enabling Mod: {0}", modName);
334  }
335  else {
336  EnabledMods.Remove(modName);
337  Logging.tML.InfoFormat("Disabling Mod: {0}", modName);
338  }
339  if (PauseSavingEnabledMods) {
340  _needsSavingEnabledMods = true;
341  }
342  else {
343  ModOrganizer.SaveEnabledMods();
344  }
345  }
346 
347  internal static void SaveConfiguration()
348  {
349  Main.Configuration.Put("ModBrowserPassphrase", modBrowserPassphrase);
350  Main.Configuration.Put("SteamID64", steamID64);
351  Main.Configuration.Put("DownloadModsFromServers", ModNet.downloadModsFromServers);
352  Main.Configuration.Put("OnlyDownloadSignedModsFromServers", ModNet.onlyDownloadSignedMods);
353  Main.Configuration.Put("AutomaticallyReloadAndEnableModsLeavingModBrowser", autoReloadAndEnableModsLeavingModBrowser);
354  Main.Configuration.Put("DontRemindModBrowserUpdateReload", dontRemindModBrowserUpdateReload);
355  Main.Configuration.Put("DontRemindModBrowserDownloadEnable", dontRemindModBrowserDownloadEnable);
356  Main.Configuration.Put("RemoveForcedMinimumZoom", removeForcedMinimumZoom);
357  Main.Configuration.Put("ShowMemoryEstimates", showMemoryEstimates);
358  Main.Configuration.Put("AvoidGithub", UI.ModBrowser.UIModBrowser.AvoidGithub);
359  Main.Configuration.Put("AvoidImgur", UI.ModBrowser.UIModBrowser.AvoidImgur);
360  Main.Configuration.Put(nameof(UI.ModBrowser.UIModBrowser.EarlyAutoUpdate), UI.ModBrowser.UIModBrowser.EarlyAutoUpdate);
361  Main.Configuration.Put("LastLaunchedTModLoaderVersion", version.ToString());
362  }
363 
364  internal static void LoadConfiguration()
365  {
366  Main.Configuration.Get("ModBrowserPassphrase", ref modBrowserPassphrase);
367  Main.Configuration.Get("SteamID64", ref steamID64);
368  Main.Configuration.Get("DownloadModsFromServers", ref ModNet.downloadModsFromServers);
369  Main.Configuration.Get("OnlyDownloadSignedModsFromServers", ref ModNet.onlyDownloadSignedMods);
370  Main.Configuration.Get("AutomaticallyReloadAndEnableModsLeavingModBrowser", ref autoReloadAndEnableModsLeavingModBrowser);
371  Main.Configuration.Get("DontRemindModBrowserUpdateReload", ref dontRemindModBrowserUpdateReload);
372  Main.Configuration.Get("DontRemindModBrowserDownloadEnable", ref dontRemindModBrowserDownloadEnable);
373  Main.Configuration.Get("RemoveForcedMinimumZoom", ref removeForcedMinimumZoom);
374  Main.Configuration.Get("ShowMemoryEstimates", ref showMemoryEstimates);
375  Main.Configuration.Get("AvoidGithub", ref UI.ModBrowser.UIModBrowser.AvoidGithub);
376  Main.Configuration.Get("AvoidImgur", ref UI.ModBrowser.UIModBrowser.AvoidImgur);
377  Main.Configuration.Get(nameof(UI.ModBrowser.UIModBrowser.EarlyAutoUpdate), ref UI.ModBrowser.UIModBrowser.EarlyAutoUpdate);
378  LastLaunchedTModLoaderVersion = new Version(Main.Configuration.Get("LastLaunchedTModLoaderVersion", "0.0"));
379  }
380 
381  internal static void MigrateSettings()
382  {
383  if (LastLaunchedTModLoaderVersion < new Version(0, 11, 7, 5))
384  showMemoryEstimates = true;
385  /*
386  if (LastLaunchedTModLoaderVersion < version)
387  ShowWhatsNew = true;
388  */
389  if (LastLaunchedTModLoaderVersion == new Version(0, 0))
390  ShowFirstLaunchWelcomeMessage = true;
391  }
392 
396  internal static void BuildGlobalHook<T, F>(ref F[] list, IList<T> providers, Expression<Func<T, F>> expr)
397  {
398  list = BuildGlobalHook(providers, expr).Select(expr.Compile()).ToArray();
399  }
400 
401  internal static T[] BuildGlobalHook<T, F>(IList<T> providers, Expression<Func<T, F>> expr)
402  {
403  return BuildGlobalHook(providers, Method(expr));
404  }
405 
406  internal static T[] BuildGlobalHook<T>(IList<T> providers, MethodInfo method)
407  {
408  if (!method.IsVirtual) throw new ArgumentException("Cannot build hook for non-virtual method " + method);
409  var argTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
410  return providers.Where(p => p.GetType().GetMethod(method.Name, argTypes).DeclaringType != typeof(T)).ToArray();
411  }
412 
413  internal static MethodInfo Method<T, F>(Expression<Func<T, F>> expr)
414  {
415  MethodInfo method;
416  try {
417  var convert = expr.Body as UnaryExpression;
418  var makeDelegate = convert.Operand as MethodCallExpression;
419  var methodArg = makeDelegate.Object as ConstantExpression;
420  method = methodArg.Value as MethodInfo;
421  if (method == null) throw new NullReferenceException();
422  }
423  catch (Exception e) {
424  throw new ArgumentException("Invalid hook expression " + expr, e);
425  }
426  return method;
427  }
428  /*
429  * Forwarder, deprecated, methods
430  * These are methods used likely by many modders, which may need some time to adjust to changes
431  */
432  [Obsolete("ModLoader.GetFileBytes is deprecated since v0.11, use ModContent.GetFileBytes instead.", true)]
433  public static byte[] GetFileBytes(string name) => ModContent.GetFileBytes(name);
434 
435  [Obsolete("ModLoader.FileExists is deprecated since v0.11, use ModContent.FileExists instead.", true)]
436  public static bool FileExists(string name) => ModContent.FileExists(name);
437 
438  [Obsolete("ModLoader.GetTexture is deprecated since v0.11, use ModContent.GetTexture instead.", true)]
439  public static Texture2D GetTexture(string name) => ModContent.GetTexture(name);
440 
441  [Obsolete("ModLoader.TextureExists is deprecated since v0.11, use ModContent.TextureExists instead.", true)]
442  public static bool TextureExists(string name) => ModContent.TextureExists(name);
443 
444  [Obsolete("ModLoader.GetSound is deprecated since v0.11, use ModContent.GetSound instead.", true)]
445  public static SoundEffect GetSound(string name) => ModContent.GetSound(name);
446 
447  [Obsolete("ModLoader.SoundExists is deprecated since v0.1, use ModContent.SoundExists instead.", true)]
448  public static bool SoundExists(string name) => ModContent.SoundExists(name);
449 
450  [Obsolete("ModLoader.GetMusic is deprecated since v0.11, use ModContent.GetMusic instead.", true)]
451  public static Music GetMusic(string name) => ModContent.GetMusic(name);
452 
453  [Obsolete("ModLoader.MusicExists is deprecated since v0.11, use ModContent.MusicExists instead.", true)]
454  public static bool MusicExists(string name) => ModContent.MusicExists(name);
455  }
456 }
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
static bool IsSignedBy(TmodFile mod, string xmlPublicKey)
Definition: ModLoader.cs:299
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:72
static bool _needsSavingEnabledMods
Definition: ModLoader.cs:310
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:323
This serves as the central class which loads mods. It contains many static fields and methods related...
Definition: ModLoader.cs:28
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:26
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:32
static void Load(CancellationToken token=default)
Definition: ModLoader.cs:120
static void DisplayLoadError(string msg, Exception e, bool fatal, bool continueIsRetry=false)
Definition: ModLoader.cs:271
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:195
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 bool _pauseSavingEnabledMods
Definition: ModLoader.cs:309
static void WarnModsStillLoaded()
Definition: ModLoader.cs:264
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
static bool ShowFirstLaunchWelcomeMessage
Definition: ModLoader.cs:34
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:57