add unloading for mod assemblies

This commit is contained in:
zznty
2022-10-29 00:26:23 +07:00
parent 0035d12789
commit 7204815c0c
2 changed files with 87 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Loader;
using CringeLauncher.Utils;
using HarmonyLib;
using NLog;
using Sandbox.Game.World;
using VRage.Scripting;
namespace CringeLauncher.Patches;
[HarmonyPatch]
public static class ModScriptCompilerPatch
{
private static ILogger Log = LogManager.GetCurrentClassLogger();
private static AssemblyLoadContext _context = new(null, true);
static ModScriptCompilerPatch()
{
MySession.OnUnloaded += OnUnloaded;
}
private static void OnUnloaded()
{
if (!_context.Assemblies.Any())
return;
Log.Info("Unloading mod scripts context");
_context.Unload();
_context = new(null, true);
}
private static MethodInfo TargetMethod()
{
return MethodTools.AsyncMethodBody(
AccessTools.Method(typeof(MyScriptCompiler), nameof(MyScriptCompiler.Compile)));
}
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
var ins = instructions.ToList();
for (var i = 0; i < ins.Count; i++)
{
var instruction = ins[i];
if (instruction.Calls(AccessTools.Method(typeof(Assembly), nameof(Assembly.Load),
new[] { typeof(byte[]) })))
{
ins[i - 1] = new(OpCodes.Nop); // memStream toArray call
ins[i] = CodeInstruction.CallClosure((Stream assembly) => _context.LoadFromStream(assembly));
}
else if (instruction.Calls(AccessTools.Method(typeof(Assembly), nameof(Assembly.Load),
new[] { typeof(byte[]), typeof(byte[]) })))
{
ins[i - 4] = new(OpCodes.Nop); // memStream toArray calls
ins[i - 1] = new(OpCodes.Nop);
ins[i] = CodeInstruction.CallClosure((Stream assembly, Stream assemblySymbols) =>
_context.LoadFromStream(assembly, assemblySymbols));
}
}
return ins;
}
}

View File

@@ -0,0 +1,20 @@
using System.Reflection;
using HarmonyLib;
using MonoMod.Utils;
namespace CringeLauncher.Utils;
public static class MethodTools
{
public static MethodInfo AsyncMethodBody(MethodInfo method)
{
var (_, operand) = PatchProcessor.ReadMethodBody(method).First();
if (operand is not LocalVariableInfo localVar)
throw new InvalidOperationException($"Method {method.GetID()} does not contain a valid async state machine");
return AccessTools.Method(localVar.LocalType, "MoveNext") ??
throw new InvalidOperationException(
$"Async State machine of method {method.GetID()} does not contain a valid MoveNext method");
}
}