using System.Reflection.Emit; using System.Runtime.CompilerServices; using HarmonyLib; using Sandbox.Definitions; using Sandbox.Engine.Networking; using Sandbox.Game.World; using VRage.Game; namespace Plugin.ClientModLoader; [HarmonyPatch] internal static class ModInjector { public static HashSet Mods = []; private static readonly List AdditionalFilledModItems = []; [HarmonyPatch(typeof(MyWorkshop), nameof(MyWorkshop.DownloadWorldModsBlockingInternal))] [HarmonyPrefix] private static void DownloadModsBlockingPrefix(ref List mods, ref List __state) { AdditionalFilledModItems.Clear(); __state = mods; AppendToList(ref mods); } [HarmonyPatch(typeof(MyWorkshop), nameof(MyWorkshop.DownloadWorldModsBlockingInternal))] [HarmonyPostfix] private static void DownloadModsBlockingPostfix(List mods, List __state) { foreach (var mod in mods) { var index = __state.FindIndex(b => b.PublishedFileId == mod.PublishedFileId && b.PublishedServiceName == mod.PublishedServiceName); if (index != -1) { var stateMod = __state[index]; stateMod.SetModData(mod.GetModData()); __state[index] = stateMod; } else if (Mods.Contains(mod.PublishedFileId)) AdditionalFilledModItems.Add(mod); else __state.Add(mod); } } [HarmonyPatch(typeof(MyWorkshop), nameof(MyWorkshop.DownloadModsAsync))] [HarmonyTranspiler] private static IEnumerable DownloadModsAsyncTranspiler(IEnumerable instructions) { var getCount = AccessTools.PropertyGetter(typeof(List), nameof(List.Count)); return new CodeMatcher(instructions) .SearchForward(b => b.Calls(getCount)) .Advance(1) .Insert( CodeInstruction.LoadField(typeof(ModInjector), nameof(Mods)), new(OpCodes.Callvirt, AccessTools.PropertyGetter(typeof(HashSet), nameof(HashSet.Count))), new(OpCodes.Add) ) .Instructions(); } [HarmonyPatch(typeof(MyDefinitionManager), nameof(MyDefinitionManager.LoadData))] [HarmonyPrefix] private static void LoadDefinitionsPrefix(ref List mods) => AppendToList(ref mods); [HarmonyPatch(typeof(MyScriptManager), nameof(MyScriptManager.LoadData))] [HarmonyTranspiler] private static IEnumerable LoadScriptsTranspiler(IEnumerable instructions, ILGenerator generator) { var staticGetter = AccessTools.PropertyGetter(typeof(MySession), nameof(MySession.Static)); var modsGetter = AccessTools.PropertyGetter(typeof(MySession), nameof(MySession.Mods)); return new CodeMatcher(instructions, generator) .Start() .DeclareLocal(typeof(List), out var modsLocal) .CreateLabel(out var start) .InsertAndAdvance( new(OpCodes.Call, staticGetter), new(OpCodes.Call, modsGetter), new(OpCodes.Stloc, modsLocal), new(OpCodes.Ldloc, modsLocal), new(OpCodes.Brfalse, start), new(OpCodes.Ldloca, modsLocal), CodeInstruction.Call(typeof(ModInjector), nameof(AppendToList)) ) .MatchStartForward(CodeMatch.Calls(staticGetter), CodeMatch.Calls(modsGetter)) .Repeat(a => a .SetAndAdvance(OpCodes.Ldloc, modsLocal) .SetAndAdvance(OpCodes.Nop, null)) .Instructions(); } private static void AppendToList(ref List mods) { // copy mods = mods.ToList(); mods.AddRange(AdditionalFilledModItems.Count > 0 ? AdditionalFilledModItems : Mods.Select(mod => new MyObjectBuilder_Checkpoint.ModItem(mod, "Steam"))); } }