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}"); + } + } } }