/// <summary> /// Creates a system of multiple assembly resolvers. The latest resolver is fired first. The list can be cleared. /// </summary> /// <param name="resolver"></param> /// <param name="handler"></param> internal static void RegisterSpecialResolveFailureHandler(this BaseAssemblyResolver resolver, AssemblyResolveEventHandler handler) { if (_resolveDictionary.ContainsKey(resolver)) { _resolveDictionary[resolver].Add(handler); } else { _resolveDictionary[resolver] = new List <AssemblyResolveEventHandler> { handler }; resolver.ResolveFailure += (sender, name) => { foreach (var curHandler in _resolveDictionary[resolver].Reverse()) { var assembly = curHandler(sender, name); if (assembly != null) { return(assembly); } } return(null); }; } }
/// <summary> /// Loads and returns the topological sorted list of unique assemblies to rewrite. /// </summary> internal static IEnumerable <AssemblyInfo> LoadAssembliesToRewrite(RewritingOptions options, AssemblyResolveEventHandler handler) { // Add all explicitly requested assemblies. var assemblies = new HashSet <AssemblyInfo>(); foreach (string path in options.AssemblyPaths) { if (!assemblies.Any(assembly => assembly.FilePath == path)) { var name = Path.GetFileName(path); if (options.IsAssemblyIgnored(name)) { throw new InvalidOperationException($"Rewriting assembly '{name}' ({path}) that is in the ignore list."); } assemblies.Add(new AssemblyInfo(name, path, options, handler)); } } // Find direct dependencies to each assembly and load them, if the corresponding option is enabled. foreach (var assembly in assemblies) { assembly.LoadDependencies(assemblies, handler); } // Validate that all assemblies are eligible for rewriting. foreach (var assembly in assemblies) { assembly.ValidateAssembly(); } return(SortAssemblies(assemblies)); }
/// <summary> /// Initializes a new instance of the <see cref="AssemblyInfo"/> class. /// </summary> private AssemblyInfo(string name, string path, RewritingOptions options, AssemblyResolveEventHandler handler) { this.Name = name; this.FilePath = path; this.Dependencies = new HashSet <AssemblyInfo>(); this.Options = options; this.IsRewritten = false; this.IsDisposed = false; // TODO: can we reuse it, or do we need a new one for each assembly? var assemblyResolver = new DefaultAssemblyResolver(); // Add known search directories for resolving assemblies. assemblyResolver.AddSearchDirectory( Path.GetDirectoryName(typeof(Types.Threading.Tasks.Task).Assembly.Location)); assemblyResolver.AddSearchDirectory(this.Options.AssembliesDirectory); if (this.Options.DependencySearchPaths != null) { foreach (var dependencySearchPath in this.Options.DependencySearchPaths) { assemblyResolver.AddSearchDirectory(dependencySearchPath); } } // Add the assembly resolution error handler. assemblyResolver.ResolveFailure += handler; this.Resolver = assemblyResolver; var readerParameters = new ReaderParameters() { AssemblyResolver = assemblyResolver, ReadSymbols = this.IsSymbolFileAvailable() }; this.Definition = AssemblyDefinition.ReadAssembly(this.FilePath, readerParameters); this.FullName = this.Definition.FullName; }
/// <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); } AssemblyResolveEventHandler resolver = (s, r) => { ModuleDefinition dep = depResolver(Modder, Modder.Module, r.Name, r.FullName); if (dep != null) { return(dep.Assembly); } return(OnRelinkerResolveFailure(s, r)); }; bool temporaryASM = false; try { MonoModder modder = Modder; modder.Input = stream; modder.OutputPath = cachedPath; modder.MissingDependencyResolver = depResolver; ((DefaultAssemblyResolver)modder.AssemblyResolver).ResolveFailure += resolver; 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 { ((DefaultAssemblyResolver)Modder.AssemblyResolver).ResolveFailure -= resolver; 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> /// Creates a system of multiple assembly resolvers. The latest resolver is fired first. The list can be cleared. /// </summary> /// <param name="resolver"></param> /// <param name="handler"></param> internal static void RegisterSpecialResolveFailureHandler(this BaseAssemblyResolver resolver, AssemblyResolveEventHandler handler) { if (_resolveDictionary.ContainsKey(resolver)) { _resolveDictionary[resolver].Add(handler); } else { _resolveDictionary[resolver] = new List<AssemblyResolveEventHandler> { handler }; resolver.ResolveFailure += (sender, name) => { foreach (var curHandler in _resolveDictionary[resolver].Reverse()) { var assembly = curHandler(sender, name); if (assembly != null) { return assembly; } } return null; }; } }