예제 #1
0
        /// <summary>
        /// Relink a .dll to point towards the game's assembly at runtime, then load it.
        /// </summary>
        /// <param name="meta">The mod metadata, used for caching, among other things.</param>
        /// <param name="stream">The stream to read the .dll from.</param>
        /// <param name="depResolver">An optional dependency resolver.</param>
        /// <param name="checksumsExtra">Any optional checksums. If you're running this at runtime, pass at least Relinker.GetChecksum(Metadata)</param>
        /// <param name="prePatch">An optional step executed before patching, but after MonoMod has loaded the input assembly.</param>
        /// <returns>The loaded, relinked assembly.</returns>
        public static Assembly GetRelinkedAssembly(ModMetadata meta, Stream stream,
                                                   MissingDependencyResolver depResolver = null, string[] checksumsExtra = null, Action <MonoModder> prePatch = null)
        {
            string cachedPath         = GetCachedPath(meta);
            string cachedChecksumPath = cachedPath.Substring(0, cachedPath.Length - 4) + ".sum";

            string[] checksums = new string[2 + (checksumsExtra?.Length ?? 0)];
            if (GameChecksum == null)
            {
                GameChecksum = GetChecksum(Assembly.GetAssembly(typeof(Utils.Relinker)).Location);
            }
            checksums[0] = GameChecksum;

            checksums[1] = GetChecksum(meta);

            if (checksumsExtra != null)
            {
                for (int i = 0; i < checksumsExtra.Length; i++)
                {
                    checksums[i + 2] = checksumsExtra[i];
                }
            }

            if (File.Exists(cachedPath) && File.Exists(cachedChecksumPath) &&
                ChecksumsEqual(checksums, File.ReadAllLines(cachedChecksumPath)))
            {
                Logger.Log(LogLevel.Verbose, "relinker", $"Loading cached assembly for {meta}");
                try {
                    return(Assembly.LoadFrom(cachedPath));
                } catch (Exception e) {
                    Logger.Log(LogLevel.Warn, "relinker", $"Failed loading {meta}");
                    e.LogDetailed();
                    return(null);
                }
            }

            if (depResolver == null)
            {
                depResolver = GenerateModDependencyResolver(meta);
            }

            try {
                MonoModder modder = Modder;

                modder.Input      = stream;
                modder.OutputPath = cachedPath;
                modder.MissingDependencyResolver = depResolver;

                string symbolPath;
                modder.ReaderParameters.SymbolStream = meta.OpenStream(out symbolPath, meta.DLL.Substring(0, meta.DLL.Length - 4) + ".pdb", meta.DLL + ".mdb");
                modder.ReaderParameters.ReadSymbols  = modder.ReaderParameters.SymbolStream != null;
                if (modder.ReaderParameters.SymbolReaderProvider != null &&
                    modder.ReaderParameters.SymbolReaderProvider is RelinkerSymbolReaderProvider)
                {
                    ((RelinkerSymbolReaderProvider)modder.ReaderParameters.SymbolReaderProvider).Format =
                        string.IsNullOrEmpty(symbolPath) ? DebugSymbolFormat.Auto :
                        symbolPath.EndsWith(".mdb") ? DebugSymbolFormat.MDB :
                        symbolPath.EndsWith(".pdb") ? DebugSymbolFormat.PDB :
                        DebugSymbolFormat.Auto;
                }

                modder.Read();

                modder.ReaderParameters.ReadSymbols = false;

                if (modder.ReaderParameters.SymbolReaderProvider != null &&
                    modder.ReaderParameters.SymbolReaderProvider is RelinkerSymbolReaderProvider)
                {
                    ((RelinkerSymbolReaderProvider)modder.ReaderParameters.SymbolReaderProvider).Format = DebugSymbolFormat.Auto;
                }

                modder.MapDependencies();

                if (RuntimeRuleContainer != null)
                {
                    modder.ParseRules(RuntimeRuleContainer);
                    RuntimeRuleContainer = null;
                }

                prePatch?.Invoke(modder);

                modder.AutoPatch();

                modder.Write();
            } catch (Exception e) {
                Logger.Log(LogLevel.Warn, "relinker", $"Failed relinking {meta}");
                e.LogDetailed();
                return(null);
            } finally {
                Modder.ClearCaches(moduleSpecific: true);
                Modder.Module.Dispose();
                Modder.Module = null;
                Modder.ReaderParameters.SymbolStream?.Dispose();
            }

            if (File.Exists(cachedChecksumPath))
            {
                File.Delete(cachedChecksumPath);
            }
            File.WriteAllLines(cachedChecksumPath, checksums);

            Logger.Log(LogLevel.Verbose, "relinker", $"Loading assembly for {meta}");
            try {
                return(Assembly.LoadFrom(cachedPath));
            } catch (Exception e) {
                Logger.Log(LogLevel.Warn, "relinker", $"Failed loading {meta}");
                e.LogDetailed();
                return(null);
            }
        }
