<commit_before>﻿using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Mdb;
using Mono.Cecil.Pdb;
using Mono.Collections.Generic;
using MonoMod.InlineRT;
using StringInject;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using MonoMod.NET40Shim;
using MonoMod.Helpers;

namespace MonoMod {
    public class MonoModder : IDisposable {

        public readonly static Version Version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;

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

        public static IDictionary<string, object> Data = new FastDictionary<string, object>() {
            { "Platform",           (PlatformHelper.Current & ~Platform.X64).ToString() },
            { "PlatformPrefix",     (PlatformHelper.Current & ~Platform.X64).ToString().ToLowerInvariant() + "_" },
            { "Arch",               (PlatformHelper.Current & Platform.X64).ToString() },
            { "Architecture",       (PlatformHelper.Current & Platform.X64).ToString() },
            { "ArchPrefix",         (PlatformHelper.Current & Platform.X64).ToString().ToLowerInvariant() + "_" },
            { "ArchitecturePrefix", (PlatformHelper.Current & Platform.X64).ToString().ToLowerInvariant() + "_" }
        };

        public IDictionary<string, object> RelinkMap = new FastDictionary<string, object>();
        public IDictionary<string, ModuleDefinition> RelinkModuleMap = new FastDictionary<string, ModuleDefinition>();
        public HashSet<string> SkipList = new HashSet<string>(EqualityComparer<string>.Default);

        public IDictionary<string, IMetadataTokenProvider> RelinkMapCache = new FastDictionary<string, IMetadataTokenProvider>();
        public IDictionary<string, TypeReference> RelinkModuleMapCache = new FastDictionary<string, TypeReference>();

        public FastDictionary<IMetadataTokenProvider, IGenericParameterProvider, IMetadataTokenProvider> RelinkerCache = new FastDictionary<IMetadataTokenProvider, IGenericParameterProvider, IMetadataTokenProvider>();

        public Relinker Relinker;
        public Relinker MainRelinker;
        public Relinker PostRelinker;

        public MethodParser MethodParser;

        public MethodRewriter MethodRewriter;
        public MethodBodyRewriter MethodBodyRewriter;

        public Action<ModuleDefinition> OnReadMod;
        public Action PostProcessors;

        public IDictionary<string, DynamicMethodDelegate> CustomAttributeHandlers = new FastDictionary<string, DynamicMethodDelegate>();
        public IDictionary<string, DynamicMethodDelegate> CustomMethodAttributeHandlers = new FastDictionary<string, DynamicMethodDelegate>();

        public MissingDependencyResolver MissingDependencyResolver;

        public Stream Input;
        public string InputPath;
        public Stream Output;
        public string OutputPath;
        public ModuleDefinition Module;
        public IDictionary<ModuleDefinition, List<ModuleDefinition>> DependencyMap = new FastDictionary<ModuleDefinition, List<ModuleDefinition>>();
        public IDictionary<string, ModuleDefinition> DependencyCache = new FastDictionary<string, ModuleDefinition>();
        public List<string> DependencyDirs = new List<string>();
        public bool CleanupEnabled;

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

        public int CurrentRID = 0;

        public DebugSymbolFormat DebugSymbolOutputFormat = DebugSymbolFormat.Auto;

        // NO-OP: Keep for compatibility with that random old installer:tm:.
        [Obsolete("The optimization pass has been removed from MonoMod.")]
        public bool SkipOptimization = false;

