Ejemplo n.º 1
0
            /// <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(EverestModuleMetadata meta, Action callback)
            {
                if (Flags.IsDisabled || !Flags.SupportRuntimeMods)
                {
                    Logger.Log(LogLevel.Warn, "loader", "Loader disabled!");
                    return;
                }

                if (meta == null)
                {
                    callback?.Invoke();
                    return;
                }

                if (DependencyLoaded(meta))
                {
                    Logger.Log(LogLevel.Warn, "loader", $"Mod {meta} already loaded!");
                    return;
                }

                if (PermanentBlacklist.TryGetValue(meta.Name, out Version minver) && meta.Version < minver)
                {
                    Logger.Log(LogLevel.Warn, "loader", $"Mod {meta} permanently blacklisted by Everest!");
                    return;
                }


                foreach (EverestModuleMetadata dep in meta.Dependencies)
                {
                    if (!DependencyLoaded(dep))
                    {
                        Logger.Log(LogLevel.Info, "loader", $"Dependency {dep} of mod {meta} not loaded! Delaying.");
                        lock (Delayed) {
                            Delayed.Add(Tuple.Create(meta, callback));
                        }
                        return;
                    }
                }

                callback?.Invoke();

                LoadMod(meta);
            }
Ejemplo n.º 2
0
            /// <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 by Everest, false otherwise.</returns>
            public static bool DependencyLoaded(EverestModuleMetadata dep)
            {
                string  depName    = dep.Name;
                Version depVersion = dep.Version;

                foreach (EverestModule other in _Modules)
                {
                    EverestModuleMetadata meta = other.Metadata;
                    if (meta.Name != depName)
                    {
                        continue;
                    }

                    Version version = meta.Version;
                    return(VersionSatisfiesDependency(depVersion, version));
                }

                return(false);
            }
Ejemplo n.º 3
0
        /// <summary>
        /// Cache the file and return a cached path.
        /// </summary>
        /// <returns>The cached file path.</returns>
        public virtual string GetCachedPath()
        {
            EverestModuleMetadata mod = Source?.Mod;

            if (mod == null)
            {
                throw new NullReferenceException("Cannot cache mod-less assets");
            }

            string path    = Path.Combine(Everest.Loader.PathCache, mod.Name, PathVirtual.Replace('/', Path.DirectorySeparatorChar));
            string pathSum = path + ".sum";

            byte[] hash = mod.Hash;
            if (hash == null)
            {
                // the mod we are looking at is not loaded - this must mean another one in the multimeta is, find it and take its hash.
                hash = mod.Multimeta.First(meta => meta.Hash != null).Hash;
            }
            string sum = hash.ToHexadecimalString();

            if (File.Exists(path))
            {
                if (File.Exists(pathSum) && File.ReadAllText(pathSum) == sum)
                {
                    return(path);
                }
                File.Delete(pathSum);
                File.Delete(path);
            }

            string dir = Path.GetDirectoryName(path);

            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }

            WriteCache(path);
            File.WriteAllText(pathSum, sum);

            return(path);
        }
Ejemplo n.º 4
0
            private static Stream OpenStream(EverestModuleMetadata meta, out string result, params string[] names)
            {
                if (!string.IsNullOrEmpty(meta.PathArchive))
                {
                    using (ZipFile zip = new ZipFile(meta.PathArchive)) {
                        foreach (ZipEntry entry in zip.Entries)
                        {
                            if (!names.Contains(entry.FileName))
                            {
                                continue;
                            }
                            result = entry.FileName;
                            return(entry.ExtractStream());
                        }
                    }
                    result = null;
                    return(null);
                }

                if (!string.IsNullOrEmpty(meta.PathDirectory))
                {
                    foreach (string name in names)
                    {
                        string path = name;
                        if (!File.Exists(path))
                        {
                            path = Path.Combine(meta.PathDirectory, name);
                        }
                        if (!File.Exists(path))
                        {
                            continue;
                        }
                        result = path;
                        return(File.OpenRead(path));
                    }
                }

                result = null;
                return(null);
            }
Ejemplo n.º 5
0
            private static MissingDependencyResolver GenerateModDependencyResolver(EverestModuleMetadata meta)
            {
                if (!string.IsNullOrEmpty(meta.PathArchive))
                {
                    return(delegate(MonoModder mod, ModuleDefinition main, string name, string fullName) {
                        string asmName = name + ".dll";
                        using (Stream zipStream = File.OpenRead(meta.PathArchive))
                            using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Read)) {
                                foreach (ZipArchiveEntry entry in zip.Entries)
                                {
                                    if (entry.FullName != asmName)
                                    {
                                        continue;
                                    }
                                    using (Stream stream = entry.Open())
                                        using (MemoryStream ms = new MemoryStream()) {
                                            stream.CopyTo(ms);
                                            ms.Seek(0, SeekOrigin.Begin);
                                            return ModuleDefinition.ReadModule(ms, mod.GenReaderParameters(false));
                                        }
                                }
                            }
                        return null;
                    });
                }

                if (!string.IsNullOrEmpty(meta.PathDirectory))
                {
                    return(delegate(MonoModder mod, ModuleDefinition main, string name, string fullName) {
                        string asmPath = Path.Combine(meta.PathDirectory, name + ".dll");
                        if (!File.Exists(asmPath))
                        {
                            return null;
                        }
                        return ModuleDefinition.ReadModule(asmPath, mod.GenReaderParameters(false, asmPath));
                    });
                }

                return(null);
            }
            private static ResolveEventHandler GenerateModAssemblyResolver(EverestModuleMetadata meta)
            {
                if (!string.IsNullOrEmpty(meta.PathArchive))
                {
                    return((sender, args) => {
                        string asmName = new AssemblyName(args.Name).Name + ".dll";
                        using (Stream zipStream = File.OpenRead(meta.PathArchive))
                            using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Read)) {
                                foreach (ZipArchiveEntry entry in zip.Entries)
                                {
                                    if (entry.FullName != asmName)
                                    {
                                        continue;
                                    }
                                    using (Stream stream = entry.Open())
                                        using (MemoryStream ms = new MemoryStream()) {
                                            stream.CopyTo(ms);
                                            ms.Seek(0, SeekOrigin.Begin);
                                            return Assembly.Load(ms.GetBuffer());
                                        }
                                }
                            }
                        return null;
                    });
                }

                if (!string.IsNullOrEmpty(meta.PathDirectory))
                {
                    return((sender, args) => {
                        string asmPath = Path.Combine(meta.PathDirectory, new AssemblyName(args.Name).Name + ".dll");
                        if (!File.Exists(asmPath))
                        {
                            return null;
                        }
                        return Assembly.LoadFrom(asmPath);
                    });
                }

                return(null);
            }
