/// <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();
            }
        }
Exemple #3
0
        /// <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);
 }
Exemple #5
0
 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();
        }
Exemple #7
0
 /// <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");
Exemple #8
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);
            }
        }
        /// <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);
        }
Exemple #10
0
 private static ResolveEventHandler GenerateModAssemblyResolver(ModMetadata meta)
 => (sender, args) => {
     return(meta.OpenAssembly(new AssemblyName(args.Name).Name + ".dll"));
 };
Exemple #11
0
        /// <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);
            }
        }