private static MissingDependencyResolver GenerateModDependencyResolver(ModMetadata meta) => (mod, main, name, fullName) => { string result; Stream stream = meta.OpenStream(out result, name + ".dll"); if (stream == null) { return(null); } using (stream) { return(ModuleDefinition.ReadModule(stream, mod.GenReaderParameters(false))); } };
/// <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); } }
/// <summary> /// Load a mod .dll given its metadata at runtime. Doesn't load the mod content. /// </summary> /// <param name="meta">Metadata of the mod to load.</param> public static void LoadMod(ModMetadata meta) { if (meta == null) { return; } // Add an AssemblyResolve handler for all bundled libraries. AppDomain.CurrentDomain.AssemblyResolve += GenerateModAssemblyResolver(meta); // Load the actual assembly. Assembly asm = null; if (!string.IsNullOrEmpty(meta.DLL)) { if (meta.Prelinked && File.Exists(meta.DLL)) { asm = Assembly.LoadFrom(meta.DLL); } else if (File.Exists(meta.DLL)) { using (FileStream stream = File.OpenRead(meta.DLL)) asm = Relinker.GetRelinkedAssembly(meta, stream); } else { string result; Stream stream = meta.OpenStream(out result, meta.DLL); if (stream != null) { using (stream) { if (meta.Prelinked) { if (stream is MemoryStream) { asm = Assembly.Load(((MemoryStream)stream).GetBuffer()); } else { using (MemoryStream ms = new MemoryStream()) { byte[] buffer = new byte[2048]; int read; while (0 < (read = stream.Read(buffer, 0, buffer.Length))) { ms.Write(buffer, 0, read); } asm = Assembly.Load(ms.ToArray()); } } } else { asm = Relinker.GetRelinkedAssembly(meta, stream); } } } else { throw new DllNotFoundException($"Cannot find DLL {meta.DLL} in mod {meta}"); } } } if (asm != null) { LoadModAssembly(meta, asm); } }