Ejemplo n.º 7
0
            /// <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(EverestModuleMetadata meta, Action callback)
            {
                if (meta == null)
                {
                    callback?.Invoke();
                    return;
                }

                foreach (EverestModuleMetadata dep in meta.Dependencies)
                {
                    if (!DependencyLoaded(dep))
                    {
                        Logger.Log(LogLevel.Info, "loader", $"Dependency {dep} of mod {meta} not loaded! Delaying.");
                        Delayed.Add(Tuple.Create(meta, callback));
                        return;
                    }
                }

                LoadMod(meta);

                callback?.Invoke();
            }
Ejemplo n.º 8
0
            /// <summary>
            /// Find and load all EverestModules 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(EverestModuleMetadata meta, Assembly asm)
            {
                if (Flags.IsDisabled || !Flags.SupportRuntimeMods)
                {
                    Logger.Log(LogLevel.Warn, "loader", "Loader disabled!");
                    return;
                }

                Content.Crawl(new AssemblyModContent(asm)
                {
                    Mod  = meta,
                    Name = meta.Name
                });

                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(EverestModule).IsAssignableFrom(type) || type.IsAbstract)
                    {
                        continue;
                    }
                    if (typeof(NullModule).IsAssignableFrom(type))
                    {
                        continue;
                    }

                    EverestModule mod = (EverestModule)type.GetConstructor(_EmptyTypeArray).Invoke(_EmptyObjectArray);
                    mod.Metadata = meta;
                    mod.Register();
                }
            }
Ejemplo n.º 9
0
            /// <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(EverestModuleMetadata meta, Action callback)
            {
                if (Flags.IsDisabled || !Flags.SupportRuntimeMods)
                {
                    Logger.Log(LogLevel.Warn, "loader", "Loader disabled!");
                    return;
                }

                if (meta == null)
                {
                    callback?.Invoke();
                    return;
                }

                if (DependencyLoaded(meta))
                {
                    Logger.Log(LogLevel.Warn, "loader", $"Mod {meta} already loaded!");
                    return;
                }


                foreach (EverestModuleMetadata dep in meta.Dependencies)
                {
                    if (!DependencyLoaded(dep))
                    {
                        Logger.Log(LogLevel.Info, "loader", $"Dependency {dep} of mod {meta} not loaded! Delaying.");
                        lock (Delayed) {
                            Delayed.Add(Tuple.Create(meta, callback));
                        }
                        return;
                    }
                }

                LoadMod(meta);

                callback?.Invoke();
            }
Ejemplo n.º 10
0
            private static MissingDependencyResolver GenerateModDependencyResolver(EverestModuleMetadata meta)
            {
                if (!string.IsNullOrEmpty(meta.PathArchive))
                {
                    return(delegate(MonoModder mod, ModuleDefinition main, string name, string fullName) {
                        string asmName = name + ".dll";
                        using (ZipFile zip = new ZipFile(meta.PathArchive)) {
                            foreach (ZipEntry entry in zip.Entries)
                            {
                                if (entry.FileName != asmName)
                                {
                                    continue;
                                }
                                using (MemoryStream stream = entry.ExtractStream()) {
                                    return ModuleDefinition.ReadModule(stream, mod.GenReaderParameters(false));
                                }
                            }
                        }
                        return null;
                    });
                }

                if (!string.IsNullOrEmpty(meta.PathDirectory))
                {
                    return(delegate(MonoModder mod, ModuleDefinition main, string name, string fullName) {
                        string asmPath = Path.Combine(meta.PathDirectory, name + ".dll");
                        if (!File.Exists(asmPath))
                        {
                            return null;
                        }
                        return ModuleDefinition.ReadModule(asmPath, mod.GenReaderParameters(false, asmPath));
                    });
                }

                return(null);
            }
Ejemplo n.º 11
0
            private static ResolveEventHandler GenerateModAssemblyResolver(EverestModuleMetadata meta)
            {
                if (!string.IsNullOrEmpty(meta.PathArchive))
                {
                    return((sender, args) => {
                        string asmName = new AssemblyName(args.Name).Name + ".dll";
                        using (ZipFile zip = new ZipFile(meta.PathArchive)) {
                            foreach (ZipEntry entry in zip.Entries)
                            {
                                if (entry.FileName != asmName)
                                {
                                    continue;
                                }
                                using (MemoryStream stream = entry.ExtractStream()) {
                                    return Assembly.Load(stream.GetBuffer());
                                }
                            }
                        }
                        return null;
                    });
                }

                if (!string.IsNullOrEmpty(meta.PathDirectory))
                {
                    return((sender, args) => {
                        string asmPath = Path.Combine(meta.PathDirectory, new AssemblyName(args.Name).Name + ".dll");
                        if (!File.Exists(asmPath))
                        {
                            return null;
                        }
                        return Assembly.LoadFrom(asmPath);
                    });
                }

                return(null);
            }
Ejemplo n.º 12
0
        /// <summary>
        /// Cache the file and return a cached path.
        /// </summary>
        /// <returns>The cached file path.</returns>
        public virtual string GetCachedPath()
        {
            EverestModuleMetadata mod = Source?.Mod;

            if (mod == null)
            {
                throw new NullReferenceException("Cannot cache mod-less assets");
            }

            string path    = Path.Combine(Everest.Loader.PathCache, mod.Name, PathVirtual.Replace('/', Path.DirectorySeparatorChar));
            string pathSum = path + ".sum";

            string sum = mod.Hash.ToHexadecimalString();

            if (File.Exists(path))
            {
                if (File.Exists(pathSum) && File.ReadAllText(pathSum) == sum)
                {
                    return(path);
                }
                File.Delete(pathSum);
                File.Delete(path);
            }

            string dir = Path.GetDirectoryName(path);

            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }

            WriteCache(path);
            File.WriteAllText(pathSum, sum);

            return(path);
        }