예제 #2
0
        public static Assembly GetRelinkedAssembly(this GameModMetadata meta, Stream stream, MissingDependencyResolver depResolver = null)
        {
            string name               = Path.GetFileName(meta.DLL);
            string cachedName         = meta.Name + "." + name.Substring(0, name.Length - 3) + "dll";
            string cachedPath         = Path.Combine(ModLoader.ModsCacheDirectory, cachedName);
            string cachedChecksumPath = Path.Combine(ModLoader.ModsCacheDirectory, cachedName + ".sum");

            string[] checksums = new string[2];
            using (MD5 md5 = MD5.Create()) {
                if (GameChecksum == null)
                {
                    using (FileStream fs = File.OpenRead(Assembly.GetAssembly(typeof(ModRelinker)).Location))
                        GameChecksum = md5.ComputeHash(fs).ToHexadecimalString();
                }
                checksums[0] = GameChecksum;

                string modPath = meta.Archive;
                if (modPath.Length == 0)
                {
                    modPath = meta.DLL;
                }
                using (FileStream fs = File.OpenRead(modPath))
                    checksums[1] = md5.ComputeHash(fs).ToHexadecimalString();
            }

            if (File.Exists(cachedPath) && File.Exists(cachedChecksumPath) &&
                checksums.ChecksumsEqual(File.ReadAllLines(cachedChecksumPath)))
            {
                return(Assembly.LoadFrom(cachedPath));
            }

            if (depResolver == null)
            {
                depResolver = _GenerateModDependencyResolver(meta);
            }

            using (MonoModder modder = new MonoModder()
            {
                Input = stream,
                OutputPath = cachedPath,
                CleanupEnabled = false,
                RelinkModuleMap = AssemblyRelinkMap,
                DependencyDirs =
                {
                    ManagedDirectory
                },
                MissingDependencyResolver = depResolver,
                RelinkMap = ModRuntimePatcher.Detourer.RelinkMap
            })
                try {
                    modder.ReaderParameters.ReadSymbols          = false;
                    modder.WriterParameters.WriteSymbols         = false;
                    modder.WriterParameters.SymbolWriterProvider = null;

                    modder.Read();
                    modder.MapDependencies();
                    modder.AutoPatch();
                    modder.Write();
                } catch (Exception e) {
                    ModLogger.Log("relinker", $"Failed relinking {meta}: {e}");
                    return(null);
                }

            return(Assembly.LoadFrom(cachedPath));
        }
