Skip to content

Commit

Permalink
feat: Add EmitStaticDelegate helper method
Browse files Browse the repository at this point in the history
  • Loading branch information
psyGamer committed Dec 15, 2024
1 parent 7852cc2 commit 54349c2
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 22 deletions.
40 changes: 20 additions & 20 deletions CelesteTAS-EverestInterop/Source/EverestInterop/EntityDataHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,31 +227,31 @@ private static void StrawberrySeedOnCtor(On.Celeste.StrawberrySeed.orig_ctor ori
private static void ModSpawnEntity(ILContext il) {
ILCursor cursor = new(il);

if (cursor.TryGotoNext(
i => i.OpCode == OpCodes.Callvirt && i.Operand.ToString() == "System.Void Monocle.Scene::Add(Monocle.Entity)")) {
cursor.Emit(OpCodes.Dup).Emit(OpCodes.Ldarg_0);
if (cursor.TryGotoNext(ins => ins.OpCode == OpCodes.Callvirt && ins.Operand.ToString() == "System.Void Monocle.Scene::Add(Monocle.Entity)")) {
cursor.EmitDup();
cursor.EmitLdarg0();

// TODO: Better match
if (il.ToString().Contains("ldfld Celeste.SeekerStatue Celeste.SeekerStatue/<>c__DisplayClass3_0::<>4__this")
&& ModUtils.VanillaAssembly.GetType("Celeste.SeekerStatue+<>c__DisplayClass3_0")?.GetFieldInfo("<>4__this") is { } seekerStatue
) {
cursor.Emit(OpCodes.Ldfld, seekerStatue);
}

cursor.EmitDelegate<Action<Entity, Entity>>(SetCustomEntityData);
}
}

private static void SetCustomEntityData(Entity spawnedEntity, Entity entity) {
if (entity.GetEntityData() is { } entityData) {
EntityData clonedEntityData = entityData.ShallowClone();
if (spawnedEntity is FireBall fireBall) {
clonedEntityData.ID = clonedEntityData.ID * -100 - fireBall.index;
} else if (entity is CS03_OshiroRooftop) {
clonedEntityData.ID = 2;
} else {
clonedEntityData.ID *= -1;
cursor.EmitLdfld(seekerStatue);
}

spawnedEntity.SetEntityData(clonedEntityData);
cursor.EmitStaticDelegate("SetCustomEntityData", static (Entity spawnedEntity, Entity entity) => {
if (entity.GetEntityData() is { } entityData) {
EntityData clonedEntityData = entityData.ShallowClone();
if (spawnedEntity is FireBall fireBall) {
clonedEntityData.ID = clonedEntityData.ID * -100 - fireBall.index;
} else if (entity is CS03_OshiroRooftop) {
clonedEntityData.ID = 2;
} else {
clonedEntityData.ID *= -1;
}

spawnedEntity.SetEntityData(clonedEntityData);
}
});
}
}

Expand Down
142 changes: 140 additions & 2 deletions CelesteTAS-EverestInterop/Source/Utils/HookHelper.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
using System;
using Celeste;
using Celeste.Mod;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using System.Reflection;
using JetBrains.Annotations;
using Mono.Cecil;
using Mono.Cecil.Cil;
using MonoMod.Cil;
using MonoMod.RuntimeDetour;
using MonoMod.Utils;
using System.Linq;
using System.Runtime.Loader;
using TAS.Module;

namespace TAS.Utils;
Expand Down Expand Up @@ -221,4 +227,136 @@ public static void ReturnZeroMethod(Type conditionType, string conditionMethodNa
}
}
}
}

/// Emits a call to a static delegate function.
/// Accessing captures is not allowed
public static void EmitStaticDelegate<T>(this ILCursor cursor, T cb) where T : Delegate
=> cursor.EmitStaticDelegate("Delegate", cb);