Ejemplo n.º 13
0
            /// <summary>
            /// Find and load all EverestModules 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(EverestModuleMetadata meta, Assembly asm)
            {
                if (!Flags.SupportRuntimeMods)
                {
                    Logger.Log(LogLevel.Warn, "loader", "Loader disabled!");
                    return;
                }

                if (string.IsNullOrEmpty(meta.PathArchive) && File.Exists(meta.DLL) && meta.SupportsCodeReload && CoreModule.Settings.CodeReload)
                {
                    try {
                        FileSystemWatcher watcher = meta.DevWatcher = new FileSystemWatcher {
                            Path         = Path.GetDirectoryName(meta.DLL),
                            NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite,
                        };

                        watcher.Changed += (s, e) => {
                            if (e.FullPath != meta.DLL)
                            {
                                return;
                            }
                            ReloadModAssembly(s, e);
                            // FIXME: Should we dispose the old .dll watcher?
                        };

                        watcher.EnableRaisingEvents = true;
                    } catch (Exception e) {
                        Logger.Log(LogLevel.Warn, "loader", $"Failed watching folder: {Path.GetDirectoryName(meta.DLL)}");
                        e.LogDetailed();
                        meta.DevWatcher?.Dispose();
                        meta.DevWatcher = null;
                    }
                }

                ApplyModHackfixes(meta, asm);

                Content.Crawl(new AssemblyModContent(asm)
                {
                    Mod  = meta,
                    Name = meta.Name
                });

                Type[] types;
                try {
                    types = asm.GetTypesSafe();
                } 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(EverestModule).IsAssignableFrom(type) && !type.IsAbstract && !typeof(NullModule).IsAssignableFrom(type))
                    {
                        EverestModule mod = (EverestModule)type.GetConstructor(_EmptyTypeArray).Invoke(_EmptyObjectArray);
                        mod.Metadata = meta;
                        mod.Register();
                    }
                }
            }
Ejemplo n.º 14
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(EverestModuleMetadata meta)
            {
                if (!Flags.SupportRuntimeMods)
                {
                    Logger.Log(LogLevel.Warn, "loader", "Loader disabled!");
                    return;
                }

                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.PathArchive))
                {
                    bool returnEarly = false;
                    using (ZipFile zip = new ZipFile(meta.PathArchive)) {
                        foreach (ZipEntry entry in zip.Entries)
                        {
                            string entryName = entry.FileName.Replace('\\', '/');
                            if (entryName == meta.DLL)
                            {
                                using (MemoryStream stream = entry.ExtractStream())
                                    asm = Relinker.GetRelinkedAssembly(meta, Path.GetFileNameWithoutExtension(meta.DLL), stream);
                            }

                            if (entryName == "main.lua")
                            {
                                new LuaModule(meta).Register();
                                returnEarly = true;
                            }
                        }
                    }

                    if (returnEarly)
                    {
                        return;
                    }
                }
                else
                {
                    if (!string.IsNullOrEmpty(meta.DLL) && File.Exists(meta.DLL))
                    {
                        using (FileStream stream = File.OpenRead(meta.DLL))
                            asm = Relinker.GetRelinkedAssembly(meta, Path.GetFileNameWithoutExtension(meta.DLL), stream);
                    }

                    if (File.Exists(Path.Combine(meta.PathDirectory, "main.lua")))
                    {
                        new LuaModule(meta).Register();
                        return;
                    }
                }

                if (asm == null)
                {
                    // Register a null module for content mods.
                    new NullModule(meta).Register();
                    return;
                }

                LoadModAssembly(meta, asm);
            }
Ejemplo n.º 15
0
            /// <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(EverestModuleMetadata meta, Action callback)
            {
                if (!Flags.SupportRuntimeMods)
                {
                    Logger.Log(LogLevel.Warn, "loader", "Loader disabled!");
                    return;
                }

                if (meta == null)
                {
                    callback?.Invoke();
                    return;
                }

                if (Modules.Any(module => module.Metadata.Name == meta.Name))
                {
                    Logger.Log(LogLevel.Warn, "loader", $"Mod {meta.Name} already loaded!");
                    return;
                }

                if (PermanentBlacklist.TryGetValue(meta.Name, out Version minver) && meta.Version < minver)
                {
                    Logger.Log(LogLevel.Warn, "loader", $"Mod {meta} permanently blacklisted by Everest!");
                    return;
                }

                Tuple <string, Version, string, Version> conflictRow = PermanentConflictlist.FirstOrDefault(row =>
                                                                                                            (meta.Name == row.Item1 && meta.Version < row.Item2 && (_Modules.FirstOrDefault(other => other.Metadata.Name == row.Item3)?.Metadata.Version ?? _VersionInvalid) < row.Item4) ||
                                                                                                            (meta.Name == row.Item3 && meta.Version < row.Item4 && (_Modules.FirstOrDefault(other => other.Metadata.Name == row.Item1)?.Metadata.Version ?? _VersionInvalid) < row.Item2)
                                                                                                            );

                if (conflictRow != null)
                {
                    throw new Exception($"CONFLICTING MODS: {conflictRow.Item1} VS {conflictRow.Item3}");
                }


                foreach (EverestModuleMetadata dep in meta.Dependencies)
                {
                    if (!DependencyLoaded(dep))
                    {
                        Logger.Log(LogLevel.Info, "loader", $"Dependency {dep} of mod {meta} not loaded! Delaying.");
                        lock (Delayed) {
                            Delayed.Add(Tuple.Create(meta, callback));
                        }
                        return;
                    }
                }

                foreach (EverestModuleMetadata dep in meta.OptionalDependencies)
                {
                    if (!DependencyLoaded(dep) && (enforceOptionalDependencies || Everest.Modules.Any(module => module.Metadata?.Name == dep.Name)))
                    {
                        Logger.Log(LogLevel.Info, "loader", $"Optional dependency {dep} of mod {meta} not loaded! Delaying.");
                        lock (Delayed) {
                            Delayed.Add(Tuple.Create(meta, callback));
                        }
                        return;
                    }
                }

                callback?.Invoke();

                LoadMod(meta);
            }
