/// <summary> /// Patches the current assembly with your patching assembly. /// </summary> /// <param name="yourAssembly">Your patching assembly.</param> /// <exception cref="System.ArgumentException">The assembly MUST have the PatchAssemblyAttribute attribute. Sorry.</exception> public void PatchAssembly(AssemblyDefinition yourAssembly) { try { /* The point of this method is to introduce all the patched elements in the correct order, * so the user doesn't have to worry about dependencies between code elements, and can have circular dependencies. * * The order is the following: * 1. Declare all the new types, in the order of their nesting level (.e.g topmost types, nested types, types nested in those, etc), * But don't set anything that references other things, like BaseType, interfaces, custom attributes, etc. * Type parameters *are* added at this stage, but no constraints are specified. * 2. Remove any methods that need removing. * 3. Declare all the new methods, including signatures and type parameters + constraints, but without setting their bodies. * This is performed now because types can reference attribute constructors that don't exist until we create them, * but method definitions alone just reference types, and don't need to know about inheritance or constraints. * Don't set custom attributes in this stage. * 4. Add any custom attributes for things that aren't part of the method/type hierarchy, such as assemblies. * 5. Set the type information we didn't set in (1), according to the same order, including custom attributes. * When/if modifying type inheritance is allowed, this process will occur at this stage. * 6. remove/define/modify fields. This is where most enum processing happens. Custom attributes are set at this stage. * 7. Remove/define/modify properties. This doesn't change anything about methods. Custom attributes are set at this stage. * 8. Fill/modify the bodies of all remaining methods, and also modify accessibility/attributes of existing ones. * Note that methods that are the source of DuplicatesBody will be resolved in their unmodified version, * so the modifications won't influence this functionality. Custom attributes are set at this stage. * Also, explicit overrides (what in C# is explicit interface implementation) are specified here. Standard order of business for order between modify/remove/update: 1. Remove Xs 2. Create new Xs 3. Update existing Xs (+ those we just created) */ Log.Information( "Patching assembly {PatchName:l} [{PatchPath:l}] => {OrigName:l} [{OrigPath:l}]", yourAssembly.Name.Name, PathHelper.GetUserFriendlyPath(yourAssembly.MainModule.FullyQualifiedName), TargetAssembly.Name.Name, PathHelper.GetUserFriendlyPath(TargetAssembly.MainModule.FullyQualifiedName) ); if (!yourAssembly.IsPatchingAssembly()) { throw new PatchDeclerationException( "The assembly MUST have the PatchAssemblyAttribute attribute. Sorry."); } var allTypesInOrder = GetAllTypesInNestingOrder(yourAssembly.MainModule.Types).ToList(); //+ Organizing types by action attribute var typesByActionSeq = from type in allTypesInOrder let typeActionAttr = GetTypeActionAttribute(type) where typeActionAttr != null && Filter(type) orderby type.UserFriendlyName() group new { yourType = type, typeActionAttr } by typeActionAttr.GetType(); var typesByAction = typesByActionSeq.ToSimpleTypeLookup(); Log.Information("Type Tasks: {@Tasks}", new { ByTask = new { Replace = typesByAction[typeof (ReplaceTypeAttribute)].Count(), Modify = typesByAction[typeof (ModifiesTypeAttribute)].Count(), Create = typesByAction[typeof (NewTypeAttribute)].Count() }, Total = typesByAction.Count }); //++ 1. Creating new types Log.Information("Creating new types"); foreach (var newTypeAction in typesByAction[typeof (NewTypeAttribute)]) { var status = CreateNewType(newTypeAction.yourType, (NewTypeAttribute) newTypeAction.typeActionAttr); if (status == NewMemberStatus.InvalidItem) { Log_failed_to_create(); typesByAction.Remove(newTypeAction); } } //+ Pairing types, organizing for modification //we pair off each yourType with the targetType it modifies (note that a targetType is also one we created just now) //we'll use these pairings throughout the rest of the method. var typePairingsSeq = from typeGroup in typesByAction from typeAction in typeGroup group new { typeAction.yourType, typeAction.typeActionAttr, targetType = GetPatchedTypeByName(typeAction.yourType) } by typeAction.typeActionAttr.GetType(); var typePairings = typePairingsSeq.ToSimpleTypeLookup(); //cache them foreach (var pairing in typePairings.SelectMany(x => x)) { if (pairing.targetType == null) { throw Errors.Missing_member("type", pairing.yourType, pairing.yourType.GetPatchedTypeFullName()); } if (pairing.targetType.IsInterface && !(pairing.typeActionAttr is NewTypeAttribute)) { throw Errors.Invalid_member("type", pairing.yourType, pairing.yourType.GetPatchedTypeFullName(), "You cannot modify existing interfaces."); } } //+ Organizing methods Log.Information("Organizing methods."); var methodActionsSeq = from pair in typePairings.SelectMany(x => x) from yourMethod in pair.yourType.Methods where !yourMethod.HasCustomAttribute<DisablePatchingAttribute>() let actionAttr = GetMemberActionAttribute(yourMethod, pair.typeActionAttr) where actionAttr != null group new { pair.targetType, pair.yourType, yourMethod, methodActionAttr = actionAttr } by actionAttr.GetType(); var methodActions = methodActionsSeq.ToSimpleTypeLookup(); Log.Information("Method Tasks: {@Tasks}", new { ByTask = new { CreateAndModify = methodActions[typeof (NewMemberAttribute)].Count(), Remove = methodActions[typeof (RemoveThisMemberAttribute)].Count(), Modify = methodActions[typeof (ModifiesMemberAttribute)].Count() }, Total = methodActions.Count }); //+ Organizing fields var fieldActionsSeq = //we create a list of all fields, as well as the action to perform with each field from typeAction in typePairings.SelectMany(x => x) from yourField in typeAction.yourType.Fields where !yourField.HasCustomAttribute<DisablePatchingAttribute>() let fieldActionAttr = GetMemberActionAttribute(yourField, typeAction.typeActionAttr) where fieldActionAttr != null group //grouping the fields is mostly for debugging purposes new { typeAction.yourType, typeAction.targetType, fieldActionAttr, yourField } by fieldActionAttr.GetType(); var fieldActions = fieldActionsSeq.ToSimpleTypeLookup(); Log.Information("Field Tasks: {@Tasks}", new { ByTask = new { CreateAndModify = fieldActions[typeof (NewMemberAttribute)].Count(), Remove = fieldActions[typeof (RemoveThisMemberAttribute)].Count(), Modify = fieldActions[typeof (ModifiesMemberAttribute)].Count() }, Total = fieldActions.Count }); //+ Organizing properties var propActionsSeq = from pair in typePairings.SelectMany(x => x) from yourProp in pair.yourType.Properties where !yourProp.HasCustomAttribute<DisablePatchingAttribute>() let propActionAttr = GetMemberActionAttribute(yourProp, pair.typeActionAttr) where propActionAttr != null group new { pair.targetType, pair.yourType, yourProp, pair.typeActionAttr, propActionAttr } by propActionAttr.GetType(); var propActions = propActionsSeq.ToSimpleTypeLookup(); Log.Information("Property Tasks: {@Tasks}", new { ByTask = new { CreateAndModify = propActions[typeof (NewMemberAttribute)].Count(), Remove = propActions[typeof (RemoveThisMemberAttribute)].Count(), Modify = propActions[typeof (ModifiesTypeAttribute)].Count() }, Total = propActions.Count }); //++ 2. Removing methods //remove all the methods Log.Header("Removing methods"); foreach (var actionParams in methodActions[typeof (RemoveThisMemberAttribute)]) { var result = actionParams.targetType.GetMethodsLike(actionParams.yourMethod).ToList().Any(x => actionParams.targetType.Methods.Remove(x)); if (!result) { LogFailedToRemove("method", actionParams.yourMethod); } } //++ 3. Creating new methods Log.Header("Creating new methods"); //create all the new methods foreach (var actionParams in methodActions[typeof (NewMemberAttribute)]) { var status = CreateNewMethod(actionParams.targetType, actionParams.yourMethod, (NewMemberAttribute) actionParams.methodActionAttr); if (status == NewMemberStatus.InvalidItem) { Log_failed_to_create(); methodActions.Remove(actionParams); } } //++ 4. Adding custom attributes to module/assembly. var assemblyImportAttribute = yourAssembly.GetCustomAttribute<ImportCustomAttributesAttribute>(); var moduleImportAttribute = yourAssembly.MainModule.GetCustomAttribute<ImportCustomAttributesAttribute>(); CopyCustomAttributesByImportAttribute(TargetAssembly, yourAssembly, assemblyImportAttribute); CopyCustomAttributesByImportAttribute(TargetAssembly.MainModule, yourAssembly.MainModule, moduleImportAttribute); //++ 5. Modifying type declerations Log.Information("Updating Type Ceclerations"); foreach (var modType in typesByAction[typeof (NewTypeAttribute)]) { AutoModifyTypeDecleration(modType.yourType); } //++ 6. Updating Fields Log.Header("Clearing fields in replaced types"); //+ Removing fields in replaced types foreach (var pairing in typePairings[typeof (ReplaceTypeAttribute)]) { Log.Information("Clearing fields in {0:l}", pairing.targetType.UserFriendlyName()); pairing.targetType.Fields.Clear(); } //+ Removing fields Log.Header("Removing fields"); foreach (var fieldAction in fieldActions[typeof (RemoveThisMemberAttribute)]) { var removed = fieldAction.targetType.Fields.RemoveWhere(x => x.Name == fieldAction.yourField.Name); if (!removed) { LogFailedToRemove("field", fieldAction.yourField); } } //+ Creating new fields Log.Header("Creating new fields"); foreach (var fieldAction in fieldActions[typeof (NewMemberAttribute)]) { var status = CreateNewField(fieldAction.targetType, fieldAction.yourField, (NewMemberAttribute) fieldAction.fieldActionAttr); if (status == NewMemberStatus.InvalidItem) { Log_failed_to_create(); fieldActions.Remove(fieldAction); } } //+ Modifying existing fields Log.Header("Modifying fields"); foreach ( var fieldAction in fieldActions[typeof (ModifiesMemberAttribute), typeof (NewMemberAttribute)]) { AutoModifyField(fieldAction.targetType, fieldAction.fieldActionAttr, fieldAction.yourField); } //++ 7. Updating properties Log.Header("Removing properties"); //+ Removing properties foreach (var propAction in propActions[typeof (RemoveThisMemberAttribute)]) { var removed = propAction.targetType.Properties.RemoveWhere(x => x.Name == propAction.yourProp.Name); if (!removed) { LogFailedToRemove("field", propAction.yourProp); } } Log.Header("Creating properties"); //+ Creating properties foreach (var propAction in propActions[typeof (NewMemberAttribute)]) { var status = CreateNewProperty(propAction.targetType, propAction.yourProp, (NewMemberAttribute) propAction.propActionAttr); if (status == NewMemberStatus.InvalidItem) { Log_failed_to_create(); propActions.Remove(propAction); } } Log.Header("Modifying properties"); //+ Modifying properties foreach ( var propAction in propActions[typeof (ModifiesMemberAttribute), typeof (NewMemberAttribute)]) { AutoModifyProperty(propAction.targetType, propAction.propActionAttr, propAction.yourProp); } //++ 8. Finalizing methods, generating method bodies. Log.Header("Modifying/generating method bodies"); foreach (var methodAction in methodActions[typeof (ModifiesMemberAttribute), typeof (NewMemberAttribute)]) { AutoModifyMethod(methodAction.targetType, methodAction.yourMethod, methodAction.methodActionAttr); } Log.Information("Patching {@PatchName:l} => {@OrigName:l} completed", yourAssembly.Name.Name, TargetAssembly.Name.Name); } catch (Exception ex) { Log.Fatal(ex, "An exception was unhandled. Since execution was halted mid-way, this means that this AssemblyPatcher has been corrupted."); throw; } }