예제 #3
0
        /// <summary>
        /// Relink a .dll to point towards Celeste.exe and FNA / XNA properly at runtime, then load it.
        /// </summary>
        /// <param name="meta">The mod metadata, used for caching, among other things.</param>
        /// <param name="stream">The stream to read the .dll from.</param>
        /// <param name="depResolver">An optional dependency resolver.</param>
        /// <param name="checksumsExtra">Any optional checksums. If you're running this at runtime, pass at least Everest.Relinker.GetChecksum(Metadata)</param>
        /// <param name="prePatch">An optional step executed before patching, but after MonoMod has loaded the input assembly.</param>
        /// <returns>The loaded, relinked assembly.</returns>
        public static Assembly GetRelinkedAssembly(Stream stream, string modName)
        {
            string cachedPath = GetCachedPath(modName);

            MissingDependencyResolver depResolver = GenerateModDependencyResolver();

            try
            {
                MonoModder modder = Modder;

                modder.Input      = stream;
                modder.OutputPath = cachedPath;
                modder.MissingDependencyResolver = depResolver;

                modder.ReaderParameters.ReadSymbols = false;
                if (modder.ReaderParameters.SymbolReaderProvider is RelinkerSymbolReaderProvider)
                {
                    ((RelinkerSymbolReaderProvider)modder.ReaderParameters.SymbolReaderProvider).Format = DebugSymbolFormat.Auto;
                }

                modder.Read();

                modder.ReaderParameters.ReadSymbols = false;

                if (modder.ReaderParameters.SymbolReaderProvider != null &&
                    modder.ReaderParameters.SymbolReaderProvider is RelinkerSymbolReaderProvider)
                {
                    ((RelinkerSymbolReaderProvider)modder.ReaderParameters.SymbolReaderProvider).Format = DebugSymbolFormat.Auto;
                }

                modder.MapDependencies();

                modder.AutoPatch();

                modder.Write();
            }
            catch (Exception e)
            {
                Logger.LogError($"[API] Failed relinking\n{e}");
                e.LogDetailed();
                return(null);
            }
            finally
            {
                Modder.ClearCaches(moduleSpecific: true);
                Modder.Module.Dispose();
                Modder.Module = null;
                Modder.ReaderParameters.SymbolStream?.Dispose();
            }

            Logger.LogDebug($"[API] Loading assembly for {modName}");
            try
            {
                return(Assembly.LoadFrom(cachedPath));
            }
            catch (Exception e)
            {
                Logger.LogError($"[API] Failed loading\n{e}");
                e.LogDetailed();
                return(null);
            }
        }