Ejemplo n.º 16
0
            /// <summary>
            /// Load a mod from a directory at runtime.
            /// </summary>
            /// <param name="dir">The path to the mod directory.</param>
            public static void LoadDir(string dir)
            {
                if (!Flags.SupportRuntimeMods)
                {
                    Logger.Log(LogLevel.Warn, "loader", "Loader disabled!");
                    return;
                }

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

                EverestModuleMetadata meta = null;

                EverestModuleMetadata[] multimetas = null;

                string metaPath = Path.Combine(dir, "metadata.yaml");

                if (File.Exists(metaPath))
                {
                    using (StreamReader reader = new StreamReader(metaPath)) {
                        try {
                            meta = YamlHelper.Deserializer.Deserialize <EverestModuleMetadata>(reader);
                            meta.PathDirectory = dir;
                            meta.PostParse();
                        } catch (Exception e) {
                            Logger.Log(LogLevel.Warn, "loader", $"Failed parsing metadata.yaml in {dir}: {e}");
                            FilesWithMetadataLoadFailures.Add(dir);
                        }
                    }
                }

                metaPath = Path.Combine(dir, "multimetadata.yaml");
                if (!File.Exists(metaPath))
                {
                    metaPath = Path.Combine(dir, "everest.yaml");
                }
                if (!File.Exists(metaPath))
                {
                    metaPath = Path.Combine(dir, "everest.yml");
                }
                if (File.Exists(metaPath))
                {
                    using (StreamReader reader = new StreamReader(metaPath)) {
                        try {
                            if (!reader.EndOfStream)
                            {
                                multimetas = YamlHelper.Deserializer.Deserialize <EverestModuleMetadata[]>(reader);
                                foreach (EverestModuleMetadata multimeta in multimetas)
                                {
                                    multimeta.PathDirectory = dir;
                                    multimeta.PostParse();
                                }
                            }
                        } catch (Exception e) {
                            Logger.Log(LogLevel.Warn, "loader", $"Failed parsing everest.yaml in {dir}: {e}");
                            FilesWithMetadataLoadFailures.Add(dir);
                        }
                    }
                }

                FileSystemModContent  contentMeta       = new FileSystemModContent(dir);
                EverestModuleMetadata contentMetaParent = null;

                Action contentCrawl = () => {
                    if (contentMeta == null)
                    {
                        return;
                    }
                    if (contentMetaParent != null)
                    {
                        contentMeta.Mod  = contentMetaParent;
                        contentMeta.Name = contentMetaParent.Name;
                    }
                    OnCrawlMod?.Invoke(dir, contentMetaParent);
                    Content.Crawl(contentMeta);
                    contentMeta = null;
                };

                if (multimetas != null)
                {
                    foreach (EverestModuleMetadata multimeta in multimetas)
                    {
                        multimeta.Multimeta = multimetas;
                        if (contentMetaParent == null)
                        {
                            contentMetaParent = multimeta;
                        }
                        LoadModDelayed(multimeta, contentCrawl);
                    }
                }
                else
                {
                    if (meta == null)
                    {
                        meta = new EverestModuleMetadata()
                        {
                            Name          = "_dir_" + Path.GetFileName(dir),
                            VersionString = "0.0.0-dummy",
                            PathDirectory = dir
                        };
                        meta.PostParse();
                    }
                    contentMetaParent = meta;
                    LoadModDelayed(meta, contentCrawl);
                }
            }
Ejemplo n.º 17
0
            /// <summary>
            /// Load a mod from a .zip archive at runtime.
            /// </summary>
            /// <param name="archive">The path to the mod .zip archive.</param>
            public static void LoadZip(string archive)
            {
                if (!Flags.SupportRuntimeMods)
                {
                    Logger.Log(LogLevel.Warn, "loader", "Loader disabled!");
                    return;
                }

                if (!File.Exists(archive)) // Relative path? Let's just make it absolute.
                {
                    archive = Path.Combine(PathMods, archive);
                }
                if (!File.Exists(archive)) // It just doesn't exist.
                {
                    return;
                }

                Logger.Log(LogLevel.Verbose, "loader", $"Loading mod .zip: {archive}");

                EverestModuleMetadata meta = null;

                EverestModuleMetadata[] multimetas = null;

                using (ZipFile zip = new ZipFile(archive)) {
                    foreach (ZipEntry entry in zip.Entries)
                    {
                        if (entry.FileName == "metadata.yaml")
                        {
                            using (MemoryStream stream = entry.ExtractStream())
                                using (StreamReader reader = new StreamReader(stream)) {
                                    try {
                                        meta             = YamlHelper.Deserializer.Deserialize <EverestModuleMetadata>(reader);
                                        meta.PathArchive = archive;
                                        meta.PostParse();
                                    } catch (Exception e) {
                                        Logger.Log(LogLevel.Warn, "loader", $"Failed parsing metadata.yaml in {archive}: {e}");
                                        FilesWithMetadataLoadFailures.Add(archive);
                                    }
                                }
                            continue;
                        }
                        if (entry.FileName == "multimetadata.yaml" ||
                            entry.FileName == "everest.yaml" ||
                            entry.FileName == "everest.yml")
                        {
                            using (MemoryStream stream = entry.ExtractStream())
                                using (StreamReader reader = new StreamReader(stream)) {
                                    try {
                                        if (!reader.EndOfStream)
                                        {
                                            multimetas = YamlHelper.Deserializer.Deserialize <EverestModuleMetadata[]>(reader);
                                            foreach (EverestModuleMetadata multimeta in multimetas)
                                            {
                                                multimeta.PathArchive = archive;
                                                multimeta.PostParse();
                                            }
                                        }
                                    } catch (Exception e) {
                                        Logger.Log(LogLevel.Warn, "loader", $"Failed parsing everest.yaml in {archive}: {e}");
                                        FilesWithMetadataLoadFailures.Add(archive);
                                    }
                                }
                            continue;
                        }
                    }
                }

                ZipModContent         contentMeta       = new ZipModContent(archive);
                EverestModuleMetadata contentMetaParent = null;

                Action contentCrawl = () => {
                    if (contentMeta == null)
                    {
                        return;
                    }
                    if (contentMetaParent != null)
                    {
                        contentMeta.Mod  = contentMetaParent;
                        contentMeta.Name = contentMetaParent.Name;
                    }
                    OnCrawlMod?.Invoke(archive, contentMetaParent);
                    Content.Crawl(contentMeta);
                    contentMeta = null;
                };

                if (multimetas != null)
                {
                    foreach (EverestModuleMetadata multimeta in multimetas)
                    {
                        multimeta.Multimeta = multimetas;
                        if (contentMetaParent == null)
                        {
                            contentMetaParent = multimeta;
                        }
                        LoadModDelayed(multimeta, contentCrawl);
                    }
                }
                else
                {
                    if (meta == null)
                    {
                        meta = new EverestModuleMetadata()
                        {
                            Name          = "_zip_" + Path.GetFileNameWithoutExtension(archive),
                            VersionString = "0.0.0-dummy",
                            PathArchive   = archive
                        };
                        meta.PostParse();
                    }
                    contentMetaParent = meta;
                    LoadModDelayed(meta, contentCrawl);
                }
            }
