/// <summary> /// Inline classes marked as [Patch], copying fields and replacing method implementations. /// As you can probably guess from the code, this is wholly incomplete and will certainly break and have to be /// extended in the future. /// </summary> public static void Patch(string modModulePath) { var baseModule = ModuleDefinition.ReadModule("PatchedTowerFall.exe"); var modModule = ModuleDefinition.ReadModule(modModulePath); Func <TypeReference, bool> patchType = (type) => { if (type.Scope == modModule) { return(type.Resolve().CustomAttributes.Any(attr => attr.AttributeType.FullName == "Patcher.PatchAttribute")); } return(false); }; // baseModule won't recognize MemberReferences from modModule without Import(), so recursively translate them. // Furthermore, we have to redirect any references to members in [Patch] classes. Func <TypeReference, TypeReference> mapType = null; mapType = (modType) => { if (modType.IsGenericParameter) { return(modType); } if (modType.IsArray) { var type = mapType(((ArrayType)modType).ElementType); return(new ArrayType(type)); } if (patchType(modType)) { modType = modType.Resolve().BaseType; } return(baseModule.Import(modType)); }; Action <MethodReference, MethodReference> mapParams = (modMethod, method) => { foreach (var param in modMethod.Parameters) { method.Parameters.Add(new ParameterDefinition(mapType(param.ParameterType))); } }; Func <MethodReference, MethodReference> mapMethod = (modMethod) => { var method = new MethodReference(modMethod.Name, mapType(modMethod.ReturnType), mapType(modMethod.DeclaringType)); method.HasThis = modMethod.HasThis; mapParams(modMethod, method); return(method as MethodReference); }; Func <MethodDefinition, string, MethodDefinition> cloneMethod = (modMethod, prefix) => { var method = new MethodDefinition(prefix + modMethod.Name, modMethod.Attributes, mapType(modMethod.ReturnType)); mapParams(modMethod, method); foreach (var modParam in modMethod.GenericParameters) { var param = new GenericParameter(modParam.Owner); method.GenericParameters.Add(param); } return(method); }; MyMInput.PatchModule(baseModule); MyMainMenu.PatchModule(baseModule); MyMenuButtons.PatchModule(baseModule); MyMenuInput.PatchModule(baseModule); MyPlayerInput.PatchModule(baseModule); MyReadyBanner.PatchModule(baseModule); MyRoundLogic.PatchModule(baseModule); MySession.PatchModule(baseModule); MyTFCommands.PatchModule(baseModule); MyTFGame.PatchModule(baseModule); MyTeamSelectOverlay.PatchModule(baseModule); MyVariant.PatchModule(baseModule); MyVersusAwards.PatchModule(baseModule); MyVersusRoundResults.PatchModule(baseModule); CleanMyVersusMatchResults.CleanModule(baseModule); CleanMyVersusPlayerMatchResults.CleanModule(baseModule); MyAwardInfo.PatchModule(baseModule); VersusPlayerMatchResultsAssembly.PatchModule(baseModule); CleanMyVariantPerPlayer.CleanModule(baseModule); foreach (TypeDefinition modType in modModule.Types.SelectMany(CecilExtensions.AllNestedTypes)) { if (patchType(modType)) { var type = baseModule.AllNestedTypes().Single(t => t.FullName == modType.BaseType.FullName); // copy over fields including their custom attributes foreach (var field in modType.Fields) { if (field.DeclaringType == modType) { var newField = new FieldDefinition(field.Name, field.Attributes, mapType(field.FieldType)); foreach (var attribute in field.CustomAttributes) { newField.CustomAttributes.Add(new CustomAttribute(mapMethod(attribute.Constructor), attribute.GetBlob())); } type.Fields.Add(newField); } } // copy over or replace methods foreach (var method in modType.Methods) { if (method.DeclaringType == modType) { var original = type.Methods.SingleOrDefault(m => m.Signature() == method.Signature()); MethodDefinition savedMethod = null; if (original == null) { type.Methods.Add(original = cloneMethod(method, "")); } else { savedMethod = cloneMethod(method, "$original_"); savedMethod.Body = original.Body; savedMethod.IsRuntimeSpecialName = false; type.Methods.Add(savedMethod); } original.Body = method.Body; // redirect any references in the body var proc = method.Body.GetILProcessor(); var amendments = new List <Action>(); foreach (var instr in method.Body.Instructions) { if (instr.Operand is MethodReference) { var callee = (MethodReference)instr.Operand; if (callee.Name == "CallRealBase") { MethodReference baseMethod; try { baseMethod = type.BaseType.Resolve().Methods.Single(m => m.Name == method.Name) as MethodReference; } catch { baseMethod = ((TypeDefinition)(type.BaseType)).BaseType.Resolve().Methods.Single(m => m.Name == method.Name) as MethodReference; } amendments.Add(() => proc.InsertBefore(instr, proc.Create(OpCodes.Ldarg_0))); amendments.Add(() => proc.Replace(instr, proc.Create(OpCodes.Call, baseMethod))); } else { callee = mapMethod((MethodReference)instr.Operand); if (callee.FullName == original.FullName) { // replace base calls with ones to $original instr.Operand = savedMethod; } else { instr.Operand = callee; } } } else if (instr.Operand is FieldReference) { var field = (FieldReference)instr.Operand; instr.Operand = new FieldReference(field.Name, mapType(field.FieldType), mapType(field.DeclaringType)); } else if (instr.Operand is TypeReference) { instr.Operand = mapType((TypeReference)instr.Operand); } } foreach (var var in method.Body.Variables) { var.VariableType = mapType(var.VariableType); } foreach (var amendment in amendments) { amendment(); } method.Body = proc.Body; } } } } CleanMyVersusMatchResults.PatchModule(baseModule); baseModule.Write("TowerFall8Player.exe"); }