diff --git a/BepinexPackages/Subnautica_Packages/QModManager/plugins/QModManager/QModInstaller.xml b/BepinexPackages/Subnautica_Packages/QModManager/plugins/QModManager/QModInstaller.xml
index 04a0010e..42e18a49 100644
--- a/BepinexPackages/Subnautica_Packages/QModManager/plugins/QModManager/QModInstaller.xml
+++ b/BepinexPackages/Subnautica_Packages/QModManager/plugins/QModManager/QModInstaller.xml
@@ -152,6 +152,14 @@
true if Nitrox is being used; otherwise, false.
+
+
+ Gets a value indicating whether Piracy was detected.
+
+
+ true if Piracy was detected; otherwise, false.
+
+
Identifies the patching class for your QMod.
@@ -354,6 +362,14 @@
true if Nitrox is being used; otherwise, false.
+
+
+ Gets a value indicating whether Piracy was detected.
+
+
+ true if Piracy was detected; otherwise, false.
+
+
Identifies a required mod and an optional minimum version.
@@ -597,6 +613,11 @@
QMMLoader - simply fires up the QModManager entry point.
+
+
+ "Only for use by Bepinex"
+
+
Prevents a default instance of the class from being created
diff --git a/QModManager/API/IQModServices.cs b/QModManager/API/IQModServices.cs
index 6aee2d35..9e90d1f0 100644
--- a/QModManager/API/IQModServices.cs
+++ b/QModManager/API/IQModServices.cs
@@ -1,5 +1,6 @@
namespace QModManager.API
{
+ using System;
using System.Reflection;
using QModManager.Utility;
@@ -48,7 +49,6 @@ public interface IQModServices : IQModAPI
///
bool NitroxRunning { get; }
-
///
/// Gets a value indicating whether Piracy was detected.
///
@@ -56,5 +56,21 @@ public interface IQModServices : IQModAPI
/// true if Piracy was detected; otherwise, false.
///
bool PirateDetected { get; }
+
+ ///
+ /// Gets the current Q Mod Manager Version.
+ ///
+ ///
+ /// Return Running QMM Version.
+ ///
+ Version QMMrunningVersion { get; }
+
+ ///
+ /// Gets a value indicating when a Savegame was already loaded. (Turns true when entering the Mainmenu again.)
+ ///
+ ///
+ /// true Mainmenu is entered AFTER a Savegame was loaded already; otherwise, false.
+ ///
+ bool AnySavegamewasalreadyloaded { get; }
}
}
\ No newline at end of file
diff --git a/QModManager/API/QModServices.cs b/QModManager/API/QModServices.cs
index c44518a5..7d1eeb92 100644
--- a/QModManager/API/QModServices.cs
+++ b/QModManager/API/QModServices.cs
@@ -2,6 +2,7 @@
namespace QModManager.API
{
+ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
@@ -165,5 +166,21 @@ public void AddCriticalMessage(string msg, int size = MainMenuMessages.defaultSi
/// true if Piracy was detected; otherwise, false.
///
public bool PirateDetected => PirateCheck.PirateDetected;
+
+ ///
+ /// Gets the current Q Mod Manager Version.
+ ///
+ ///
+ /// Return Running QMM Version.
+ ///
+ public Version QMMrunningVersion => Assembly.GetExecutingAssembly().GetName().Version;
+
+ ///
+ /// Gets a value indicating when a Savegame was already loaded (Turn true when entering the Mainmenu again.
+ ///
+ ///
+ /// true Mainmenu is entered AFTER a Savegame was loaded already; otherwise, false.
+ ///
+ public bool AnySavegamewasalreadyloaded => ReturnfromSavegameWarning.AnySavegamewasloaded;
}
}
diff --git a/QModManager/Checks/PirateCheck.cs b/QModManager/Checks/PirateCheck.cs
index 3ef1e34b..241318ca 100644
--- a/QModManager/Checks/PirateCheck.cs
+++ b/QModManager/Checks/PirateCheck.cs
@@ -7,13 +7,12 @@ namespace QModManager.Checks
{
internal static class PirateCheck
{
- internal static string Steamapi => "steam_api64.dll";
- internal static int Steamapilengh => 220000;
-
-
- internal static string folder = Environment.CurrentDirectory;
+ internal const string SteamApiName = "steam_api64.dll";
+ internal const int SteamApiLength = 220000;
internal static bool PirateDetected;
+ private static readonly string _folder = Environment.CurrentDirectory;
+
internal static readonly HashSet CrackedFiles = new HashSet()
{
"steam_api64.cdx",
@@ -26,18 +25,20 @@ internal static class PirateCheck
"Subnautica_Data/Plugins/steam_emu.ini",
"Profile/SteamUserID.cfg",
"Profile/Stats/Achievements.Bin",
+ "Profile/VALVE/SteamUserID.cfg",
+ "Profile/VALVE/Stats/Achievements.Bin",
"launcher.bat",
"chuj.cdx",
};
internal static void IsPirate()
{
- string steamDll = Path.Combine(folder, Steamapi);
+ string steamDll = Path.Combine(_folder, SteamApiName);
bool steamStore = File.Exists(steamDll);
if (steamStore)
{
FileInfo fileInfo = new FileInfo(steamDll);
- if (fileInfo.Length > Steamapilengh)
+ if (fileInfo.Length > SteamApiLength)
{
PirateDetected = true;
}
@@ -47,7 +48,7 @@ internal static void IsPirate()
{
foreach (string file in CrackedFiles)
{
- if (File.Exists(Path.Combine(folder, file)))
+ if (File.Exists(Path.Combine(_folder, file)))
{
PirateDetected = true;
break;
@@ -55,7 +56,7 @@ internal static void IsPirate()
}
}
- Logger.Info(PirateDetected? "Ahoy, matey! Ye be a pirate!":"Seems Legit.");
+ Logger.Info(PirateDetected ? "Ahoy, matey! Ye be a pirate!" : "Seems legit.");
}
}
}
diff --git a/QModManager/Checks/VersionCheck.cs b/QModManager/Checks/VersionCheck.cs
index 612f23dc..b615256f 100644
--- a/QModManager/Checks/VersionCheck.cs
+++ b/QModManager/Checks/VersionCheck.cs
@@ -19,13 +19,13 @@ internal static void Check()
{
if (!Config.CheckForUpdates)
{
- Logger.Info("Update check disabled");
+ Logger.Info("QMM Internal Versionchecker: Update check disabled");
return;
}
if (!NetworkUtilities.CheckConnection())
{
- Logger.Info("Cannot check for updates, internet disabled");
+ Logger.Info("QMM Internal Versionchecker: Cannot check for updates, internet disabled");
return;
}
@@ -37,14 +37,14 @@ internal static void Check()
{
if (e.Error != null)
{
- Logger.Error("There was an error retrieving the latest version from GitHub!");
+ Logger.Error("QMM Internal Versionchecker: There was an error retrieving the latest version from GitHub!");
Logger.Exception(e.Error);
return;
}
Parse(e.Result);
};
- Logger.Debug("Getting the latest version...");
+ Logger.Debug("QMM Internal Versionchecker: Getting the latest version...");
client.DownloadStringAsync(new Uri(VersionURL));
}
}
@@ -56,15 +56,23 @@ internal static void Parse(string versionStr)
Version currentVersion = Assembly.GetExecutingAssembly().GetName().Version;
if (versionStr == null)
{
- Logger.Error("There was an error retrieving the latest version from GitHub!");
+ Logger.Error("QMM Internal Versionchecker: There was an error retrieving the latest version from GitHub!");
return;
}
- var latestVersion = new Version(versionStr);
+
+ string[] versionStr_splittet = versionStr.Split('.');
+ string version_builder = $"{versionStr_splittet[0]}.{(versionStr_splittet.Length >= 2 ? $"{versionStr_splittet[1]}" : "0")}.{(versionStr_splittet.Length >= 3 ? $"{versionStr_splittet[2]}" : "0")}.{(versionStr_splittet.Length >= 4 ? $"{versionStr_splittet[3]}" : "0")}";
+ var latestVersion = new Version(version_builder);
+
if (latestVersion == null)
{
- Logger.Error("There was an error retrieving the latest version from GitHub!");
+ Logger.Error("QMM Internal Versionchecker: There was an error retrieving the latest version from GitHub!");
return;
}
+
+ //Logger.Debug($"QMM Version Checker - Parse - current Version value: {currentVersion}");
+ //Logger.Debug($"QMM Version Checker - Parse - latest Version value: {latestVersion}");
+
if (latestVersion > currentVersion)
{
Logger.Info($"Newer version found: {latestVersion.ToStringParsed()} (current version: {currentVersion.ToStringParsed()})");
@@ -72,16 +80,16 @@ internal static void Parse(string versionStr)
}
else if (latestVersion < currentVersion)
{
- Logger.Info($"Received latest version from GitHub. We're ahead. This is probably a development build.");
+ Logger.Info($"QMM Internal Versionchecker: Received latest version ({latestVersion.ToStringParsed()}) from GitHub. We're ahead. This is probably a development build (current version: {currentVersion.ToStringParsed()}).");
}
else
{
- Logger.Info($"Received latest version from GitHub. We are up to date!");
+ Logger.Info($"QMM Internal Versionchecker: Received 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.Error("QMM Internal Versionchecker: There was an error retrieving the latest version from GitHub!");
Logger.Exception(e);
return;
}
diff --git a/QModManager/HarmonyPatches/ReturnfromSavegameWarning.cs b/QModManager/HarmonyPatches/ReturnfromSavegameWarning.cs
new file mode 100644
index 00000000..efd9ad18
--- /dev/null
+++ b/QModManager/HarmonyPatches/ReturnfromSavegameWarning.cs
@@ -0,0 +1,58 @@
+namespace QModManager.Patching
+{
+ using HarmonyLib;
+ using QModManager.API;
+ using MyLogger = Utility;
+ using UWE;
+ using System.Collections;
+ using UnityEngine;
+
+ internal static class ReturnfromSavegameWarning
+ {
+ public static bool AnySavegamewasloaded = false;
+ }
+
+ [HarmonyPatch(typeof(Player), nameof(Player.Awake))]
+ internal static class ReturnfromSavegameWarningPlayerAwake
+ {
+ [HarmonyPostfix]
+ internal static void Postfix(Player __instance)
+ {
+ if (ReturnfromSavegameWarning.AnySavegamewasloaded)
+ {
+ MyLogger.Logger.Error("Entering a Savegame after playing a other one without restarting the Game. Modders do not recommend that. Restart the Game to prevent errors or unexpected behaviour");
+ if (Utility.Config.ShowWarnOnLoadSecondSave)
+ {
+ CoroutineHost.StartCoroutine(ShowIngameMessage_async("Modders do not recommend loading multiple savegames without restarting the game."));
+ }
+ }
+ else
+ {
+ ReturnfromSavegameWarning.AnySavegamewasloaded = true;
+ }
+ }
+ public static IEnumerator ShowIngameMessage_async(string Message)
+ {
+ yield return new WaitForSecondsRealtime(2);
+ yield return new WaitForSeconds(3);
+ ErrorMessage.AddMessage(Message);
+ }
+ }
+
+ [HarmonyPatch(typeof(uGUI_MainMenu), nameof(uGUI_MainMenu.Awake))]
+ internal static class ReturnfromSavegameWarninguGUI_MainMenuAwake
+ {
+ [HarmonyPostfix]
+ internal static void Postfix(uGUI_OptionsPanel __instance)
+ {
+ if (ReturnfromSavegameWarning.AnySavegamewasloaded)
+ {
+ MyLogger.Logger.Error("Entering Main Menu after playing a Savegame. Modders do not recommend to start or load a Savegame now. Restart the Game to prevent errors or unexpected behaviour");
+ if (Utility.Config.ShowWarnOnLoadSecondSave)
+ {
+ //QModServices.Main.AddCriticalMessage("Note that Modders recommend to restart the Game before loading the next Savegame", 15, "orange");
+ }
+ }
+ }
+ }
+}
diff --git a/QModManager/OptionsManager.cs b/QModManager/OptionsManager.cs
index be4921ba..b26ffde4 100644
--- a/QModManager/OptionsManager.cs
+++ b/QModManager/OptionsManager.cs
@@ -6,10 +6,15 @@
using QModManager.Utility;
using System.Reflection;
using UnityEngine.Events;
+ using System.Collections.Generic;
+ using System.Linq;
internal static class OptionsManager
{
internal static int ModsTab;
+ internal static string ModsListTabName = "QMods List";
+ internal static int ModListTab;
+ public static List ModListPendingChanges;
[HarmonyPatch(typeof(uGUI_OptionsPanel), nameof(uGUI_OptionsPanel.AddTabs))]
internal static class OptionsPatch
@@ -17,10 +22,10 @@ internal static class OptionsPatch
[HarmonyPostfix]
internal static void Postfix(uGUI_OptionsPanel __instance)
{
+ #region Mod Config
ModsTab = __instance.AddTab("Mods");
__instance.AddHeading(ModsTab, "QModManager");
-
MethodInfo AddToggleOption = null;
if(Patcher.CurrentlyRunningGame == QModGame.Subnautica)
{
@@ -40,6 +45,8 @@ internal static void Postfix(uGUI_OptionsPanel __instance)
AddToggleOption.Invoke(__instance, new object[] { ModsTab, "Enable debug logs", Config.EnableDebugLogs, new UnityAction(value => Config.EnableDebugLogs = value) });
AddToggleOption.Invoke(__instance, new object[] { ModsTab, "Enable developer mode", Config.EnableDevMode, new UnityAction(value => Config.EnableDevMode = value) });
+ AddToggleOption.Invoke(__instance, new object[] { ModsTab, "Enable Mod List Menu", Config.EnableModListMenu, new UnityAction(value => Config.EnableModListMenu = value) });
+ AddToggleOption.Invoke(__instance, new object[] { ModsTab, "Show Warning on Loading Second Save", Config.ShowWarnOnLoadSecondSave, new UnityAction(value => Config.ShowWarnOnLoadSecondSave = value) });
}
else
{
@@ -59,8 +66,266 @@ internal static void Postfix(uGUI_OptionsPanel __instance)
AddToggleOption.Invoke(__instance, new object[] { ModsTab, "Enable debug logs", Config.EnableDebugLogs, new UnityAction(value => Config.EnableDebugLogs = value), null });
AddToggleOption.Invoke(__instance, new object[] { ModsTab, "Enable developer mode", Config.EnableDevMode, new UnityAction(value => Config.EnableDevMode = value), null });
+ AddToggleOption.Invoke(__instance, new object[] { ModsTab, "Enable Mod List Menu", Config.EnableModListMenu, new UnityAction(value => Config.EnableModListMenu = value), null });
+ AddToggleOption.Invoke(__instance, new object[] { ModsTab, "Show Warning on Loading Second Save", Config.ShowWarnOnLoadSecondSave, new UnityAction(value => Config.ShowWarnOnLoadSecondSave = value), null });
}
+ #endregion Mod Config
+ #region Mod List
+ Logger.Log(Logger.Level.Debug, "OptionsMenu - Reset Mod List Menu");
+
+ //Reset ModList and Pending Changes on Reload the List Menu - We do not want to save any Data or take over Data from previous time here
+ //Part 1 things we should reset anyway
+ ModListPendingChanges = new List();
+
+ if (Config.EnableModListMenu)
+ {
+ //Part 2 things we only need to reset when activly using the Mod List Menu
+ List mods = new List();
+ List activeMods = new List();
+ List inactiveMods = new List();
+ foreach (var iQMod in QModServices.Main.GetAllMods().OrderBy(mod => mod.DisplayName))
+ if (iQMod is QMod qMod)
+ mods.Add(qMod);
+ List erroredMods = mods.FindAll(m => !m.IsLoaded && m.Status > 0);
+ int erroredModscount = erroredMods.Count;
+
+ //Start creating Ingame Mod List
+ Logger.Log(Logger.Level.Debug, "OptionsMenu - Start creating Modlist");
+
+ //Create new Tab in the Menu
+ ModListTab = __instance.AddTab("QMods List");
+
+ if(QModServices.Main.PirateDetected)
+ {
+ __instance.AddHeading(ModListTab, $"If you like the Game, please Support it and buy it. Thanks.");
+ }
+
+ //Add QMM Informations
+#if SUBNAUTICA_STABLE
+ __instance.AddHeading(ModListTab, $"Running QModManager {Assembly.GetExecutingAssembly().GetName().Version.ToStringParsed()} for Subnautica");
+#elif SUBNAUTICA_EXP
+ __instance.AddHeading(ModListTab, $"Running QModManager -Experimental- {Assembly.GetExecutingAssembly().GetName().Version.ToStringParsed()} for Subnautica");
+#elif BELOWZERO_STABLE
+ __instance.AddHeading(ModListTab, $"Running QModManager {Assembly.GetExecutingAssembly().GetName().Version.ToStringParsed()} for Below Zero");
+#elif BELOWZERO_EXP
+ __instance.AddHeading(ModListTab, $"Running QModManager -Experimental- {Assembly.GetExecutingAssembly().GetName().Version.ToStringParsed()} for Below Zero");
+#endif
+
+ //Add SML Informations to the Top as its pretty Important
+ var modprio_sml = QModServices.Main.GetMod("SMLHelper");
+ if (modprio_sml != null)
+ {
+ __instance.AddHeading(ModListTab, $"{modprio_sml.DisplayName} {(modprio_sml.Enable ? $"v{modprio_sml.ParsedVersion}" : string.Empty)} is {(modprio_sml.IsLoaded ? "enabled" : "disabled")}");
+ }
+ else
+ {
+ __instance.AddHeading(ModListTab, $"SMLHelper is not installed");
+ }
+
+ //Now lets create the enable and disable Mod Lists
+ foreach (var mod in mods)
+ {
+ if (mod.Id != "SMLHelper")
+ {
+ if (mod.Enable)
+ {
+ activeMods.Add(mod);
+
+ }
+ else
+ {
+ inactiveMods.Add(mod);
+ }
+ }
+ }
+
+ //Now show some Statistics ahead of the List.
+ __instance.AddHeading(ModListTab, $"- - Statistics - -");
+ __instance.AddHeading(ModListTab, $"{mods.Count()} Mods found");
+ if (erroredModscount != 0)
+ {
+ __instance.AddHeading(ModListTab, $"WARNING: {erroredModscount} Mods failed to load");
+ }
+ __instance.AddHeading(ModListTab, $"{activeMods.Count} Mods enabled");
+ __instance.AddHeading(ModListTab, $"{inactiveMods.Count} Mods disabled");
+
+
+ //At first show the Mods with errors. But show it only, when Mod errors exist
+ if (erroredModscount != 0)
+ {
+ __instance.AddHeading(ModListTab, $"- - List of Mods wit Loading Errors. You need to take Actions on them ! - -");
+ foreach (var mod in erroredMods)
+ {
+ //This is the Header Entry
+ //__instance.AddHeading(ModListTab, $"{mod.DisplayName} from {mod.Author}");
+
+ //This is the Collapse SubMenu of the Entry
+ if (Patcher.CurrentlyRunningGame == QModGame.Subnautica)
+ {
+ MethodInfo Modlist_AddToggleOption = null;
+ Modlist_AddToggleOption = typeof(uGUI_OptionsPanel).GetMethod(nameof(AddToggleOption), new System.Type[] { typeof(int), typeof(string), typeof(bool), typeof(UnityAction) });
+#if SUBNAUTICA_STABLE
+ Modlist_AddToggleOption.Invoke(__instance, new object[] { ModListTab, $"{mod.DisplayName}", mod.Enable, new UnityAction(value => OnChangeModStatus(mod, value, __instance)) });
+#else
+ Modlist_AddToggleOption.Invoke(__instance, new object[] { ModListTab, $"{mod.DisplayName} from {mod.Author}", mod.Enable, new UnityAction(value => OnChangeModStatus(mod, value, __instance)) });
+#endif
+ }
+ else
+ {
+ MethodInfo Modlist_AddToggleOption = null;
+ Modlist_AddToggleOption = typeof(uGUI_OptionsPanel).GetMethod(nameof(AddToggleOption), new System.Type[] { typeof(int), typeof(string), typeof(bool), typeof(UnityAction), typeof(string) });
+ Modlist_AddToggleOption.Invoke(__instance, new object[] { ModListTab, $"{mod.DisplayName} from {mod.Author}", mod.Enable, new UnityAction(value => OnChangeModStatus(mod, value, __instance)), null });
+ }
+ }
+ }
+
+ //Now we write down the actull List with all Mods. Starting with the Active Mods
+ __instance.AddHeading(ModListTab, $"- - List of currently running Mods - -");
+ foreach (var mod in activeMods)
+ {
+ //This is the Header Entry
+ //__instance.AddHeading(ModListTab, $"{mod.DisplayName} v{mod.ParsedVersion.ToString()} from {mod.Author}");
+
+ //This is the Collapse SubMenu of the Entry
+ if (Patcher.CurrentlyRunningGame == QModGame.Subnautica)
+ {
+ MethodInfo Modlist_AddToggleOption = null;
+ Modlist_AddToggleOption = typeof(uGUI_OptionsPanel).GetMethod(nameof(AddToggleOption), new System.Type[] { typeof(int), typeof(string), typeof(bool), typeof(UnityAction) });
+#if SUBNAUTICA_STABLE
+ Modlist_AddToggleOption.Invoke(__instance, new object[] { ModListTab, $"{mod.DisplayName}", mod.Enable, new UnityAction(value => OnChangeModStatus(mod, value, __instance)) });
+#else
+ Modlist_AddToggleOption.Invoke(__instance, new object[] { ModListTab, $"{mod.DisplayName} v{mod.ParsedVersion.ToString()} from {mod.Author}", mod.Enable, new UnityAction(value => OnChangeModStatus(mod, value, __instance)) });
+#endif
+ }
+ else
+ {
+ MethodInfo Modlist_AddToggleOption = null;
+ Modlist_AddToggleOption = typeof(uGUI_OptionsPanel).GetMethod(nameof(AddToggleOption), new System.Type[] { typeof(int), typeof(string), typeof(bool), typeof(UnityAction), typeof(string) });
+ Modlist_AddToggleOption.Invoke(__instance, new object[] { ModListTab, $"{mod.DisplayName} v{mod.ParsedVersion.ToString()} from {mod.Author}", mod.Enable, new UnityAction(value => OnChangeModStatus(mod, value, __instance)), null });
+ }
+ }
+
+ //Continue with Disabled Mods
+ __instance.AddHeading(ModListTab, $"- - List of Disabled Mods - -");
+ foreach (var mod in inactiveMods)
+ {
+ //This is the Header Entry
+ //__instance.AddHeading(ModListTab, $"{mod.DisplayName} from {mod.Author}");
+
+ //This is the Collapse SubMenu of the Entry
+ if (Patcher.CurrentlyRunningGame == QModGame.Subnautica)
+ {
+ MethodInfo Modlist_AddToggleOption = null;
+ Modlist_AddToggleOption = typeof(uGUI_OptionsPanel).GetMethod(nameof(AddToggleOption), new System.Type[] { typeof(int), typeof(string), typeof(bool), typeof(UnityAction) });
+#if SUBNAUTICA_STABLE
+ Modlist_AddToggleOption.Invoke(__instance, new object[] { ModListTab, $"{mod.DisplayName}", mod.Enable, new UnityAction(value => OnChangeModStatus(mod, value, __instance)) });
+#else
+ Modlist_AddToggleOption.Invoke(__instance, new object[] { ModListTab, $"{mod.DisplayName} from {mod.Author}", mod.Enable, new UnityAction(value => OnChangeModStatus(mod, value, __instance)) });
+#endif
+ }
+ else
+ {
+ MethodInfo Modlist_AddToggleOption = null;
+ Modlist_AddToggleOption = typeof(uGUI_OptionsPanel).GetMethod(nameof(AddToggleOption), new System.Type[] { typeof(int), typeof(string), typeof(bool), typeof(UnityAction), typeof(string) });
+ Modlist_AddToggleOption.Invoke(__instance, new object[] { ModListTab, $"{mod.DisplayName} from {mod.Author}", mod.Enable, new UnityAction(value => OnChangeModStatus(mod, value, __instance)), null });
+ }
+ }
+
+ Logger.Log(Logger.Level.Debug, "OptionsMenu - ModList - Creating Modlist Ending");
+ }
+ else
+ {
+ Logger.Log(Logger.Level.Info, "OptionsMenu - ModList - No Mod List Menu was created due to User wish.");
+ }
+
+#endregion Mod List
+
+ }
+
+ static void OnChangeModStatus(QMod ChangedMod,bool status, uGUI_OptionsPanel __instance)
+ {
+ //write the new Status to the Mod Variable
+ ChangedMod.Enable = status;
+
+ QMod _modexist = new QMod();
+ //Now check if the Mod is already in the Pending list
+ try
+ {
+ //Is there a better way than try/catch using Find while determind a null or Empty when no Mod is found ?
+ _modexist = ModListPendingChanges.Find(mdt => mdt.Id == ChangedMod.Id);
+ }
+ catch
+ {
+ //if not it will result in an exception so i can set it to null
+ _modexist = null;
+ }
+
+ if (_modexist == null)
+ {
+ //if the Mod is not in list add it to pending
+ try
+ {
+ ModListPendingChanges.Add(ChangedMod);
+ }
+ catch
+ {
+ Logger.Log(Logger.Level.Warn, "OptionsMenu - ModList - Error on Adding Mod to Pending Status Change List");
+ }
+ }
+ else if(_modexist.Id == ChangedMod.Id)
+ {
+ //if the Mod is already in the List the user revert the change. Remove it
+ try
+ {
+ ModListPendingChanges.Remove(ChangedMod);
+ }
+ catch
+ {
+ Logger.Log(Logger.Level.Warn, "OptionsMenu - ModList - Error on Removing Mod to Pending Status Change List");
+ }
+ }
+ else
+ {
+ Logger.Log(Logger.Level.Warn, "OptionsMenu - ModList - Error on Adding AND Removing Mod to Pending Status Change List.");
+ //mhh that should not happen....
+ }
+
+ if (ModListPendingChanges.Count != 0)
+ {
+ //enable Apply Button
+ __instance.applyButton.gameObject.SetActive(true);
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(uGUI_OptionsPanel), nameof(uGUI_OptionsPanel.OnApplyButton))]
+ internal static class OptionsPatch_OnApplyButton
+ {
+ [HarmonyPostfix]
+ internal static void Postfix(uGUI_OptionsPanel __instance)
+ {
+ if (Config.EnableModListMenu)
+ {
+ //Warning Save Button is shared over the hole Option Menu !
+ if (OptionsManager.ModListPendingChanges.Count > 0)
+ {
+ //if the List Contains Pending entries show Info Box
+ Dialog dialog = new Dialog();
+ dialog.message = "Important ! Changes on Mods will enforce a Game Reboot after all changes are saved to system.";
+ dialog.color = Dialog.DialogColor.Red;
+ dialog.rightButton = Dialog.Button.CancelModChanges;
+ dialog.leftButton = Dialog.Button.ApplyModChanges;
+ dialog.Show();
+ }
+
+ //In case User Canceled or the Quit was not executed properly
+ if (OptionsManager.ModListPendingChanges.Count != 0)
+ {
+ //As the Original Methode would be disable the Button anyway. We need to Enable it again.
+ __instance.applyButton.gameObject.SetActive(true);
+ }
+ }
}
}
}
diff --git a/QModManager/Patching/GameDetector.cs b/QModManager/Patching/GameDetector.cs
index 83c8d010..669b5bfe 100644
--- a/QModManager/Patching/GameDetector.cs
+++ b/QModManager/Patching/GameDetector.cs
@@ -60,9 +60,39 @@ internal GameDetector()
CurrentGameVersion = SNUtils.GetPlasticChangeSetOfBuild(-1);
- Logger.Info($"Game Version: {CurrentGameVersion} Build Date: {SNUtils.GetDateTimeOfBuild():dd-MMMM-yyyy} Store: {StoreDetector.GetUsedGameStore()}");
- Logger.Info($"Loading QModManager v{Assembly.GetExecutingAssembly().GetName().Version.ToStringParsed()}{(IsValidGameRunning && MinimumBuildVersion != 0 ? $" built for {CurrentlyRunningGame} v{MinimumBuildVersion}" : string.Empty)}...");
- Logger.Info($"Today is {DateTime.Now:dd-MMMM-yyyy_HH:mm:ss}");
+ try
+ {
+ Logger.Info($"Game Version: {CurrentGameVersion} Build Date: {SNUtils.GetDateTimeOfBuild():dd-MMMM-yyyy} Store: {StoreDetector.GetUsedGameStore()}");
+ }
+ catch
+ {
+ Logger.Warn($"Game Version: {CurrentGameVersion} Build Date: [Error in displaying Time and Date] Store: {StoreDetector.GetUsedGameStore()}");
+ }
+
+#if SUBNAUTICA_STABLE
+ Logger.Info($"Loading QModManager v{Assembly.GetExecutingAssembly().GetName().Version.ToStringParsed()} built for Subnautica v{SupportedGameVersions[QModGame.Subnautica]}...");
+#elif SUBNAUTICA_EXP
+ Logger.Info($"Loading QModManager -Experimental- v{Assembly.GetExecutingAssembly().GetName().Version.ToStringParsed()} built for Subnautica v{SupportedGameVersions[QModGame.Subnautica]}...");
+#elif BELOWZERO_STABLE
+ Logger.Info($"Loading QModManager v{Assembly.GetExecutingAssembly().GetName().Version.ToStringParsed()} built for Below Zero v{SupportedGameVersions[QModGame.BelowZero]}...");
+#elif BELOWZERO_EXP
+ Logger.Info($"Loading QModManager -Experimental- v{Assembly.GetExecutingAssembly().GetName().Version.ToStringParsed()} built for Below Zero v{SupportedGameVersions[QModGame.BelowZero]}...");
+#endif
+ try
+ {
+ Logger.Info($"Today is {DateTime.Now:dd-MMMM-yyyy_HH:mm:ss}");
+ }
+ catch
+ {
+ try
+ {
+ Logger.Warn($"Today is: Unable to format Time here is the raw Version - {DateTime.Now}");
+ }
+ catch
+ {
+ Logger.Error($"Today is: Unable to Read Time and Date. Possible due to unsupported Calender settings.");
+ }
+ }
if (!IsValidGameVersion)
{
diff --git a/QModManager/Patching/StoreDetector.cs b/QModManager/Patching/StoreDetector.cs
index c642145a..3e03dd0b 100644
--- a/QModManager/Patching/StoreDetector.cs
+++ b/QModManager/Patching/StoreDetector.cs
@@ -1,97 +1,53 @@
-namespace QModManager.Patching
-{
- using System;
- using System.IO;
- using Checks;
+using System;
+using QModManager.Checks;
+namespace QModManager.Patching
+{
internal class StoreDetector
{
internal static string GetUsedGameStore()
{
- string directory = null;
-
- try
+ if (NonValidStore())
{
- directory ??= Environment.CurrentDirectory;
- }
- catch
- {
- return "Error on getting Store";
+ return "Invalid store";
}
- if (NonValidStore(directory))
- {
- return "free Store";
- }
- else if (IsSteam(directory))
+ if (IsSteam())
{
return "Steam";
}
- else if (IsEpic(directory))
+
+ if (IsEpic())
{
- return "Eic Games";
+ return "Epic Games";
}
- else if (IsMSStore(directory))
+
+ if (IsMSStore())
{
return "MSStore";
}
- else
- {
- return "was not able to identify Store";
- }
+
+ return "Unable to identify game store.";
}
- internal static bool IsSteam(string directory)
+ private static bool IsSteam()
{
- string checkfile = Path.Combine(directory, "steam_api64.dll");
- if (File.Exists(checkfile))
- {
- return true;
- }
- return false;
+ return PlatformServicesUtils.IsRuntimePluginDllPresent("CSteamworks");
}
- internal static bool IsEpic(string directory)
+ private static bool IsEpic()
{
- string checkfolder = Path.Combine(directory, ".eggstore");
- if (Directory.Exists(checkfolder))
- {
- return true;
- }
- return false;
+ return Array.IndexOf(Environment.GetCommandLineArgs(), "-EpicPortal") != -1;
}
- internal static bool IsMSStore(string directory)
+ private static bool IsMSStore()
{
- string checkfile = Path.Combine(directory, "MicrosoftGame.config");
- if (File.Exists(checkfile))
- {
- return true;
- }
- return false;
+ return PlatformServicesUtils.IsRuntimePluginDllPresent("XGamingRuntimeThunks");
}
- internal static bool NonValidStore(string folder)
+ private static bool NonValidStore()
{
- string steamDll = Path.Combine(folder, PirateCheck.Steamapi);
- if (File.Exists(steamDll))
- {
- FileInfo fileInfo = new FileInfo(steamDll);
-
- if (fileInfo.Length > PirateCheck.Steamapilengh)
- {
- return true;
- }
- }
-
- foreach (string file in PirateCheck.CrackedFiles)
- {
- if (File.Exists(Path.Combine(folder, file)))
- {
- return false;
- }
- }
- return false;
+ return PirateCheck.PirateDetected;
}
}
}
diff --git a/QModManager/QModManager.csproj b/QModManager/QModManager.csproj
index 620039b5..e630c5d9 100644
--- a/QModManager/QModManager.csproj
+++ b/QModManager/QModManager.csproj
@@ -137,6 +137,7 @@
+
diff --git a/QModManager/Utility/Config.cs b/QModManager/Utility/Config.cs
index 2153a8b9..45dbd681 100644
--- a/QModManager/Utility/Config.cs
+++ b/QModManager/Utility/Config.cs
@@ -35,6 +35,17 @@ internal static bool EnableDevMode
set => Set("Enable developer mode", value);
}
+ internal static bool EnableModListMenu
+ {
+ get => Get("Enable Mod List Menu", true);
+ set => Set("Enable Mod List Menu", value);
+ }
+ internal static bool ShowWarnOnLoadSecondSave
+ {
+ get => Get("Show Warning on Loading Second Save", false);
+ set => Set("Show Warning on Loading Second Save", value);
+ }
+
private static readonly string ConfigPath = Path.Combine(Environment.CurrentDirectory, "qmodmanager-config.json");
private static Dictionary Cfg = new Dictionary();
diff --git a/QModManager/Utility/Dialog.cs b/QModManager/Utility/Dialog.cs
index 289c060e..6d5e5ad1 100644
--- a/QModManager/Utility/Dialog.cs
+++ b/QModManager/Utility/Dialog.cs
@@ -10,6 +10,7 @@
using QModManager.Patching;
using UnityEngine;
using UnityEngine.UI;
+ using UWE;
internal class Dialog
{
@@ -43,6 +44,7 @@ internal class Button
}
});
internal static readonly Button Close = new Button("Close", () => { });
+ internal static readonly Button CancelModChanges = new Button("Cancel", () => { });
internal static readonly Button Download = new Button("Download", () =>
{
if (Patcher.CurrentlyRunningGame == QModGame.Subnautica)
@@ -50,12 +52,21 @@ internal class Button
else
Process.Start(VersionCheck.bzNexus);
});
-
internal static readonly Button Pirate = new Button("Close", () =>
{
Process.Start("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
Application.Quit();
});
+ internal static readonly Button ExitGame = new Button("Exit Game", () =>
+ {
+ Application.Quit();
+ }
+ );
+ internal static readonly Button ApplyModChanges = new Button("Apply", () =>
+ {
+ CoroutineHost.StartCoroutine(ApplychangesandExit());
+ }
+ );
internal Button() { }
internal Button(string text, Action action)
@@ -65,6 +76,23 @@ internal Button(string text, Action action)
}
}
+ public static IEnumerator ApplychangesandExit()
+ {
+ //Apply every pending Mod Change
+ foreach (QMod _mod in OptionsManager.ModListPendingChanges)
+ {
+ IOUtilities.ChangeModStatustoFile(_mod);
+ }
+ //Clear the List to make sure the Check after the Button works valid when the User Canceled or the Quit Game didn't executed correct.
+ OptionsManager.ModListPendingChanges.Clear();
+
+ //It seems that the Filewriter Process need some time after saving. Otherwise the Game crashes.
+ yield return new WaitForSecondsRealtime(1);
+
+ //Close the Game now.
+ Application.Quit();
+ }
+
internal enum DialogColor
{
Red,
diff --git a/QModManager/Utility/IOUtilities.cs b/QModManager/Utility/IOUtilities.cs
index 382cb8ca..e031057f 100644
--- a/QModManager/Utility/IOUtilities.cs
+++ b/QModManager/Utility/IOUtilities.cs
@@ -1,7 +1,13 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Text;
+using QModManager.Patching;
+
+#if SUBNAUTICA_STABLE
+using Oculus.Newtonsoft.Json;
+#else
+using Newtonsoft.Json;
+#endif
namespace QModManager.Utility
{
@@ -54,22 +60,32 @@ internal static void WriteFolderStructure(string directory)
string[] files = Directory.GetFiles(directory);
for (int i = 1; i <= files.Length; i++)
{
- FileInfo fileinfo = new FileInfo(files[i - 1]);
- if (i != files.Length)
- Console.WriteLine($"{GenerateSpaces(0)}|---- {fileinfo.Name} ({ParseSize(fileinfo.Length)})");
- else
- Console.WriteLine($"{GenerateSpaces(0)}|---- {fileinfo.Name} ({ParseSize(fileinfo.Length)})");
+ try
+ {
+ FileInfo fileinfo = new FileInfo(files[i - 1]);
+ if (i != files.Length)
+ Console.WriteLine($"{GenerateSpaces(0)}|---- {fileinfo.Name} ({ParseSize(fileinfo.Length)})");
+ else
+ Console.WriteLine($"{GenerateSpaces(0)}`---- {fileinfo.Name} ({ParseSize(fileinfo.Length)})");
+ }
+ catch (Exception e)
+ {
+ if (i != files.Length)
+ Console.WriteLine($"{GenerateSpaces(0)}|---- ERROR ON GETTING FILE INFORMATIONS - {e.Message}");
+ else
+ Console.WriteLine($"{GenerateSpaces(0)}`---- ERROR ON GETTING FILE INFORMATIONS - {e.Message}");
+ }
}
}
catch (Exception e)
{
throw e;
}
- }
+}
internal static void WriteFolderStructureRecursively(string directory, int spaces = 0)
{
try
- {
+ {
DirectoryInfo dirInfo = new DirectoryInfo(directory);
Console.WriteLine($"{GenerateSpaces(spaces)}|---+ {dirInfo.Name}");
@@ -87,11 +103,37 @@ internal static void WriteFolderStructureRecursively(string directory, int space
string[] files = Directory.GetFiles(directory);
for (int i = 1; i <= files.Length; i++)
{
- FileInfo fileinfo = new FileInfo(files[i - 1]);
- if (i != files.Length)
- Console.WriteLine($"{GenerateSpaces(spaces + 4)}|---- {fileinfo.Name} ({ParseSize(fileinfo.Length)})");
- else
- Console.WriteLine($"{GenerateSpaces(spaces + 4)}`---- {fileinfo.Name} ({ParseSize(fileinfo.Length)})");
+ try
+ {
+ FileInfo fileinfo = new FileInfo(files[i - 1]);
+ if (fileinfo.Name == "mod.json")
+ {
+ var modjson = JsonConvert.DeserializeObject(File.ReadAllText(fileinfo.FullName));
+ if (i != files.Length)
+ {
+
+ Console.WriteLine($"{GenerateSpaces(spaces + 4)}|---- {fileinfo.Name} [{modjson.Id} v{modjson.Version} by {modjson.Author} for {modjson.Game}] ({ParseSize(fileinfo.Length)})");
+ }
+ else
+ {
+ Console.WriteLine($"{GenerateSpaces(spaces + 4)}`---- {fileinfo.Name} [{modjson.Id} v{modjson.Version} by {modjson.Author} for {modjson.Game}] ({ParseSize(fileinfo.Length)})");
+ }
+ }
+ else
+ {
+ if (i != files.Length)
+ Console.WriteLine($"{GenerateSpaces(spaces + 4)}|---- {fileinfo.Name} ({ParseSize(fileinfo.Length)})");
+ else
+ Console.WriteLine($"{GenerateSpaces(spaces + 4)}`---- {fileinfo.Name} ({ParseSize(fileinfo.Length)})");
+ }
+ }
+ catch (Exception e)
+ {
+ if (i != files.Length)
+ Console.WriteLine($"{GenerateSpaces(spaces + 4)}|---- ERROR ON GETTING FILE INFORMATIONS - {e.Message}");
+ else
+ Console.WriteLine($"{GenerateSpaces(spaces + 4)}`---- ERROR ON GETTING FILE INFORMATIONS - {e.Message}");
+ }
}
}
catch (Exception e)
@@ -126,5 +168,46 @@ internal static string GenerateSpaces(int spaces)
s += "| ";
return s;
}
+
+ internal static void ChangeModStatustoFile(QMod qmod)
+ {
+ //Get the Configfile (The mod.json in the Mod Directory)
+ string modconfigpath = Path.Combine(Path.GetFullPath(qmod.SubDirectory), "mod.json");
+ if (File.Exists(modconfigpath))
+ {
+ //we doing it with a Dictionary instead of because we have Data in the File that is NOT handles by QMM we would loose this information and that is bad.
+ var modconfig = JsonConvert.DeserializeObject>(File.ReadAllText(modconfigpath));
+
+ //Modify the Configfile
+ foreach (var kvp in modconfig)
+ {
+ if (kvp.Key.ToLower() == "enable")
+ {
+ modconfig[kvp.Key] = qmod.Enable;
+ break;
+ }
+ }
+
+ //Create a JSON Format so it will be Readable by User in a Text Editor
+ Formatting myformat = new Formatting();
+ myformat = Formatting.Indented;
+
+ //Save it back to File
+ string jsonstr = JsonConvert.SerializeObject(modconfig, myformat);
+ try
+ {
+ File.WriteAllText(modconfigpath, jsonstr);
+ Logger.Log(Logger.Level.Info, $"IOUtilities - ChangeModStatustoFile - Enabled Status Update for {qmod.Id} was succesful written to: {modconfigpath}");
+ }
+ catch (Exception ex)
+ {
+ Logger.Log(Logger.Level.Error, $"ErrorID:5713/36B - Saving mod.json for Mod {qmod.Id} to {modconfigpath} failed. - Was the File open in a other Program ? Permission Error ? Original Message:\n {ex.Message}");
+ }
+ }
+ else
+ {
+ Logger.Log(Logger.Level.Error, $"ErrorID:5713/17A - File {modconfigpath} does not exist! - Mod {qmod.Id}");
+ }
+ }
}
}