Ejemplo n.º 18
0
            /// <summary>
            /// Load a mod from a .zip archive at runtime.
            /// </summary>
            /// <param name="archive">The path to the mod .zip archive.</param>
            public static void LoadZip(string archive) {
                if (Flags.IsDisabled || !Flags.SupportRuntimeMods) {
                    Logger.Log(LogLevel.Warn, "loader", "Loader disabled!");
                    return;
                }

                if (!File.Exists(archive)) // Relative path? Let's just make it absolute.
                    archive = Path.Combine(PathMods, archive);
                if (!File.Exists(archive)) // It just doesn't exist.
                    return;

                Logger.Log(LogLevel.Verbose, "loader", $"Loading mod .zip: {archive}");

                EverestModuleMetadata meta = null;
                EverestModuleMetadata[] multimetas = null;

                // In case the icon appears before the metadata in the .zip, store it temporarily, set it later.
                Texture2D icon = null;
                using (ZipFile zip = new ZipFile(archive)) {
                    foreach (ZipEntry entry in zip.Entries) {
                        if (entry.FileName == "metadata.yaml") {
                            using (MemoryStream stream = entry.ExtractStream())
                            using (StreamReader reader = new StreamReader(stream)) {
                                try {
                                    meta = YamlHelper.Deserializer.Deserialize<EverestModuleMetadata>(reader);
                                    meta.PathArchive = archive;
                                    meta.PostParse();
                                } catch (Exception e) {
                                    Logger.Log(LogLevel.Warn, "loader", $"Failed parsing metadata.yaml in {archive}: {e}");
                                }
                            }
                            continue;
                        }
                        if (entry.FileName == "multimetadata.yaml" ||
                            entry.FileName == "everest.yaml" ||
                            entry.FileName == "everest.yml") {
                            using (MemoryStream stream = entry.ExtractStream())
                            using (StreamReader reader = new StreamReader(stream)) {
                                try {
                                    if (!reader.EndOfStream) {
                                        multimetas = YamlHelper.Deserializer.Deserialize<EverestModuleMetadata[]>(reader);
                                        foreach (EverestModuleMetadata multimeta in multimetas) {
                                            multimeta.PathArchive = archive;
                                            multimeta.PostParse();
                                        }
                                    }
                                } catch (Exception e) {
                                    Logger.Log(LogLevel.Warn, "loader", $"Failed parsing multimetadata.yaml in {archive}: {e}");
                                }
                            }
                            continue;
                        }
                        if (entry.FileName == "icon.png") {
                            using (Stream stream = entry.ExtractStream())
                                icon = Texture2D.FromStream(Celeste.Instance.GraphicsDevice, stream);
                            continue;
                        }
                    }
                }

                if (meta != null) {
                    if (icon != null)
                        meta.Icon = icon;
                }

                ZipModContent contentMeta = new ZipModContent(archive);
                EverestModuleMetadata contentMetaParent = null;

                Action contentCrawl = () => {
                    if (contentMeta == null)
                        return;
                    if (contentMetaParent != null) {
                        contentMeta.Mod = contentMetaParent;
                        contentMeta.Name = contentMetaParent.Name;
                    }
                    Content.Crawl(contentMeta);
                    contentMeta = null;
                };

                if (multimetas != null) {
                    foreach (EverestModuleMetadata multimeta in multimetas) {
                        multimeta.Multimeta = multimetas;
                        if (contentMetaParent == null)
                            contentMetaParent = multimeta;
                        LoadModDelayed(multimeta, contentCrawl);
                    }
                } else {
                    if (meta == null) {
                        meta = new EverestModuleMetadata() {
                            Name = "_zip_" + Path.GetFileNameWithoutExtension(archive),
                            VersionString = "0.0.0-dummy",
                            PathArchive = archive
                        };
                        meta.PostParse();
                    }
                    contentMetaParent = meta;
                    LoadModDelayed(meta, contentCrawl);
                }
            }
Ejemplo n.º 19
0
        /// <summary>
        /// Register a new EverestModule (mod) dynamically. Invokes LoadSettings and Load.
        /// </summary>
        /// <param name="module">Mod to register.</param>
        public static void Register(this EverestModule module)
        {
            lock (_Modules) {
                _Modules.Add(module);
                _ModuleTypes.Add(module.GetType());
            }

            LuaLoader.Precache(module.GetType().Assembly);

            foreach (Type type in module.GetType().Assembly.GetTypes())
            {
                // Search for all entities marked with the CustomEntityAttribute.
                foreach (CustomEntityAttribute attrib in type.GetCustomAttributes <CustomEntityAttribute>())
                {
                    foreach (string idFull in attrib.IDs)
                    {
                        string   id;
                        string   genName;
                        string[] split = idFull.Split('=');

                        if (split.Length == 1)
                        {
                            id      = split[0];
                            genName = "Load";
                        }
                        else if (split.Length == 2)
                        {
                            id      = split[0];
                            genName = split[1];
                        }
                        else
                        {
                            Logger.Log(LogLevel.Warn, "core", $"Invalid number of custom entity ID elements: {idFull} ({type.FullName})");
                            continue;
                        }

                        id      = id.Trim();
                        genName = genName.Trim();

                        patch_Level.EntityLoader loader = null;

                        ConstructorInfo ctor;
                        MethodInfo      gen;

                        gen = type.GetMethod(genName, new Type[] { typeof(Level), typeof(LevelData), typeof(Vector2), typeof(EntityData) });
                        if (gen != null && gen.IsStatic && gen.ReturnType.IsCompatible(typeof(Entity)))
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)gen.Invoke(null, new object[] { level, levelData, offset, entityData });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(new Type[] { typeof(EntityData), typeof(Vector2), typeof(EntityID) });
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(new object[] { entityData, offset, new EntityID(levelData.Name, entityData.ID) });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(new Type[] { typeof(EntityData), typeof(Vector2) });
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(new object[] { entityData, offset });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(new Type[] { typeof(Vector2) });
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(new object[] { offset });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(_EmptyTypeArray);
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(_EmptyObjectArray);
                            goto RegisterEntityLoader;
                        }

RegisterEntityLoader:
                        if (loader == null)
                        {
                            Logger.Log(LogLevel.Warn, "core", $"Found custom entity without suitable constructor / {genName}(Level, LevelData, Vector2, EntityData): {id} ({type.FullName})");
                            continue;
                        }
                        patch_Level.EntityLoaders[id] = loader;
                    }
                }
            }

            module.LoadSettings();
            module.Load();
            if (_Initialized)
            {
                module.Initialize();
            }

            InvalidateInstallationHash();

            EverestModuleMetadata meta = module.Metadata;

            meta.Hash = GetChecksum(meta);

            Logger.Log(LogLevel.Info, "core", $"Module {module.Metadata} registered.");

            // Attempt to load mods after their dependencies have been loaded.
            // Only load and lock the delayed list if we're not already loading delayed mods.
            if (Interlocked.CompareExchange(ref Loader.DelayedLock, 1, 0) == 0)
            {
                lock (Loader.Delayed) {
                    for (int i = Loader.Delayed.Count - 1; i > -1; i--)
                    {
                        Tuple <EverestModuleMetadata, Action> entry = Loader.Delayed[i];
                        if (!Loader.DependenciesLoaded(entry.Item1))
                        {
                            continue;
                        }

                        Loader.LoadMod(entry.Item1);
                        Loader.Delayed.RemoveAt(i);

                        entry.Item2?.Invoke();
                    }
                }
                Interlocked.Decrement(ref Loader.DelayedLock);
            }
        }
