private static void LogStatus(List <QMod> mods, ModStatus statusToReport, string summary, Logger.Level logLevel) { List <QMod> specificMods = mods.FindAll(mod => mod.Status == statusToReport); if (specificMods.Count == 0) { return; } Logger.Log(logLevel, summary); foreach (QMod mod in specificMods) { Console.WriteLine($"- {mod.DisplayName} ({mod.Id})"); } }
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); }
private static void CheckOldHarmony(IEnumerable <QMod> mods) { var modsThatUseOldHarmony = new List <QMod>(); foreach (QMod mod in mods) { if (mod.IsLoaded && mod.HarmonyOutdated) { modsThatUseOldHarmony.Add(mod); } } if (modsThatUseOldHarmony.Count > 0) { Logger.Warn($"Some mods are using an old version of harmony! This will NOT cause any problems, but it's not recommended:"); foreach (QMod mod in modsThatUseOldHarmony) { Console.WriteLine($"- {mod.DisplayName} ({mod.Id})"); } } }
/// <inheritdoc /> public override GameObject GetGameObject() { switch (_type) { case CompactType.Ingot: // ReSharper disable once AccessToStaticMemberViaDerivedType var prefab = GameObject.Instantiate(CraftData.GetPrefabForTechType(TechType.TitaniumIngot)); Logger.Log(Logger.Level.Debug, $"Game Object instantiated for custom ingot ({ClassID})"); //try to use a custom material if (ModAssets.Materials.TryGetValue(_baseType, out var mat)) { //material exists var renderer = prefab.GetComponentInChildren <Renderer>(); renderer.material = mat; } return(prefab); default: throw new NotImplementedException($"Compact type {_type.ToString()} does not exist yet."); } }
internal static void Parse(string versionStr) { try { Version currentVersion = Assembly.GetExecutingAssembly().GetName().Version; if (versionStr == null) { Logger.Error("There was an error retrieving the latest version from GitHub!"); return; } Version latestVersion = new Version(versionStr); if (latestVersion == null) { Logger.Error("There was an error retrieving the latest version from GitHub!"); return; } if (latestVersion > currentVersion) { Logger.Info($"Newer version found: {latestVersion.ToStringParsed()} (current version: {currentVersion.ToStringParsed()}"); if (Patcher.erroredMods.Count <= 0) { Dialog.Show( $"There is a newer version of QModManager available: {latestVersion.ToStringParsed()} (current version: {currentVersion.ToStringParsed()})", Dialog.Button.download, Dialog.Button.close, true); } } else { Logger.Info($"Recieved latest version from GitHub. We are up to date!"); } } catch (Exception e) { Logger.Error("There was an error retrieving the latest version from GitHub!"); Logger.Exception(e); return; } }
internal static void RemoveDuplicateModIDs() { List <QMod> duplicateModIDs = new List <QMod>(); foreach (QMod mod in sortedMods) { List <QMod> matchingMods = sortedMods.Where((QMod qmod) => qmod.Id == mod.Id).ToList(); if (matchingMods.Count > 1) { foreach (QMod duplicateMod in matchingMods) { if (!duplicateModIDs.Contains(duplicateMod)) { duplicateModIDs.Add(duplicateMod); } if (!erroredMods.Contains(duplicateMod)) { erroredMods.Add(duplicateMod); } } } } if (duplicateModIDs.Count > 0) { string toWrite = $"Multiple mods with the same ID found:\n"; foreach (QMod mod in duplicateModIDs) { if (sortedMods.Contains(mod)) { sortedMods.Remove(mod); } toWrite += $"- {mod.DisplayName} ({mod.Id})\n"; } Logger.Error(toWrite); } }
internal static void Patch() { try { if (patched) { Logger.Warn("Patch method was called multiple times!"); return; } patched = true; Logger.Info($"Loading QModManager v{Assembly.GetExecutingAssembly().GetName().Version.ToStringParsed()}..."); if (QModBaseDir == null) { Logger.Fatal("A fatal error has occurred."); Logger.Fatal("There was an error with the QMods directory"); Logger.Fatal("Please make sure that you ran Subnautica from Steam/Epic/Discord, and not from the executable file!"); return; } try { Logger.Info($"Folder structure:\n{IOUtilities.GetFolderStructureAsTree()}\n"); } catch (Exception e) { Logger.Error("There was an error while trying to display the folder structure."); Logger.Exception(e); } QModHooks.Load(); #pragma warning disable CS0618 // Type or member is obsolete Hooks.Load(); #pragma warning restore CS0618 // Type or member is obsolete PirateCheck.IsPirate(Environment.CurrentDirectory); if (!DetectGame()) { return; } PatchHarmony(); if (NitroxCheck.IsInstalled) { Logger.Fatal($"Nitrox was detected!"); Dialog.Show("Both QModManager and Nitrox detected. QModManager is not compatible with Nitrox. Please uninstall one of them.", Dialog.Button.disabled, Dialog.Button.disabled, false); return; } StartLoadingMods(); ShowErroredMods(); VersionCheck.Check(); QModHooks.Start += PrefabDebugger.Main; QModHooks.OnLoadEnd?.Invoke(); #pragma warning disable CS0618 // Type or member is obsolete Hooks.OnLoadEnd?.Invoke(); #pragma warning restore CS0618 // Type or member is obsolete Logger.Info($"Finished loading QModManager. Loaded {loadedMods.Count} mods"); } catch (Exception e) { Logger.Error("EXCEPTION CAUGHT!"); Logger.Exception(e); } }
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 void PatchHarmony() { HarmonyInstance.Create("qmodmanager").PatchAll(); Logger.Debug("Patched!"); }
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); }
public void OnHandHover(GUIHand hand) { if (!enabled) { return; } Light light = this.gameObject.GetComponentInChildren <Light>(); Renderer[] renderers = this.gameObject.GetComponentsInChildren <Renderer>(); if (light == null) { Logger.Log(Logger.Level.Error, "[ColorizableSpotlight] Light Component is missing!"); return; } var reticle = HandReticle.main; reticle.SetIcon(HandReticle.IconType.Hand, 1f); if (Input.GetKeyDown(KeyCode.R)) { if (light.color.r >= 1.0f) { light.color = new Color(0f, light.color.g, light.color.b); } else { light.color = new Color(light.color.r + 0.1f, light.color.g, light.color.b); } savedColor.r = light.color.r; ErrorMessage.AddDebug($"Spotlight: Red levels updated ({light.color.r:0.00}/1)"); } else if (Input.GetKeyDown(KeyCode.G)) { if (light.color.g >= 1.0f) { light.color = new Color(light.color.r, 0f, light.color.b); } else { light.color = new Color(light.color.r, light.color.g + 0.1f, light.color.b); } savedColor.g = light.color.g; ErrorMessage.AddDebug($"Spotlight: Green levels updated ({light.color.g:0.00}/1)"); } else if (Input.GetKeyDown(KeyCode.B)) { if (light.color.b >= 1.0f) { light.color = new Color(light.color.r, light.color.g, 0f); } else { light.color = new Color(light.color.r, light.color.g, light.color.b + 0.1f); } savedColor.b = light.color.b; ErrorMessage.AddDebug($"Spotlight: Blue levels updated ({light.color.b:0.00}/1)"); } foreach (var renderer in renderers) { foreach (var material in renderer.materials) { material.SetColor(ShaderPropertyID._GlowColor, new Color(light.color.r, light.color.g, light.color.b)); } } reticle.SetInteractText($"Press \"R\" to change the Red Levels, current red Level: {light.color.r:0.00}\nPress \"G\" to change the Green Levels, current Green Level: {light.color.g:0.00}\nPress \"B\" to change the Blue Levels, current Blue level: {light.color.b:0.00}"); }