Files
ClientModLoader/Plugin.ClientModLoader/ModInjector.cs
pas2704 e475c32843
Some checks failed
Build / Compute Version (push) Successful in 5s
Build / Build Nuget package (push) Failing after 6s
build workflow and spacing cleanup
2025-05-11 16:37:46 -04:00

103 lines
4.2 KiB
C#

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<ulong> Mods = [];
private static readonly List<MyObjectBuilder_Checkpoint.ModItem> AdditionalFilledModItems = [];
[HarmonyPatch(typeof(MyWorkshop), nameof(MyWorkshop.DownloadWorldModsBlockingInternal))]
[HarmonyPrefix]
private static void DownloadModsBlockingPrefix(ref List<MyObjectBuilder_Checkpoint.ModItem> mods, ref List<MyObjectBuilder_Checkpoint.ModItem> __state)
{
AdditionalFilledModItems.Clear();
__state = mods;
AppendToList(ref mods);
}
[HarmonyPatch(typeof(MyWorkshop), nameof(MyWorkshop.DownloadWorldModsBlockingInternal))]
[HarmonyPostfix]
private static void DownloadModsBlockingPostfix(List<MyObjectBuilder_Checkpoint.ModItem> mods, List<MyObjectBuilder_Checkpoint.ModItem> __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<CodeInstruction> DownloadModsAsyncTranspiler(IEnumerable<CodeInstruction> instructions)
{
var getCount = AccessTools.PropertyGetter(typeof(List<MyObjectBuilder_Checkpoint.ModItem>), nameof(List<MyObjectBuilder_Checkpoint.ModItem>.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<ulong>), nameof(HashSet<ulong>.Count))),
new(OpCodes.Add)
)
.Instructions();
}
[HarmonyPatch(typeof(MyDefinitionManager), nameof(MyDefinitionManager.LoadData))]
[HarmonyPrefix]
private static void LoadDefinitionsPrefix(ref List<MyObjectBuilder_Checkpoint.ModItem> mods) => AppendToList(ref mods);
[HarmonyPatch(typeof(MyScriptManager), nameof(MyScriptManager.LoadData))]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> LoadScriptsTranspiler(IEnumerable<CodeInstruction> 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<MyObjectBuilder_Checkpoint.ModItem>), 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<MyObjectBuilder_Checkpoint.ModItem> mods)
{
// copy
mods = mods.ToList();
mods.AddRange(AdditionalFilledModItems.Count > 0
? AdditionalFilledModItems
: Mods.Select(mod => new MyObjectBuilder_Checkpoint.ModItem(mod, "Steam")));
}
}