Ejemplo n.º 20
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(EverestModuleMetadata meta)
 => Path.Combine(Loader.PathCache, meta.Name + "." + Path.GetFileNameWithoutExtension(meta.DLL) + ".dll");
Ejemplo n.º 21
0
 internal static void LoadMod(EverestModuleMetadata meta)
 => OnLoadMod?.Invoke(meta);
Ejemplo n.º 22
0
            public static void LoadDir(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("loader", $"Loading mod directory: {dir}");

                EverestModuleMetadata meta = null;
                Assembly asm = null;

                // First read the metadata, ...
                string metaPath = Path.Combine(dir, "metadata.yaml");

                if (File.Exists(metaPath))
                {
                    using (StreamReader reader = new StreamReader(metaPath))
                        meta = EverestModuleMetadata.Parse("", dir, reader);
                }

                if (meta != null)
                {
                    // ... then check if the dependencies are loaded ...
                    foreach (EverestModuleMetadata dep in meta.Dependencies)
                    {
                        if (!DependencyLoaded(dep))
                        {
                            Logger.Log("loader", $"Dependency {dep} of mod {meta} not loaded!");
                            return;
                        }
                    }

                    // ... then add an AssemblyResolve handler for all the  .zip-ped libraries
                    AppDomain.CurrentDomain.AssemblyResolve += GenerateModAssemblyResolver(meta);
                }

                // ... then handle the assembly and all assets.
                Content.Crawl(null, dir);

                if (meta == null || !File.Exists(meta.DLL))
                {
                    return;
                }

                if (meta.Prelinked)
                {
                    asm = Assembly.LoadFrom(meta.DLL);
                }
                else
                {
                    using (FileStream stream = File.OpenRead(meta.DLL))
                        asm = Relinker.GetRelinkedAssembly(meta, stream);
                }

                if (asm != null)
                {
                    LoadMod(meta, asm);
                }
            }
Ejemplo n.º 23
0
            public static void LoadZip(string archive)
            {
                if (!File.Exists(archive)) // Relative path?
                {
                    archive = Path.Combine(PathMods, archive);
                }
                if (!File.Exists(archive)) // It just doesn't exist.
                {
                    return;
                }

                Logger.Log("loader", $"Loading mod .zip: {archive}");

                EverestModuleMetadata meta = null;
                Assembly asm = null;

                using (ZipFile zip = new ZipFile(archive)) {
                    // In case the icon appears before the metadata in the .zip, store it temporarily.
                    Texture2D icon = null;

                    // First read the metadata, ...
                    foreach (ZipEntry entry in zip.Entries)
                    {
                        if (entry.FileName == "metadata.yaml")
                        {
                            using (MemoryStream stream = entry.ExtractStream())
                                using (StreamReader reader = new StreamReader(stream))
                                    meta = EverestModuleMetadata.Parse(archive, "", reader);
                            continue;
                        }
                        if (entry.FileName == "icon.png")
                        {
                            using (Stream stream = entry.ExtractStream())
                                icon = Texture2D.FromStream(Celeste.Instance.GraphicsDevice, stream);
                            continue;
                        }
                    }

                    if (meta != null)
                    {
                        if (icon != null)
                        {
                            meta.Icon = icon;
                        }

                        // ... then check if the dependencies are loaded ...
                        // TODO: Enqueue the mod, reload it on Register of other mods, rechecking if deps loaded.
                        foreach (EverestModuleMetadata dep in meta.Dependencies)
                        {
                            if (!DependencyLoaded(dep))
                            {
                                Logger.Log("loader", $"Dependency {dep} of mod {meta} not loaded!");
                                return;
                            }
                        }

                        // ... then add an AssemblyResolve handler for all the .zip-ped libraries
                        AppDomain.CurrentDomain.AssemblyResolve += GenerateModAssemblyResolver(meta);
                    }

                    // ... then handle the assembly ...
                    foreach (ZipEntry entry in zip.Entries)
                    {
                        string entryName = entry.FileName.Replace('\\', '/');
                        if (meta != null && entryName == meta.DLL)
                        {
                            using (MemoryStream stream = entry.ExtractStream()) {
                                if (meta.Prelinked)
                                {
                                    asm = Assembly.Load(stream.GetBuffer());
                                }
                                else
                                {
                                    asm = Relinker.GetRelinkedAssembly(meta, stream);
                                }
                            }
                        }
                    }

                    // ... then tell the Content class to crawl through the zip.
                    // (This also registers the zip for recrawls further down the line.)
                    Content.Crawl(null, archive, zip);
                }

                if (meta != null && asm != null)
                {
                    LoadMod(meta, asm);
                }
            }
Ejemplo n.º 24
0
            /// <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);
                }
            }
Ejemplo n.º 25
0
 /// <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, Stream stream,
                                            MissingDependencyResolver depResolver = null, string[] checksumsExtra = null, Action <MonoModder> prePatch = null)
 => GetRelinkedAssembly(meta, Path.GetFileNameWithoutExtension(meta.DLL), stream, depResolver, checksumsExtra, prePatch);
