private static void LogModLoadTime(QMod mod) { if (elapsedTimes.TryGetValue(mod, out string elapsed)) { AddLog($"- {mod.DisplayName} ({mod.Id}) - {elapsed}"); } else { AddLog($"- {mod.DisplayName} ({mod.Id}) - Unknown load time"); } }
//// <summary> //// The <see cref="MessageReceiver"/>s and <see cref="GlobalMessageReceiver"/>s defined in this mod //// </summary> //[JsonIgnore] public Dictionary<IQMod, List<MethodInfo>> MessageReceivers { get; set; } internal static QMod FromJsonFile(string file) { try { JsonSerializerSettings settings = new JsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Ignore, }; string json = File.ReadAllText(file); QMod mod = JsonConvert.DeserializeObject <QMod>(json); if (mod == null) { return(null); } if (mod.Game == "BelowZero") { mod.ParsedGame = Patcher.Game.BelowZero; } else if (mod.Game == "Both") { mod.ParsedGame = Patcher.Game.Both; } else { mod.ParsedGame = Patcher.Game.Subnautica; } try { mod.ParsedVersion = new Version(mod.Version); } catch (Exception e) { Logger.Error($"There was an error parsing version \"{mod.Version}\" for mod \"{mod.DisplayName}\""); Logger.Exception(e); mod.ParsedVersion = null; } return(mod); } catch (Exception e) { Logger.Error($"\"mod.json\" deserialization failed for file \"{file}\"!"); Logger.Exception(e); return(null); } }
internal static bool LoadMod(QMod mod) { if (mod == null || mod.Loaded) { return(false); } try { string[] entryMethodSig = mod.EntryMethod.Split('.'); string entryType = string.Join(".", entryMethodSig.Take(entryMethodSig.Length - 1).ToArray()); string entryMethod = entryMethodSig[entryMethodSig.Length - 1]; MethodInfo patchMethod = mod.LoadedAssembly.GetType(entryType).GetMethod(entryMethod); patchMethod.Invoke(mod.LoadedAssembly, new object[] { }); } catch (ArgumentNullException e) { Logger.Error($"Could not parse entry method \"{mod.AssemblyName}\" for mod \"{mod.Id}\""); Logger.Exception(e); erroredMods.Add(mod); return(false); } catch (TargetInvocationException e) { Logger.Error($"Invoking the specified entry method \"{mod.EntryMethod}\" failed for mod \"{mod.Id}\""); Logger.Exception(e); return(false); } catch (Exception e) { Logger.Error($"An unexpected error occurred whilst trying to load mod \"{mod.Id}\""); Logger.Exception(e); return(false); } if (QModAPI.ErroredMods.Contains(mod?.LoadedAssembly)) { Logger.Error($"Mod \"{mod.Id}\" could not be loaded."); QModAPI.ErroredMods.Remove(mod?.LoadedAssembly); return(false); } mod.Loaded = true; Logger.Info($"Loaded mod \"{mod.Id}\""); return(true); }
public string[] LoadBeforeOtherMods = new string[] { }; // Might not be needed //public QMod() { } public static QMod FromJsonFile(string file) { try { JsonSerializerSettings settings = new JsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Ignore }; string json = File.ReadAllText(file); QMod mod = JsonConvert.DeserializeObject <QMod>(json); return(mod); } catch (Exception e) { AddLog("ERROR! mod.json deserialization failed!"); AddLog(e.Message); AddLog(e.StackTrace); return(null); } }
/// <summary> /// Uses reflection to load a mod /// </summary> /// <param name="mod">The mod to load</param> /// <returns>The loaded mod</returns> internal static QMod LoadMod(QMod mod) { if (mod == null) { return(null); } if (string.IsNullOrEmpty(mod.EntryMethod)) { AddLog($"ERROR! No EntryMethod specified for mod {mod.DisplayName}"); } else { try { if (sw.IsRunning) { sw.Stop(); } sw.Reset(); sw.Start(); var entryMethodSig = mod.EntryMethod.Split('.'); var entryType = String.Join(".", entryMethodSig.Take(entryMethodSig.Length - 1).ToArray()); var entryMethod = entryMethodSig[entryMethodSig.Length - 1]; MethodInfo qPatchMethod = mod.LoadedAssembly.GetType(entryType).GetMethod(entryMethod); qPatchMethod.Invoke(mod.LoadedAssembly, new object[] { }); sw.Stop(); string elapsedTime = ParseTime(sw); elapsedTimes.Add(mod, elapsedTime); } catch (ArgumentNullException e) { AddLog($"ERROR! Could not parse entry method {mod.AssemblyName} for mod {mod.DisplayName}"); if (e.InnerException != null) { AddLog(e.InnerException.Message); AddLog(e.InnerException.StackTrace); } return(null); } catch (TargetInvocationException e) { AddLog($"ERROR! Invoking the specified entry method {mod.EntryMethod} failed for mod {mod.Id}"); if (e.InnerException != null) { AddLog(e.InnerException.Message); AddLog(e.InnerException.StackTrace); } return(null); } catch (Exception e) { AddLog("ERROR! An unexpected error occurred!"); AddLog(e.Message); AddLog(e.StackTrace); if (e.InnerException != null) { AddLog(e.InnerException.Message); AddLog(e.InnerException.StackTrace); } return(null); } } return(mod); }
/// <summary> /// Loads the mods /// </summary> internal static void LoadMods() { AppDomain.CurrentDomain.AssemblyResolve += delegate(object sender, ResolveEventArgs args) { var allDlls = new DirectoryInfo(QModBaseDir).GetFiles("*.dll", SearchOption.AllDirectories); foreach (var dll in allDlls) { if (args.Name.Contains(Path.GetFileNameWithoutExtension(dll.Name))) { FileInfo[] modjson = dll.Directory.GetFiles("mod.json", SearchOption.TopDirectoryOnly); if (modjson.Length != 0) { try { if (Newtonsoft.Json.Linq.JObject.Parse(File.ReadAllText(modjson[0].FullName)) .TryGetValue("Enabled", out Newtonsoft.Json.Linq.JToken isEnabled) && !(bool)isEnabled) { Console.WriteLine("Cannot resolve Assembly " + dll.Name + " - Disabled by mod.json"); continue; } } catch { /*fail silently*/ } } Console.WriteLine(); return(Assembly.LoadFrom(dll.FullName)); } } Console.WriteLine("Could not find assembly " + args.Name); return(null); }; if (patched) { return; } patched = true; if (!Directory.Exists(QModBaseDir)) { AddLog("QMods directory was not found! Creating..."); if (QModBaseDir == "ERR") { AddLog("There was an error creating the QMods directory"); AddLog("Please make sure that you ran TerraTech from Steam"); } try { Directory.CreateDirectory(QModBaseDir); AddLog("QMods directory created successfully!"); } catch (Exception e) { AddLog("EXCEPTION CAUGHT!"); AddLog(e.Message); AddLog(e.StackTrace); if (e.InnerException != null) { AddLog("INNER EXCEPTION:"); AddLog(e.InnerException.Message); AddLog(e.InnerException.StackTrace); } } Console.WriteLine(ParseLog()); return; } var subDirs = Directory.GetDirectories(QModBaseDir); var lastMods = new List <QMod>(); var firstMods = new List <QMod>(); var otherMods = new List <QMod>(); foreach (var subDir in subDirs) { try { var jsonFile = Path.Combine(subDir, "mod.json"); if (!File.Exists(jsonFile)) { AddLog($"ERROR! No \"mod.json\" file found in folder \"{subDir}\""); File.WriteAllText(jsonFile, JsonConvert.SerializeObject(new QMod())); AddLog("A template file was created"); continue; } QMod mod = QMod.FromJsonFile(Path.Combine(subDir, "mod.json")); if (mod == (null)) { continue; } if (mod.Enable == false) { AddLog($"- {mod.DisplayName} is Disabled, skipping"); continue; } var modAssemblyPath = Path.Combine(subDir, mod.AssemblyName); if (!File.Exists(modAssemblyPath)) { AddLog($"ERROR! No matching dll found at \"{modAssemblyPath}\" for mod \"{mod.DisplayName}\""); continue; } mod.LoadedAssembly = Assembly.LoadFrom(modAssemblyPath); mod.ModAssemblyPath = modAssemblyPath; if (mod.Priority.Equals("Last")) { lastMods.Add(mod); continue; } else if (mod.Priority.Equals("First")) { firstMods.Add(mod); continue; } else { otherMods.Add(mod); continue; } } catch (Exception E) { AddLog($"ERROR! Failed to read mod \"{subDir}\": {E.Message}\n{E.StackTrace}"); } } // LoadBefore and LoadAfter stuff AddLog(" "); AddLog("Installed mods:"); foreach (var mod in firstMods) { if (mod != null) { loadedMods.Add(LoadMod(mod)); LogModLoadTime(mod); } } foreach (var mod in otherMods) { if (mod != null) { loadedMods.Add(LoadMod(mod)); LogModLoadTime(mod); } } foreach (var mod in lastMods) { if (mod != null) { loadedMods.Add(LoadMod(mod)); LogModLoadTime(mod); } } if (sw.IsRunning) { sw.Stop(); } FlagGame(); Console.WriteLine(ParseLog()); }
internal static void LoadAllMods() { string toWrite = "Loaded mods:\n"; List <QMod> loadingErrorMods = new List <QMod>(); QMod smlHelper = null; foreach (QMod mod in sortedMods) { if (mod != null && !mod.Loaded) { if (mod.Id != "SMLHelper") { if (!LoadMod(mod)) { if (!erroredMods.Contains(mod)) { erroredMods.Add(mod); } if (!loadingErrorMods.Contains(mod)) { loadingErrorMods.Add(mod); } continue; } else { toWrite += $"- {mod.DisplayName} ({mod.Id})\n"; loadedMods.Add(mod); } } else { smlHelper = mod; } } } if (smlHelper != null) { if (!LoadMod(smlHelper)) { if (!erroredMods.Contains(smlHelper)) { erroredMods.Add(smlHelper); } if (!loadingErrorMods.Contains(smlHelper)) { loadingErrorMods.Add(smlHelper); } } else { toWrite += $"- {smlHelper.DisplayName} ({smlHelper.Id})\n"; loadedMods.Add(smlHelper); } } if (loadingErrorMods.Count != 0) { string write = "The following mods could not be loaded:\n"; foreach (QMod mod in loadingErrorMods) { write += $"- {mod.DisplayName} ({mod.Id})\n"; } Logger.Error(write); } Logger.Info(toWrite); CheckOldHarmony(); }
internal static void StartLoadingMods() { Logger.Info("Started loading mods"); AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { FileInfo[] allDlls = new DirectoryInfo(QModBaseDir).GetFiles("*.dll", SearchOption.AllDirectories); foreach (FileInfo dll in allDlls) { if (args.Name.Contains(Path.GetFileNameWithoutExtension(dll.Name))) { return(Assembly.LoadFrom(dll.FullName)); } } return(null); }; Logger.Debug("Added AssemblyResolve event"); if (!Directory.Exists(QModBaseDir)) { Logger.Info("QMods directory was not found! Creating..."); return; } string[] subDirs = Directory.GetDirectories(QModBaseDir); foreach (string subDir in subDirs) { if (Directory.GetFiles(subDir, "*.dll", SearchOption.TopDirectoryOnly).Length < 1) { continue; } string folderName = new DirectoryInfo(subDir).Name; string jsonFile = Path.Combine(subDir, "mod.json"); if (!File.Exists(jsonFile)) { Logger.Error($"No \"mod.json\" file found for mod located in folder \"{subDir}\". A template file will be created"); File.WriteAllText(jsonFile, JsonConvert.SerializeObject(new QMod())); erroredMods.Add(QMod.CreateFakeQMod(folderName)); continue; } QMod mod = QMod.FromJsonFile(Path.Combine(subDir, "mod.json")); if (!QMod.QModValid(mod, folderName)) { erroredMods.Add(QMod.CreateFakeQMod(folderName)); continue; } if (mod.Enable == false) { Logger.Info($"Mod \"{mod.DisplayName}\" is disabled via config, skipping..."); continue; } string modAssemblyPath = Path.Combine(subDir, mod.AssemblyName); if (!File.Exists(modAssemblyPath)) { Logger.Error($"No matching dll found at \"{modAssemblyPath}\" for mod \"{mod.DisplayName}\""); erroredMods.Add(mod); continue; } mod.LoadedAssembly = Assembly.LoadFrom(modAssemblyPath); mod.ModAssemblyPath = modAssemblyPath; //mod.MessageReceivers = GetMessageRecievers(mod.LoadedAssembly); foundMods.Add(mod); } // Add the found mods into the sortedMods list sortedMods.AddRange(foundMods); // Disable mods that are not for the detected game // (Disable Subnautica mods if Below Zero is detected and disable Below Zero mods if Subnautica is detected) DisableNonApplicableMods(); // Remove mods with duplicate mod ids if any are found RemoveDuplicateModIDs(); // Sort the mods based on their LoadBefore and LoadAfter properties // If any mods break (i.e., a loop is found), they are removed from the list so that they aren't loaded // And are outputted into the log. SortMods(); // Check if all the mods' dependencies are present // If a mod's dependecies aren't present, that mods isn't loaded and it is outputted in the log. CheckForDependencies(); // Finally, load all the mods after sorting and checking for dependencies. // If anything goes wrong during loading, it is outputted in the log. LoadAllMods(); }
internal static bool QModValid(QMod mod, string folderName) { bool success = true; if (mod == null) { Logger.Error($"Skipped a null mod found in folder \"{folderName}\""); return(false); } if (string.IsNullOrEmpty(mod.DisplayName)) { Logger.Error($"Mod found in folder \"{folderName}\" is missing a display name!"); success = false; } if (string.IsNullOrEmpty(mod.Id)) { Logger.Error($"Mod found in folder \"{folderName}\" is missing an ID!"); success = false; } else if (mod.Id != Regex.Replace(mod.Id, Patcher.IDRegex, "", RegexOptions.IgnoreCase)) { Logger.Warn($"Mod found in folder \"{folderName}\" has an invalid ID! All invalid characters have been removed. (This can cause issues!)"); mod.Id = Regex.Replace(mod.Id, Patcher.IDRegex, "", RegexOptions.IgnoreCase); } if (string.IsNullOrEmpty(mod.Author)) { Logger.Error($"Mod found in folder \"{folderName}\" is missing an author!"); success = false; } if (string.IsNullOrEmpty(mod.Version)) { Logger.Error($"Mod found in folder \"{folderName}\" is missing a version!"); success = false; } if (mod.ParsedVersion == null) { Logger.Warn($"Mod found in folder \"{folderName}\" has an invalid version!"); } if (string.IsNullOrEmpty(mod.AssemblyName)) { Logger.Error($"Mod found in folder \"{folderName}\" is missing an assembly name!"); success = false; } else if (!mod.AssemblyName.EndsWith(".dll")) { Logger.Error($"Mod found in folder \"{folderName}\" is has an invalid assembly name!"); success = false; } if (string.IsNullOrEmpty(mod.EntryMethod)) { Logger.Error($"Mod found in folder \"{folderName}\" is missing an entry point!"); success = false; } else if (mod.EntryMethod?.Count(c => c == '.') < 2) { Logger.Error($"Mod found in folder \"{folderName}\" has an invalid entry point!"); success = false; } for (int i = 0; i < mod.LoadAfter.Length; i++) { string good = Regex.Replace(mod.LoadAfter[i], Patcher.IDRegex, "", RegexOptions.IgnoreCase); if (mod.LoadAfter[i] != good) { mod.LoadAfter[i] = good; } } for (int i = 0; i < mod.LoadBefore.Length; i++) { string good = Regex.Replace(mod.LoadBefore[i], Patcher.IDRegex, "", RegexOptions.IgnoreCase); if (mod.LoadBefore[i] != good) { mod.LoadBefore[i] = good; } } Dictionary <string, string> versionDependenciesLoop = new Dictionary <string, string>(mod.VersionDependencies); foreach (KeyValuePair <string, string> kvp in versionDependenciesLoop) { string good = Regex.Replace(kvp.Key, Patcher.IDRegex, "", RegexOptions.IgnoreCase); if (kvp.Key != good) { mod.VersionDependencies.Remove(kvp.Key); mod.VersionDependencies.Add(good, kvp.Value); } } return(success); }