<commit_before>﻿using System;
using Mono.Cecil;
using Mono.Cecil.Cil;
using System.Collections.Generic;
using System.IO;
using Mono.Collections.Generic;

namespace MonoMod {
    public class MonoMod {

        public readonly static List<BlacklistItem> GlobalBlacklist = new List<BlacklistItem>() {
            new BlacklistItem("Assembly-CSharp", "globalGameState.DemoVersion"), //MegaSphere first alpha demo
            new BlacklistItem("Assembly-CSharp", "globalGameState.isDemo"), //MegaSphere first alpha demo
        };

        private readonly static List<BlacklistItem> loadedBlacklist = new List<BlacklistItem>();

        public FileInfo In;
        public DirectoryInfo Dir;
        public FileInfo Out;

        public ModuleDefinition Module;
        public List<ModuleDefinition> Dependencies = new List<ModuleDefinition>();
        public MethodDefinition Entry;
        
        public List<TypeDefinition> TypesPatched = new List<TypeDefinition>();
        public List<string> TypesAdded = new List<string>();
        
        public static Action<string> DefaultLogger;
        public Action<string> Logger;

        public MonoMod() {
        }

        public MonoMod(string input)
            : this(new FileInfo(input)) {
        }

        public MonoMod(FileInfo input)
            : this() {
            In = input;
            Dir = input.Directory;
            Out = new FileInfo(input.FullName.Substring(0, input.FullName.Length-4)+".mm.exe");
        }

        public static void Main(string[] args) {
            Console.WriteLine("MonoMod "+System.Reflection.Assembly.GetCallingAssembly().GetName().Version);

            if (args.Length != 1) {
                Console.WriteLine("No valid arguments (executable path) passed.");
                return;
            }

            MonoMod mm = new MonoMod(args[0]);

            mm.AutoPatch();
        }

        /// <summary>
        /// Reads the main assembly to mod.
        /// </summary>
        /// <param name="loadDependencies">If set to <c>true</c> load dependencies when not already loaded.</param>
        public virtual void Read(bool loadDependencies = true) {
            if (Module == null) {
                Log("Reading assembly as Mono.Cecil ModuleDefinition and AssemblyDefinition...");
                Module = ModuleDefinition.ReadModule(In.FullName, new ReaderParameters(ReadingMode.Immediate));
                LoadBlacklist(Module);
            }

            if (loadDependencies && Dependencies.Count == 0 && Dir != null) {
                Log("Reading module dependencies...");
                for (int mi = 0; mi < Module.ModuleReferences.Count; mi++) {
                    LoadDependency(Module.ModuleReferences[mi].Name);
                }

                Log("Reading assembly dependencies...");
                for (int mi = 0; mi < Module.AssemblyReferences.Count; mi++) {
                    LoadDependency(Module.AssemblyReferences[mi].Name);
                }

                Dependencies.Remove(Module);
            }

            //TODO make this return a status code or something
        }

        /// <summary>
        /// Write the modded module to the given file or the default output.
        /// </summary>
        /// <param name="output">Output file. If none given, Out will be used.</param>
        public virtual void Write(FileInfo output = null) {
            if (output == null) {
                output = Out;
            }

            PatchWasHere();

            Log("Writing to output file...");
            Module.Write(output.FullName);

            //TODO make this return a status code or something
        }

