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