/// <summary>Rewrite custom attributes if needed.</summary> /// <param name="attributes">The current custom attributes.</param> private bool RewriteCustomAttributes(Collection <CustomAttribute> attributes) { bool rewritten = false; for (int attrIndex = 0; attrIndex < attributes.Count; attrIndex++) { CustomAttribute attribute = attributes[attrIndex]; bool curChanged = false; // attribute type TypeReference newAttrType = null; rewritten |= this.RewriteTypeReference(attribute.AttributeType, newType => { newAttrType = newType; curChanged = true; }); // constructor arguments TypeReference[] argTypes = new TypeReference[attribute.ConstructorArguments.Count]; for (int i = 0; i < argTypes.Length; i++) { var arg = attribute.ConstructorArguments[i]; argTypes[i] = arg.Type; rewritten |= this.RewriteTypeReference(arg.Type, newType => { argTypes[i] = newType; curChanged = true; }); } // swap attribute if (curChanged) { // get constructor MethodDefinition constructor = (newAttrType ?? attribute.AttributeType) .Resolve() .Methods .Where(method => method.IsConstructor) .FirstOrDefault(ctor => RewriteHelper.HasMatchingSignature(ctor, attribute.Constructor)); if (constructor == null) { throw new InvalidOperationException($"Can't rewrite attribute type '{attribute.AttributeType.FullName}' to '{newAttrType?.FullName}', no equivalent constructor found."); } // create new attribute var newAttr = new CustomAttribute(this.Module.ImportReference(constructor)); for (int i = 0; i < argTypes.Length; i++) { newAttr.ConstructorArguments.Add(new CustomAttributeArgument(argTypes[i], attribute.ConstructorArguments[i].Value)); } foreach (var prop in attribute.Properties) { newAttr.Properties.Add(new CustomAttributeNamedArgument(prop.Name, prop.Argument)); } foreach (var field in attribute.Fields) { newAttr.Fields.Add(new CustomAttributeNamedArgument(field.Name, field.Argument)); } // swap attribute attributes[attrIndex] = newAttr; rewritten = true; } } return(rewritten); }
/********* ** Private methods *********/ /// <summary>Rewrite a loaded type definition.</summary> /// <param name="type">The type definition to rewrite.</param> /// <returns>Returns whether the type was modified.</returns> private bool RewriteTypeDefinition(TypeDefinition type) { bool changed = false; changed |= this.RewriteCustomAttributes(type.CustomAttributes); changed |= this.RewriteGenericParameters(type.GenericParameters); foreach (InterfaceImplementation @interface in type.Interfaces) { changed |= this.RewriteTypeReference(@interface.InterfaceType, newType => @interface.InterfaceType = newType); } if (type.BaseType.FullName != "System.Object") { changed |= this.RewriteTypeReference(type.BaseType, newType => type.BaseType = newType); } foreach (MethodDefinition method in type.Methods) { changed |= this.RewriteTypeReference(method.ReturnType, newType => method.ReturnType = newType); changed |= this.RewriteGenericParameters(method.GenericParameters); changed |= this.RewriteCustomAttributes(method.CustomAttributes); foreach (ParameterDefinition parameter in method.Parameters) { changed |= this.RewriteTypeReference(parameter.ParameterType, newType => parameter.ParameterType = newType); } foreach (var methodOverride in method.Overrides) { changed |= this.RewriteMethodReference(methodOverride); } if (method.HasBody) { foreach (VariableDefinition variable in method.Body.Variables) { changed |= this.RewriteTypeReference(variable.VariableType, newType => variable.VariableType = newType); } // rewrite CIL instructions ILProcessor cil = method.Body.GetILProcessor(); Collection <Instruction> instructions = cil.Body.Instructions; bool addedInstructions = false; for (int i = 0; i < instructions.Count; i++) { var instruction = instructions[i]; if (instruction.OpCode.Code == Code.Nop) { continue; } int oldCount = cil.Body.Instructions.Count; changed |= this.RewriteInstruction(instruction, cil, newInstruction => { changed = true; cil.Replace(instruction, newInstruction); instruction = newInstruction; }); if (cil.Body.Instructions.Count > oldCount) { addedInstructions = true; } } // special case: added instructions may cause an instruction to be out of range // of a short jump that references it if (addedInstructions) { foreach (var instruction in instructions) { var longJumpCode = RewriteHelper.GetEquivalentLongJumpCode(instruction.OpCode); if (longJumpCode != null) { instruction.OpCode = longJumpCode.Value; } } changed = true; } } } return(changed); }
/// <inheritdoc /> public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // get method ref MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); if (methodRef == null || !this.ShouldValidate(methodRef.DeclaringType)) { return(false); } // skip if not broken if (methodRef.Resolve() != null) { return(false); } // get type var type = methodRef.DeclaringType.Resolve(); if (type == null) { return(false); } // get method definition MethodDefinition method = null; foreach (var match in type.Methods.Where(p => p.Name == methodRef.Name)) { // reference matches initial parameters of definition if (methodRef.Parameters.Count >= match.Parameters.Count || !this.InitialParametersMatch(methodRef, match)) { continue; } // all remaining parameters in definition are optional if (!match.Parameters.Skip(methodRef.Parameters.Count).All(p => p.IsOptional)) { continue; } method = match; break; } if (method == null) { return(false); } // get instructions to inject parameter values var loadInstructions = method.Parameters.Skip(methodRef.Parameters.Count) .Select(p => RewriteHelper.GetLoadValueInstruction(p.Constant)) .ToArray(); if (loadInstructions.Any(p => p == null)) { return(false); // SMAPI needs to load the value onto the stack before the method call, but the optional parameter type wasn't recognized } // rewrite method reference foreach (Instruction loadInstruction in loadInstructions) { cil.InsertBefore(instruction, loadInstruction); } instruction.Operand = module.ImportReference(method); this.Phrases.Add($"{methodRef.DeclaringType.Name}.{methodRef.Name} (added missing optional parameters)"); return(this.MarkRewritten()); }