        public bool PreventInline = 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,
                        ReadSymbols = true
                    };
                }
                return _readerParameters;
            }
            set {
                _readerParameters = value;
            }
        }

        protected WriterParameters _writerParameters;
        public virtual WriterParameters WriterParameters {
            get {
                if (_writerParameters == null) {
                    bool pdb = DebugSymbolOutputFormat == DebugSymbolFormat.PDB;
                    bool mdb = DebugSymbolOutputFormat == DebugSymbolFormat.MDB;
                    if (DebugSymbolOutputFormat == DebugSymbolFormat.Auto) {
                        if (((int) PlatformHelper.Current & (int) Platform.Windows) == (int) Platform.Windows)
                            pdb = true;
                        else mdb = true;
                    }
                    _writerParameters = new WriterParameters() {
                        WriteSymbols = true,
                        SymbolWriterProvider = 
                            pdb ? new NativePdbWriterProvider() :
                            mdb ? new MdbWriterProvider() :
                            (ISymbolWriterProvider) null
                    };
                }
                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");
                        _GACPath = Path.Combine(_GACPath, "assembly");
                        _GACPath = Path.Combine(_GACPath, "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");
                        _GACPath = Path.Combine(_GACPath, "mono");
                        _GACPath = Path.Combine(_GACPath, "gac");
                    }
                }

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

        public MonoModder() {
            Relinker = DefaultRelinker;
            MainRelinker = DefaultMainRelinker;
            PostRelinker = DefaultPostRelinker;

            MethodParser = DefaultParser;

            MissingDependencyResolver = DefaultMissingDependencyResolver;

            PostProcessors += DefaultPostProcessor;

            if (
                Environment.GetEnvironmentVariable("MONOMOD_LOG_VERBOSE") == "1" ||
                Environment.GetEnvironmentVariable("MONOMOD_VERBOSE") == "1" // Backwards-compatible
            ) {
                VerboseLogger = Log;
            }

            if (Environment.GetEnvironmentVariable("MONOMOD_RELINKER_CACHED") == "0") {
                Relinker = DefaultUncachedRelinker;
            }

            CleanupEnabled = Environment.GetEnvironmentVariable("MONOMOD_CLEANUP") != "0";

            PreventInline = Environment.GetEnvironmentVariable("MONOMOD_PREVENTINLINE") == "1";

            MMILProxyManager.Register(this);
        }

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

        public virtual void ClearCaches(bool all = false, bool shareable = false, bool moduleSpecific = false) {
            if (all || shareable) {
                foreach (KeyValuePair<string, ModuleDefinition> dep in DependencyCache)
                    dep.Value.Dispose();
                DependencyCache.Clear();
            }

            if (all || moduleSpecific) {
                RelinkMapCache.Clear();
                RelinkModuleMapCache.Clear();
                RelinkerCache.Clear();
            }
        }

        public virtual void Dispose() {
            ClearCaches(all: true);

            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);
        }

        public virtual void LogVerbose(object obj) {
            LogVerbose(obj.ToString());
        }
        public virtual void LogVerbose(string txt) {
            if (VerboseLogger != null) {
                VerboseLogger(txt);
                return;
            }
            if (DefaultVerboseLogger != null) {
                DefaultVerboseLogger(txt);
                return;
            }
        }

        /// <summary>
        /// Reads the main module from the Input stream / InputPath file to Module.
        /// </summary>
        public virtual void Read() {
            if (Module == null) {
                if (Input != null) {
                    Log("Reading input stream into module.");
                    Module = MonoModExt.ReadModule(Input, GenReaderParameters(true));
                } else if (InputPath != null) {
                    Log("Reading input file into module.");
                    DependencyDirs.Add(Path.GetDirectoryName(InputPath));
                    Module = MonoModExt.ReadModule(InputPath, GenReaderParameters(true, InputPath));
                }
            }
        }

        [Obsolete("Use Read() and MapDependencies() instead.")]
        public virtual void Read(bool loadDependencies = true) {
            Read();
            if (loadDependencies)
                MapDependencies();
        }

        public virtual void MapDependencies() {
            foreach (ModuleDefinition mod in Mods)
                MapDependencies(mod);
            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) {
            ModuleDefinition dep;
            if ((fullName != null && DependencyCache.TryGetValue(fullName, out dep)) ||
                                     DependencyCache.TryGetValue(name    , out dep)) {
                LogVerbose($"[MapDependency] {main.Name} -> {dep.Name} (({fullName}), ({name})) from cache");
                DependencyMap[main].Add(dep);
                MapDependencies(dep);
                return;
            }

            // Used to fix Mono.Cecil.pdb being loaded instead of Mono.Cecil.Pdb.dll
            string ext = Path.GetExtension(name).ToLowerInvariant();
            bool nameRisky = ext == "pdb" || ext == "mdb";

            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) && !nameRisky)
                    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)) {
                dep = MonoModExt.ReadModule(path, GenReaderParameters(false, path));
            } else if ((dep = MissingDependencyResolver?.Invoke(this, main, name, fullName)) == null) return;

            LogVerbose($"[MapDependency] {main.Name} -> {dep.Name} (({fullName}), ({name})) loaded");
            DependencyMap[main].Add(dep);
            DependencyCache[fullName] = dep;
            DependencyCache[name] = dep;
            MapDependencies(dep);
        }
        public virtual ModuleDefinition DefaultMissingDependencyResolver(MonoModder mod, ModuleDefinition main, string name, string fullName) {
            if (Environment.GetEnvironmentVariable("MONOMOD_DEPENDENCY_MISSING_THROW") != "0")
                throw new InvalidOperationException($"MonoMod cannot map dependency {main.Name} -> (({fullName}), ({name})) - not found");
            return null;
        }

        /// <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, string outputPath = null) {
            output = output ?? Output;
            outputPath = outputPath ?? OutputPath;

            PatchWasHere();

            if (output != null) {
                Log("[Write] Writing modded module into output stream.");
                Module.Write(output, WriterParameters);
            } else {
                Log("[Write] Writing modded module into output file.");
                Module.Write(outputPath, WriterParameters);
            }
        }

        public virtual ReaderParameters GenReaderParameters(bool mainModule, string path = null) {
            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;

            if (path != null && !File.Exists(path + ".mdb") && !File.Exists(Path.ChangeExtension(path, "pdb")))
                rp.ReadSymbols = false;

            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);
				string mainNameSpaceless = mainName.Replace(" ", "");
                DependencyDirs.Add(path);
                foreach (string modFile in Directory.GetFiles(path))
                    if ((Path.GetFileName(modFile).StartsWith(mainName) ||
						Path.GetFileName(modFile).StartsWith(mainNameSpaceless)) &&
						modFile.ToLower().EndsWith(".mm.dll"))
                        ReadMod(modFile);
                return;
            }

            Log($"[ReadMod] Loading mod: {path}");
            ModuleDefinition mod = MonoModExt.ReadModule(path, GenReaderParameters(false, path));
            string dir = Path.GetDirectoryName(path);
            if (!DependencyDirs.Contains(dir))
                DependencyDirs.Add(path);
            ParseRules(mod);
            Mods.Add(mod);
            OnReadMod?.Invoke(mod);
        }
        public virtual void ReadMod(Stream stream) {
            Log($"[ReadMod] Loading mod: stream#{(uint) stream.GetHashCode()}");
            ModuleDefinition mod = MonoModExt.ReadModule(stream, GenReaderParameters(false));
            ParseRules(mod);
            Mods.Add(mod);
            OnReadMod?.Invoke(mod);
        }


        public virtual void ParseRules(ModuleDefinition mod) {
            TypeDefinition rulesType = mod.GetType("MonoMod.MonoModRules");
            Type rulesTypeMMILRT = null;
            if (rulesType != null) {
                rulesTypeMMILRT = MMILExec.ExecuteRules(this, rulesType);
                // Finally, remove the type, otherwise it'll easily conflict with other mods' rules.<commit_msg>Tabs vs spaces (considering switch to tabs right now)<commit_after>﻿using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Mdb;
using Mono.Cecil.Pdb;
using Mono.Collections.Generic;
using MonoMod.InlineRT;
using StringInject;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using MonoMod.NET40Shim;
using MonoMod.Helpers;

namespace MonoMod {
    public class MonoModder : IDisposable {

        public readonly static Version Version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;

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

        public static IDictionary<string, object> Data = new FastDictionary<string, object>() {
            { "Platform",           (PlatformHelper.Current & ~Platform.X64).ToString() },
            { "PlatformPrefix",     (PlatformHelper.Current & ~Platform.X64).ToString().ToLowerInvariant() + "_" },
            { "Arch",               (PlatformHelper.Current & Platform.X64).ToString() },
            { "Architecture",       (PlatformHelper.Current & Platform.X64).ToString() },
            { "ArchPrefix",         (PlatformHelper.Current & Platform.X64).ToString().ToLowerInvariant() + "_" },
            { "ArchitecturePrefix", (PlatformHelper.Current & Platform.X64).ToString().ToLowerInvariant() + "_" }
        };

        public IDictionary<string, object> RelinkMap = new FastDictionary<string, object>();
        public IDictionary<string, ModuleDefinition> RelinkModuleMap = new FastDictionary<string, ModuleDefinition>();
        public HashSet<string> SkipList = new HashSet<string>(EqualityComparer<string>.Default);

        public IDictionary<string, IMetadataTokenProvider> RelinkMapCache = new FastDictionary<string, IMetadataTokenProvider>();
        public IDictionary<string, TypeReference> RelinkModuleMapCache = new FastDictionary<string, TypeReference>();

        public FastDictionary<IMetadataTokenProvider, IGenericParameterProvider, IMetadataTokenProvider> RelinkerCache = new FastDictionary<IMetadataTokenProvider, IGenericParameterProvider, IMetadataTokenProvider>();

        public Relinker Relinker;
        public Relinker MainRelinker;
        public Relinker PostRelinker;

        public MethodParser MethodParser;

        public MethodRewriter MethodRewriter;
        public MethodBodyRewriter MethodBodyRewriter;

        public Action<ModuleDefinition> OnReadMod;
        public Action PostProcessors;

        public IDictionary<string, DynamicMethodDelegate> CustomAttributeHandlers = new FastDictionary<string, DynamicMethodDelegate>();
        public IDictionary<string, DynamicMethodDelegate> CustomMethodAttributeHandlers = new FastDictionary<string, DynamicMethodDelegate>();

        public MissingDependencyResolver MissingDependencyResolver;

        public Stream Input;
        public string InputPath;
        public Stream Output;
        public string OutputPath;
        public ModuleDefinition Module;
        public IDictionary<ModuleDefinition, List<ModuleDefinition>> DependencyMap = new FastDictionary<ModuleDefinition, List<ModuleDefinition>>();
        public IDictionary<string, ModuleDefinition> DependencyCache = new FastDictionary<string, ModuleDefinition>();
        public List<string> DependencyDirs = new List<string>();
        public bool CleanupEnabled;

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

        public int CurrentRID = 0;

        public DebugSymbolFormat DebugSymbolOutputFormat = DebugSymbolFormat.Auto;

        // NO-OP: Keep for compatibility with that random old installer:tm:.
        [Obsolete("The optimization pass has been removed from MonoMod.")]
        public bool SkipOptimization = false;

        public bool PreventInline = 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,
                        ReadSymbols = true
                    };
                }
                return _readerParameters;
            }
            set {
                _readerParameters = value;
            }
        }

        protected WriterParameters _writerParameters;
        public virtual WriterParameters WriterParameters {
            get {
                if (_writerParameters == null) {
                    bool pdb = DebugSymbolOutputFormat == DebugSymbolFormat.PDB;
                    bool mdb = DebugSymbolOutputFormat == DebugSymbolFormat.MDB;
                    if (DebugSymbolOutputFormat == DebugSymbolFormat.Auto) {
                        if (((int) PlatformHelper.Current & (int) Platform.Windows) == (int) Platform.Windows)
                            pdb = true;
                        else mdb = true;
                    }
                    _writerParameters = new WriterParameters() {
                        WriteSymbols = true,
                        SymbolWriterProvider = 
                            pdb ? new NativePdbWriterProvider() :
                            mdb ? new MdbWriterProvider() :
                            (ISymbolWriterProvider) null
                    };
                }
                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");
                        _GACPath = Path.Combine(_GACPath, "assembly");
                        _GACPath = Path.Combine(_GACPath, "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");
                        _GACPath = Path.Combine(_GACPath, "mono");
                        _GACPath = Path.Combine(_GACPath, "gac");
                    }
                }

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

        public MonoModder() {
            Relinker = DefaultRelinker;
            MainRelinker = DefaultMainRelinker;
            PostRelinker = DefaultPostRelinker;

            MethodParser = DefaultParser;

            MissingDependencyResolver = DefaultMissingDependencyResolver;

            PostProcessors += DefaultPostProcessor;

            if (
                Environment.GetEnvironmentVariable("MONOMOD_LOG_VERBOSE") == "1" ||
                Environment.GetEnvironmentVariable("MONOMOD_VERBOSE") == "1" // Backwards-compatible
            ) {
                VerboseLogger = Log;
            }

            if (Environment.GetEnvironmentVariable("MONOMOD_RELINKER_CACHED") == "0") {
                Relinker = DefaultUncachedRelinker;
            }

            CleanupEnabled = Environment.GetEnvironmentVariable("MONOMOD_CLEANUP") != "0";

            PreventInline = Environment.GetEnvironmentVariable("MONOMOD_PREVENTINLINE") == "1";

            MMILProxyManager.Register(this);
        }

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

        public virtual void ClearCaches(bool all = false, bool shareable = false, bool moduleSpecific = false) {
            if (all || shareable) {
                foreach (KeyValuePair<string, ModuleDefinition> dep in DependencyCache)
                    dep.Value.Dispose();
                DependencyCache.Clear();
            }

            if (all || moduleSpecific) {
                RelinkMapCache.Clear();
                RelinkModuleMapCache.Clear();
                RelinkerCache.Clear();
            }
        }

        public virtual void Dispose() {
            ClearCaches(all: true);

            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);
        }

        public virtual void LogVerbose(object obj) {
            LogVerbose(obj.ToString());
        }
        public virtual void LogVerbose(string txt) {
            if (VerboseLogger != null) {
                VerboseLogger(txt);
                return;
            }
            if (DefaultVerboseLogger != null) {
                DefaultVerboseLogger(txt);
                return;
            }
        }

        /// <summary>
        /// Reads the main module from the Input stream / InputPath file to Module.
        /// </summary>
        public virtual void Read() {
            if (Module == null) {
                if (Input != null) {
                    Log("Reading input stream into module.");
                    Module = MonoModExt.ReadModule(Input, GenReaderParameters(true));
                } else if (InputPath != null) {
                    Log("Reading input file into module.");
                    DependencyDirs.Add(Path.GetDirectoryName(InputPath));
                    Module = MonoModExt.ReadModule(InputPath, GenReaderParameters(true, InputPath));
                }
            }
        }

        [Obsolete("Use Read() and MapDependencies() instead.")]
        public virtual void Read(bool loadDependencies = true) {
            Read();
            if (loadDependencies)
                MapDependencies();
        }

        public virtual void MapDependencies() {
            foreach (ModuleDefinition mod in Mods)
                MapDependencies(mod);
            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) {
            ModuleDefinition dep;
            if ((fullName != null && DependencyCache.TryGetValue(fullName, out dep)) ||
                                     DependencyCache.TryGetValue(name    , out dep)) {
                LogVerbose($"[MapDependency] {main.Name} -> {dep.Name} (({fullName}), ({name})) from cache");
                DependencyMap[main].Add(dep);
                MapDependencies(dep);
                return;
            }

            // Used to fix Mono.Cecil.pdb being loaded instead of Mono.Cecil.Pdb.dll
            string ext = Path.GetExtension(name).ToLowerInvariant();
            bool nameRisky = ext == "pdb" || ext == "mdb";

            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) && !nameRisky)
                    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)) {
                dep = MonoModExt.ReadModule(path, GenReaderParameters(false, path));
            } else if ((dep = MissingDependencyResolver?.Invoke(this, main, name, fullName)) == null) return;

            LogVerbose($"[MapDependency] {main.Name} -> {dep.Name} (({fullName}), ({name})) loaded");
            DependencyMap[main].Add(dep);
            DependencyCache[fullName] = dep;
            DependencyCache[name] = dep;
            MapDependencies(dep);
        }
        public virtual ModuleDefinition DefaultMissingDependencyResolver(MonoModder mod, ModuleDefinition main, string name, string fullName) {
            if (Environment.GetEnvironmentVariable("MONOMOD_DEPENDENCY_MISSING_THROW") != "0")
                throw new InvalidOperationException($"MonoMod cannot map dependency {main.Name} -> (({fullName}), ({name})) - not found");
            return null;
        }

        /// <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, string outputPath = null) {
            output = output ?? Output;
            outputPath = outputPath ?? OutputPath;

            PatchWasHere();

            if (output != null) {
                Log("[Write] Writing modded module into output stream.");
                Module.Write(output, WriterParameters);
            } else {
                Log("[Write] Writing modded module into output file.");
                Module.Write(outputPath, WriterParameters);
            }
        }

        public virtual ReaderParameters GenReaderParameters(bool mainModule, string path = null) {
            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;

            if (path != null && !File.Exists(path + ".mdb") && !File.Exists(Path.ChangeExtension(path, "pdb")))
                rp.ReadSymbols = false;

            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);
                string mainNameSpaceless = mainName.Replace(" ", "");
                DependencyDirs.Add(path);
                foreach (string modFile in Directory.GetFiles(path))
                    if ((Path.GetFileName(modFile).StartsWith(mainName) ||
                        Path.GetFileName(modFile).StartsWith(mainNameSpaceless)) &&
                        modFile.ToLower().EndsWith(".mm.dll"))
                        ReadMod(modFile);
                return;
            }

            Log($"[ReadMod] Loading mod: {path}");
            ModuleDefinition mod = MonoModExt.ReadModule(path, GenReaderParameters(false, path));
            string dir = Path.GetDirectoryName(path);
            if (!DependencyDirs.Contains(dir))
                DependencyDirs.Add(path);
            ParseRules(mod);
            Mods.Add(mod);
            OnReadMod?.Invoke(mod);
        }
        public virtual void ReadMod(Stream stream) {
            Log($"[ReadMod] Loading mod: stream#{(uint) stream.GetHashCode()}");
            ModuleDefinition mod = MonoModExt.ReadModule(stream, GenReaderParameters(false));
            ParseRules(mod);
            Mods.Add(mod);
            OnReadMod?.Invoke(mod);
        }


        public virtual void ParseRules(ModuleDefinition mod) {
            TypeDefinition rulesType = mod.GetType("MonoMod.MonoModRules");
            Type rulesTypeMMILRT = null;
            if (rulesType != null) {
                rulesTypeMMILRT = MMILExec.ExecuteRules(this, rulesType);
                // Finally, remove the type, otherwise it'll easily conflict with other mods' rules.
{'fraction_non_alphanumeric': 0.09299924924924925, 'fraction_numerical': 0.001266891891891892, 'mean_word_length': 1.7199923425435517, 'pattern_counts': {'":': 0, '<': 95, '<?xml version=': 0, '>': 101, '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': '4971429', 'n_tokens_mistral': 10420, 'n_tokens_neox': 9644, 'n_words': 2323}