예제 #4
0
            /// <summary>
            /// Relink a .dll to point towards Celeste.exe and FNA / XNA properly at runtime, then load it.
            /// </summary>
            /// <param name="meta">The mod metadata, used for caching, among other things.</param>
            /// <param name="stream">The stream to read the .dll from.</param>
            /// <param name="depResolver">An optional dependency resolver.</param>
            /// <param name="checksumsExtra">Any optional checksums</param>
            /// <param name="prePatch">An optional step executed before patching, but after MonoMod has loaded the input assembly.</param>
            /// <returns>The loaded, relinked assembly.</returns>
            public static Assembly GetRelinkedAssembly(EverestModuleMetadata meta, string asmname, Stream stream,
                                                       MissingDependencyResolver depResolver = null, string[] checksumsExtra = null, Action <MonoModder> prePatch = null)
            {
                if (!Flags.SupportRelinkingMods)
                {
                    Logger.Log(LogLevel.Warn, "relinker", "Relinker disabled!");
                    return(null);
                }

                string cachedPath         = GetCachedPath(meta, asmname);
                string cachedChecksumPath = cachedPath.Substring(0, cachedPath.Length - 4) + ".sum";

                string[] checksums = new string[2 + (checksumsExtra?.Length ?? 0)];
                if (GameChecksum == null)
                {
                    GameChecksum = Everest.GetChecksum(Assembly.GetAssembly(typeof(Relinker)).Location).ToHexadecimalString();
                }
                checksums[0] = GameChecksum;

                checksums[1] = Everest.GetChecksum(ref stream).ToHexadecimalString();

                if (checksumsExtra != null)
                {
                    for (int i = 0; i < checksumsExtra.Length; i++)
                    {
                        checksums[i + 2] = checksumsExtra[i];
                    }
                }

                if (File.Exists(cachedPath) && File.Exists(cachedChecksumPath) &&
                    ChecksumsEqual(checksums, File.ReadAllLines(cachedChecksumPath)))
                {
                    Logger.Log(LogLevel.Verbose, "relinker", $"Loading cached assembly for {meta} - {asmname}");
                    try {
                        Assembly asm = Assembly.LoadFrom(cachedPath);
                        _RelinkedAssemblies.Add(asm);
                        return(asm);
                    } catch (Exception e) {
                        Logger.Log(LogLevel.Warn, "relinker", $"Failed loading {meta} - {asmname}");
                        e.LogDetailed();
                        return(null);
                    }
                }

                if (depResolver == null)
                {
                    depResolver = GenerateModDependencyResolver(meta);
                }

                bool temporaryASM = false;

                try {
                    MonoModder modder = Modder;

                    modder.Input      = stream;
                    modder.OutputPath = cachedPath;
                    modder.MissingDependencyResolver = depResolver;

                    string symbolPath;
                    modder.ReaderParameters.SymbolStream = OpenStream(meta, out symbolPath, meta.DLL.Substring(0, meta.DLL.Length - 4) + ".pdb", meta.DLL + ".mdb");
                    modder.ReaderParameters.ReadSymbols  = modder.ReaderParameters.SymbolStream != null;
                    if (modder.ReaderParameters.SymbolReaderProvider != null &&
                        modder.ReaderParameters.SymbolReaderProvider is RelinkerSymbolReaderProvider)
                    {
                        ((RelinkerSymbolReaderProvider)modder.ReaderParameters.SymbolReaderProvider).Format =
                            string.IsNullOrEmpty(symbolPath) ? DebugSymbolFormat.Auto :
                            symbolPath.EndsWith(".mdb") ? DebugSymbolFormat.MDB :
                            symbolPath.EndsWith(".pdb") ? DebugSymbolFormat.PDB :
                            DebugSymbolFormat.Auto;
                    }

                    try {
                        modder.ReaderParameters.ReadSymbols = true;
                        modder.Read();
                    } catch {
                        modder.ReaderParameters.SymbolStream?.Dispose();
                        modder.ReaderParameters.SymbolStream = null;
                        modder.ReaderParameters.ReadSymbols  = false;
                        stream.Seek(0, SeekOrigin.Begin);
                        modder.Read();
                    }

                    if (modder.ReaderParameters.SymbolReaderProvider != null &&
                        modder.ReaderParameters.SymbolReaderProvider is RelinkerSymbolReaderProvider)
                    {
                        ((RelinkerSymbolReaderProvider)modder.ReaderParameters.SymbolReaderProvider).Format = DebugSymbolFormat.Auto;
                    }

                    modder.MapDependencies();

                    if (!RuntimeRulesParsed)
                    {
                        RuntimeRulesParsed = true;

                        InitMMSharedData();

                        string rulesPath = Path.Combine(
                            Path.GetDirectoryName(typeof(Celeste).Assembly.Location),
                            Path.GetFileNameWithoutExtension(typeof(Celeste).Assembly.Location) + ".Mod.mm.dll"
                            );
                        if (!File.Exists(rulesPath))
                        {
                            // Fallback if someone renamed Celeste.exe
                            rulesPath = Path.Combine(
                                Path.GetDirectoryName(typeof(Celeste).Assembly.Location),
                                "Celeste.Mod.mm.dll"
                                );
                        }
                        if (File.Exists(rulesPath))
                        {
                            ModuleDefinition rules = ModuleDefinition.ReadModule(rulesPath, new ReaderParameters(ReadingMode.Immediate));
                            modder.ParseRules(rules);
                            rules.Dispose(); // Is this safe?
                        }
                    }

                    prePatch?.Invoke(modder);

                    modder.ParseRules(modder.Module);

                    modder.AutoPatch();

RetryWrite:
                    try {
                        modder.WriterParameters.WriteSymbols = true;
                        modder.Write();
                    } catch {
                        try {
                            modder.WriterParameters.WriteSymbols = false;
                            modder.Write();
                        } catch when(!temporaryASM)
                        {
                            temporaryASM = true;
                            long stamp = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;

                            cachedPath          = Path.Combine(Path.GetTempPath(), $"Everest.Relinked.{Path.GetFileNameWithoutExtension(cachedPath)}.{stamp}.dll");
                            modder.Module.Name += "." + stamp;
                            modder.Module.Assembly.Name.Name += "." + stamp;
                            modder.OutputPath = cachedPath;
                            modder.WriterParameters.WriteSymbols = true;
                            goto RetryWrite;
                        }
                    }
                } catch (Exception e) {
                    Logger.Log(LogLevel.Warn, "relinker", $"Failed relinking {meta} - {asmname}");
                    e.LogDetailed();
                    return(null);
                } finally {
                    Modder.ReaderParameters.SymbolStream?.Dispose();
                    if (SharedModder)
                    {
                        Modder.ClearCaches(moduleSpecific: true);
                        Modder.Module.Dispose();
                        Modder.Module = null;
                    }
                    else
                    {
                        Modder.Dispose();
                        Modder = null;
                    }
                }

                if (File.Exists(cachedChecksumPath))
                {
                    File.Delete(cachedChecksumPath);
                }
                if (!temporaryASM)
                {
                    File.WriteAllLines(cachedChecksumPath, checksums);
                }

                Logger.Log(LogLevel.Verbose, "relinker", $"Loading assembly for {meta} - {asmname}");
                try {
                    Assembly asm = Assembly.LoadFrom(cachedPath);
                    _RelinkedAssemblies.Add(asm);
                    return(asm);
                } catch (Exception e) {
                    Logger.Log(LogLevel.Warn, "relinker", $"Failed loading {meta} - {asmname}");
                    e.LogDetailed();
                    return(null);
                }
            }
