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  OnSuccessfulLoad = null;
136  }
137  else {
138  Main.menuMode = 0;
139  }
140  }
141  catch when (token.IsCancellationRequested) {
142  if (Unload())
143  Main.menuMode = Interface.modsMenuID;
144  }
145  catch (Exception e) {
146  var responsibleMods = new List<string>();
147  if (e.Data.Contains("mod"))
148  responsibleMods.Add((string)e.Data["mod"]);
149  if (e.Data.Contains("mods"))
150  responsibleMods.AddRange((IEnumerable<string>)e.Data["mods"]);
151  responsibleMods.Remove("ModLoader");
152 
153  if (responsibleMods.Count == 0 && AssemblyManager.FirstModInStackTrace(new StackTrace(e), out var stackMod))
154  responsibleMods.Add(stackMod);
155 
156  var msg = Language.GetTextValue("tModLoader.LoadError", string.Join(", ", responsibleMods));
157  if (responsibleMods.Count == 1) {
158  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)
159  if (mod != null && mod.tModLoaderVersion != version)
160  msg += "\n" + Language.GetTextValue("tModLoader.LoadErrorVersionMessage", mod.tModLoaderVersion, versionedName);
161  }
162  if (responsibleMods.Count > 0)
163  msg += "\n" + Language.GetTextValue("tModLoader.LoadErrorDisabled");
164  else
165  msg += "\n" + Language.GetTextValue("tModLoader.LoadErrorCulpritUnknown");
166 
167  if (e is ReflectionTypeLoadException reflectionTypeLoadException)
168  msg += "\n\n" + string.Join("\n", reflectionTypeLoadException.LoaderExceptions.Select(x => x.Message));
169 
170  Logging.tML.Error(msg, e);
171 
172  foreach (var mod in responsibleMods)
173  DisableMod(mod);
174 
175  DisplayLoadError(msg, e, e.Data.Contains("fatal"), responsibleMods.Count == 0);
176  }
177  finally {
178  isLoading = false;
179  }
180  }
181 
182  private static void DotNet45Check() {
183  if (FrameworkVersion.Framework != Framework.NetFramework || FrameworkVersion.Version >= new Version(4, 5))
184  return;
185 
186  var msg = Language.GetTextValue("tModLoader.LoadErrorDotNet45Required");
187 #if CLIENT
188  Interface.MessageBoxShow(msg);
189  Process.Start("https://www.microsoft.com/net/download/thank-you/net472");
190 #else
191  Console.ForegroundColor = ConsoleColor.Red;
192  Console.WriteLine(msg);
193  Console.ResetColor();
194  Console.WriteLine("Press any key to exit...");
195  Console.ReadKey();
196 #endif
197  Environment.Exit(-1);
198  }
199 
200  internal static void Reload() {
201  if (Main.dedServ)
202  Load();
203  else
204  Main.menuMode = Interface.loadModsID;
205  }
206 
207  internal static List<string> badUnloaders = new List<string>();
208  private static bool Unload() {
209  if (Mods.Length == 0)
210  return true;
211 
212  try {
213  Logging.tML.Info("Unloading mods");
214  if (Main.dedServ) {
215  Console.WriteLine("Unloading mods...");
216  } else {
217  Interface.loadMods.SetLoadStage("tModLoader.MSUnloading", Mods.Length);
218  }
219 
220  ModContent.UnloadModContent();
221  Mods = new Mod[0];
222  modsByName.Clear();
223  ModContent.Unload();
224 
225  MemoryTracking.Clear();
226  Thread.MemoryBarrier();
227  GC.Collect();
228  badUnloaders.Clear();
229  foreach (var mod in weakModReferences.Where(r => r.IsAlive).Select(r => (Mod)r.Target)) {
230  Logging.tML.WarnFormat("{0} not fully unloaded during unload.", mod.Name);
231  badUnloaders.Add(mod.Name);
232  }
233 
234  return true;
235  }
236  catch (Exception e) {
237  var msg = Language.GetTextValue("tModLoader.UnloadError");
238 
239  if (e.Data.Contains("mod"))
240  msg += "\n" + Language.GetTextValue("tModLoader.DefensiveUnload", e.Data["mod"]);
241 
242  Logging.tML.Fatal(msg, e);
243  DisplayLoadError(msg, e, true);
244 
245  return false;
246  }
247  }
248 
249  private static void DisplayLoadError(string msg, Exception e, bool fatal, bool continueIsRetry = false) {
250  msg += "\n\n" + (e.Data.Contains("hideStackTrace") ? e.Message : e.ToString());
251 
252  if (Main.dedServ) {
253  Console.ForegroundColor = ConsoleColor.Red;
254  Console.WriteLine(msg);
255  Console.ResetColor();
256 
257  if (fatal) {
258  Console.WriteLine("Press any key to exit...");
259  Console.ReadKey();
260  Environment.Exit(-1);
261  }
262  else {
263  Reload();
264  }
265  }
266  else {
267  Interface.errorMessage.Show(msg,
268  gotoMenu: fatal ? -1 : Interface.reloadModsID,
269  webHelpURL: e.HelpLink,
270  showRetry: continueIsRetry,
271  showSkip: !fatal);
272  }
273  }
274 
275  // TODO: This doesn't work on mono for some reason. Investigate.
276  public static bool IsSignedBy(TmodFile mod, string xmlPublicKey) {
277  var f = new RSAPKCS1SignatureDeformatter();
278  var v = AsymmetricAlgorithm.Create("RSA");
279  f.SetHashAlgorithm("SHA1");
280  v.FromXmlString(xmlPublicKey);
281  f.SetKey(v);
282  return f.VerifySignature(mod.hash, mod.signature);
283  }
284 
286  private static HashSet<string> _enabledMods;
287  internal static HashSet<string> EnabledMods => _enabledMods ?? (_enabledMods = ModOrganizer.LoadEnabledMods());
288 
289  internal static bool IsEnabled(string modName) => EnabledMods.Contains(modName);
290  internal static void EnableMod(string modName) => SetModEnabled(modName, true);
291  internal static void DisableMod(string modName) => SetModEnabled(modName, false);
292  internal static void SetModEnabled(string modName, bool active) {
293  if (active) {
294  EnabledMods.Add(modName);
295  Logging.tML.InfoFormat("Enabling Mod: {0}", modName);
296  }
297  else {
298  EnabledMods.Remove(modName);
299  Logging.tML.InfoFormat("Disabling Mod: {0}", modName);
300  }
301 
302  ModOrganizer.SaveEnabledMods();
303  }
304 
305  internal static void SaveConfiguration() {
306  Main.Configuration.Put("ModBrowserPassphrase", modBrowserPassphrase);
307  Main.Configuration.Put("SteamID64", steamID64);
308  Main.Configuration.Put("DownloadModsFromServers", ModNet.downloadModsFromServers);
309  Main.Configuration.Put("OnlyDownloadSignedModsFromServers", ModNet.onlyDownloadSignedMods);
310  Main.Configuration.Put("AutomaticallyReloadAndEnableModsLeavingModBrowser", autoReloadAndEnableModsLeavingModBrowser);
311  Main.Configuration.Put("DontRemindModBrowserUpdateReload", dontRemindModBrowserUpdateReload);
312  Main.Configuration.Put("DontRemindModBrowserDownloadEnable", dontRemindModBrowserDownloadEnable);
313  Main.Configuration.Put("RemoveForcedMinimumZoom", removeForcedMinimumZoom);
314  Main.Configuration.Put("ShowMemoryEstimates", showMemoryEstimates);
315  Main.Configuration.Put("AvoidGithub", UI.ModBrowser.UIModBrowser.AvoidGithub);
316  Main.Configuration.Put("AvoidImgur", UI.ModBrowser.UIModBrowser.AvoidImgur);
317  Main.Configuration.Put("LastLaunchedTModLoaderVersion", version.ToString());
318  }
319 
320  internal static void LoadConfiguration() {
321  Main.Configuration.Get("ModBrowserPassphrase", ref modBrowserPassphrase);
322  Main.Configuration.Get("SteamID64", ref steamID64);
323  Main.Configuration.Get("DownloadModsFromServers", ref ModNet.downloadModsFromServers);
324  Main.Configuration.Get("OnlyDownloadSignedModsFromServers", ref ModNet.onlyDownloadSignedMods);
325  Main.Configuration.Get("AutomaticallyReloadAndEnableModsLeavingModBrowser", ref autoReloadAndEnableModsLeavingModBrowser);
326  Main.Configuration.Get("DontRemindModBrowserUpdateReload", ref dontRemindModBrowserUpdateReload);
327  Main.Configuration.Get("DontRemindModBrowserDownloadEnable", ref dontRemindModBrowserDownloadEnable);
328  Main.Configuration.Get("RemoveForcedMinimumZoom", ref removeForcedMinimumZoom);
329  Main.Configuration.Get("ShowMemoryEstimates", ref showMemoryEstimates);
330  Main.Configuration.Get("AvoidGithub", ref UI.ModBrowser.UIModBrowser.AvoidGithub);
331  Main.Configuration.Get("AvoidImgur", ref UI.ModBrowser.UIModBrowser.AvoidImgur);
332  }
333 
334  internal static void MigrateSettings() {
335  if (LastLaunchedTModLoaderVersion != null) return;
336 
337  LastLaunchedTModLoaderVersion = new Version(Main.Configuration.Get("LastLaunchedTModLoaderVersion", "0.0"));
338  if(LastLaunchedTModLoaderVersion <= new Version(0, 11, 4))
339  Main.Configuration.Put("Support4K", true); // This reverts a potentially bad setting change.
340  // Subsequent migrations here.
341  /*
342  if (LastLaunchedTModLoaderVersion < version)
343  ShowWhatsNew = true;
344  */
345  if (LastLaunchedTModLoaderVersion == new Version(0, 0))
346  ShowFirstLaunchWelcomeMessage = true;
347  }
348 
352  internal static void BuildGlobalHook<T, F>(ref F[] list, IList<T> providers, Expression<Func<T, F>> expr) {
353  list = BuildGlobalHook(providers, expr).Select(expr.Compile()).ToArray();
354  }
355 
356  internal static T[] BuildGlobalHook<T, F>(IList<T> providers, Expression<Func<T, F>> expr) {
357  return BuildGlobalHook(providers, Method(expr));
358  }
359 
360  internal static T[] BuildGlobalHook<T>(IList<T> providers, MethodInfo method) {
361  if (!method.IsVirtual) throw new ArgumentException("Cannot build hook for non-virtual method " + method);
362  var argTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
363  return providers.Where(p => p.GetType().GetMethod(method.Name, argTypes).DeclaringType != typeof(T)).ToArray();
364  }
365 
366  internal static MethodInfo Method<T, F>(Expression<Func<T, F>> expr) {
367  MethodInfo method;
368  try {
369  var convert = expr.Body as UnaryExpression;
370  var makeDelegate = convert.Operand as MethodCallExpression;
371  var methodArg = makeDelegate.Object as ConstantExpression;
372  method = methodArg.Value as MethodInfo;
373  if (method == null) throw new NullReferenceException();
374  }
375  catch (Exception e) {
376  throw new ArgumentException("Invalid hook expression " + expr, e);
377  }
378  return method;
379  }
380  /*
381  * Forwarder, deprecated, methods
382  * These are methods used likely by many modders, which may need some time to adjust to changes
383  */
384  [Obsolete("ModLoader.GetFileBytes is deprecated since v0.11, use ModContent.GetFileBytes instead.", true)]
385  public static byte[] GetFileBytes(string name) => ModContent.GetFileBytes(name);
386 
387  [Obsolete("ModLoader.FileExists is deprecated since v0.11, use ModContent.FileExists instead.", true)]
388  public static bool FileExists(string name) => ModContent.FileExists(name);
389 
390  [Obsolete("ModLoader.GetTexture is deprecated since v0.11, use ModContent.GetTexture instead.", true)]
391  public static Texture2D GetTexture(string name) => ModContent.GetTexture(name);
392 
393  [Obsolete("ModLoader.TextureExists is deprecated since v0.11, use ModContent.TextureExists instead.", true)]
394  public static bool TextureExists(string name) => ModContent.TextureExists(name);
395 
396  [Obsolete("ModLoader.GetSound is deprecated since v0.11, use ModContent.GetSound instead.", true)]
397  public static SoundEffect GetSound(string name) => ModContent.GetSound(name);
398 
399  [Obsolete("ModLoader.SoundExists is deprecated since v0.1, use ModContent.SoundExists instead.", true)]
400  public static bool SoundExists(string name) => ModContent.SoundExists(name);
401 
402  [Obsolete("ModLoader.GetMusic is deprecated since v0.11, use ModContent.GetMusic instead.", true)]
403  public static Music GetMusic(string name) => ModContent.GetMusic(name);
404 
405  [Obsolete("ModLoader.MusicExists is deprecated since v0.11, use ModContent.MusicExists instead.", true)]
406  public static bool MusicExists(string name) => ModContent.MusicExists(name);
407  }
408 }
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:276
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:286
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:249
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:182
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