/// Emits a call to a static delegate function.
/// Accessing captures is not allowed
public static void EmitStaticDelegate<T>(this ILCursor cursor, string methodName, T cb) where T : Delegate {
// Simple static method group
if (cb.GetInvocationList().Length == 1 && cb.Target == null) {
cursor.EmitCall(cb.Method);
return;
}

var methodDef = cb.Method.ResolveDefinition();

// Extract hook name from delegate
string hookName = cb.Method.Name.Split('>')[0][1..];
string name = $"{hookName}_{methodName}";

var parameters = cb.Method.GetParameters();

var dynamicMethod = new DynamicMethodDefinition(name,
cb.Method.ReturnType,
parameters
.Select(p => p.ParameterType)
.ToArray());
dynamicMethod.Definition.Body = methodDef.Body;
for (int i = 0; i < dynamicMethod.Definition.Parameters.Count; i++) {
dynamicMethod.Definition.Parameters[i].Name = parameters[i].Name;
}

// Shift over arguments, since "this" was removed
var processor = dynamicMethod.GetILProcessor();
foreach (var instr in processor.Body.Instructions) {
if (!instr.MatchLdarg(out int index)) {
continue;
}

switch (index) {
case 0:
throw new Exception("Using captured variables inside a static delegate is not allowed");

case 1:
instr.OpCode = OpCodes.Ldarg_0;
break;
case 2:
instr.OpCode = OpCodes.Ldarg_1;
break;
case 3:
instr.OpCode = OpCodes.Ldarg_2;
break;
case 4:
instr.OpCode = OpCodes.Ldarg_3;
break;

default:
instr.OpCode = OpCodes.Ldarg;
instr.Operand = index - 1;
break;
}
}

var targetMethod = dynamicMethod.Generate();
var targetReference = cursor.Context.Import(targetMethod);
targetReference.Name = name;
targetReference.DeclaringType = cb.Method.DeclaringType?.DeclaringType.ResolveDefinition();
targetReference.ReturnType = dynamicMethod.Definition.ReturnType;
targetReference.Parameters.AddRange(dynamicMethod.Definition.Parameters);

cursor.EmitCall(targetReference);
}

/// Resolves the TypeDefinition of a runtime TypeInfo
public static TypeDefinition ResolveDefinition(this Type type) {
var asm = type.Assembly;
var asmName = type.Assembly.GetName();

// Find assembly path
string asmPath;
if (AssemblyLoadContext.GetLoadContext(asm) is EverestModuleAssemblyContext asmCtx) {
asmPath = Everest.Relinker.GetCachedPath(asmCtx.ModuleMeta, asmName.Name);
} else {
asmPath = asm.Location;
}

var asmDef = AssemblyDefinition.ReadAssembly(asmPath, new ReaderParameters { ReadSymbols = false });
var typeDef = asmDef.MainModule.GetType(type.FullName, runtimeName: true).Resolve();

return typeDef;
}

/// Resolves the MethodDefinition of a runtime MethodBase
public static MethodDefinition ResolveDefinition(this MethodBase method) {
var asm = method.DeclaringType!.Assembly;
var asmName = method.DeclaringType!.Assembly.GetName();

// Find assembly path
string asmPath;
if (AssemblyLoadContext.GetLoadContext(asm) is EverestModuleAssemblyContext asmCtx) {
asmPath = Everest.Relinker.GetCachedPath(asmCtx.ModuleMeta, asmName.Name);
} else {
asmPath = asm.Location;
}

var asmDef = AssemblyDefinition.ReadAssembly(asmPath, new ReaderParameters { ReadSymbols = false });
var typeDef = asmDef.MainModule.GetType(method.DeclaringType!.FullName, runtimeName: true).Resolve();
var methodDef = typeDef.Methods.Single(m => {
if (method.Name != m.Name) {
return false;
}

var runtimeParams = method.GetParameters();
if (runtimeParams.Length != m.Parameters.Count) {
return false;
}

for (int i = 0; i < runtimeParams.Length; i++) {
var runtimeParam = runtimeParams[i];
var asmParam = m.Parameters[i];

if (runtimeParam.ParameterType.FullName != asmParam.ParameterType.FullName) {
return false;
}
}

return true;
});

return methodDef;
}
}

0 comments on commit 54349c2

Please sign in to comment.