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