diff --git a/Source/Client/EarlyInit.cs b/Source/Client/EarlyInit.cs index 7e565f57..e7d6beb6 100644 --- a/Source/Client/EarlyInit.cs +++ b/Source/Client/EarlyInit.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Reflection; using HarmonyLib; using Multiplayer.Client.Patches; @@ -12,7 +11,6 @@ namespace Multiplayer.Client; public static class EarlyInit { public const string RestartConnectVariable = "MultiplayerRestartConnect"; - public const string RestartConfigsVariable = "MultiplayerRestartConfigs"; internal static void ProcessEnvironment() { @@ -22,11 +20,7 @@ internal static void ProcessEnvironment() Environment.SetEnvironmentVariable(RestartConnectVariable, ""); // Effectively unsets it } - if (!Environment.GetEnvironmentVariable(RestartConfigsVariable).NullOrEmpty()) - { - Multiplayer.restartConfigs = Environment.GetEnvironmentVariable(RestartConfigsVariable) == "true"; - Environment.SetEnvironmentVariable(RestartConfigsVariable, ""); - } + SyncConfigs.Init(); } internal static void EarlyPatches(Harmony harmony) diff --git a/Source/Client/EarlyPatches/SettingsPatches.cs b/Source/Client/EarlyPatches/SettingsPatches.cs index 0a10fcfa..a5075807 100644 --- a/Source/Client/EarlyPatches/SettingsPatches.cs +++ b/Source/Client/EarlyPatches/SettingsPatches.cs @@ -1,6 +1,4 @@ using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Reflection; using HarmonyLib; using Multiplayer.Client.Patches; @@ -104,74 +102,4 @@ static IEnumerable TargetMethods() static bool Prefix() => !TickPatch.Simulating; } - - // Affects both reading and writing - [EarlyPatch] - [HarmonyPatch(typeof(LoadedModManager), nameof(LoadedModManager.GetSettingsFilename))] - static class OverrideConfigsPatch - { - private static Dictionary<(string, string), ModContentPack> modCache = new(); - - static void Postfix(string modIdentifier, string modHandleName, ref string __result) - { - if (!Multiplayer.restartConfigs) - return; - - if (!modCache.TryGetValue((modIdentifier, modHandleName), out var mod)) - { - mod = modCache[(modIdentifier, modHandleName)] = - LoadedModManager.RunningModsListForReading.FirstOrDefault(m => - m.FolderName == modIdentifier - && m.assemblies.loadedAssemblies.Any(a => a.GetTypes().Any(t => t.Name == modHandleName)) - ); - } - - if (mod == null) - return; - - if (JoinData.ignoredConfigsModIds.Contains(mod.ModMetaData.PackageIdNonUnique)) - return; - - // Example: MultiplayerTempConfigs/rwmt.multiplayer-Multiplayer - var newPath = Path.Combine( - GenFilePaths.FolderUnderSaveData(JoinData.TempConfigsDir), - GenText.SanitizeFilename(mod.PackageIdPlayerFacing.ToLowerInvariant() + "-" + modHandleName) - ); - - __result = newPath; - } - } - - [EarlyPatch] - [HarmonyPatch] - static class HugsLib_OverrideConfigsPatch - { - public static string HugsLibConfigOverridenPath; - - private static MethodInfo MethodToPatch = AccessTools.Method("HugsLib.Core.PersistentDataManager:GetSettingsFilePath"); - - static bool Prepare() => MethodToPatch != null; - - static MethodInfo TargetMethod() => MethodToPatch; - - static void Prefix(object __instance) - { - if (!Multiplayer.restartConfigs) - return; - - if (__instance.GetType().Name != "ModSettingsManager") - return; - - var newPath = Path.Combine( - GenFilePaths.FolderUnderSaveData(JoinData.TempConfigsDir), - GenText.SanitizeFilename($"{JoinData.HugsLibId}-{JoinData.HugsLibSettingsFile}") - ); - - if (File.Exists(newPath)) - { - __instance.SetPropertyOrField("OverrideFilePath", newPath); - HugsLibConfigOverridenPath = newPath; - } - } - } } diff --git a/Source/Client/Multiplayer.cs b/Source/Client/Multiplayer.cs index 94ebc317..575526c7 100644 --- a/Source/Client/Multiplayer.cs +++ b/Source/Client/Multiplayer.cs @@ -75,7 +75,6 @@ public static class Multiplayer public static Stopwatch harmonyWatch = new(); public static string restartConnect; - public static bool restartConfigs; public static ModContentPack modContentPack; diff --git a/Source/Client/Networking/JoinData.cs b/Source/Client/Networking/JoinData.cs index 71fca8f2..f8a08446 100644 --- a/Source/Client/Networking/JoinData.cs +++ b/Source/Client/Networking/JoinData.cs @@ -1,12 +1,10 @@ -using System; using System.Collections; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using HarmonyLib; using Ionic.Zlib; -using Multiplayer.Client.EarlyPatches; +using Multiplayer.Client.Util; using Multiplayer.Common; using RimWorld; using Steamworks; @@ -49,7 +47,7 @@ public static byte[] WriteServerData(bool writeConfigs) data.WriteBool(writeConfigs); if (writeConfigs) { - var configs = GetSyncableConfigContents( + var configs = SyncConfigs.GetSyncableConfigContents( activeModsSnapshot.Select(m => m.PackageIdNonUnique).ToList() ); @@ -126,93 +124,13 @@ public static ModMetaData GetInstalledMod(string id) return ModLister.GetModWithIdentifier(id); } - [SuppressMessage("ReSharper", "StringLiteralTypo")] - public static string[] ignoredConfigsModIds = - { - // todo unhardcode it - "rwmt.multiplayer", - "hodlhodl.twitchtoolkit", // contains username - "dubwise.dubsmintmenus", - "dubwise.dubsmintminimap", - "arandomkiwi.rimthemes", - "brrainz.cameraplus", - "giantspacehamster.moody", - "fluffy.modmanager", - "jelly.modswitch", - "betterscenes.rimconnect", // contains secret key for streamer - "jaxe.rimhud", - "telefonmast.graphicssettings", - "derekbickley.ltocolonygroupsfinal", - "dra.multiplayercustomtickrates", // syncs its own settings - "merthsoft.designatorshapes", // settings for UI and stuff meaningless for MP - //"zetrith.prepatcher", - }; - - public const string TempConfigsDir = "MultiplayerTempConfigs"; - public const string HugsLibId = "unlimitedhugs.hugslib"; - public const string HugsLibSettingsFile = "ModSettings"; - - public static List GetSyncableConfigContents(List modIds) - { - var list = new List(); - - foreach (var modId in modIds) - { - if (ignoredConfigsModIds.Contains(modId)) continue; - - var mod = LoadedModManager.RunningModsListForReading.FirstOrDefault(m => m.PackageIdPlayerFacing.ToLowerInvariant() == modId); - if (mod == null) continue; - - foreach (var modInstance in LoadedModManager.runningModClasses.Values) - { - if (modInstance.modSettings == null) continue; - if (!mod.assemblies.loadedAssemblies.Contains(modInstance.GetType().Assembly)) continue; - - var instanceName = modInstance.GetType().Name; - - // This path may point to configs downloaded from the server - var file = LoadedModManager.GetSettingsFilename(mod.FolderName, instanceName); - - if (File.Exists(file)) - list.Add(GetConfigCatchError(file, modId, instanceName)); - } - } - - // Special case for HugsLib - if (modIds.Contains(HugsLibId) && GetInstalledMod(HugsLibId) is { Active: true }) - { - var hugsConfig = - HugsLib_OverrideConfigsPatch.HugsLibConfigOverridenPath ?? - Path.Combine(GenFilePaths.SaveDataFolderPath, "HugsLib", "ModSettings.xml"); - - if (File.Exists(hugsConfig)) - list.Add(GetConfigCatchError(hugsConfig, HugsLibId, HugsLibSettingsFile)); - } - - return list; - - ModConfig GetConfigCatchError(string path, string id, string file) - { - try - { - var configContents = File.ReadAllText(path); - return new ModConfig(id, file, configContents); - } - catch (Exception e) - { - Log.Error($"Exception getting config contents {file}: {e}"); - return new ModConfig(id, "ERROR", ""); - } - } - } - public static bool CompareToLocal(RemoteData remote) { return remote.remoteRwVersion == VersionControl.CurrentVersionString && remote.CompareMods(activeModsSnapshot) == ModListDiff.None && remote.remoteFiles.DictsEqual(modFilesSnapshot) && - (!remote.hasConfigs || remote.remoteModConfigs.EqualAsSets(GetSyncableConfigContents(remote.RemoteModIds.ToList()))); + (!remote.hasConfigs || remote.remoteModConfigs.EqualAsSets(SyncConfigs.GetSyncableConfigContents(remote.RemoteModIds.ToList()))); } internal static void TakeModDataSnapshot() diff --git a/Source/Client/Util/SyncConfigs.cs b/Source/Client/Util/SyncConfigs.cs new file mode 100644 index 00000000..f03ef9cf --- /dev/null +++ b/Source/Client/Util/SyncConfigs.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Reflection; +using HarmonyLib; +using Multiplayer.Client.Patches; +using Verse; + +namespace Multiplayer.Client.Util; + +/// Responsible for saving a server's config files and retrieving them later. +public static class SyncConfigs +{ + private static readonly string TempConfigsPath = GenFilePaths.FolderUnderSaveData("MultiplayerTempConfigs"); + private const string RestartConfigsVariable = "MultiplayerRestartConfigs"; + + public static bool Applicable { private set; get; } + + // The env variable will get inherited by the child process started in GenCommandLine.Restart + public static void MarkApplicableForChildProcess() => + Environment.SetEnvironmentVariable(RestartConfigsVariable, "true"); + + public static void Init() + { + Applicable = Environment.GetEnvironmentVariable(RestartConfigsVariable) is "true"; + Environment.SetEnvironmentVariable(RestartConfigsVariable, ""); + } + + public static void SaveConfigs(List configs) + { + var tempDir = new DirectoryInfo(TempConfigsPath); + tempDir.Delete(true); + tempDir.Create(); + + foreach (var config in configs) + File.WriteAllText(Path.Combine(TempConfigsPath, $"Mod_{config.ModId}_{config.FileName}.xml"), config.Contents); + } + + [SuppressMessage("ReSharper", "StringLiteralTypo")] + public static string[] ignoredConfigsModIds = + [ + // todo unhardcode it + "rwmt.multiplayer", + "hodlhodl.twitchtoolkit", // contains username + "dubwise.dubsmintmenus", + "dubwise.dubsmintminimap", + "arandomkiwi.rimthemes", + "brrainz.cameraplus", + "giantspacehamster.moody", + "fluffy.modmanager", + "jelly.modswitch", + "betterscenes.rimconnect", // contains secret key for streamer + "jaxe.rimhud", + "telefonmast.graphicssettings", + "derekbickley.ltocolonygroupsfinal", + "dra.multiplayercustomtickrates", // syncs its own settings + "merthsoft.designatorshapes" // settings for UI and stuff meaningless for MP + //"zetrith.prepatcher", + ]; + + public const string HugsLibId = "unlimitedhugs.hugslib"; + public const string HugsLibSettingsFile = "ModSettings"; + + public static List GetSyncableConfigContents(List modIds) + { + var list = new List(); + + foreach (var modId in modIds.Except(ignoredConfigsModIds)) + { + var mod = LoadedModManager.RunningMods + .FirstOrDefault(m => m.PackageIdPlayerFacing.EqualsIgnoreCase(modId)); + if (mod == null) continue; + + foreach (var modInstance in LoadedModManager.runningModClasses.Values) + { + if (modInstance.modSettings == null) continue; + if (!mod.assemblies.loadedAssemblies.Contains(modInstance.GetType().Assembly)) continue; + + var instanceName = modInstance.GetType().Name; + + // This path may point to configs downloaded from the server + var file = LoadedModManager.GetSettingsFilename(mod.FolderName, instanceName); + + if (File.Exists(file)) + list.Add(GetConfigCatchError(file, modId, instanceName)); + } + } + + // Special case for HugsLib + if (modIds.Contains(HugsLibId) && JoinData.GetInstalledMod(HugsLibId) is { Active: true }) + { + var hugsConfig = HugsLib_OverrideConfigsPatch.HugsLibConfigIsOverriden + ? HugsLib_OverrideConfigsPatch.HugsLibConfigOverridePath + : Path.Combine(GenFilePaths.SaveDataFolderPath, "HugsLib", "ModSettings.xml"); + + if (File.Exists(hugsConfig)) + list.Add(GetConfigCatchError(hugsConfig, HugsLibId, HugsLibSettingsFile)); + } + + return list; + + ModConfig GetConfigCatchError(string path, string id, string file) + { + try + { + return new ModConfig(id, file, Contents: File.ReadAllText(path)); + } + catch (Exception e) + { + Log.Error($"Exception getting config contents {file}: {e}"); + return new ModConfig(id, "ERROR", ""); + } + } + } + + public static string GetConfigPath(string modId, string handleName) => + Path.Combine(TempConfigsPath, GenText.SanitizeFilename($"Mod_{modId}_{handleName}.xml")); +} + +// Affects both reading and writing +[EarlyPatch] +[HarmonyPatch(typeof(LoadedModManager), nameof(LoadedModManager.GetSettingsFilename))] +static class OverrideConfigsPatch +{ + private static Dictionary<(string, string), ModContentPack> modCache = new(); + + static void Postfix(string modIdentifier, string modHandleName, ref string __result) + { + if (!SyncConfigs.Applicable) return; + if (!modCache.TryGetValue((modIdentifier, modHandleName), out var mod)) + { + mod = modCache[(modIdentifier, modHandleName)] = + LoadedModManager.RunningModsListForReading.FirstOrDefault(m => + m.FolderName == modIdentifier + && m.assemblies.loadedAssemblies.Any(a => a.GetTypes().Any(t => t.Name == modHandleName)) + ); + } + + if (mod == null) return; + if (SyncConfigs.ignoredConfigsModIds.Contains(mod.ModMetaData.PackageIdNonUnique)) return; + + __result = SyncConfigs.GetConfigPath(mod.PackageIdPlayerFacing.ToLowerInvariant(), modHandleName); + } +} + +[EarlyPatch] +[HarmonyPatch] +static class HugsLib_OverrideConfigsPatch +{ + public static string HugsLibConfigOverridePath = + SyncConfigs.GetConfigPath(SyncConfigs.HugsLibId, SyncConfigs.HugsLibSettingsFile); + public static bool HugsLibConfigIsOverriden => File.Exists(HugsLibConfigOverridePath); + + private static readonly MethodInfo MethodToPatch = + AccessTools.Method("HugsLib.Core.PersistentDataManager:GetSettingsFilePath"); + + static bool Prepare() => MethodToPatch != null; + + static MethodInfo TargetMethod() => MethodToPatch; + + static void Prefix(object __instance) + { + if (!SyncConfigs.Applicable) return; + if (__instance.GetType().Name != "ModSettingsManager") return; + if (!HugsLibConfigIsOverriden) return; + __instance.SetPropertyOrField("OverrideFilePath", HugsLibConfigOverridePath); + } +} diff --git a/Source/Client/Windows/JoinDataWindow.cs b/Source/Client/Windows/JoinDataWindow.cs index ffd2aff2..31a73cc8 100644 --- a/Source/Client/Windows/JoinDataWindow.cs +++ b/Source/Client/Windows/JoinDataWindow.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Linq; using Multiplayer.Client.Util; using Multiplayer.Common; @@ -168,7 +167,7 @@ void AddConfigs(List one, List two, NodeStatus notInTwo) } } - var localConfigs = JoinData.GetSyncableConfigContents(remote.RemoteModIds.ToList()); + var localConfigs = SyncConfigs.GetSyncableConfigContents(remote.RemoteModIds.ToList()); if (remote.hasConfigs) { @@ -750,13 +749,8 @@ private void DoRestart() if (applyConfigs) { - var tempPath = GenFilePaths.FolderUnderSaveData(JoinData.TempConfigsDir); - var tempDir = new DirectoryInfo(tempPath); - tempDir.Delete(true); - tempDir.Create(); - - foreach (var config in data.remoteModConfigs) - File.WriteAllText(Path.Combine(tempPath, $"{config.ModId}-{config.FileName}"), config.Contents); + SyncConfigs.SaveConfigs(data.remoteModConfigs); + SyncConfigs.MarkApplicableForChildProcess(); } var connectTo = data.remoteSteamHost != null @@ -765,7 +759,6 @@ private void DoRestart() // The env variables will get inherited by the child process started in GenCommandLine.Restart Environment.SetEnvironmentVariable(EarlyInit.RestartConnectVariable, connectTo); - Environment.SetEnvironmentVariable(EarlyInit.RestartConfigsVariable, applyConfigs ? "true" : "false"); GenCommandLine.Restart(); }