Ejemplo n.º 26
0
            public static Assembly GetRelinkedAssembly(EverestModuleMetadata meta, Stream stream, MissingDependencyResolver depResolver = null)
            {
                string name               = Path.GetFileName(meta.DLL);
                string cachedName         = meta.Name + "." + name.Substring(0, name.Length - 3) + "dll";
                string cachedPath         = Path.Combine(Loader.PathCache, cachedName);
                string cachedChecksumPath = Path.Combine(Loader.PathCache, cachedName + ".sum");

                string[] checksums = new string[2];
                using (MD5 md5 = MD5.Create()) {
                    if (GameChecksum == null)
                    {
                        using (FileStream fs = File.OpenRead(Assembly.GetAssembly(typeof(Relinker)).Location))
                            GameChecksum = md5.ComputeHash(fs).ToHexadecimalString();
                    }
                    checksums[0] = GameChecksum;

                    string modPath = meta.PathArchive;
                    if (modPath.Length == 0)
                    {
                        modPath = meta.DLL;
                    }
                    using (FileStream fs = File.OpenRead(modPath))
                        checksums[1] = md5.ComputeHash(fs).ToHexadecimalString();
                }

                if (File.Exists(cachedPath) && File.Exists(cachedChecksumPath) &&
                    ChecksumsEqual(checksums, File.ReadAllLines(cachedChecksumPath)))
                {
                    return(Assembly.LoadFrom(cachedPath));
                }

                if (depResolver == null)
                {
                    depResolver = GenerateModDependencyResolver(meta);
                }

                using (MonoModder modder = new MonoModder()
                {
                    Input = stream,
                    OutputPath = cachedPath,
                    CleanupEnabled = false,
                    RelinkModuleMap = SharedRelinkModuleMap,
                    RelinkMap = SharedRelinkMap,
                    DependencyDirs =
                    {
                        PathGame
                    },
                    MissingDependencyResolver = depResolver
                })
                    try {
                        modder.ReaderParameters.ReadSymbols          = false;
                        modder.WriterParameters.WriteSymbols         = false;
                        modder.WriterParameters.SymbolWriterProvider = null;

                        modder.Read();
                        modder.MapDependencies();
                        modder.AutoPatch();
                        modder.Write();
                    } catch (Exception e) {
                        Logger.Log("relinker", $"Failed relinking {meta}: {e}");
                        return(null);
                    }

                if (File.Exists(cachedChecksumPath))
                {
                    File.Delete(cachedChecksumPath);
                }
                File.WriteAllLines(cachedChecksumPath, checksums);

                return(Assembly.LoadFrom(cachedPath));
            }
Ejemplo n.º 27
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(EverestModuleMetadata meta, string asmname)
 => Path.Combine(Loader.PathCache, meta.Name + "." + asmname + ".dll");
Ejemplo n.º 28
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(EverestModuleMetadata meta)
            {
                if (!Flags.SupportRuntimeMods)
                {
                    Logger.Log(LogLevel.Warn, "loader", "Loader disabled!");
                    return;
                }

                if (meta == null)
                {
                    return;
                }

                // Add an AssemblyResolve handler for all bundled libraries.
                AppDomain.CurrentDomain.AssemblyResolve += GenerateModAssemblyResolver(meta);

                if (string.IsNullOrEmpty(meta.DLL))
                {
                    return;
                }

                // Load the actual assembly.
                Assembly asm = null;

                if (!string.IsNullOrEmpty(meta.PathArchive))
                {
                    using (ZipFile zip = new ZipFile(meta.PathArchive)) {
                        foreach (ZipEntry entry in zip.Entries)
                        {
                            string entryName = entry.FileName.Replace('\\', '/');
                            if (entryName == meta.DLL)
                            {
                                using (MemoryStream stream = entry.ExtractStream()) {
                                    if (meta.Prelinked)
                                    {
                                        asm = Assembly.Load(stream.GetBuffer());
                                    }
                                    else
                                    {
                                        asm = Relinker.GetRelinkedAssembly(meta, stream);
                                    }
                                }
                            }
                        }
                    }
                }
                else
                {
                    if (meta.Prelinked)
                    {
                        asm = Assembly.LoadFrom(meta.DLL);
                    }
                    else
                    {
                        using (FileStream stream = File.OpenRead(meta.DLL))
                            asm = Relinker.GetRelinkedAssembly(meta, stream);
                    }
                }

                if (asm != null)
                {
                    LoadModAssembly(meta, asm);
                }
            }
Ejemplo n.º 29
0
        /// <summary>
        /// Register a new EverestModule (mod) dynamically. Invokes LoadSettings and Load.
        /// </summary>
        /// <param name="module">Mod to register.</param>
        public static void Register(this EverestModule module)
        {
            lock (_Modules) {
                _Modules.Add(module);
            }

            LuaLoader.Precache(module.GetType().Assembly);

            bool newStrawberriesRegistered = false;

            foreach (Type type in module.GetType().Assembly.GetTypesSafe())
            {
                // Search for all entities marked with the CustomEntityAttribute.
                foreach (CustomEntityAttribute attrib in type.GetCustomAttributes <CustomEntityAttribute>())
                {
                    foreach (string idFull in attrib.IDs)
                    {
                        string   id;
                        string   genName;
                        string[] split = idFull.Split('=');

                        if (split.Length == 1)
                        {
                            id      = split[0];
                            genName = "Load";
                        }
                        else if (split.Length == 2)
                        {
                            id      = split[0];
                            genName = split[1];
                        }
                        else
                        {
                            Logger.Log(LogLevel.Warn, "core", $"Invalid number of custom entity ID elements: {idFull} ({type.FullName})");
                            continue;
                        }

                        id      = id.Trim();
                        genName = genName.Trim();

                        patch_Level.EntityLoader loader = null;

                        ConstructorInfo ctor;
                        MethodInfo      gen;

                        gen = type.GetMethod(genName, new Type[] { typeof(Level), typeof(LevelData), typeof(Vector2), typeof(EntityData) });
                        if (gen != null && gen.IsStatic && gen.ReturnType.IsCompatible(typeof(Entity)))
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)gen.Invoke(null, new object[] { level, levelData, offset, entityData });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(new Type[] { typeof(EntityData), typeof(Vector2), typeof(EntityID) });
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(new object[] { entityData, offset, new EntityID(levelData.Name, entityData.ID) });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(new Type[] { typeof(EntityData), typeof(Vector2) });
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(new object[] { entityData, offset });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(new Type[] { typeof(Vector2) });
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(new object[] { offset });
                            goto RegisterEntityLoader;
                        }

                        ctor = type.GetConstructor(_EmptyTypeArray);
                        if (ctor != null)
                        {
                            loader = (level, levelData, offset, entityData) => (Entity)ctor.Invoke(_EmptyObjectArray);
                            goto RegisterEntityLoader;
                        }

RegisterEntityLoader:
                        if (loader == null)
                        {
                            Logger.Log(LogLevel.Warn, "core", $"Found custom entity without suitable constructor / {genName}(Level, LevelData, Vector2, EntityData): {id} ({type.FullName})");
                            continue;
                        }
                        patch_Level.EntityLoaders[id] = loader;
                    }
                }
                // Register with the StrawberryRegistry all entities marked with RegisterStrawberryAttribute.
                foreach (RegisterStrawberryAttribute attrib in type.GetCustomAttributes <RegisterStrawberryAttribute>())
                {
                    List <string> names = new List <string>();
                    foreach (CustomEntityAttribute nameAttrib in type.GetCustomAttributes <CustomEntityAttribute>())
                    {
                        foreach (string idFull in nameAttrib.IDs)
                        {
                            string[] split = idFull.Split('=');
                            if (split.Length == 0)
                            {
                                Logger.Log(LogLevel.Warn, "core", $"Invalid number of custom entity ID elements: {idFull} ({type.FullName})");
                                continue;
                            }
                            names.Add(split[0]);
                        }
                    }
                    if (names.Count == 0)
                    {
                        goto NoDefinedBerryNames; // no customnames? skip out on registering berry
                    }
                    foreach (string name in names)
                    {
                        StrawberryRegistry.Register(type, name, attrib.isTracked, attrib.blocksNormalCollection);
                        newStrawberriesRegistered = true;
                    }
                }
