<commit_before>﻿using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace MonoMod {
    public class MonoModder : IDisposable {

        public static Action<string> DefaultLogger;
        public Action<string> Logger;

        public Relinker Relinker;

        public Stream Input;
        public Stream Output;
        public ModuleDefinition Module;
        public Dictionary<ModuleDefinition, List<ModuleDefinition>> DependencyMap = new Dictionary<ModuleDefinition, List<ModuleDefinition>>();
        public Dictionary<string, ModuleDefinition> DependencyCache = new Dictionary<string, ModuleDefinition>();
        public List<string> DependencyDirs = new List<string>();

        public List<ModuleDefinition> Mods = new List<ModuleDefinition>();

        public int CurrentRID = 0;
        public bool SkipOptimization = false;

        public ReadingMode ReadingMode = ReadingMode.Deferred;

        protected IAssemblyResolver _assemblyResolver;
        public virtual IAssemblyResolver AssemblyResolver {
            get {
                if (_assemblyResolver == null) {
                    DefaultAssemblyResolver assemblyResolver = new DefaultAssemblyResolver();
                    foreach (string dir in DependencyDirs)
                        assemblyResolver.AddSearchDirectory(dir);
                    _assemblyResolver = assemblyResolver;
                }
                return _assemblyResolver;
            }
            set {
                _assemblyResolver = value;
            }
        }

        protected ReaderParameters _readerParameters;
        public virtual ReaderParameters ReaderParameters {
            get {
                if (_readerParameters == null) {
                    _readerParameters = new ReaderParameters(ReadingMode) {
                        AssemblyResolver = AssemblyResolver
                    };
                }
                return _readerParameters;
            }
            set {
                _readerParameters = value;
            }
        }

        protected WriterParameters _writerParameters;
        public virtual WriterParameters WriterParameters {
            get {
                if (_writerParameters == null) {
                    _writerParameters = new WriterParameters() {
                        // WriteSymbols = true
                    };
                }
                return _writerParameters;
            }
            set {
                _writerParameters = value;
            }
        }

        protected string _GACPath;
        public string GACPath {
            get {
                if (_GACPath == null) {
                    string os;
                    System.Reflection.PropertyInfo property_platform = typeof(Environment).GetProperty("Platform", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
                    if (property_platform != null) {
                        // For mono, get from
                        // static extern PlatformID Platform
                        os = property_platform.GetValue(null, null).ToString().ToLower();
                    } else {
                        // For .NET, use default value
                        os = Environment.OSVersion.Platform.ToString().ToLower();
                        // .NET also prefixes the version with a v
                    }
                    if (os.Contains("win")) {
                        // C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Xml
                        _GACPath = Path.Combine(Environment.GetEnvironmentVariable("windir"), "Microsoft.NET", "assembly", "GAC_MSIL");

                        /*} else if (os.Contains("mac") || os.Contains("osx")) {
                        // TODO test GAC path for Mono on Mac
                        // should be <prefix>/lib/mono/gac, too, but what's prefix on Mac?
                    } else if (os.Contains("lin") || os.Contains("unix")) {*/
                        // For now let's just pretend it's the same as with Linux...
                    } else if (os.Contains("mac") || os.Contains("osx") || os.Contains("lin") || os.Contains("unix")) {
                        // <prefix>/lib/mono/gac

                        Process which = new Process();
                        which.StartInfo.FileName = "which";
                        which.StartInfo.Arguments = "mono";
                        which.StartInfo.CreateNoWindow = true;
                        which.StartInfo.RedirectStandardOutput = true;
                        which.StartInfo.UseShellExecute = false;
                        which.EnableRaisingEvents = true;

                        StringBuilder whichOutputBuilder = new StringBuilder();

                        which.OutputDataReceived += new DataReceivedEventHandler(
                            delegate (object sender, DataReceivedEventArgs e) {
                                whichOutputBuilder.Append(e.Data);
                            }
                        );
                        which.Start();
                        which.BeginOutputReadLine();
                        which.WaitForExit();
                        which.CancelOutputRead();

                        _GACPath = Directory.GetParent(whichOutputBuilder.ToString().Trim()).Parent.FullName;
                        _GACPath = Path.Combine(_GACPath, "lib", "mono", "gac");
                    }
                }

                return _GACPath;
            }
            set {
                _GACPath = value;
            }
        }

        public MonoModder() {
            Relinker = DefaultRelinker;
        }

        public void SetupLegacy() {
            ReadingMode = ReadingMode.Immediate;
        }

        public virtual void Dispose() {
            Module?.Dispose();
            Module = null;

            AssemblyResolver?.Dispose();
            AssemblyResolver = null;

            foreach (ModuleDefinition mod in Mods)
                mod?.Dispose();

            foreach (List<ModuleDefinition> dependencies in DependencyMap.Values)
                foreach (ModuleDefinition dep in dependencies)
                    dep?.Dispose();
            DependencyMap.Clear();

            Input?.Dispose();
            Output?.Dispose();
        }

        public virtual void Log(object obj) {
            Log(obj.ToString());
        }

        public virtual void Log(string txt) {
            if (Logger != null) {
                Logger(txt);
                return;
            }
            if (DefaultLogger != null) {
                DefaultLogger(txt);
                return;
            }
            Console.Write("[MonoMod] ");
            Console.WriteLine(txt);
        }

        /// <summary>
        /// Reads the main module from the Input stream to Module.
        /// </summary>
        public virtual void Read(bool loadDependencies = true) {
            if (Module == null) {
                Log("Reading input stream into module.");
                Module = ModuleDefinition.ReadModule(Input, GenReaderParameters(true));
            }

            if (loadDependencies) MapDependencies(Module);
        }

        public virtual void MapDependencies(ModuleDefinition main) {
            if (DependencyMap.ContainsKey(main)) return;
            DependencyMap[main] = new List<ModuleDefinition>();

            foreach (AssemblyNameReference dep in main.AssemblyReferences)
                MapDependency(main, dep);
        }
        public virtual void MapDependency(ModuleDefinition main, AssemblyNameReference dep) {
            MapDependency(main, dep.Name, dep.FullName);
        }
        public virtual void MapDependency(ModuleDefinition main, string name, string fullName = null) {
            if ((fullName != null && fullName == Module.Assembly.Name.FullName) || name == Module.Assembly.Name.Name || Module.Assembly.Name.Name.EndsWith(".mm"))
                return; // Don't load / map the main module or other mods!

            ModuleDefinition dep;
            if ((fullName != null && DependencyCache.TryGetValue(fullName, out dep)) ||
                                     DependencyCache.TryGetValue(name    , out dep)) {
                Log($"[MapDependency] {main.Name} -> {dep.Name} (({fullName}), ({name})) from cache");
                DependencyMap[main].Add(dep);
                return;
            }

            string path = null;
            foreach (string depDir in DependencyDirs) {
                path = Path.Combine(depDir, name + ".dll");
                if (!File.Exists(path))
                    path = Path.Combine(depDir, name + ".exe");
                if (!File.Exists(path))
                    path = Path.Combine(depDir, name);
                if (File.Exists(path)) break;
                else path = null;
            }

            // Check if available in GAC
            if (path == null && fullName != null) {
                System.Reflection.Assembly asm = null;
                try {
                    asm = System.Reflection.Assembly.ReflectionOnlyLoad(fullName);
                } catch { }
                path = asm?.Location;
            }

            // Manually check in GAC
            if (path == null && fullName == null) {
                path = Path.Combine(GACPath, name);

                if (Directory.Exists(path)) {
                    string[] versions = Directory.GetDirectories(path);
                    int highest = 0;
                    int highestIndex = 0;
                    for (int i = 0; i < versions.Length; i++) {
                        Match versionMatch = Regex.Match(versions[i].Substring(path.Length + 1), "\\d+");
                        if (!versionMatch.Success) {
                            continue;
                        }
                        int version = int.Parse(versionMatch.Value);
                        if (version > highest) {
                            highest = version;
                            highestIndex = i;
                        }
                        // Maybe check minor versions?
                    }
                    path = Path.Combine(versions[highestIndex], name + ".dll");
                } else {
                    path = null;
                }
            }

            if (path == null || !File.Exists(path)) {
                Log($"[MapDependency] {main.Name} -> (({fullName}), ({name})) not found; ignoring...");
                return;
            }

            dep = ModuleDefinition.ReadModule(path, GenReaderParameters(false));
            Log($"[MapDependency] {main.Name} -> {dep.Name} (({fullName}), ({name})) loaded");
            DependencyMap[main].Add(dep);
            DependencyCache[fullName] = dep;
            DependencyCache[name] = dep;
            MapDependencies(dep);
        }

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

            PatchWasHere();

            Log("[Write] Writing modded module into output stream.");
            Module.Write(output, WriterParameters);
        }

        public virtual ReaderParameters GenReaderParameters(bool mainModule) {
            ReaderParameters _rp = ReaderParameters;
            ReaderParameters rp = new ReaderParameters(_rp.ReadingMode);
            rp.AssemblyResolver = _rp.AssemblyResolver;
            rp.MetadataResolver = _rp.MetadataResolver;
            rp.MetadataImporterProvider = _rp.MetadataImporterProvider;
            rp.ReflectionImporterProvider = _rp.ReflectionImporterProvider;
            rp.SymbolStream = _rp.SymbolStream;
            rp.SymbolReaderProvider = _rp.SymbolReaderProvider;
            rp.ReadSymbols = _rp.ReadSymbols;

            // TODO debug symbol support

            return rp;
        }


        public virtual void ReadMod(string path) {
            if (Directory.Exists(path)) {
                Log($"[ReadMod] Loading mod dir: {path}");
                string mainName = Module.Name.Substring(0, Module.Name.Length - 3);
                DependencyDirs.Add(path);
                foreach (string modFile in Directory.GetFiles(path))
                    if (Path.GetFileName(modFile).StartsWith(mainName) && modFile.ToLower().EndsWith(".mm.dll"))
                        ReadMod(modFile);
                return;
            }

            Log($"[ReadMod] Loading mod: {path}");
            ModuleDefinition mod = ModuleDefinition.ReadModule(path, GenReaderParameters(false));
            MapDependencies(mod);
            Mods.Add(mod);
        }
        public virtual void ReadMod(Stream stream) {
            Mods.Add(ModuleDefinition.ReadModule(stream, GenReaderParameters(false)));
        }


        /// <summary>
        /// Automatically mods the module, loading Input, writing the modded module to Output.
        /// </summary>
        public virtual void AutoPatch() {
            /* WHY PRE-PATCH?
             * Custom attributes and other stuff refering to possibly new types
             * 1. could access yet undefined types that need to be copied over
             * 2. need to be copied over themselves anyway, regardless if new type or not
             * To define the order of origMethoding (first types, then references), PrePatch does
             * the "type addition" job by creating stub types, which then get filled in
             * the Patch pass.
             */

            Log("[AutoPatch] PrePatch pass");
            foreach (ModuleDefinition mod in Mods)
                PrePatchModule(mod);

            Log("[AutoPatch] Patch pass");
            foreach (ModuleDefinition mod in Mods)
                PatchModule(mod);

            /* The PatchRefs pass fixes all references referring to stuff
             * possibly added in the PrePatch or Patch passes.
             */

            Log("[AutoPatch] PatchRefs pass");
            PatchRefs();

            Log("[AutoPatch] Optimization pass");
            Optimize();
        }

        /// <summary>
        /// Runs some basic optimization (f.e. disables NoOptimization, removes nops)
        /// </summary>
        public virtual void Optimize() {
            if (SkipOptimization) return;
            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) {
                        for (int instri = 0; instri < method.Body.Instructions.Count; instri++) {
                            Instruction instruction = method.Body.Instructions[instri];
                            if (instruction.OpCode == OpCodes.Nop) {
                                method.Body.Instructions.RemoveAt(instri);
                                instri = Math.Max(0, instri - 1);
                                method.Body.UpdateOffsets(instri, -1);
                            }
                        }
                    }
                }
            }
        }


        public virtual MemberReference GetLinkToRef(ICustomAttributeProvider orig, IGenericParameterProvider context) {
            CustomAttribute linkto = orig?.GetMMAttribute("LinkTo");
            if (linkto == null) return null;

            TypeDefinition type = null;
            MemberReference member = null;
            for (int i = 0; i < linkto.ConstructorArguments.Count; i++) {
                CustomAttributeArgument arg = linkto.ConstructorArguments[i];
                object value = arg.Value;

                if (i == 0) { // Type
                    if (value is string)
                        type = FindType((string) value).Resolve();
                    else if (value is TypeReference)
                        type = Relink((TypeReference) value, context).Resolve();

                } else if (i == 1) { // Member
                    if (orig is MethodReference)
                        member = type.FindMethod((string) value);
                    else if (orig is FieldReference)
                        member = type.FindField((string) value);
                }

            }

            if (orig is TypeReference)
                return Module.ImportReference(type);
            if (orig is MethodReference)
                return Module.ImportReference((MethodReference) member);
            if (orig is FieldReference)
                return Module.ImportReference((FieldReference) member);

            throw new InvalidOperationException($"Cannot link from {orig} - unknown type {orig.GetType()}");
        }

        public virtual IMetadataTokenProvider DefaultRelinker(IMetadataTokenProvider mtp, IGenericParameterProvider context) {
            ICustomAttributeProvider cap = mtp as ICustomAttributeProvider;
            CustomAttribute linkto = cap?.GetMMAttribute("LinkTo");
            if (linkto != null)
                return GetLinkToRef(cap, context);

            if (mtp is TypeReference) {
                TypeReference type = (TypeReference) mtp;

                // Type isn't coming from a mod module - just return the original.
                if (!Mods.Contains(type.Module))
                    return type;

                type = FindType(RemovePrefixes(type.FullName, type), false);
                if (type == null) {
                    type = (TypeReference) mtp; // Reset temporarily to not typecast mtp every time

                    // Type may come from a dependency. If the assembly reference is missing, add.
                    AssemblyNameReference dllRef = type.Scope as AssemblyNameReference;
                    if (dllRef != null && !Module.AssemblyReferences.Any(n => n.Name == dllRef.Name))
                        Module.AssemblyReferences.Add(dllRef);

                    // Check in the dependencies of the mod modules.
                    string fullName = RemovePrefixes(type.FullName, type);
                    Stack<ModuleDefinition> crawled = new Stack<ModuleDefinition>();
                    // Set type to null so that an actual break condition exists
                    type = null;
                    foreach (ModuleDefinition mod in Mods)
                        foreach (ModuleDefinition dep in DependencyMap[mod])
                            if ((type = FindType(dep, fullName, crawled)) != null)
                                return Module.ImportReference(type);
                }

                return Module.ImportReference(type);
            }

            if (mtp is FieldReference || mtp is MethodReference)
                // Don't relink those. It'd be useful to f.e. link to member B instead of member A.
                // MonoModExt already handles the defualt "deep" relinking.
                return mtp;

            throw new InvalidOperationException($"MonoMod default relinker can't handle metadata token providers of the type {mtp.GetType()}");
        }

        public virtual IMetadataTokenProvider Relink(IMetadataTokenProvider mtp, IGenericParameterProvider context) {
            return mtp.Relink(Relinker, context);
        }
        public virtual TypeReference Relink(TypeReference type, IGenericParameterProvider context) {
            return type.Relink(Relinker, context);
        }
        public virtual MethodReference Relink(MethodReference method, IGenericParameterProvider context) {
            return method.Relink(Relinker, context);
        }
        public virtual CustomAttribute Relink(CustomAttribute attrib, IGenericParameterProvider context) {
            return attrib.Relink(Relinker, context);
        }

        public virtual TypeReference FindType(string name)
            => FindType(Module, name, new Stack<ModuleDefinition>()) ?? Module.GetType(name, true);
        public virtual TypeReference FindType(string name, bool runtimeName)
            => FindType(Module, name, new Stack<ModuleDefinition>()) ?? Module.GetType(name, runtimeName);
        protected virtual TypeReference FindType(ModuleDefinition main, string fullName, Stack<ModuleDefinition> crawled) {
            TypeReference type;
            if ((type = main.GetType(fullName, false)) != null)
                return type;
            if (fullName.StartsWith("<PrivateImplementationDetails>/"))
                return null;
            if (crawled.Contains(main))
                return null;
            crawled.Push(main);
            foreach (ModuleDefinition dep in DependencyMap[main])
                if ((type = FindType(dep, fullName, crawled)) != null)
                    return type;
            return null;
        }

        #region Pre-Patch Pass
        /// <summary>
        /// Pre-Patches the module (adds new types, module references, resources, ...).
        /// </summary>
        /// <param name="mod">Mod to patch into the input module.</param>
        public virtual void PrePatchModule(ModuleDefinition mod) {
            foreach (TypeDefinition type in mod.Types)
                PrePatchType(type);

            foreach (ModuleReference @ref in mod.ModuleReferences)
                if (!Module.ModuleReferences.Contains(@ref))
                    Module.ModuleReferences.Add(@ref);

            foreach (Resource res in mod.Resources)
                if (res is EmbeddedResource) 
                    Module.Resources.Add(new EmbeddedResource(
                        res.Name.StartsWith(mod.Assembly.Name.Name) ?
                            Module.Assembly.Name.Name + res.Name.Substring(mod.Assembly.Name.Name.Length) :
                            res.Name,
                        res.Attributes,
                        ((EmbeddedResource) res).GetResourceData()
                    ));
        }

        /// <summary>
        /// Patches the type (adds new types).
        /// </summary>
        /// <param name="type">Type to patch into the input module.</param>
        public virtual void PrePatchType(TypeDefinition type) {
            string typeName = RemovePrefixes(type.FullName, type);

            // Fix legacy issue: Copy / inline any used modifiers.
            if ((type.Namespace != "MonoMod" && type.HasMMAttribute("Ignore")) || !type.MatchingPlatform())
                return;

            // Check if type exists in target module or dependencies.
            TypeReference targetType = FindType(typeName, false);
            TypeDefinition targetTypeDef = targetType?.Resolve();
            if (targetType != null) {
                if (targetTypeDef != null && (type.Name.StartsWith("remove_") || type.HasMMAttribute("Remove")))
                    if (targetTypeDef.DeclaringType == null)
                        Module.Types.Remove(targetTypeDef);
                    else
                        targetTypeDef.DeclaringType.Resolve().NestedTypes.Remove(targetTypeDef);
                else
                    PrePatchNested(type);
                return;
            }

            // Add the type.
            Log($"[PrePatchType] Adding {typeName} to the target module.");

            TypeDefinition newType = new TypeDefinition(type.Namespace, type.Name, type.Attributes, type.BaseType);
            newType.AddAttribute(GetMonoModAddedCtor());

            foreach (GenericParameter genParam in type.GenericParameters)
                newType.GenericParameters.Add(genParam.Clone());
<commit_msg>FindTypeDeep, adding required dependencies if any<commit_after>﻿using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace MonoMod {
    public class MonoModder : IDisposable {

        public static Action<string> DefaultLogger;
        public Action<string> Logger;

        public Relinker Relinker;

        public Stream Input;
        public Stream Output;
        public ModuleDefinition Module;
        public Dictionary<ModuleDefinition, List<ModuleDefinition>> DependencyMap = new Dictionary<ModuleDefinition, List<ModuleDefinition>>();
        public Dictionary<string, ModuleDefinition> DependencyCache = new Dictionary<string, ModuleDefinition>();
        public List<string> DependencyDirs = new List<string>();

        public List<ModuleDefinition> Mods = new List<ModuleDefinition>();

        public int CurrentRID = 0;
        public bool SkipOptimization = false;

        public ReadingMode ReadingMode = ReadingMode.Deferred;

        protected IAssemblyResolver _assemblyResolver;
        public virtual IAssemblyResolver AssemblyResolver {
            get {
                if (_assemblyResolver == null) {
                    DefaultAssemblyResolver assemblyResolver = new DefaultAssemblyResolver();
                    foreach (string dir in DependencyDirs)
                        assemblyResolver.AddSearchDirectory(dir);
                    _assemblyResolver = assemblyResolver;
                }
                return _assemblyResolver;
            }
            set {
                _assemblyResolver = value;
            }
        }

        protected ReaderParameters _readerParameters;
        public virtual ReaderParameters ReaderParameters {
            get {
                if (_readerParameters == null) {
                    _readerParameters = new ReaderParameters(ReadingMode) {
                        AssemblyResolver = AssemblyResolver
                    };
                }
                return _readerParameters;
            }
            set {
                _readerParameters = value;
            }
        }

        protected WriterParameters _writerParameters;
        public virtual WriterParameters WriterParameters {
            get {
                if (_writerParameters == null) {
                    _writerParameters = new WriterParameters() {
                        // WriteSymbols = true
                    };
                }
                return _writerParameters;
            }
            set {
                _writerParameters = value;
            }
        }

        protected string _GACPath;
        public string GACPath {
            get {
                if (_GACPath == null) {
                    string os;
                    System.Reflection.PropertyInfo property_platform = typeof(Environment).GetProperty("Platform", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
                    if (property_platform != null) {
                        // For mono, get from
                        // static extern PlatformID Platform
                        os = property_platform.GetValue(null, null).ToString().ToLower();
                    } else {
                        // For .NET, use default value
                        os = Environment.OSVersion.Platform.ToString().ToLower();
                        // .NET also prefixes the version with a v
                    }
                    if (os.Contains("win")) {
                        // C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Xml
                        _GACPath = Path.Combine(Environment.GetEnvironmentVariable("windir"), "Microsoft.NET", "assembly", "GAC_MSIL");

                        /*} else if (os.Contains("mac") || os.Contains("osx")) {
                        // TODO test GAC path for Mono on Mac
                        // should be <prefix>/lib/mono/gac, too, but what's prefix on Mac?
                    } else if (os.Contains("lin") || os.Contains("unix")) {*/
                        // For now let's just pretend it's the same as with Linux...
                    } else if (os.Contains("mac") || os.Contains("osx") || os.Contains("lin") || os.Contains("unix")) {
                        // <prefix>/lib/mono/gac

                        Process which = new Process();
                        which.StartInfo.FileName = "which";
                        which.StartInfo.Arguments = "mono";
                        which.StartInfo.CreateNoWindow = true;
                        which.StartInfo.RedirectStandardOutput = true;
                        which.StartInfo.UseShellExecute = false;
                        which.EnableRaisingEvents = true;

                        StringBuilder whichOutputBuilder = new StringBuilder();

                        which.OutputDataReceived += new DataReceivedEventHandler(
                            delegate (object sender, DataReceivedEventArgs e) {
                                whichOutputBuilder.Append(e.Data);
                            }
                        );
                        which.Start();
                        which.BeginOutputReadLine();
                        which.WaitForExit();
                        which.CancelOutputRead();

                        _GACPath = Directory.GetParent(whichOutputBuilder.ToString().Trim()).Parent.FullName;
                        _GACPath = Path.Combine(_GACPath, "lib", "mono", "gac");
                    }
                }

                return _GACPath;
            }
            set {
                _GACPath = value;
            }
        }

        public MonoModder() {
            Relinker = DefaultRelinker;
        }

        public void SetupLegacy() {
            ReadingMode = ReadingMode.Immediate;
        }

        public virtual void Dispose() {
            Module?.Dispose();
            Module = null;

            AssemblyResolver?.Dispose();
            AssemblyResolver = null;

            foreach (ModuleDefinition mod in Mods)
                mod?.Dispose();

            foreach (List<ModuleDefinition> dependencies in DependencyMap.Values)
                foreach (ModuleDefinition dep in dependencies)
                    dep?.Dispose();
            DependencyMap.Clear();

            Input?.Dispose();
            Output?.Dispose();
        }

        public virtual void Log(object obj) {
            Log(obj.ToString());
        }

        public virtual void Log(string txt) {
            if (Logger != null) {
                Logger(txt);
                return;
            }
            if (DefaultLogger != null) {
                DefaultLogger(txt);
                return;
            }
            Console.Write("[MonoMod] ");
            Console.WriteLine(txt);
        }

        /// <summary>
        /// Reads the main module from the Input stream to Module.
        /// </summary>
        public virtual void Read(bool loadDependencies = true) {
            if (Module == null) {
                Log("Reading input stream into module.");
                Module = ModuleDefinition.ReadModule(Input, GenReaderParameters(true));
            }

            if (loadDependencies) MapDependencies(Module);
        }

        public virtual void MapDependencies(ModuleDefinition main) {
            if (DependencyMap.ContainsKey(main)) return;
            DependencyMap[main] = new List<ModuleDefinition>();

            foreach (AssemblyNameReference dep in main.AssemblyReferences)
                MapDependency(main, dep);
        }
        public virtual void MapDependency(ModuleDefinition main, AssemblyNameReference dep) {
            MapDependency(main, dep.Name, dep.FullName);
        }
        public virtual void MapDependency(ModuleDefinition main, string name, string fullName = null) {
            if ((fullName != null && fullName == Module.Assembly.Name.FullName) || name == Module.Assembly.Name.Name || Module.Assembly.Name.Name.EndsWith(".mm"))
                return; // Don't load / map the main module or other mods!

            ModuleDefinition dep;
            if ((fullName != null && DependencyCache.TryGetValue(fullName, out dep)) ||
                                     DependencyCache.TryGetValue(name    , out dep)) {
                Log($"[MapDependency] {main.Name} -> {dep.Name} (({fullName}), ({name})) from cache");
                DependencyMap[main].Add(dep);
                return;
            }

            string path = null;
            foreach (string depDir in DependencyDirs) {
                path = Path.Combine(depDir, name + ".dll");
                if (!File.Exists(path))
                    path = Path.Combine(depDir, name + ".exe");
                if (!File.Exists(path))
                    path = Path.Combine(depDir, name);
                if (File.Exists(path)) break;
                else path = null;
            }

            // Check if available in GAC
            if (path == null && fullName != null) {
                System.Reflection.Assembly asm = null;
                try {
                    asm = System.Reflection.Assembly.ReflectionOnlyLoad(fullName);
                } catch { }
                path = asm?.Location;
            }

            // Manually check in GAC
            if (path == null && fullName == null) {
                path = Path.Combine(GACPath, name);

                if (Directory.Exists(path)) {
                    string[] versions = Directory.GetDirectories(path);
                    int highest = 0;
                    int highestIndex = 0;
                    for (int i = 0; i < versions.Length; i++) {
                        Match versionMatch = Regex.Match(versions[i].Substring(path.Length + 1), "\\d+");
                        if (!versionMatch.Success) {
                            continue;
                        }
                        int version = int.Parse(versionMatch.Value);
                        if (version > highest) {
                            highest = version;
                            highestIndex = i;
                        }
                        // Maybe check minor versions?
                    }
                    path = Path.Combine(versions[highestIndex], name + ".dll");
                } else {
                    path = null;
                }
            }

            if (path == null || !File.Exists(path)) {
                Log($"[MapDependency] {main.Name} -> (({fullName}), ({name})) not found; ignoring...");
                return;
            }

            dep = ModuleDefinition.ReadModule(path, GenReaderParameters(false));
            Log($"[MapDependency] {main.Name} -> {dep.Name} (({fullName}), ({name})) loaded");
            DependencyMap[main].Add(dep);
            DependencyCache[fullName] = dep;
            DependencyCache[name] = dep;
            MapDependencies(dep);
        }

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

            PatchWasHere();

            Log("[Write] Writing modded module into output stream.");
            Module.Write(output, WriterParameters);
        }

        public virtual ReaderParameters GenReaderParameters(bool mainModule) {
            ReaderParameters _rp = ReaderParameters;
            ReaderParameters rp = new ReaderParameters(_rp.ReadingMode);
            rp.AssemblyResolver = _rp.AssemblyResolver;
            rp.MetadataResolver = _rp.MetadataResolver;
            rp.MetadataImporterProvider = _rp.MetadataImporterProvider;
            rp.ReflectionImporterProvider = _rp.ReflectionImporterProvider;
            rp.SymbolStream = _rp.SymbolStream;
            rp.SymbolReaderProvider = _rp.SymbolReaderProvider;
            rp.ReadSymbols = _rp.ReadSymbols;

            // TODO debug symbol support

            return rp;
        }


        public virtual void ReadMod(string path) {
            if (Directory.Exists(path)) {
                Log($"[ReadMod] Loading mod dir: {path}");
                string mainName = Module.Name.Substring(0, Module.Name.Length - 3);
                DependencyDirs.Add(path);
                foreach (string modFile in Directory.GetFiles(path))
                    if (Path.GetFileName(modFile).StartsWith(mainName) && modFile.ToLower().EndsWith(".mm.dll"))
                        ReadMod(modFile);
                return;
            }

            Log($"[ReadMod] Loading mod: {path}");
            ModuleDefinition mod = ModuleDefinition.ReadModule(path, GenReaderParameters(false));
            MapDependencies(mod);
            Mods.Add(mod);
        }
        public virtual void ReadMod(Stream stream) {
            Mods.Add(ModuleDefinition.ReadModule(stream, GenReaderParameters(false)));
        }


        /// <summary>
        /// Automatically mods the module, loading Input, writing the modded module to Output.
        /// </summary>
        public virtual void AutoPatch() {
            /* WHY PRE-PATCH?
             * Custom attributes and other stuff refering to possibly new types
             * 1. could access yet undefined types that need to be copied over
             * 2. need to be copied over themselves anyway, regardless if new type or not
             * To define the order of origMethoding (first types, then references), PrePatch does
             * the "type addition" job by creating stub types, which then get filled in
             * the Patch pass.
             */

            Log("[AutoPatch] PrePatch pass");
            foreach (ModuleDefinition mod in Mods)
                PrePatchModule(mod);

            Log("[AutoPatch] Patch pass");
            foreach (ModuleDefinition mod in Mods)
                PatchModule(mod);

            /* The PatchRefs pass fixes all references referring to stuff
             * possibly added in the PrePatch or Patch passes.
             */

            Log("[AutoPatch] PatchRefs pass");
            PatchRefs();

            Log("[AutoPatch] Optimization pass");
            Optimize();
        }

        /// <summary>
        /// Runs some basic optimization (f.e. disables NoOptimization, removes nops)
        /// </summary>
        public virtual void Optimize() {
            if (SkipOptimization) return;
            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) {
                        for (int instri = 0; instri < method.Body.Instructions.Count; instri++) {
                            Instruction instruction = method.Body.Instructions[instri];
                            if (instruction.OpCode == OpCodes.Nop) {
                                method.Body.Instructions.RemoveAt(instri);
                                instri = Math.Max(0, instri - 1);
                                method.Body.UpdateOffsets(instri, -1);
                            }
                        }
                    }
                }
            }
        }


        public virtual MemberReference GetLinkToRef(ICustomAttributeProvider orig, IGenericParameterProvider context) {
            CustomAttribute linkto = orig?.GetMMAttribute("LinkTo");
            if (linkto == null) return null;

            TypeDefinition type = null;
            MemberReference member = null;
            for (int i = 0; i < linkto.ConstructorArguments.Count; i++) {
                CustomAttributeArgument arg = linkto.ConstructorArguments[i];
                object value = arg.Value;

                if (i == 0) { // Type
                    if (value is string)
                        type = FindTypeDeep((string) value).Resolve();
                    else if (value is TypeReference)
                        type = Relink((TypeReference) value, context).Resolve();

                } else if (i == 1) { // Member
                    if (orig is MethodReference)
                        member = type.FindMethod((string) value);
                    else if (orig is FieldReference)
                        member = type.FindField((string) value);
                }

            }

            if (orig is TypeReference)
                return Module.ImportReference(type);
            if (orig is MethodReference)
                return Module.ImportReference((MethodReference) member);
            if (orig is FieldReference)
                return Module.ImportReference((FieldReference) member);

            throw new InvalidOperationException($"Cannot link from {orig} - unknown type {orig.GetType()}");
        }

        public virtual IMetadataTokenProvider DefaultRelinker(IMetadataTokenProvider mtp, IGenericParameterProvider context) {
            ICustomAttributeProvider cap = mtp as ICustomAttributeProvider;
            CustomAttribute linkto = cap?.GetMMAttribute("LinkTo");
            if (linkto != null)
                return GetLinkToRef(cap, context);

            if (mtp is TypeReference) {
                TypeReference type = (TypeReference) mtp;

                // Type isn't coming from a mod module - just return the original.
                if (!Mods.Contains(type.Module))
                    return type;

                return Module.ImportReference(FindTypeDeep(RemovePrefixes(type.FullName, type)));
            }

            if (mtp is FieldReference || mtp is MethodReference)
                // Don't relink those. It'd be useful to f.e. link to member B instead of member A.
                // MonoModExt already handles the defualt "deep" relinking.
                return mtp;

            throw new InvalidOperationException($"MonoMod default relinker can't handle metadata token providers of the type {mtp.GetType()}");
        }

        public virtual IMetadataTokenProvider Relink(IMetadataTokenProvider mtp, IGenericParameterProvider context) {
            return mtp.Relink(Relinker, context);
        }
        public virtual TypeReference Relink(TypeReference type, IGenericParameterProvider context) {
            return type.Relink(Relinker, context);
        }
        public virtual MethodReference Relink(MethodReference method, IGenericParameterProvider context) {
            return method.Relink(Relinker, context);
        }
        public virtual CustomAttribute Relink(CustomAttribute attrib, IGenericParameterProvider context) {
            return attrib.Relink(Relinker, context);
        }

        public virtual TypeReference FindType(string name)
            => FindType(Module, name, new Stack<ModuleDefinition>()) ?? Module.GetType(name, true);
        public virtual TypeReference FindType(string name, bool runtimeName)
            => FindType(Module, name, new Stack<ModuleDefinition>()) ?? Module.GetType(name, runtimeName);
        protected virtual TypeReference FindType(ModuleDefinition main, string fullName, Stack<ModuleDefinition> crawled) {
            TypeReference type;
            if ((type = main.GetType(fullName, false)) != null)
                return type;
            if (fullName.StartsWith("<PrivateImplementationDetails>/"))
                return null;
            if (crawled.Contains(main))
                return null;
            crawled.Push(main);
            foreach (ModuleDefinition dep in DependencyMap[main])
                if ((type = FindType(dep, fullName, crawled)) != null)
                    return type;
            return null;
        }
        public virtual TypeReference FindTypeDeep(string name) {
            TypeReference type = FindType(name, false);
            if (type != null)
                return type;

            // Check in the dependencies of the mod modules.
            Stack<ModuleDefinition> crawled = new Stack<ModuleDefinition>();
            // Set type to null so that an actual break condition exists
            type = null;
            foreach (ModuleDefinition mod in Mods)
                foreach (ModuleDefinition dep in DependencyMap[mod])
                    if ((type = FindType(dep, name, crawled)) != null) {
                        // Type may come from a dependency. If the assembly reference is missing, add.
                        AssemblyNameReference dllRef = type.Scope as AssemblyNameReference;
                        if (dllRef != null && !Module.AssemblyReferences.Any(n => n.Name == dllRef.Name))
                            Module.AssemblyReferences.Add(dllRef);
                        return Module.ImportReference(type);
                    }

            return null;
        }

        #region Pre-Patch Pass
        /// <summary>
        /// Pre-Patches the module (adds new types, module references, resources, ...).
        /// </summary>
        /// <param name="mod">Mod to patch into the input module.</param>
        public virtual void PrePatchModule(ModuleDefinition mod) {
            foreach (TypeDefinition type in mod.Types)
                PrePatchType(type);

            foreach (ModuleReference @ref in mod.ModuleReferences)
                if (!Module.ModuleReferences.Contains(@ref))
                    Module.ModuleReferences.Add(@ref);

            foreach (Resource res in mod.Resources)
                if (res is EmbeddedResource) 
                    Module.Resources.Add(new EmbeddedResource(
                        res.Name.StartsWith(mod.Assembly.Name.Name) ?
                            Module.Assembly.Name.Name + res.Name.Substring(mod.Assembly.Name.Name.Length) :
                            res.Name,
                        res.Attributes,
                        ((EmbeddedResource) res).GetResourceData()
                    ));
        }

        /// <summary>
        /// Patches the type (adds new types).
        /// </summary>
        /// <param name="type">Type to patch into the input module.</param>
        public virtual void PrePatchType(TypeDefinition type) {
            string typeName = RemovePrefixes(type.FullName, type);

            // Fix legacy issue: Copy / inline any used modifiers.
            if ((type.Namespace != "MonoMod" && type.HasMMAttribute("Ignore")) || !type.MatchingPlatform())
                return;

            // Check if type exists in target module or dependencies.
            TypeReference targetType = FindTypeDeep(typeName);
            TypeDefinition targetTypeDef = targetType?.Resolve();
            if (targetType != null) {
                if (targetTypeDef != null && (type.Name.StartsWith("remove_") || type.HasMMAttribute("Remove")))
                    if (targetTypeDef.DeclaringType == null)
                        Module.Types.Remove(targetTypeDef);
                    else
                        targetTypeDef.DeclaringType.Resolve().NestedTypes.Remove(targetTypeDef);
                else
                    PrePatchNested(type);
                return;
            }

            // Add the type.
            Log($"[PrePatchType] Adding {typeName} to the target module.");

            TypeDefinition newType = new TypeDefinition(type.Namespace, type.Name, type.Attributes, type.BaseType);
            newType.AddAttribute(GetMonoModAddedCtor());

            foreach (GenericParameter genParam in type.GenericParameters)
                newType.GenericParameters.Add(genParam.Clone());

{'fraction_non_alphanumeric': 0.0914283314434505, 'fraction_numerical': 0.0007559531309058839, 'mean_word_length': 1.575469147152669, 'pattern_counts': {'":': 0, '<': 93, '<?xml version=': 0, '>': 97, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '195573', 'n_tokens_mistral': 11390, 'n_tokens_neox': 10576, 'n_words': 2911}