예제 #5
0
 /// <summary>
 /// Relink a .dll to point towards Celeste.exe and FNA / XNA properly at runtime, then load it.
 /// </summary>
 /// <param name="meta">The mod metadata, used for caching, among other things.</param>
 /// <param name="stream">The stream to read the .dll from.</param>
 /// <param name="depResolver">An optional dependency resolver.</param>
 /// <param name="checksumsExtra">Any optional checksums</param>
 /// <param name="prePatch">An optional step executed before patching, but after MonoMod has loaded the input assembly.</param>
 /// <returns>The loaded, relinked assembly.</returns>
 public static Assembly GetRelinkedAssembly(EverestModuleMetadata meta, Stream stream,
                                            MissingDependencyResolver depResolver = null, string[] checksumsExtra = null, Action <MonoModder> prePatch = null)
 => GetRelinkedAssembly(meta, Path.GetFileNameWithoutExtension(meta.DLL), stream, depResolver, checksumsExtra, prePatch);
예제 #6
0
            public static Assembly GetRelinkedAssembly(EverestModuleMetadata meta, Stream stream,
                                                       MissingDependencyResolver depResolver = null, string[] checksumsExtra = null, Action <MonoModder> prePatch = null)
            {
                string cachedPath         = GetCachedPath(meta);
                string cachedChecksumPath = cachedPath.Substring(0, cachedPath.Length - 4) + ".sum";

                string[] checksums = new string[2 + (checksumsExtra?.Length ?? 0)];
                if (GameChecksum == null)
                {
                    GameChecksum = GetChecksum(Assembly.GetAssembly(typeof(Relinker)).Location);
                }
                checksums[0] = GameChecksum;

                checksums[1] = GetChecksum(meta);

                if (checksumsExtra != null)
                {
                    for (int i = 0; i < checksumsExtra.Length; i++)
                    {
                        checksums[i + 2] = checksumsExtra[i];
                    }
                }

                if (File.Exists(cachedPath) && File.Exists(cachedChecksumPath) &&
                    ChecksumsEqual(checksums, File.ReadAllLines(cachedChecksumPath)))
                {
                    return(Assembly.LoadFrom(cachedPath));
                }

                if (depResolver == null)
                {
                    depResolver = GenerateModDependencyResolver(meta);
                }

                try {
                    MonoModder modder = Modder;

                    modder.Input      = stream;
                    modder.OutputPath = cachedPath;
                    modder.MissingDependencyResolver = depResolver;

                    modder.Read();
                    modder.MapDependencies();
                    prePatch?.Invoke(modder);
                    modder.AutoPatch();
                    modder.Write();
                } catch (Exception e) {
                    Logger.Log("relinker", $"Failed relinking {meta}: {e}");
                    return(null);
                } finally {
                    Modder.ClearCaches(moduleSpecific: true);
                    Modder.Module.Dispose();
                    Modder.Module = null;
                }

                if (File.Exists(cachedChecksumPath))
                {
                    File.Delete(cachedChecksumPath);
                }
                File.WriteAllLines(cachedChecksumPath, checksums);

                return(Assembly.LoadFrom(cachedPath));
            }
