Terraria ModLoader  0.11.5
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, 5);
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://www.microsoft.com/net/download/thank-you/net472");
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  internal static List<string> badUnloaders = new List<string>();
214  private static bool Unload() {
215  if (Mods.Length == 0)
216  return true;
217 
218  try {
219  Logging.tML.Info("Unloading mods");
220  if (Main.dedServ) {
221  Console.WriteLine("Unloading mods...");
222  } else {
223  Interface.loadMods.SetLoadStage("tModLoader.MSUnloading", Mods.Length);
224  }
225 
226  ModContent.UnloadModContent();
227  Mods = new Mod[0];
228  modsByName.Clear();
229  ModContent.Unload();
230 
231  MemoryTracking.Clear();
232  Thread.MemoryBarrier();
233  GC.Collect();
234  badUnloaders.Clear();
235  foreach (var mod in weakModReferences.Where(r => r.IsAlive).Select(r => (Mod)r.Target)) {
236  Logging.tML.WarnFormat("{0} not fully unloaded during unload.", mod.Name);
237  badUnloaders.Add(mod.Name);
238  }
239 
240  return true;
241  }
242  catch (Exception e) {
243  var msg = Language.GetTextValue("tModLoader.UnloadError");
244 
245  if (e.Data.Contains("mod"))
246  msg += "\n" + Language.GetTextValue("tModLoader.DefensiveUnload", e.Data["mod"]);
247 
248  Logging.tML.Fatal(msg, e);
249  DisplayLoadError(msg, e, true);
250 
251  return false;
252  }
253  }
254 
255  private static void DisplayLoadError(string msg, Exception e, bool fatal, bool continueIsRetry = false) {
256  msg += "\n\n" + (e.Data.Contains("hideStackTrace") ? e.Message : e.ToString());
257 
258  if (Main.dedServ) {
259  Console.ForegroundColor = ConsoleColor.Red;
260  Console.WriteLine(msg);
261  Console.ResetColor();
262 
263  if (fatal) {
264  Console.WriteLine("Press any key to exit...");
265  Console.ReadKey();
266  Environment.Exit(-1);
267  }
268  else {
269  Reload();
270  }
271  }
272  else {
273  Interface.errorMessage.Show(msg,
274  gotoMenu: fatal ? -1 : Interface.reloadModsID,
275  webHelpURL: e.HelpLink,
276  showRetry: continueIsRetry,
277  showSkip: !fatal);
278  }
279  }
280 
281  // TODO: This doesn't work on mono for some reason. Investigate.
282  public static bool IsSignedBy(TmodFile mod, string xmlPublicKey) {
283  var f = new RSAPKCS1SignatureDeformatter();
284  var v = AsymmetricAlgorithm.Create("RSA");
285  f.SetHashAlgorithm("SHA1");
286  v.FromXmlString(xmlPublicKey);
287  f.SetKey(v);
288  return f.VerifySignature(mod.hash, mod.signature);
289  }
290 
292  private static HashSet<string> _enabledMods;
293  internal static HashSet<string> EnabledMods => _enabledMods ?? (_enabledMods = ModOrganizer.LoadEnabledMods());
294 
295  internal static bool IsEnabled(string modName) => EnabledMods.Contains(modName);
296  internal static void EnableMod(string modName) => SetModEnabled(modName, true);
297  internal static void DisableMod(string modName) => SetModEnabled(modName, false);
298  internal static void SetModEnabled(string modName, bool active) {
299  if (active) {
300  EnabledMods.Add(modName);
301  Logging.tML.InfoFormat("Enabling Mod: {0}", modName);
302  }
303  else {
304  EnabledMods.Remove(modName);
305  Logging.tML.InfoFormat("Disabling Mod: {0}", modName);
306  }
307 
308  ModOrganizer.SaveEnabledMods();
309  }
310 
311  internal static void SaveConfiguration() {
312  Main.Configuration.Put("ModBrowserPassphrase", modBrowserPassphrase);
313  Main.Configuration.Put("SteamID64", steamID64);
314  Main.Configuration.Put("DownloadModsFromServers", ModNet.downloadModsFromServers);
315  Main.Configuration.Put("OnlyDownloadSignedModsFromServers", ModNet.onlyDownloadSignedMods);
316  Main.Configuration.Put("AutomaticallyReloadAndEnableModsLeavingModBrowser", autoReloadAndEnableModsLeavingModBrowser);
317  Main.Configuration.Put("DontRemindModBrowserUpdateReload", dontRemindModBrowserUpdateReload);
318  Main.Configuration.Put("DontRemindModBrowserDownloadEnable", dontRemindModBrowserDownloadEnable);
319  Main.Configuration.Put("RemoveForcedMinimumZoom", removeForcedMinimumZoom);
320  Main.Configuration.Put("ShowMemoryEstimates", showMemoryEstimates);
321  Main.Configuration.Put("AvoidGithub", UI.ModBrowser.UIModBrowser.AvoidGithub);
322  Main.Configuration.Put("AvoidImgur", UI.ModBrowser.UIModBrowser.AvoidImgur);
323  Main.Configuration.Put("LastLaunchedTModLoaderVersion", version.ToString());
324  }
325 
326  internal static void LoadConfiguration() {
327  Main.Configuration.Get("ModBrowserPassphrase", ref modBrowserPassphrase);
328  Main.Configuration.Get("SteamID64", ref steamID64);
329  Main.Configuration.Get("DownloadModsFromServers", ref ModNet.downloadModsFromServers);
330  Main.Configuration.Get("OnlyDownloadSignedModsFromServers", ref ModNet.onlyDownloadSignedMods);
331  Main.Configuration.Get("AutomaticallyReloadAndEnableModsLeavingModBrowser", ref autoReloadAndEnableModsLeavingModBrowser);
332  Main.Configuration.Get("DontRemindModBrowserUpdateReload", ref dontRemindModBrowserUpdateReload);
333  Main.Configuration.Get("DontRemindModBrowserDownloadEnable", ref dontRemindModBrowserDownloadEnable);
334  Main.Configuration.Get("RemoveForcedMinimumZoom", ref removeForcedMinimumZoom);
335  Main.Configuration.Get("ShowMemoryEstimates", ref showMemoryEstimates);
336  Main.Configuration.Get("AvoidGithub", ref UI.ModBrowser.UIModBrowser.AvoidGithub);
337  Main.Configuration.Get("AvoidImgur", ref UI.ModBrowser.UIModBrowser.AvoidImgur);
338  }
339 
340  internal static void MigrateSettings() {
341  if (LastLaunchedTModLoaderVersion != null) return;
342 
343  LastLaunchedTModLoaderVersion = new Version(Main.Configuration.Get("LastLaunchedTModLoaderVersion", "0.0"));
344  if(LastLaunchedTModLoaderVersion <= new Version(0, 11, 4))
345  Main.Configuration.Put("Support4K", true); // This reverts a potentially bad setting change.
346  // Subsequent migrations here.
347  /*
348  if (LastLaunchedTModLoaderVersion < version)
349  ShowWhatsNew = true;
350  */
351  if (LastLaunchedTModLoaderVersion == new Version(0, 0))
352  ShowFirstLaunchWelcomeMessage = true;
353  }
354 
358  internal static void BuildGlobalHook<T, F>(ref F[] list, IList<T> providers, Expression<Func<T, F>> expr) {
359  list = BuildGlobalHook(providers, expr).Select(expr.Compile()).ToArray();
360  }
361 
362  internal static T[] BuildGlobalHook<T, F>(IList<T> providers, Expression<Func<T, F>> expr) {
363  return BuildGlobalHook(providers, Method(expr));
364  }
365 
366  internal static T[] BuildGlobalHook<T>(IList<T> providers, MethodInfo method) {
367  if (!method.IsVirtual) throw new ArgumentException("Cannot build hook for non-virtual method " + method);
368  var argTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
369  return providers.Where(p => p.GetType().GetMethod(method.Name, argTypes).DeclaringType != typeof(T)).ToArray();
370  }
371 
372  internal static MethodInfo Method<T, F>(Expression<Func<T, F>> expr) {
373  MethodInfo method;
374  try {
375  var convert = expr.Body as UnaryExpression;
376  var makeDelegate = convert.Operand as MethodCallExpression;
377  var methodArg = makeDelegate.Object as ConstantExpression;
378  method = methodArg.Value as MethodInfo;
379  if (method == null) throw new NullReferenceException();
380  }
381  catch (Exception e) {
382  throw new ArgumentException("Invalid hook expression " + expr, e);
383  }
384  return method;
385  }
386  /*
387  * Forwarder, deprecated, methods
388  * These are methods used likely by many modders, which may need some time to adjust to changes
389  */
390  [Obsolete("ModLoader.GetFileBytes is deprecated since v0.11, use ModContent.GetFileBytes instead.", true)]
391  public static byte[] GetFileBytes(string name) => ModContent.GetFileBytes(name);
392 
393  [Obsolete("ModLoader.FileExists is deprecated since v0.11, use ModContent.FileExists instead.", true)]
394  public static bool FileExists(string name) => ModContent.FileExists(name);
395 
396  [Obsolete("ModLoader.GetTexture is deprecated since v0.11, use ModContent.GetTexture instead.", true)]
397  public static Texture2D GetTexture(string name) => ModContent.GetTexture(name);
398 
399  [Obsolete("ModLoader.TextureExists is deprecated since v0.11, use ModContent.TextureExists instead.", true)]
400  public static bool TextureExists(string name) => ModContent.TextureExists(name);
401 
402  [Obsolete("ModLoader.GetSound is deprecated since v0.11, use ModContent.GetSound instead.", true)]
403  public static SoundEffect GetSound(string name) => ModContent.GetSound(name);
404 
405  [Obsolete("ModLoader.SoundExists is deprecated since v0.1, use ModContent.SoundExists instead.", true)]
406  public static bool SoundExists(string name) => ModContent.SoundExists(name);
407 
408  [Obsolete("ModLoader.GetMusic is deprecated since v0.11, use ModContent.GetMusic instead.", true)]
409  public static Music GetMusic(string name) => ModContent.GetMusic(name);
410 
411  [Obsolete("ModLoader.MusicExists is deprecated since v0.11, use ModContent.MusicExists instead.", true)]
412  public static bool MusicExists(string name) => ModContent.MusicExists(name);
413  }
414 }
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:282
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:292
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:255
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 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