        /// <summary>
        /// Runs some basic optimization (f.e. disables NoOptimization, removes nops)
        /// </summary>
        public virtual void Optimize() {
            for (int ti = 0; ti < Module.Types.Count; ti++) {
                TypeDefinition type = Module.Types[ti];
                for (int mi = 0; mi < type.Methods.Count; mi++) {
                    MethodDefinition method = type.Methods[mi];

                    method.NoInlining = false;
                    method.NoOptimization = false;

                    if (method.HasBody) {
                        List<int> nops = new List<int>();
                        for (int i = 0; i < method.Body.Instructions.Count; i++) {
                            Instruction instruction = method.Body.Instructions[i];
                            if (instruction.OpCode == OpCodes.Nop) {
                                method.Body.Instructions.RemoveAt(i);
                                nops.Add(i);
                                i = i-1 < 0 ? 0 : i-1;

                                for (int ii = method.Body.Instructions.Count - 1; i <= ii; ii--) {
                                    Instruction next = method.Body.Instructions[ii];
                                    next.Offset--;
                                }
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Automatically mods the module, loading In, based on the files in Dir and writes it to Out.
        /// If Dir and Out are not set, it will use the input file to create Dir and Out.
        /// </summary>
        public virtual void AutoPatch(bool read = true, bool write = true) {
            if (read) {
                if (Dir == null) {
                    Dir = In.Directory;
                }
            }
            if (Out == null) {
                Out = new FileInfo(In.FullName.Substring(0, In.FullName.Length-4)+".mm.exe");
            }

            Log("Patching "+In.Name+" ...");
            
            if (read) {
                Read(true);
            }

            Log("Replacing main EntryPoint...");
            Entry = PatchEntry(Module.EntryPoint);
            
            if (Dir != null) {
                string fileName = In.Name.Substring(0, In.Name.LastIndexOf("."));
                Log("Scanning for files matching "+fileName+".*.mm.dll ...");
                foreach (FileInfo f in Dir.GetFiles()) {
                    if (f.Name.StartsWith(fileName) && f.Name.ToLower().EndsWith(".mm.dll")) {
                        Log("Found "+f.Name+" , reading...");
                        ModuleDefinition mod = ModuleDefinition.ReadModule(f.FullName, new ReaderParameters(ReadingMode.Immediate));
                        PatchModule(mod);
                    }
                }
                Log("Patching / fixing references...");
                PatchRefs();
            }

            Optimize();

            Log("Done.");
            
            if (write) {
                Write();
            }
        }

        /// <summary>
        /// Patches the module and adds the patched types to the given list.
        /// </summary>
        /// <param name="mod">Mod to patch into the input module.</param>
        /// <param name="types">Type list containing all patched types.</param>
        public virtual void PatchModule(ModuleDefinition mod) {
            Module.AssemblyReferences.Add(mod.Assembly.Name);

            for (int i = 0; i < mod.Types.Count; i++) {
                PatchType(mod.Types[i]);
            }
        }

        /// <summary>
        /// Patches the type and adds it to TypesPatched if it's actually patched.
        /// </summary>
        /// <returns>The original type that has been patched.</returns>
        /// <param name="type">Type to patch into the input module.</param>
        public virtual TypeReference PatchType(TypeDefinition type) {
            string typeName = type.FullName;
            Log("T: " + typeName);
            
            if (TypesPatched.Contains(type)) {
                PatchNested(type);
                return null;
            }

            typeName = RemovePrefixes(typeName, type.Name);

            /*if (type.Attributes.HasFlag(TypeAttributes.NotPublic) &&
                type.Attributes.HasFlag(TypeAttributes.Interface)) {
                Log("Type is a private interface; ignore...");
                PatchNested(type);
                return null;
            }*/

            if (HasAttribute(type, "MonoModIgnore")) {
                PatchNested(type);
                return null;
            }

            //check if type exists at all
            TypeReference origType = Module.GetType(typeName, true);
            if (origType == null) {
                if (type.Name.StartsWith("patch_")) {
                    PatchNested(type);
                    return null;
                }
            }

            //check if type exists in module to patch
            origType = Module.GetType(typeName, false);
            if (origType == null) {
                //(un?)fortunately we're forced to add types ever since some workarounds stopped working
                Log("T+: " + typeName);
                
                TypeDefinition newType = new TypeDefinition(type.Namespace, type.Name, type.Attributes, null);
                newType.ClassSize = type.ClassSize;
                //TODO yell about custom attribute support in Mono.Cecil
                //newType.CustomAttributes = type.CustomAttributes;
                if (type.DeclaringType != null) {
                    newType.DeclaringType = (FindType(type.DeclaringType, newType, false) ?? PatchType(type.DeclaringType)).Resolve();
                    newType.DeclaringType.NestedTypes.Add(newType);
                } else {
                    Module.Types.Add(newType);
                }
                TypesAdded.Add(typeName);
                newType.MetadataToken = type.MetadataToken;
                for (int i = 0; i < type.GenericParameters.Count; i++) {
                    newType.GenericParameters.Add(new GenericParameter(type.GenericParameters[i].Name, type) {
                        Attributes = type.GenericParameters[i].Attributes,
                        MetadataToken = type.GenericParameters[i].MetadataToken
                    });
                }
                for (int i = 0; i < type.Interfaces.Count; i++) {
                    newType.Interfaces.Add(FindType(type.Interfaces[i], newType, false) ?? PatchType(type.Interfaces[i].Resolve()));
                }
                newType.BaseType = type.BaseType == null ? null : (FindType(type.BaseType, newType, false) ?? PatchType(type.BaseType.Resolve()));
                newType.PackingSize = type.PackingSize;
                //Methods and Fields gets filled automatically
                
                origType = newType;
            }
            
            TypeDefinition origTypeResolved = origType.Resolve();

            IsBlacklisted(origTypeResolved.Module.Name, origTypeResolved.FullName, HasAttribute(origTypeResolved, "MonoModBlacklisted"));

            if (type.Name.StartsWith("remove_") || HasAttribute(type, "MonoModRemove")) {
                Module.Types.Remove(origTypeResolved);
                return null;
            }

            //type = Module.Import(type).Resolve();
            
            for (int ii = 0; ii < type.Methods.Count; ii++) {
                MethodDefinition method = type.Methods[ii];
                Log("M: "+method.FullName);
                
                if (!AllowedSpecialName(method) || HasAttribute(method, "MonoModIgnore")) {
                    continue;
                }

                //method = Module.Import(method).Resolve();
                PatchMethod(method);
            }
            
            for (int ii = 0; ii < type.Properties.Count; ii++) {
                PropertyDefinition @property = type.Properties[ii];
                Log("P: "+@property.FullName);

                MethodDefinition getter = @property.GetMethod;
                if (getter != null && !HasAttribute(getter, "MonoModIgnore")) {
                    //getter = Module.Import(getter).Resolve();
                    PatchMethod(getter);
                }
                
                MethodDefinition setter = @property.SetMethod;
                if (setter != null && !HasAttribute(setter, "MonoModIgnore")) {
                    //setter = Module.Import(setter).Resolve();
                    PatchMethod(setter);
                }
            }
            
            if (HasAttribute(type, "MonoModEnumReplace")) {
                for (int ii = 0; ii < origTypeResolved.Fields.Count;) {
                    if (origTypeResolved.Fields[ii].Name == "value__") {
                        ii++;
                        continue;
                    }
                    
                    origTypeResolved.Fields.RemoveAt(ii);
                }
            }

            for (int ii = 0; ii < type.Fields.Count; ii++) {
                FieldDefinition field = type.Fields[ii];
                /*if (field.Attributes.HasFlag(FieldAttributes.SpecialName)) {
                    continue;
                }*/
                
                if (HasAttribute(field, "MonoModIgnore")) {
                    continue;
                }

                bool hasField = false;
                for (int iii = 0; iii < origTypeResolved.Fields.Count; iii++) {
                    if (origTypeResolved.Fields[iii].Name == field.Name) {
                        hasField = true;
                        break;
                    }
                }
                if (hasField) {
                    continue;
                }
                Log("F: "+field.FullName);

                FieldDefinition newField = new FieldDefinition(field.Name, field.Attributes, FindType(field.FieldType, type));
                newField.InitialValue = field.InitialValue;
                newField.Constant = field.Constant;
                origTypeResolved.Fields.Add(newField);
            }

            TypesPatched.Add(type);
            PatchNested(type);
            return origType;
        }
        
        protected virtual void PatchNested(TypeDefinition type) {
            for (int i = 0; i < type.NestedTypes.Count; i++) {
                PatchType(type.NestedTypes[i]);
            }
        }

        /// <summary>
        /// Patches the given method into the input module.
        /// </summary>
        /// <param name="method">Method to patch in.</param>
        public virtual void PatchMethod(MethodDefinition method) {
            if (method.Name.StartsWith("orig_")) {
                Log(method.Name + " is an orig_ method; ignoring...");
                return;
            }
            
            Log("Patching "+method.Name+" ...");

            Log("Checking for already existing methods...");

            TypeDefinition origType = Module.GetType(RemovePrefixes(method.DeclaringType.FullName, method.DeclaringType.Name));
            bool isTypeAdded = TypesAdded.Contains(origType.FullName);

            MethodDefinition origMethod = null; //original method that is going to be changed if existing (f.e. X)
            MethodDefinition origMethodOrig = null; //orig_ method (f.e. orig_X)

            //TODO the orig methods of replace_ methods can't be found
            for (int i = 0; i < origType.Methods.Count; i++) {
                if (origType.Methods[i].FullName == RemovePrefixes(method.FullName, method.DeclaringType.Name)) {
                    origMethod = origType.Methods[i];
                }
                if (origType.Methods[i].FullName == RemovePrefixes(method.FullName.Replace(method.Name, "orig_"+method.Name), method.DeclaringType.Name)) {
                    origMethodOrig = origType.Methods[i];
                }
            }

            if (origMethod != null && origMethodOrig == null && !isTypeAdded) {
                IsBlacklisted(origMethod.Module.Name, origMethod.DeclaringType.FullName+"."+origMethod.Name, HasAttribute(origMethod, "MonoModBlacklisted"));
                if (method.Name.StartsWith("replace_") || HasAttribute(method, "MonoModReplace")) {
                    Log("Method existing; replacing...");
                } else {
                    Log("Method existing; creating copy...");

                    MethodDefinition copy = new MethodDefinition("orig_"+origMethod.Name, origMethod.Attributes & ~MethodAttributes.SpecialName & ~MethodAttributes.RTSpecialName, origMethod.ReturnType);
                    copy.DeclaringType = origMethod.DeclaringType;
                    copy.MetadataToken = origMethod.MetadataToken;
                    copy.Body = origMethod.Body;
                    
                    for (int i = 0; i < origMethod.GenericParameters.Count; i++) {
                        copy.GenericParameters.Add(new GenericParameter(origMethod.GenericParameters[i].Name, copy) {
                            Attributes = origMethod.GenericParameters[i].Attributes,
                            MetadataToken = origMethod.GenericParameters[i].MetadataToken
                        });
                    }

                    for (int i = 0; i < origMethod.Parameters.Count; i++) {
                        copy.Parameters.Add(origMethod.Parameters[i]);
                    }

                    origType.Methods.Add(copy);
                    origMethodOrig = copy;
                    Log("Added copy of original method to "+copy.FullName);
                }
            } else if (origMethod != null) {
                Log("Prefixed method existing; ignoring...");
            }

            //fix for .cctor not linking to orig_.cctor
            if (origMethodOrig != null && origMethod.IsConstructor && origMethod.IsStatic && !isTypeAdded) {
                Collection<Instruction> instructions = method.Body.Instructions;
                ILProcessor ilProcessor = method.Body.GetILProcessor();
                ilProcessor.InsertBefore(instructions[instructions.Count - 1], ilProcessor.Create(OpCodes.Call, origMethodOrig));
            }

            for (int i = 0; method.HasBody && i < method.Body.Variables.Count; i++) {
                //TODO debug! (Import crashes)
                method.Body.Variables[i].VariableType = FindType(method.Body.Variables[i].VariableType, method);
            }

            Log("Storing method to main module...");

            if (origMethod != null) {
                origMethod.Body = method.Body;
                method = origMethod;
            } else {
                MethodDefinition clone = new MethodDefinition(method.Name, (origMethodOrig ?? method).Attributes, Module.Import(typeof(void)));
                origType.Methods.Add(clone);
                clone.MetadataToken = (origMethodOrig ?? method).MetadataToken;
                clone.CallingConvention = (origMethodOrig ?? method).CallingConvention;
                clone.ExplicitThis = (origMethodOrig ?? method).ExplicitThis;
                clone.MethodReturnType = (origMethodOrig ?? method).MethodReturnType;
                clone.NoInlining = (origMethodOrig ?? method).NoInlining;
                clone.NoOptimization = (origMethodOrig ?? method).NoOptimization;
                clone.Attributes = (origMethodOrig ?? method).Attributes;
                clone.ImplAttributes = (origMethodOrig ?? method).ImplAttributes;
                clone.SemanticsAttributes = (origMethodOrig ?? method).SemanticsAttributes;
                clone.DeclaringType = origType;
                for (int i = 0; i < (origMethodOrig ?? method).GenericParameters.Count; i++) {
                    clone.GenericParameters.Add(new GenericParameter((origMethodOrig ?? method).GenericParameters[i].Name, origType));
                }
                for (int i = 0; i < (origMethodOrig ?? method).Parameters.Count; i++) {
                    clone.Parameters.Add(new ParameterDefinition(FindType((origMethodOrig ?? method).Parameters[i].ParameterType, clone)));
                }
                for (int i = 0; i < (origMethodOrig ?? method).Overrides.Count; i++) {
                    clone.Overrides.Add((origMethodOrig ?? method).Overrides[i]);
                }
                clone.ReturnType = FindType((origMethodOrig ?? method).ReturnType, clone);
                clone.Body = method.Body;
                method = clone;
            }

            if (method.Name.StartsWith("get_") || method.Name.StartsWith("set_")) {
                Log("Finding property...");

                PropertyDefinition property = null;
                for (int i = 0; i < origType.Properties.Count; i++) {
                    if (method.Name.EndsWith(origType.Properties[i].Name)) {
                        property = origType.Properties[i];
                        if (method.Name.StartsWith("get_")) {
                            property.PropertyType = method.ReturnType;
                        }
                        break;
                    }
                }

                if (property == null) {
                    Log("Property not found; creating new property...");
                    property = new PropertyDefinition(method.Name.Substring(4), PropertyAttributes.None, method.ReturnType);
                    origType.Properties.Add(property);
                }

                if (method.Name.StartsWith("get_")) {
                    Log("Replacing getter...");
                    property.GetMethod = method;
                } else {
                    Log("Replacing setter...");
                    property.SetMethod = method;
                }
            }
        }

        /// <summary>
        /// Patches the references in all of the types in TypesPatched.
        /// </summary>
        public virtual void PatchRefs() {
            foreach (TypeDefinition type in TypesPatched) {
                if (type == null) {
                    continue;
                }
                string typeName = type.FullName;
                Log("TR: "+typeName);

                typeName = RemovePrefixes(typeName, type.Name);
                bool isTypeAdded = TypesAdded.Contains(typeName);

                TypeDefinition origType = Module.GetType(typeName);
                IsBlacklisted(origType.Module.Name, origType.FullName, HasAttribute(origType, "MonoModBlacklisted"));
                for (int ii = 0; ii < type.Methods.Count; ii++) {
                    MethodDefinition method = type.Methods[ii];

                    if (!AllowedSpecialName(method) || HasAttribute(method, "MonoModIgnore")) {
                        continue;
                    }

                    for (int iii = 0; iii < origType.Methods.Count; iii++) {
                        MethodDefinition origMethod = origType.Methods[iii];
                        if (origMethod.FullName == RemovePrefixes(method.FullName, method.DeclaringType.Name)) {
                            method = origMethod;
                            Log("MR: "+method.FullName);
                            PatchRefsInMethod(method);
                            break;
                        }
                    }
                }
                for (int ii = 0; ii < type.Properties.Count; ii++) {
                    PropertyDefinition @property = type.Properties[ii];
                    
                    for (int iii = 0; iii < origType.Properties.Count; iii++) {
                        PropertyDefinition origProperty = origType.Properties[iii];
                        if (origProperty.FullName == RemovePrefixes(@property.FullName, @property.DeclaringType.Name)) {
                            @property = origProperty;
                            Log("PR: "+@property.FullName);
                            MethodDefinition getter = @property.GetMethod;
                            if (getter != null && !HasAttribute(getter, "MonoModIgnore")) {
                                //getter = Module.Import(getter).Resolve();
                                PatchRefsInMethod(getter);
                            }
                            
                            MethodDefinition setter = @property.SetMethod;
                            if (setter != null && !HasAttribute(setter, "MonoModIgnore")) {
                                //setter = Module.Import(setter).Resolve();
                                PatchRefsInMethod(setter);
                            }
                            break;
                        }
                    }
                }
                if (isTypeAdded) {
                    for (int ii = 0; ii < type.Fields.Count; ii++) {
                        FieldDefinition field = type.Fields[ii];
                        
                        if (HasAttribute(field, "MonoModIgnore")) {
                            continue;
                        }
        
                        FieldDefinition origField = null;
                        for (int iii = 0; iii < origType.Fields.Count; iii++) {
                            if (origType.Fields[iii].Name == field.Name) {
                                origField = origType.Fields[iii];
                                break;
                            }
                        }
                        if (origField == null) {
                            //The field should've been added...
                            continue;
                        }
                        Log("FR: "+origField.FullName);
                        
                        origField.FieldType = FindType(origField.FieldType, type);
                    }
                }
            }
        }

        /// <summary>
        /// Patches the references in method.
        /// </summary>
        /// <param name="method">Method to patch.</param>
        public virtual void PatchRefsInMethod(MethodDefinition method) {
            if (method.Name.StartsWith("orig_")) {
                Log(method.Name + " is an orig_ method; ignoring...");
                return;
            }

            Log("Patching references in "+method.Name+" ...");

            Log("Checking for original methods...");

            TypeDefinition origType = Module.GetType(RemovePrefixes(method.DeclaringType.FullName, method.DeclaringType.Name));
            bool isTypeAdded = TypesAdded.Contains(origType.FullName);

            MethodDefinition origMethodOrig = null; //orig_ method (f.e. orig_X)

            for (int i = 0; i < origType.Methods.Count; i++) {
                if (origType.Methods[i].FullName == RemovePrefixes(method.FullName.Replace(method.Name, "orig_"+method.Name), method.DeclaringType.Name)) {
                    origMethodOrig = origType.Methods[i];
                }
            }

            if (origMethodOrig != null) {
                IsBlacklisted(origMethodOrig.Module.Name, method.DeclaringType+"."+RemovePrefixes(method.Name), HasAttribute(origMethodOrig, "MonoModBlacklisted"));
                Log("Prefixed method existing; ignoring...");
            }

            Log("Modifying method body...");
            for (int i = 0; method.HasBody && i < method.Body.Instructions.Count; i++) {
                Instruction instruction = method.Body.Instructions[i];
                object operand = instruction.Operand;
                
                if (operand is MethodReference) {
                    MethodReference methodCalled = FindLinked((MethodReference) operand);
                    if (methodCalled.FullName == RemovePrefixes(method.FullName, method.DeclaringType.Name)) {
                        operand = method;
                    } else {
                        MethodReference findMethod = FindMethod(methodCalled, method, false);

                        if (origMethodOrig != null && methodCalled.FullName == origMethodOrig.FullName) {
                            Log("Found call to the original method; linking...");
                            findMethod = origMethodOrig;
                        }
                        
                        if (findMethod == null && methodCalled.IsGenericInstance) {
                            GenericInstanceMethod genericMethodCalled = ((GenericInstanceMethod) methodCalled);
                            Log("Calling method: " + genericMethodCalled.FullName);
                            Log("Element method: " + genericMethodCalled.ElementMethod.FullName);
                            GenericInstanceMethod genericMethod = new GenericInstanceMethod(FindMethod(genericMethodCalled.ElementMethod, method, true));

                            for (int gi = 0; gi < genericMethodCalled.GenericArguments.Count; gi++) {
                                Log("Generic argument: " + genericMethodCalled.GenericArguments[gi]);
                                //genericMethod.GenericArguments.Add(genericMethodCalled.GenericArguments[gi]);
                                bool found = false;
                                for (int gii = 0; gii < method.GenericParameters.Count && !found; gii++) {
                                    GenericParameter genericParam = method.GenericParameters[gii];
                                    Log("Checking against: " + genericParam.FullName);
                                    if (genericParam.FullName == genericMethodCalled.GenericArguments[gi].FullName) {
                                        Log("Success!");
                                        genericMethod.GenericArguments.Add(genericParam);
                                        found = true;
                                    }
                                }
                                for (int gii = 0; gii < method.DeclaringType.GenericParameters.Count && !found; gii++) {
                                    GenericParameter genericParam = method.DeclaringType.GenericParameters[gii];
                                    Log("Checking against: " + genericParam.FullName);
                                    if (genericParam.FullName == genericMethodCalled.GenericArguments[gi].FullName) {
                                        Log("Success!");
                                        genericMethod.GenericArguments.Add(genericParam);
                                        found = true;
                                    }
                                }
                                if (!found) {
                                    genericMethod.GenericArguments.Add(FindType(genericMethodCalled.GenericArguments[gi], method, true));
                                }
                            }

                            findMethod = genericMethod;
                        }

                        if (findMethod == null) {
                            try {
                                findMethod = Module.Import(methodCalled);
                            } catch {
                                //uh. generic type failed importing?
                            }
                        }

                        MethodDefinition findMethodDef = findMethod == null ? null : findMethod.Resolve();
                        if (findMethodDef != null && !isTypeAdded) {
                            IsBlacklisted(findMethod.Module.Name, findMethod.DeclaringType.FullName+"."+findMethod.Name, HasAttribute(findMethodDef, "MonoModBlacklisted"));
                            //Quite untested - fixes invalid IL when calling virtual methods when not virtual in patch
                            if (findMethodDef.Attributes.HasFlag(MethodAttributes.Virtual)) {
                                instruction.OpCode = OpCodes.Callvirt;
                            }
                            //Fixes linkto base methods being called as callvirt
                            //FIXME find out other cases where this should be set due to linkto
                            //FIXME test something better than name...
                            if (method.DeclaringType.BaseType != null && findMethodDef.DeclaringType.Name == method.DeclaringType.BaseType.Name) {
                                instruction.OpCode = OpCodes.Call;
                            }
                        }
                        
                        operand = findMethod ?? Module.Import(methodCalled);
                    }
                }

                if (operand is FieldReference) {
                    FieldReference field = FindLinked((FieldReference) operand);

                    TypeReference findTypeRef = FindType(field.DeclaringType, method, false);
                    TypeDefinition findType = findTypeRef == null ? null : findTypeRef.Resolve();
                    
                    if (findType != null) {
                        for (int ii = 0; ii < findType.Fields.Count; ii++) {
                            if (findType.Fields[ii].Name == field.Name) {
                                field = findType.Fields[ii];
                                
                                if (field.DeclaringType.IsGenericInstance) {
                                    field = Module.Import(new FieldReference(field.Name, FindType(field.FieldType, findTypeRef), findTypeRef));
                                }
                                
                                if (field.Module != Module) {
                                    field = Module.Import(field);
                                }
                                
                                break;
                            }
                        }
                    }
                    
                    if (field == operand) {
                        field = new FieldReference(field.Name, FindType(field.FieldType, method), FindType(field.DeclaringType, method));
                    }

                    if (field != null) {
                        IsBlacklisted(field.Module.Name, field.DeclaringType.FullName+"."+field.Name);
                    }
                    operand = field;
                }

                if (operand is TypeReference) {
                    operand = FindType((TypeReference) operand, method);
                }

                instruction.Operand = operand;
            }

            for (int i = 0; method.HasBody && i < method.Body.Variables.Count; i++) {
                method.Body.Variables[i].VariableType = FindType(method.Body.Variables[i].VariableType, method);
            }

            if (method.ReturnType.IsGenericParameter) {
                method.ReturnType = FindTypeGeneric(method.ReturnType, method);

                /*
                TypeReference returnType = method.ReturnType;

                Log("Generic param wanted: " + returnType.FullName);
                Log("Method: " + method.FullName);
                for (int gi = 0; gi < method.GenericParameters.Count; gi++) {
                    GenericParameter genericParam = method.GenericParameters[gi];<commit_msg>Every field the mod touches is our kingdom<commit_after>﻿using System;
using Mono.Cecil;
using Mono.Cecil.Cil;
using System.Collections.Generic;
using System.IO;
using Mono.Collections.Generic;

namespace MonoMod {
    public class MonoMod {

        public readonly static List<BlacklistItem> GlobalBlacklist = new List<BlacklistItem>() {
            new BlacklistItem("Assembly-CSharp", "globalGameState.DemoVersion"), //MegaSphere first alpha demo
            new BlacklistItem("Assembly-CSharp", "globalGameState.isDemo"), //MegaSphere first alpha demo
        };

        private readonly static List<BlacklistItem> loadedBlacklist = new List<BlacklistItem>();

        public FileInfo In;
        public DirectoryInfo Dir;
        public FileInfo Out;

        public ModuleDefinition Module;
        public List<ModuleDefinition> Dependencies = new List<ModuleDefinition>();
        public MethodDefinition Entry;
        
        public List<TypeDefinition> TypesPatched = new List<TypeDefinition>();
        public List<string> TypesAdded = new List<string>();
        
        public static Action<string> DefaultLogger;
        public Action<string> Logger;

        public MonoMod() {
        }

        public MonoMod(string input)
            : this(new FileInfo(input)) {
        }

        public MonoMod(FileInfo input)
            : this() {
            In = input;
            Dir = input.Directory;
            Out = new FileInfo(input.FullName.Substring(0, input.FullName.Length-4)+".mm.exe");
        }

        public static void Main(string[] args) {
            Console.WriteLine("MonoMod "+System.Reflection.Assembly.GetCallingAssembly().GetName().Version);

            if (args.Length != 1) {
                Console.WriteLine("No valid arguments (executable path) passed.");
                return;
            }

            MonoMod mm = new MonoMod(args[0]);

            mm.AutoPatch();
        }

        /// <summary>
        /// Reads the main assembly to mod.
        /// </summary>
        /// <param name="loadDependencies">If set to <c>true</c> load dependencies when not already loaded.</param>
        public virtual void Read(bool loadDependencies = true) {
            if (Module == null) {
                Log("Reading assembly as Mono.Cecil ModuleDefinition and AssemblyDefinition...");
                Module = ModuleDefinition.ReadModule(In.FullName, new ReaderParameters(ReadingMode.Immediate));
                LoadBlacklist(Module);
            }

            if (loadDependencies && Dependencies.Count == 0 && Dir != null) {
                Log("Reading module dependencies...");
                for (int mi = 0; mi < Module.ModuleReferences.Count; mi++) {
                    LoadDependency(Module.ModuleReferences[mi].Name);
                }

                Log("Reading assembly dependencies...");
                for (int mi = 0; mi < Module.AssemblyReferences.Count; mi++) {
                    LoadDependency(Module.AssemblyReferences[mi].Name);
                }

                Dependencies.Remove(Module);
            }

            //TODO make this return a status code or something
        }

        /// <summary>
        /// Write the modded module to the given file or the default output.
        /// </summary>
        /// <param name="output">Output file. If none given, Out will be used.</param>
        public virtual void Write(FileInfo output = null) {
            if (output == null) {
                output = Out;
            }

            PatchWasHere();

            Log("Writing to output file...");
            Module.Write(output.FullName);

            //TODO make this return a status code or something
        }

        /// <summary>
        /// Runs some basic optimization (f.e. disables NoOptimization, removes nops)
        /// </summary>
        public virtual void Optimize() {
            for (int ti = 0; ti < Module.Types.Count; ti++) {
                TypeDefinition type = Module.Types[ti];
                for (int mi = 0; mi < type.Methods.Count; mi++) {
                    MethodDefinition method = type.Methods[mi];

                    method.NoInlining = false;
                    method.NoOptimization = false;

                    if (method.HasBody) {
                        List<int> nops = new List<int>();
                        for (int i = 0; i < method.Body.Instructions.Count; i++) {
                            Instruction instruction = method.Body.Instructions[i];
                            if (instruction.OpCode == OpCodes.Nop) {
                                method.Body.Instructions.RemoveAt(i);
                                nops.Add(i);
                                i = i-1 < 0 ? 0 : i-1;

                                for (int ii = method.Body.Instructions.Count - 1; i <= ii; ii--) {
                                    Instruction next = method.Body.Instructions[ii];
                                    next.Offset--;
                                }
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Automatically mods the module, loading In, based on the files in Dir and writes it to Out.
        /// If Dir and Out are not set, it will use the input file to create Dir and Out.
        /// </summary>
        public virtual void AutoPatch(bool read = true, bool write = true) {
            if (read) {
                if (Dir == null) {
                    Dir = In.Directory;
                }
            }
            if (Out == null) {
                Out = new FileInfo(In.FullName.Substring(0, In.FullName.Length-4)+".mm.exe");
            }

            Log("Patching "+In.Name+" ...");
            
            if (read) {
                Read(true);
            }

            Log("Replacing main EntryPoint...");
            Entry = PatchEntry(Module.EntryPoint);
            
            if (Dir != null) {
                string fileName = In.Name.Substring(0, In.Name.LastIndexOf("."));
                Log("Scanning for files matching "+fileName+".*.mm.dll ...");
                foreach (FileInfo f in Dir.GetFiles()) {
                    if (f.Name.StartsWith(fileName) && f.Name.ToLower().EndsWith(".mm.dll")) {
                        Log("Found "+f.Name+" , reading...");
                        ModuleDefinition mod = ModuleDefinition.ReadModule(f.FullName, new ReaderParameters(ReadingMode.Immediate));
                        PatchModule(mod);
                    }
                }
                Log("Patching / fixing references...");
                PatchRefs();
            }

            Optimize();

            Log("Done.");
            
            if (write) {
                Write();
            }
        }

        /// <summary>
        /// Patches the module and adds the patched types to the given list.
        /// </summary>
        /// <param name="mod">Mod to patch into the input module.</param>
        /// <param name="types">Type list containing all patched types.</param>
        public virtual void PatchModule(ModuleDefinition mod) {
            Module.AssemblyReferences.Add(mod.Assembly.Name);

            for (int i = 0; i < mod.Types.Count; i++) {
                PatchType(mod.Types[i]);
            }
        }

        /// <summary>
        /// Patches the type and adds it to TypesPatched if it's actually patched.
        /// </summary>
        /// <returns>The original type that has been patched.</returns>
        /// <param name="type">Type to patch into the input module.</param>
        public virtual TypeReference PatchType(TypeDefinition type) {
            string typeName = type.FullName;
            Log("T: " + typeName);
            
            if (TypesPatched.Contains(type)) {
                PatchNested(type);
                return null;
            }

            typeName = RemovePrefixes(typeName, type.Name);

            /*if (type.Attributes.HasFlag(TypeAttributes.NotPublic) &&
                type.Attributes.HasFlag(TypeAttributes.Interface)) {
                Log("Type is a private interface; ignore...");
                PatchNested(type);
                return null;
            }*/

            if (HasAttribute(type, "MonoModIgnore")) {
                PatchNested(type);
                return null;
            }

            //check if type exists at all
            TypeReference origType = Module.GetType(typeName, true);
            if (origType == null) {
                if (type.Name.StartsWith("patch_")) {
                    PatchNested(type);
                    return null;
                }
            }

            //check if type exists in module to patch
            origType = Module.GetType(typeName, false);
            if (origType == null) {
                //(un?)fortunately we're forced to add types ever since some workarounds stopped working
                Log("T+: " + typeName);
                
                TypeDefinition newType = new TypeDefinition(type.Namespace, type.Name, type.Attributes, null);
                newType.ClassSize = type.ClassSize;
                //TODO yell about custom attribute support in Mono.Cecil
                //newType.CustomAttributes = type.CustomAttributes;
                if (type.DeclaringType != null) {
                    newType.DeclaringType = (FindType(type.DeclaringType, newType, false) ?? PatchType(type.DeclaringType)).Resolve();
                    newType.DeclaringType.NestedTypes.Add(newType);
                } else {
                    Module.Types.Add(newType);
                }
                TypesAdded.Add(typeName);
                newType.MetadataToken = type.MetadataToken;
                for (int i = 0; i < type.GenericParameters.Count; i++) {
                    newType.GenericParameters.Add(new GenericParameter(type.GenericParameters[i].Name, type) {
                        Attributes = type.GenericParameters[i].Attributes,
                        MetadataToken = type.GenericParameters[i].MetadataToken
                    });
                }
                for (int i = 0; i < type.Interfaces.Count; i++) {
                    newType.Interfaces.Add(FindType(type.Interfaces[i], newType, false) ?? PatchType(type.Interfaces[i].Resolve()));
                }
                newType.BaseType = type.BaseType == null ? null : (FindType(type.BaseType, newType, false) ?? PatchType(type.BaseType.Resolve()));
                newType.PackingSize = type.PackingSize;
                //Methods and Fields gets filled automatically
                
                origType = newType;
            }
            
            TypeDefinition origTypeResolved = origType.Resolve();

            IsBlacklisted(origTypeResolved.Module.Name, origTypeResolved.FullName, HasAttribute(origTypeResolved, "MonoModBlacklisted"));

            if (type.Name.StartsWith("remove_") || HasAttribute(type, "MonoModRemove")) {
                Module.Types.Remove(origTypeResolved);
                return null;
            }

            //type = Module.Import(type).Resolve();
            
            for (int ii = 0; ii < type.Methods.Count; ii++) {
                MethodDefinition method = type.Methods[ii];
                Log("M: "+method.FullName);
                
                if (!AllowedSpecialName(method) || HasAttribute(method, "MonoModIgnore")) {
                    continue;
                }

                //method = Module.Import(method).Resolve();
                PatchMethod(method);
            }
            
            for (int ii = 0; ii < type.Properties.Count; ii++) {
                PropertyDefinition @property = type.Properties[ii];
                Log("P: "+@property.FullName);

                MethodDefinition getter = @property.GetMethod;
                if (getter != null && !HasAttribute(getter, "MonoModIgnore")) {
                    //getter = Module.Import(getter).Resolve();
                    PatchMethod(getter);
                }
                
                MethodDefinition setter = @property.SetMethod;
                if (setter != null && !HasAttribute(setter, "MonoModIgnore")) {
                    //setter = Module.Import(setter).Resolve();
                    PatchMethod(setter);
                }
            }
            
            if (HasAttribute(type, "MonoModEnumReplace")) {
                for (int ii = 0; ii < origTypeResolved.Fields.Count;) {
                    if (origTypeResolved.Fields[ii].Name == "value__") {
                        ii++;
                        continue;
                    }
                    
                    origTypeResolved.Fields.RemoveAt(ii);
                }
            }

            for (int ii = 0; ii < type.Fields.Count; ii++) {
                FieldDefinition field = type.Fields[ii];
                /*if (field.Attributes.HasFlag(FieldAttributes.SpecialName)) {
                    continue;
                }*/
                
                if (HasAttribute(field, "MonoModIgnore")) {
                    continue;
                }

                bool hasField = false;
                for (int iii = 0; iii < origTypeResolved.Fields.Count; iii++) {
                    if (origTypeResolved.Fields[iii].Name == field.Name) {
                        hasField = true;
                        break;
                    }
                }
                if (hasField) {
                    continue;
                }
                Log("F: "+field.FullName);

                FieldDefinition newField = new FieldDefinition(field.Name, field.Attributes, FindType(field.FieldType, type));
                newField.InitialValue = field.InitialValue;
                newField.Constant = field.Constant;
                origTypeResolved.Fields.Add(newField);
            }

            TypesPatched.Add(type);
            PatchNested(type);
            return origType;
        }
        
        protected virtual void PatchNested(TypeDefinition type) {
            for (int i = 0; i < type.NestedTypes.Count; i++) {
                PatchType(type.NestedTypes[i]);
            }
        }

        /// <summary>
        /// Patches the given method into the input module.
        /// </summary>
        /// <param name="method">Method to patch in.</param>
        public virtual void PatchMethod(MethodDefinition method) {
            if (method.Name.StartsWith("orig_")) {
                Log(method.Name + " is an orig_ method; ignoring...");
                return;
            }
            
            Log("Patching "+method.Name+" ...");

            Log("Checking for already existing methods...");

            TypeDefinition origType = Module.GetType(RemovePrefixes(method.DeclaringType.FullName, method.DeclaringType.Name));
            bool isTypeAdded = TypesAdded.Contains(origType.FullName);

            MethodDefinition origMethod = null; //original method that is going to be changed if existing (f.e. X)
            MethodDefinition origMethodOrig = null; //orig_ method (f.e. orig_X)

            //TODO the orig methods of replace_ methods can't be found
            for (int i = 0; i < origType.Methods.Count; i++) {
                if (origType.Methods[i].FullName == RemovePrefixes(method.FullName, method.DeclaringType.Name)) {
                    origMethod = origType.Methods[i];
                }
                if (origType.Methods[i].FullName == RemovePrefixes(method.FullName.Replace(method.Name, "orig_"+method.Name), method.DeclaringType.Name)) {
                    origMethodOrig = origType.Methods[i];
                }
            }

            if (origMethod != null && origMethodOrig == null && !isTypeAdded) {
                IsBlacklisted(origMethod.Module.Name, origMethod.DeclaringType.FullName+"."+origMethod.Name, HasAttribute(origMethod, "MonoModBlacklisted"));
                if (method.Name.StartsWith("replace_") || HasAttribute(method, "MonoModReplace")) {
                    Log("Method existing; replacing...");
                } else {
                    Log("Method existing; creating copy...");

                    MethodDefinition copy = new MethodDefinition("orig_"+origMethod.Name, origMethod.Attributes & ~MethodAttributes.SpecialName & ~MethodAttributes.RTSpecialName, origMethod.ReturnType);
                    copy.DeclaringType = origMethod.DeclaringType;
                    copy.MetadataToken = origMethod.MetadataToken;
                    copy.Body = origMethod.Body;
                    
                    for (int i = 0; i < origMethod.GenericParameters.Count; i++) {
                        copy.GenericParameters.Add(new GenericParameter(origMethod.GenericParameters[i].Name, copy) {
                            Attributes = origMethod.GenericParameters[i].Attributes,
                            MetadataToken = origMethod.GenericParameters[i].MetadataToken
                        });
                    }

                    for (int i = 0; i < origMethod.Parameters.Count; i++) {
                        copy.Parameters.Add(origMethod.Parameters[i]);
                    }

                    origType.Methods.Add(copy);
                    origMethodOrig = copy;
                    Log("Added copy of original method to "+copy.FullName);
                }
            } else if (origMethod != null) {
                Log("Prefixed method existing; ignoring...");
            }

            //fix for .cctor not linking to orig_.cctor
            if (origMethodOrig != null && origMethod.IsConstructor && origMethod.IsStatic && !isTypeAdded) {
                Collection<Instruction> instructions = method.Body.Instructions;
                ILProcessor ilProcessor = method.Body.GetILProcessor();
                ilProcessor.InsertBefore(instructions[instructions.Count - 1], ilProcessor.Create(OpCodes.Call, origMethodOrig));
            }

            for (int i = 0; method.HasBody && i < method.Body.Variables.Count; i++) {
                //TODO debug! (Import crashes)
                method.Body.Variables[i].VariableType = FindType(method.Body.Variables[i].VariableType, method);
            }

            Log("Storing method to main module...");

            if (origMethod != null) {
                origMethod.Body = method.Body;
                method = origMethod;
            } else {
                MethodDefinition clone = new MethodDefinition(method.Name, (origMethodOrig ?? method).Attributes, Module.Import(typeof(void)));
                origType.Methods.Add(clone);
                clone.MetadataToken = (origMethodOrig ?? method).MetadataToken;
                clone.CallingConvention = (origMethodOrig ?? method).CallingConvention;
                clone.ExplicitThis = (origMethodOrig ?? method).ExplicitThis;
                clone.MethodReturnType = (origMethodOrig ?? method).MethodReturnType;
                clone.NoInlining = (origMethodOrig ?? method).NoInlining;
                clone.NoOptimization = (origMethodOrig ?? method).NoOptimization;
                clone.Attributes = (origMethodOrig ?? method).Attributes;
                clone.ImplAttributes = (origMethodOrig ?? method).ImplAttributes;
                clone.SemanticsAttributes = (origMethodOrig ?? method).SemanticsAttributes;
                clone.DeclaringType = origType;
                for (int i = 0; i < (origMethodOrig ?? method).GenericParameters.Count; i++) {
                    clone.GenericParameters.Add(new GenericParameter((origMethodOrig ?? method).GenericParameters[i].Name, origType));
                }
                for (int i = 0; i < (origMethodOrig ?? method).Parameters.Count; i++) {
                    clone.Parameters.Add(new ParameterDefinition(FindType((origMethodOrig ?? method).Parameters[i].ParameterType, clone)));
                }
                for (int i = 0; i < (origMethodOrig ?? method).Overrides.Count; i++) {
                    clone.Overrides.Add((origMethodOrig ?? method).Overrides[i]);
                }
                clone.ReturnType = FindType((origMethodOrig ?? method).ReturnType, clone);
                clone.Body = method.Body;
                method = clone;
            }

            if (method.Name.StartsWith("get_") || method.Name.StartsWith("set_")) {
                Log("Finding property...");

                PropertyDefinition property = null;
                for (int i = 0; i < origType.Properties.Count; i++) {
                    if (method.Name.EndsWith(origType.Properties[i].Name)) {
                        property = origType.Properties[i];
                        if (method.Name.StartsWith("get_")) {
                            property.PropertyType = method.ReturnType;
                        }
                        break;
                    }
                }

                if (property == null) {
                    Log("Property not found; creating new property...");
                    property = new PropertyDefinition(method.Name.Substring(4), PropertyAttributes.None, method.ReturnType);
                    origType.Properties.Add(property);
                }

                if (method.Name.StartsWith("get_")) {
                    Log("Replacing getter...");
                    property.GetMethod = method;
                } else {
                    Log("Replacing setter...");
                    property.SetMethod = method;
                }
            }
        }

        /// <summary>
        /// Patches the references in all of the types in TypesPatched.
        /// </summary>
        public virtual void PatchRefs() {
            foreach (TypeDefinition type in TypesPatched) {
                if (type == null) {
                    continue;
                }
                string typeName = type.FullName;
                Log("TR: "+typeName);

                typeName = RemovePrefixes(typeName, type.Name);
                bool isTypeAdded = TypesAdded.Contains(typeName);

                TypeDefinition origType = Module.GetType(typeName);
                IsBlacklisted(origType.Module.Name, origType.FullName, HasAttribute(origType, "MonoModBlacklisted"));
                for (int ii = 0; ii < type.Methods.Count; ii++) {
                    MethodDefinition method = type.Methods[ii];

                    if (!AllowedSpecialName(method) || HasAttribute(method, "MonoModIgnore")) {
                        continue;
                    }

                    for (int iii = 0; iii < origType.Methods.Count; iii++) {
                        MethodDefinition origMethod = origType.Methods[iii];
                        if (origMethod.FullName == RemovePrefixes(method.FullName, method.DeclaringType.Name)) {
                            method = origMethod;
                            Log("MR: "+method.FullName);
                            PatchRefsInMethod(method);
                            break;
                        }
                    }
                }
                for (int ii = 0; ii < type.Properties.Count; ii++) {
                    PropertyDefinition @property = type.Properties[ii];
                    
                    for (int iii = 0; iii < origType.Properties.Count; iii++) {
                        PropertyDefinition origProperty = origType.Properties[iii];
                        if (origProperty.FullName == RemovePrefixes(@property.FullName, @property.DeclaringType.Name)) {
                            @property = origProperty;
                            Log("PR: "+@property.FullName);
                            MethodDefinition getter = @property.GetMethod;
                            if (getter != null && !HasAttribute(getter, "MonoModIgnore")) {
                                //getter = Module.Import(getter).Resolve();
                                PatchRefsInMethod(getter);
                            }
                            
                            MethodDefinition setter = @property.SetMethod;
                            if (setter != null && !HasAttribute(setter, "MonoModIgnore")) {
                                //setter = Module.Import(setter).Resolve();
                                PatchRefsInMethod(setter);
                            }
                            break;
                        }
                    }
                }
                if (isTypeAdded) {
                    for (int ii = 0; ii < type.Fields.Count; ii++) {
                        FieldDefinition field = type.Fields[ii];
                        
                        if (HasAttribute(field, "MonoModIgnore")) {
                            continue;
                        }
        
                        FieldDefinition origField = null;
                        for (int iii = 0; iii < origType.Fields.Count; iii++) {
                            if (origType.Fields[iii].Name == field.Name) {
                                origField = origType.Fields[iii];
                                break;
                            }
                        }
                        if (origField == null) {
                            //The field should've been added...
                            continue;
                        }
                        Log("FR: "+origField.FullName);
                        
                        origField.FieldType = FindType(origField.FieldType, type);
                    }
                }
            }
        }

        /// <summary>
        /// Patches the references in method.
        /// </summary>
        /// <param name="method">Method to patch.</param>
        public virtual void PatchRefsInMethod(MethodDefinition method) {
            if (method.Name.StartsWith("orig_")) {
                Log(method.Name + " is an orig_ method; ignoring...");
                return;
            }

            Log("Patching references in "+method.Name+" ...");

            Log("Checking for original methods...");

            TypeDefinition origType = Module.GetType(RemovePrefixes(method.DeclaringType.FullName, method.DeclaringType.Name));
            bool isTypeAdded = TypesAdded.Contains(origType.FullName);

            MethodDefinition origMethodOrig = null; //orig_ method (f.e. orig_X)

            for (int i = 0; i < origType.Methods.Count; i++) {
                if (origType.Methods[i].FullName == RemovePrefixes(method.FullName.Replace(method.Name, "orig_"+method.Name), method.DeclaringType.Name)) {
                    origMethodOrig = origType.Methods[i];
                }
            }

            if (origMethodOrig != null) {
                IsBlacklisted(origMethodOrig.Module.Name, method.DeclaringType+"."+RemovePrefixes(method.Name), HasAttribute(origMethodOrig, "MonoModBlacklisted"));
                Log("Prefixed method existing; ignoring...");
            }

            Log("Modifying method body...");
            for (int i = 0; method.HasBody && i < method.Body.Instructions.Count; i++) {
                Instruction instruction = method.Body.Instructions[i];
                object operand = instruction.Operand;
                
                if (operand is MethodReference) {
                    MethodReference methodCalled = FindLinked((MethodReference) operand);
                    if (methodCalled.FullName == RemovePrefixes(method.FullName, method.DeclaringType.Name)) {
                        operand = method;
                    } else {
                        MethodReference findMethod = FindMethod(methodCalled, method, false);

                        if (origMethodOrig != null && methodCalled.FullName == origMethodOrig.FullName) {
                            Log("Found call to the original method; linking...");
                            findMethod = origMethodOrig;
                        }
                        
                        if (findMethod == null && methodCalled.IsGenericInstance) {
                            GenericInstanceMethod genericMethodCalled = ((GenericInstanceMethod) methodCalled);
                            Log("Calling method: " + genericMethodCalled.FullName);
                            Log("Element method: " + genericMethodCalled.ElementMethod.FullName);
                            GenericInstanceMethod genericMethod = new GenericInstanceMethod(FindMethod(genericMethodCalled.ElementMethod, method, true));

                            for (int gi = 0; gi < genericMethodCalled.GenericArguments.Count; gi++) {
                                Log("Generic argument: " + genericMethodCalled.GenericArguments[gi]);
                                //genericMethod.GenericArguments.Add(genericMethodCalled.GenericArguments[gi]);
                                bool found = false;
                                for (int gii = 0; gii < method.GenericParameters.Count && !found; gii++) {
                                    GenericParameter genericParam = method.GenericParameters[gii];
                                    Log("Checking against: " + genericParam.FullName);
                                    if (genericParam.FullName == genericMethodCalled.GenericArguments[gi].FullName) {
                                        Log("Success!");
                                        genericMethod.GenericArguments.Add(genericParam);
                                        found = true;
                                    }
                                }
                                for (int gii = 0; gii < method.DeclaringType.GenericParameters.Count && !found; gii++) {
                                    GenericParameter genericParam = method.DeclaringType.GenericParameters[gii];
                                    Log("Checking against: " + genericParam.FullName);
                                    if (genericParam.FullName == genericMethodCalled.GenericArguments[gi].FullName) {
                                        Log("Success!");
                                        genericMethod.GenericArguments.Add(genericParam);
                                        found = true;
                                    }
                                }
                                if (!found) {
                                    genericMethod.GenericArguments.Add(FindType(genericMethodCalled.GenericArguments[gi], method, true));
                                }
                            }

                            findMethod = genericMethod;
                        }

                        if (findMethod == null) {
                            try {
                                findMethod = Module.Import(methodCalled);
                            } catch {
                                //uh. generic type failed importing?
                            }
                        }

                        MethodDefinition findMethodDef = findMethod == null ? null : findMethod.Resolve();
                        if (findMethodDef != null && !isTypeAdded) {
                            IsBlacklisted(findMethod.Module.Name, findMethod.DeclaringType.FullName+"."+findMethod.Name, HasAttribute(findMethodDef, "MonoModBlacklisted"));
                            //Quite untested - fixes invalid IL when calling virtual methods when not virtual in patch
                            if (findMethodDef.Attributes.HasFlag(MethodAttributes.Virtual)) {
                                instruction.OpCode = OpCodes.Callvirt;
                            }
                            //Fixes linkto base methods being called as callvirt
                            //FIXME find out other cases where this should be set due to linkto
                            //FIXME test something better than name...
                            if (method.DeclaringType.BaseType != null && findMethodDef.DeclaringType.Name == method.DeclaringType.BaseType.Name) {
                                instruction.OpCode = OpCodes.Call;
                            }
                        }
                        
                        operand = findMethod ?? Module.Import(methodCalled);
                    }
                }

                if (operand is FieldReference) {
                    FieldReference field = FindLinked((FieldReference) operand);

                    TypeReference findTypeRef = FindType(field.DeclaringType, method, false);
                    TypeDefinition findType = findTypeRef == null ? null : findTypeRef.Resolve();
                    
                    if (findType != null) {
                        for (int ii = 0; ii < findType.Fields.Count; ii++) {
                            if (findType.Fields[ii].Name == field.Name) {
                                field = findType.Fields[ii];
                                
                                if (field.DeclaringType.IsGenericInstance) {
                                    field = Module.Import(new FieldReference(field.Name, FindType(field.FieldType, findTypeRef), findTypeRef));
                                }
                                
                                if (field.Module != Module) {
                                    field = Module.Import(field);
                                }
                                
                                break;
                            }
                        }
                    }
                    
                    if (field == operand) {
                        field = new FieldReference(field.Name, FindType(field.FieldType, method), FindType(field.DeclaringType, method));
                    }

                    if (field != null) {
                        IsBlacklisted(field.Module.Name, field.DeclaringType.FullName+"."+field.Name);
                        //Everything the mod touches is our kingdom
                        if (field.IsDefinition) {
                            ((FieldDefinition) field).IsPrivate = false;
                            ((FieldDefinition) field).IsPublic = true;
                        }
                    }
                    operand = field;
                }

                if (operand is TypeReference) {
                    operand = FindType((TypeReference) operand, method);
                }

                instruction.Operand = operand;
            }

            for (int i = 0; method.HasBody && i < method.Body.Variables.Count; i++) {
                method.Body.Variables[i].VariableType = FindType(method.Body.Variables[i].VariableType, method);
            }

            if (method.ReturnType.IsGenericParameter) {
                method.ReturnType = FindTypeGeneric(method.ReturnType, method);

                /*
                TypeReference returnType = method.ReturnType;

                Log("Generic param wanted: " + returnType.FullName);
                Log("Method: " + method.FullName);
                for (int gi = 0; gi < method.GenericParameters.Count; gi++) {
                    GenericParameter genericParam = method.GenericParameters[gi];
{'fraction_non_alphanumeric': 0.10091795836864863, 'fraction_numerical': 0.0014652856588757523, 'mean_word_length': 1.3369141936350208, 'pattern_counts': {'":': 0, '<': 181, '<?xml version=': 0, '>': 105, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 60, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '304826', 'n_tokens_mistral': 16765, 'n_tokens_neox': 15700, 'n_words': 3503}