NoDefinedBerryNames:
                ;

                // Search for all Entities marked with the CustomEventAttribute.
                foreach (CustomEventAttribute attrib in type.GetCustomAttributes <CustomEventAttribute>())
                {
                    foreach (string idFull in attrib.IDs)
                    {
                        string   id;
                        string   genName;
                        string[] split = idFull.Split('=');

                        if (split.Length == 1)
                        {
                            id      = split[0];
                            genName = "Load";
                        }
                        else if (split.Length == 2)
                        {
                            id      = split[0];
                            genName = split[1];
                        }
                        else
                        {
                            Logger.Log(LogLevel.Warn, "core", $"Invalid number of custom cutscene ID elements: {idFull} ({type.FullName})");
                            continue;
                        }

                        id      = id.Trim();
                        genName = genName.Trim();

                        patch_EventTrigger.CutsceneLoader loader = null;

                        ConstructorInfo ctor;
                        MethodInfo      gen;

                        gen = type.GetMethod(genName, new Type[] { typeof(EventTrigger), typeof(Player), typeof(string) });
                        if (gen != null && gen.IsStatic && gen.ReturnType.IsCompatible(typeof(Entity)))
                        {
                            loader = (trigger, player, eventID) => (Entity)gen.Invoke(null, new object[] { trigger, player, eventID });
                            goto RegisterCutsceneLoader;
                        }

                        ctor = type.GetConstructor(new Type[] { typeof(EventTrigger), typeof(Player), typeof(string) });
                        if (ctor != null)
                        {
                            loader = (trigger, player, eventID) => (Entity)ctor.Invoke(new object[] { trigger, player, eventID });
                            goto RegisterCutsceneLoader;
                        }

                        ctor = type.GetConstructor(_EmptyTypeArray);
                        if (ctor != null)
                        {
                            loader = (trigger, player, eventID) => (Entity)ctor.Invoke(_EmptyObjectArray);
                            goto RegisterCutsceneLoader;
                        }

RegisterCutsceneLoader:
                        if (loader == null)
                        {
                            Logger.Log(LogLevel.Warn, "core", $"Found custom cutscene without suitable constructor / {genName}(EventTrigger, Player, string): {id} ({type.FullName})");
                            continue;
                        }
                        patch_EventTrigger.CutsceneLoaders[id] = loader;
                    }
                }
            }

            module.LoadSettings();
            module.Load();
            if (_ContentLoaded)
            {
                module.LoadContent(true);
            }
            if (_Initialized)
            {
                Tracker.Initialize();
                module.Initialize();
                Input.Initialize();

                if (SaveData.Instance != null)
                {
                    // we are in a save. we are expecting the save data to already be loaded at this point
                    Logger.Log("core", $"Loading save data slot {SaveData.Instance.FileSlot} for {module.Metadata}");
                    module.LoadSaveData(SaveData.Instance.FileSlot);

                    if (SaveData.Instance.CurrentSession?.InArea ?? false)
                    {
                        // we are in a level. we are expecting the session to already be loaded at this point
                        Logger.Log("core", $"Loading session slot {SaveData.Instance.FileSlot} for {module.Metadata}");
                        module.LoadSession(SaveData.Instance.FileSlot, false);
                    }
                }

                // Check if the module defines a PrepareMapDataProcessors method. If this is the case, we want to reload maps so that they are applied.
                // We should also run the map data processors again if new berry types are registered, so that CoreMapDataProcessor assigns them checkpoint IDs and orders.
                if (newStrawberriesRegistered || module.GetType().GetMethod("PrepareMapDataProcessors", new Type[] { typeof(MapDataFixup) })?.DeclaringType == module.GetType())
                {
                    Logger.Log("core", $"Module {module.Metadata} has custom strawberries or map data processors: reloading maps.");
                    AssetReloadHelper.ReloadAllMaps();
                }
            }

            if (Engine.Instance != null && Engine.Scene is Overworld overworld)
            {
                // we already are in the overworld. Register new Ouis real quick!
                Type[] types = FakeAssembly.GetFakeEntryAssembly().GetTypesSafe();
                foreach (Type type in types)
                {
                    if (typeof(Oui).IsAssignableFrom(type) && !type.IsAbstract && !overworld.UIs.Any(ui => ui.GetType() == type))
                    {
                        Logger.Log("core", $"Instanciating UI from {module.Metadata}: {type.FullName}");

                        Oui oui = (Oui)Activator.CreateInstance(type);
                        oui.Visible = false;
                        overworld.Add(oui);
                        overworld.UIs.Add(oui);
                    }
                }
            }

            InvalidateInstallationHash();

            EverestModuleMetadata meta = module.Metadata;

            meta.Hash = GetChecksum(meta);

            // Audio banks are cached, and as such use the module's hash. We can only ingest those now.
            if (patch_Audio.AudioInitialized)
            {
                patch_Audio.IngestNewBanks();
            }

            Logger.Log(LogLevel.Info, "core", $"Module {module.Metadata} registered.");

            CheckDependenciesOfDelayedMods();
        }
Ejemplo n.º 30
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(EverestModuleMetadata meta)
 => GetCachedPath(meta, Path.GetFileNameWithoutExtension(meta.DLL));