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  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();
228  WarnModsStillLoaded();
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 {
313  get => _pauseSavingEnabledMods;
314  set {
315  if (_pauseSavingEnabledMods == value) { return; }
316  if (!value && _needsSavingEnabledMods) {
317  ModOrganizer.SaveEnabledMods();
318  _needsSavingEnabledMods = false;
319  }
320  _pauseSavingEnabledMods = value;
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) {
341  _needsSavingEnabledMods = true;
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))
391  ShowFirstLaunchWelcomeMessage = true;
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 }
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:300
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:311
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:324
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:272
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:196
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:310
static void WarnModsStillLoaded()
Definition: ModLoader.cs:265
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