/// <summary> /// Checks if an dependency is loaded. /// Can be used by mods manually to f.e. activate / disable functionality. /// </summary> /// <param name="dep">Dependency to check for. Name and Version will be checked.</param> /// <returns>True if the dependency has already been loaded, false otherwise.</returns> public static bool DependencyLoaded(ModMetadata dep) { string depName = dep.Name; Version depVersion = dep.Version; foreach (ModBase other in ModManager._Mods) { ModMetadata meta = other.Metadata; if (meta.Name != depName) { continue; } Version version = meta.Version; // Special case: Always true if version == 0.0.* if (version.Major == 0 && version.Minor == 0) { return(true); } // Major version, breaking changes, must match. if (version.Major != depVersion.Major) { return(false); } // Minor version, non-breaking changes, installed can't be lower than what we depend on. if (version.Minor < depVersion.Minor) { return(false); } return(true); } return(false); }
/// <summary> /// Find and load all ModBases in the given assembly. /// </summary> /// <param name="meta">The mod metadata, preferably from the mod metadata.yaml file.</param> /// <param name="asm">The mod assembly, preferably relinked.</param> public static void LoadModAssembly(ModMetadata meta, Assembly asm) { if (meta != null) { ModContentManager.Crawl(new AssemblyModContent(asm)); } Type[] types; try { types = asm.GetTypes(); } catch (Exception e) { Logger.Log(LogLevel.Warn, "loader", $"Failed reading assembly: {e}"); e.LogDetailed(); return; } for (int i = 0; i < types.Length; i++) { Type type = types[i]; if (!typeof(ModBase).IsAssignableFrom(type) || type.IsAbstract) { continue; } ModBase mod = (ModBase)type.GetConstructor(ModManager._EmptyTypeArray).Invoke(ModManager._EmptyObjectArray); if (meta != null) { mod.Metadata = meta; } mod.Register(); } }
/// <summary> /// Get the checksum for a given mod's .dll or the containing container /// </summary> /// <param name="meta">The mod metadata.</param> /// <returns>A checksum to be used with other Relinker methods.</returns> public static string GetChecksum(ModMetadata meta) { string path = meta.DLL; if (!File.Exists(path)) { path = meta.Container; } return(GetChecksum(path)); }
/// <summary> /// Checks if all dependencies are loaded. /// Can be used by mods manually to f.e. activate / disable functionality. /// </summary> /// <param name="meta">The metadata of the mod listing the dependencies.</param> /// <returns>True if the dependencies have already been loaded, false otherwise.</returns> public static bool DependenciesLoaded(ModMetadata meta) { foreach (ModMetadata dep in meta.Dependencies) { if (!DependencyLoaded(dep)) { return(false); } } return(true); }
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> /// Load a mod .dll given its metadata at runtime. Doesn't load the mod content. /// If required, loads the mod after all of its dependencies have been loaded. /// </summary> /// <param name="meta">Metadata of the mod to load.</param> /// <param name="callback">Callback to be executed after the mod has been loaded. Executed immediately if meta == null.</param> public static void LoadModDelayed(ModMetadata meta, Action callback) { if (meta == null) { callback?.Invoke(); return; } foreach (ModMetadata dep in meta.Dependencies) { if (!DependencyLoaded(dep)) { Logger.Log(LogLevel.Info, "loader", $"Dependency {dep} of mod {meta} not loaded! Delaying."); Delayed.Add(new DelayedEntry { Meta = meta, Callback = callback }); return; } } LoadMod(meta); callback?.Invoke(); }
/// <summary> /// Get the cached path of a given mod's relinked .dll /// </summary> /// <param name="meta">The mod metadata.</param> /// <returns>The full path to the cached relinked .dll</returns> public static string GetCachedPath(ModMetadata meta) => Path.Combine(ModLoader.PathCache, meta.Name + "." + Path.GetFileNameWithoutExtension(meta.DLL) + ".dll");
/// <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 from a directory at runtime. /// </summary> /// <param name="dir">The path to the mod directory.</param> public static void DefaultLoadDir(string dir) { if (!Directory.Exists(dir)) // Relative path? { dir = Path.Combine(PathMods, dir); } if (!Directory.Exists(dir)) // It just doesn't exist. { return; } Logger.Log(LogLevel.Verbose, "loader", $"Loading mod directory: {dir}"); ModMetadata meta = null; ModMetadata[] multimetas = null; string metaPath = Path.Combine(dir, "metadata.yaml"); if (File.Exists(metaPath)) { using (StreamReader reader = new StreamReader(metaPath)) { try { meta = YamlHelper.Deserializer.Deserialize <DirectoryModMetadata>(reader); meta.Container = dir; meta.PostParse(); } catch (Exception e) { Logger.Log(LogLevel.Warn, "loader", $"Failed parsing metadata.yaml in {dir}: {e}"); } } } metaPath = Path.Combine(dir, "multimetadata.yaml"); if (File.Exists(metaPath)) { using (StreamReader reader = new StreamReader(metaPath)) { try { multimetas = YamlHelper.Deserializer.Deserialize <DirectoryModMetadata[]>(reader); foreach (ModMetadata multimeta in multimetas) { multimeta.Container = dir; multimeta.PostParse(); } } catch (Exception e) { Logger.Log(LogLevel.Warn, "loader", $"Failed parsing multimetadata.yaml in {dir}: {e}"); } } } ModContentSource contentMeta = new DirectoryModContent(dir); Action contentCrawl = () => { if (contentMeta == null) { return; } ModContentManager.Crawl(contentMeta); contentMeta = null; }; if (multimetas != null) { foreach (ModMetadata multimeta in multimetas) { LoadModDelayed(multimeta, contentCrawl); } } LoadModDelayed(meta, contentCrawl); }
private static ResolveEventHandler GenerateModAssemblyResolver(ModMetadata meta) => (sender, args) => { return(meta.OpenAssembly(new AssemblyName(args.Name).Name + ".dll")); };
/// <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); } }