private static void Hook_BugReportFlagger(ModuleDefMD module) { TypeDef bugReportFlagger = module.GetNuterraType(typeof(Hooks.BugReports)); MethodDef markReportForm = bugReportFlagger.Methods.Single(m => m.Name == nameof(Hooks.BugReports.MarkReportForm)); MethodDef markUserMessage = bugReportFlagger.Methods.Single(m => m.Name == nameof(Hooks.BugReports.MarkUserMessage)); TypeDef bugReporter = module.Find("UIScreenBugReport", isReflectionName: true); TypeDef postIterator = bugReporter.NestedTypes.Single(t => t.FullName.Contains("UIScreenBugReport/<PostIt>")); MethodDef moveNext = postIterator.Methods.Single(m => m.Name == "MoveNext"); FieldDef bodyField = bugReporter.Fields.Single(f => f.Name == "m_Body"); var body = moveNext.Body.Instructions; Instruction formInit = body.First(i => i.OpCode == OpCodes.Newobj); int index = body.IndexOf(formInit); Instruction storeForm = body[index + 1]; FieldDef formField = (FieldDef)storeForm.Operand; index += 2; body.Insert(index++, new Instruction(OpCodes.Ldarg_0)); body.Insert(index++, new Instruction(OpCodes.Ldfld, formField)); body.Insert(index++, new Instruction(OpCodes.Call, markReportForm)); Instruction loadBodyField = body.First(i => i.OpCode == OpCodes.Ldfld && i.Operand == bodyField); index = body.IndexOf(loadBodyField); Instruction readBodyText = body[index + 1]; body.Insert(index + 2, new Instruction(OpCodes.Call, markUserMessage)); }
private static void Hook_StringLookup_GetString(ModuleDefMD module) { TypeDef cecilSource = module.Find("StringLookup", isReflectionName: true); MethodDef sourceMethod = cecilSource.Methods.Single(m => m.Name == "GetString"); TypeDef cecilTarget = module.GetNuterraType(typeof(Hooks.ResourceLookup)); MethodDef targetMethod = cecilTarget.Methods.Single(m => m.Name == nameof(Hooks.ResourceLookup.GetString)); var body = sourceMethod.Body.Instructions; var originalMethodStart = body.First(); int index = 0; body.Insert(index++, OpCodes.Ldarg_0.ToInstruction()); body.Insert(index++, OpCodes.Ldarg_2.ToInstruction()); body.Insert(index++, OpCodes.Call.ToInstruction(targetMethod)); body.Insert(index++, OpCodes.Stloc_0.ToInstruction()); body.Insert(index++, OpCodes.Ldloc_0.ToInstruction()); body.Insert(index++, OpCodes.Brfalse_S.ToInstruction(originalMethodStart)); body.Insert(index++, OpCodes.Ldloc_0.ToInstruction()); body.Insert(index++, OpCodes.Ret.ToInstruction()); /* * 0 0000 ldarg.0 * 1 0001 ldarg.2 * 2 0002 call string Maritaria.BlockLoader::StringLookup_GetString(int32, valuetype LocalisationEnums/StringBanks) * 3 0007 stloc.0 * 4 0008 ldloc.0 * 5 0009 brfalse.s { original method start instruction } * 6 000B ldloc.0 * 7 000C ret * ... remaining method code ... */ }
private static void CreateHook(ModuleDefMD module, string source, Type target, string methodName) { TypeDef sourceType = module.Find(source, isReflectionName: true); MethodDef sourceMethod = sourceType.Methods.Single(m => m.Name == methodName); TypeDef targetType = module.GetNuterraType(target); MethodDef targetMethod = targetType.Methods.Single(m => m.Name == methodName); MethodDefUser clonedSource = CloneMethod(sourceMethod); sourceMethod.Body.Variables.Clear(); var defaultValueLocal = new Local(clonedSource.ReturnType, "defaultValue"); sourceMethod.Body.Variables.Add(defaultValueLocal); var body = sourceMethod.Body.Instructions; body.Clear(); int i = 0; for (int j = 0; j < sourceMethod.Parameters.Count; j++) { body.Insert(i++, OpCodes.Ldarg_S.ToInstruction(sourceMethod.Parameters[j])); } body.Insert(i++, OpCodes.Call.ToInstruction(clonedSource)); body.Insert(i++, OpCodes.Stloc.ToInstruction(defaultValueLocal)); body.Insert(i++, OpCodes.Ldarg_S.ToInstruction(sourceMethod.Parameters[0])); body.Insert(i++, OpCodes.Ldloc.ToInstruction(defaultValueLocal)); for (int j = 1; j < sourceMethod.Parameters.Count; j++) { body.Insert(i++, OpCodes.Ldarg_S.ToInstruction(sourceMethod.Parameters[j])); } body.Insert(i++, OpCodes.Call.ToInstruction(targetMethod)); body.Insert(i++, OpCodes.Ret.ToInstruction()); }
private static void Redirect(ModuleDefMD module, string sourceType, Type targetType, RedirectSettings settings) { TypeDef cecilSource = module.Find(sourceType, isReflectionName: true); MethodDef sourceMethod = cecilSource.Methods.Single(m => m.Name == settings.SourceMethod); TypeDef cecilTarget = module.GetNuterraType(targetType); MethodDef targetMethod = cecilTarget.Methods.Single(m => m.Name == settings.TargetMethod); var body = sourceMethod.Body.Instructions; if (settings.ReplaceBody) { sourceMethod.Body.Variables.Clear(); sourceMethod.Body.ExceptionHandlers.Clear(); body.Clear(); body.Add(OpCodes.Ret.ToInstruction()); } int insertedInstructionCounter = settings.InsertionStart; //TODO: Use insertbefore instead if (settings.AppendToEnd) { if (body.Count > 0) { insertedInstructionCounter = body.Count - 1; //Last IL before ret instruction } } int insertionStart = insertedInstructionCounter; if (settings.PassArguments) { if (sourceMethod.Parameters.Count != targetMethod.Parameters.Count) { throw new Exception($"Parameter count mismatch for {sourceMethod} -> {targetMethod}"); } for (int i = 0; i < sourceMethod.Parameters.Count; i++) { body.Insert(insertedInstructionCounter++, GetLoadArgOpCode(i, sourceMethod, targetMethod)); } } body.Insert(insertedInstructionCounter++, OpCodes.Call.ToInstruction(targetMethod)); if (settings.AppendToEnd) { Instruction ret = body.Last(); foreach (Instruction instr in body) { if (instr.Operand is Instruction) { Instruction jumpTarget = instr.Operand as Instruction; if (jumpTarget == ret) { instr.Operand = body[insertionStart]; } } } } }
private static void Hook_SpriteFetcher_GetSprite(ModuleDefMD module) { TypeDef cecilSource = module.Find("SpriteFetcher", isReflectionName: true); MethodDef sourceMethod = cecilSource.Methods.Single(m => m.FullName == "UnityEngine.Sprite SpriteFetcher::GetSprite(ObjectTypes,System.Int32)"); TypeDef cecilTarget = module.GetNuterraType(typeof(Hooks.ResourceLookup)); MethodDef targetMethod = cecilTarget.Methods.Single(m => m.Name == nameof(Hooks.ResourceLookup.GetSprite)); AssemblyRef unityEngine = module.GetAssemblyRef(new UTF8String("UnityEngine")); TypeRefUser unityEngine_Object = new TypeRefUser(module, new UTF8String("UnityEngine"), new UTF8String("Object"), unityEngine); TypeSig objectSig = unityEngine_Object.ToTypeSig(); MethodSig op_Equality = MethodSig.CreateStatic(module.CorLibTypes.Boolean, objectSig, objectSig); MemberRefUser op_EqualityMethod = new MemberRefUser(module, new UTF8String("op_Inequality"), op_Equality, unityEngine_Object); var body = sourceMethod.Body.Instructions; var originalMethodStart = body.First(); int index = 0; sourceMethod.Body.MaxStack = 6; body.Insert(index++, new Instruction(OpCodes.Ldarg_1)); body.Insert(index++, new Instruction(OpCodes.Ldarg_2)); body.Insert(index++, new Instruction(OpCodes.Call, targetMethod)); body.Insert(index++, new Instruction(OpCodes.Stloc_0)); body.Insert(index++, new Instruction(OpCodes.Ldloc_0)); body.Insert(index++, new Instruction(OpCodes.Ldnull)); body.Insert(index++, new Instruction(OpCodes.Call, op_EqualityMethod)); body.Insert(index++, new Instruction(OpCodes.Brfalse_S, originalMethodStart)); body.Insert(index++, new Instruction(OpCodes.Ldloc_0)); body.Insert(index++, new Instruction(OpCodes.Ret)); /* * 0 0000 ldarg.1 * 1 0001 ldarg.2 * 2 0002 call class [UnityEngine]UnityEngine.Sprite Maritaria.BlockLoader::SpriteFetcher_GetSprite(valuetype ObjectTypes, int32) * 3 0007 stloc.0 * 4 0008 ldloc.0 * 5 0009 ldnull * 6 000A call bool [UnityEngine]UnityEngine.Object::op_Inequality(class [UnityEngine]UnityEngine.Object, class [UnityEngine]UnityEngine.Object) * 7 000F brfalse.s { original method start instruction } * 8 0011 ldloc.0 * 9 0012 ret * ... remaining method code ... */ }
public static void Hook_TankControl_PlayerInput(ModuleDefMD module) { const string methodName = nameof(Hooks.Modules.TankControl.PlayerInput); TypeDef cecilSource = module.Find("TankControl", isReflectionName: true); MethodDef sourceMethod = cecilSource.Methods.Single(m => m.Name == methodName); TypeDef cecilTarget = module.GetNuterraType(typeof(Hooks.Modules.TankControl)); MethodDef targetMethod = cecilTarget.Methods.Single(m => m.Name == methodName); var body = sourceMethod.Body.Instructions; for (int i = 0; i < 9; i++) { body.RemoveAt(0); } body.Insert(0, new Instruction(OpCodes.Ldarg_0)); body.Insert(1, new Instruction(OpCodes.Call, targetMethod)); /* * 0 0000 ldsfld !0 class Singleton/Manager`1<class CameraManager>::inst * 1 0005 callvirt instance bool CameraManager::IsCurrent<class TankCamera>() * 2 000A brtrue 10 (0032) ldarg.0 * 3 000F ldsfld !0 class Singleton/Manager`1<class CameraManager>::inst * 4 0014 callvirt instance bool CameraManager::IsCurrent<class DebugCamera>() * 5 0019 brfalse 50 (00B6) ret * 6 001E ldsfld !0 class Singleton/Manager`1<class CameraManager>::inst * 7 0023 callvirt instance class DebugCamera CameraManager::GetDebugCamera() * 8 0028 callvirt instance bool DebugCamera::get_IsLocked() * 9 002D brfalse 50 (00B6) ret * ... remaining code ... * * Remove all instructions up to 9, then insert: * load self * call hook * * So it becomes: * call hook * if hook returns false then jump to ret instruction * */ }
public static void Hook_ManSaveGame_SaveSaveData(ModuleDefMD module) { const string methodName = nameof(Hooks.Managers.SaveGame.SaveSaveData); TypeDef cecilSource = module.Find("ManSaveGame", isReflectionName: true); MethodDef sourceMethod = cecilSource.Methods.Single(m => m.Name == methodName); TypeDef cecilTarget = module.GetNuterraType(typeof(Hooks.Managers.SaveGame)); MethodDef targetMethod = cecilTarget.Methods.Single(m => m.Name == methodName); var body = sourceMethod.Body.Instructions; body.Insert(0, new Instruction(OpCodes.Ldarg_0)); body.Insert(1, new Instruction(OpCodes.Ldarg_1)); body.Insert(2, new Instruction(OpCodes.Call, targetMethod)); body.Insert(3, OpCodes.Brtrue_S.ToInstruction(body.Last())); /* * Load arguments * Call hook * If hook returns true, jump to last instruction (ret) */ }