예제 #7
0
            /// <summary>
            /// Relink a .dll to point towards Celeste.exe and FNA / XNA properly at runtime, then load it.
            /// </summary>
            /// <param name="meta">The mod metadata, used for caching, among other things.</param>
            /// <param name="stream">The stream to read the .dll from.</param>
            /// <param name="depResolver">An optional dependency resolver.</param>
            /// <param name="checksumsExtra">Any optional checksums. If you're running this at runtime, pass at least Everest.Relinker.GetChecksum(Metadata)</param>
            /// <param name="prePatch">An optional step executed before patching, but after MonoMod has loaded the input assembly.</param>
            /// <returns>The loaded, relinked assembly.</returns>
            public static Assembly GetRelinkedAssembly(EverestModuleMetadata meta, Stream stream,
                                                       MissingDependencyResolver depResolver = null, string[] checksumsExtra = null, Action <MonoModder> prePatch = null)
            {
                if (!Flags.SupportRelinkingMods)
                {
                    Logger.Log(LogLevel.Warn, "relinker", "Relinker disabled!");
                    return(null);
                }

                string cachedPath         = GetCachedPath(meta);
                string cachedChecksumPath = cachedPath.Substring(0, cachedPath.Length - 4) + ".sum";

                string[] checksums = new string[2 + (checksumsExtra?.Length ?? 0)];
                if (GameChecksum == null)
                {
                    GameChecksum = Everest.GetChecksum(Assembly.GetAssembly(typeof(Relinker)).Location).ToHexadecimalString();
                }
                checksums[0] = GameChecksum;

                checksums[1] = Everest.GetChecksum(meta).ToHexadecimalString();

                if (checksumsExtra != null)
                {
                    for (int i = 0; i < checksumsExtra.Length; i++)
                    {
                        checksums[i + 2] = checksumsExtra[i];
                    }
                }

                if (File.Exists(cachedPath) && File.Exists(cachedChecksumPath) &&
                    ChecksumsEqual(checksums, File.ReadAllLines(cachedChecksumPath)))
                {
                    Logger.Log(LogLevel.Verbose, "relinker", $"Loading cached assembly for {meta}");
                    try {
                        return(Assembly.LoadFrom(cachedPath));
                    } catch (Exception e) {
                        Logger.Log(LogLevel.Warn, "relinker", $"Failed loading {meta}");
                        e.LogDetailed();
                        return(null);
                    }
                }

                if (depResolver == null)
                {
                    depResolver = GenerateModDependencyResolver(meta);
                }

                try {
                    MonoModder modder = Modder;

                    modder.Input      = stream;
                    modder.OutputPath = cachedPath;
                    modder.MissingDependencyResolver = depResolver;

                    string symbolPath;
                    modder.ReaderParameters.SymbolStream = OpenStream(meta, out symbolPath, meta.DLL.Substring(0, meta.DLL.Length - 4) + ".pdb", meta.DLL + ".mdb");
                    modder.ReaderParameters.ReadSymbols  = modder.ReaderParameters.SymbolStream != null;
                    if (modder.ReaderParameters.SymbolReaderProvider != null &&
                        modder.ReaderParameters.SymbolReaderProvider is RelinkerSymbolReaderProvider)
                    {
                        ((RelinkerSymbolReaderProvider)modder.ReaderParameters.SymbolReaderProvider).Format =
                            string.IsNullOrEmpty(symbolPath) ? DebugSymbolFormat.Auto :
                            symbolPath.EndsWith(".mdb") ? DebugSymbolFormat.MDB :
                            symbolPath.EndsWith(".pdb") ? DebugSymbolFormat.PDB :
                            DebugSymbolFormat.Auto;
                    }

                    modder.Read();

                    modder.ReaderParameters.ReadSymbols = false;

                    if (modder.ReaderParameters.SymbolReaderProvider != null &&
                        modder.ReaderParameters.SymbolReaderProvider is RelinkerSymbolReaderProvider)
                    {
                        ((RelinkerSymbolReaderProvider)modder.ReaderParameters.SymbolReaderProvider).Format = DebugSymbolFormat.Auto;
                    }

                    modder.MapDependencies();

                    if (!RuntimeRulesParsed)
                    {
                        RuntimeRulesParsed = true;

                        InitMMSharedData();

                        string rulesPath = Path.Combine(
                            Path.GetDirectoryName(typeof(Celeste).Assembly.Location),
                            Path.GetFileNameWithoutExtension(typeof(Celeste).Assembly.Location) + ".Mod.mm.dll"
                            );
                        if (!File.Exists(rulesPath))
                        {
                            // Fallback if someone renamed Celeste.exe
                            rulesPath = Path.Combine(
                                Path.GetDirectoryName(typeof(Celeste).Assembly.Location),
                                "Celeste.Mod.mm.dll"
                                );
                        }
                        if (File.Exists(rulesPath))
                        {
                            ModuleDefinition rules = ModuleDefinition.ReadModule(rulesPath, new ReaderParameters(ReadingMode.Immediate));
                            modder.ParseRules(rules);
                            rules.Dispose(); // Is this safe?
                        }

                        // Fix old mods built against HookIL instead of ILContext.
                        _Modder.RelinkMap["MonoMod.RuntimeDetour.HookGen.ILManipulator"]  = "MonoMod.Cil.ILContext/Manipulator";
                        _Modder.RelinkMap["MonoMod.RuntimeDetour.HookGen.HookIL"]         = "MonoMod.Cil.ILContext";
                        _Modder.RelinkMap["MonoMod.RuntimeDetour.HookGen.HookILCursor"]   = "MonoMod.Cil.ILCursor";
                        _Modder.RelinkMap["MonoMod.RuntimeDetour.HookGen.HookILLabel"]    = "MonoMod.Cil.ILLabel";
                        _Modder.RelinkMap["MonoMod.RuntimeDetour.HookGen.HookExtensions"] = "MonoMod.Cil.ILPatternMatchingExt";

                        _Shim("MonoMod.Utils.ReflectionHelper", typeof(MonoModUpdateShim._ReflectionHelper));
                        _Shim("MonoMod.Cil.ILCursor", typeof(MonoModUpdateShim._ILCursor));

                        // If no entry for MonoMod.Utils exists already, add one.
                        modder.MapDependency(_Modder.Module, "MonoMod.Utils");
                    }

                    prePatch?.Invoke(modder);

                    modder.AutoPatch();

                    modder.Write();
                } catch (Exception e) {
                    Logger.Log(LogLevel.Warn, "relinker", $"Failed relinking {meta}");
                    e.LogDetailed();
                    return(null);
                } finally {
                    Modder.ClearCaches(moduleSpecific: true);
                    Modder.Module.Dispose();
                    Modder.Module = null;
                    Modder.ReaderParameters.SymbolStream?.Dispose();
                }

                if (File.Exists(cachedChecksumPath))
                {
                    File.Delete(cachedChecksumPath);
                }
                File.WriteAllLines(cachedChecksumPath, checksums);

                Logger.Log(LogLevel.Verbose, "relinker", $"Loading assembly for {meta}");
                try {
                    return(Assembly.LoadFrom(cachedPath));
                } catch (Exception e) {
                    Logger.Log(LogLevel.Warn, "relinker", $"Failed loading {meta}");
                    e.LogDetailed();
                    return(null);
                }
            }