From 015efbc271aa6ab57a28a33d365b943d16218737 Mon Sep 17 00:00:00 2001 From: Cold-Mint Date: Tue, 23 Jul 2024 23:03:03 +0800 Subject: [PATCH] =?UTF-8?q?Support=20for=20loading=20module=20directories?= =?UTF-8?q?=20now.=20=E6=94=AF=E6=8C=81=E5=8A=A0=E8=BD=BD=E6=A8=A1?= =?UTF-8?q?=E7=BB=84=E7=9B=AE=E5=BD=95=E4=BA=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 + README_JA.md | 4 + README_ZH.md | 4 + locals/Log.csv | 6 +- scripts/Config.cs | 28 +- scripts/inventory/ItemTypeRegister.cs | 2 +- scripts/loader/uiLoader/SplashScreenLoader.cs | 9 +- scripts/mod/ModLoader.cs | 259 ++++++++++++++---- scripts/mod/ModManifest.cs | 10 +- scripts/serialization/YamlSerialization.cs | 4 +- scripts/utils/ExplorerUtils.cs | 1 + scripts/utils/ResUtils.cs | 8 +- 12 files changed, 266 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 845c392..d3e6bc6 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,10 @@ You need to fill in the Export Presets > Resources > Filter to export non-resour data/* ``` +#### Custom feature + +- **disableVersionIsolation** Disable version isolation. + ## Configuring Openobserve > This is optional, and the game will work even if you do not configure Openobserve. diff --git a/README_JA.md b/README_JA.md index 0608bf5..02a9d86 100644 --- a/README_JA.md +++ b/README_JA.md @@ -50,6 +50,10 @@ git clone https://github.com/Cold-Mint/Traveller.git data/* ``` +#### 風習特徴 + +- **disableVersionIsolation** 版孤立を無効化する。 + ## はいちOpenobserve > これはオプションなので、Openobserveを設定しなくてもゲームは正常に動作します。 diff --git a/README_ZH.md b/README_ZH.md index 5cef5e2..a61c06b 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -52,6 +52,10 @@ git clone https://github.com/Cold-Mint/Traveller.git data/* ``` +#### 自定义特性 + +- **disableVersionIsolation** 禁用版本隔离。 + ## 配置Openobserve > 这是可选的操作,即使您不配置Openobserve,游戏也能正常运行。 diff --git a/locals/Log.csv b/locals/Log.csv index 1d1a5f9..3b809ad 100644 --- a/locals/Log.csv +++ b/locals/Log.csv @@ -97,4 +97,8 @@ log_load_dll_argument_null_exception,{0}参数为空。,The {0} parameter is emp log_load_dll_file_load_exception,加载DLL文件{0}失败。,Failed to load the DLL file {0}.,DLLファイル{0}のロードに失敗しました。 log_load_dll_bad_image_format_exception,{0}不是有效的程序集。,{0} is not a valid assembly.,{0}は有効なアセンブリではありません。 log_load_dll_success,成功加载位于{0}的dll文件。,Successfully loaded the dll file located at {0}.,{0}にあるDLLファイルを正常にロードしました。 -log_mod_folder_does_not_exist,位于{0}的模组文件夹不存在。,The module folder at {0} does not exist.,{0}に位置するモジュールフォルダは存在しません。 \ No newline at end of file +log_mod_folder_does_not_exist,位于{0}的模组文件夹不存在。,The module folder at {0} does not exist.,{0}に位置するモジュールフォルダは存在しません。 +log_mod_not_contain_dll,模组{0}不包含DLL文件。,The module {0} does not contain a DLL file.,モジュール{0}にDLLファイルが含まれていません。 +log_mod_not_contain_pck,模组{0}不包含PCK文件。,The module {0} does not contain a PCK file.,モジュール{0}にPCKファイルが含まれていません。 +log_load_pck_success,成功加载位于{0}的PCK文件。,Successfully loaded the PCK file located at {0}.,{0}にあるPCKファイルを正常にロードしました。 +log_load_pck_failed,加载PCK文件{0}失败。,Failed to load the PCK file {0}.,PCKファイル{0}のロードに失敗しました。 \ No newline at end of file diff --git a/scripts/Config.cs b/scripts/Config.cs index 4c9668b..716823d 100644 --- a/scripts/Config.cs +++ b/scripts/Config.cs @@ -95,7 +95,7 @@ public static class Config /// 公司/创作者名字 /// public const string CompanyName = "ColdMint"; - + /// /// Solution Name /// 解决方案名称 @@ -119,7 +119,12 @@ public static class Config /// Whether version isolation is enabled /// 是否启用版本隔离 /// - public const bool EnableVersionIsolation = true; + public static bool EnableVersionIsolation() + { + //By default, we enable version isolation, but special feature identifiers can be set to disable version isolation. + //默认情况,我们启用版本隔离,但是可以设置特殊的功能标识来禁用版本隔离。 + return !OS.HasFeature("disableVersionIsolation"); + } /// /// Default version name @@ -210,10 +215,14 @@ public static class Config /// Get what platform is currently running on /// 获取当前在什么平台上运行 /// + /// + ///Whether to include an editor environment + ///是否包含编辑器环境 + /// /// - public static OsEnum GetOs() + public static OsEnum GetOs(bool containEditor = false) { - if (OS.HasFeature("editor")) + if (containEditor && OS.HasFeature("editor")) { return OsEnum.Editor; } @@ -271,7 +280,7 @@ public static class Config /// public static string GetGameDataDirectory() { - if (EnableVersionIsolation) + if (EnableVersionIsolation()) { return Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), CompanyName, ProjectSettings.GetSetting("application/config/name").AsString(), UserId, @@ -285,6 +294,15 @@ public static class Config } } + /// + /// GetModDataDirectory + /// 获取模组文件夹 + /// + /// + public static string GetModDataDirectory() + { + return Path.Join(GetGameDataDirectory(), "Mods"); + } /// /// Get the export directory for the level graph diff --git a/scripts/inventory/ItemTypeRegister.cs b/scripts/inventory/ItemTypeRegister.cs index 7ab2fed..1a2b071 100644 --- a/scripts/inventory/ItemTypeRegister.cs +++ b/scripts/inventory/ItemTypeRegister.cs @@ -69,7 +69,7 @@ public static class ItemTypeRegister /// /// /// - private static IList ParseFile(string filePath) + private static IList? ParseFile(string filePath) { var yamlFile = FileAccess.Open(filePath, FileAccess.ModeFlags.Read); diff --git a/scripts/loader/uiLoader/SplashScreenLoader.cs b/scripts/loader/uiLoader/SplashScreenLoader.cs index a47c30a..464eb90 100644 --- a/scripts/loader/uiLoader/SplashScreenLoader.cs +++ b/scripts/loader/uiLoader/SplashScreenLoader.cs @@ -82,7 +82,6 @@ public partial class SplashScreenLoader : UiLoaderTemplate //Disable all logs in the release version. //在发行版禁用所有日志。 LogCat.MinLogLevel = Config.IsDebug() ? LogCat.InfoLogLevel : LogCat.DisableAllLogLevel; - ModLoader.Init(); ContributorDataManager.RegisterAllContributorData(); DeathInfoGenerator.RegisterDeathInfoHandler(new SelfDeathInfoHandler()); MapGenerator.RegisterRoomInjectionProcessor(new ChanceRoomInjectionProcessor()); @@ -97,6 +96,14 @@ public partial class SplashScreenLoader : UiLoaderTemplate Directory.CreateDirectory(dataPath); } + ModLoader.Init(); + var modPath = Config.GetModDataDirectory(); + if (!Directory.Exists(modPath)) + { + Directory.CreateDirectory(modPath); + } + + ModLoader.LoadAllMods(modPath); //Registered camp //注册阵营 var defaultCamp = new Camp(Config.CampId.Default) diff --git a/scripts/mod/ModLoader.cs b/scripts/mod/ModLoader.cs index 380b7bb..173005f 100644 --- a/scripts/mod/ModLoader.cs +++ b/scripts/mod/ModLoader.cs @@ -1,8 +1,10 @@ using System; +using System.Data; using System.IO; using System.Runtime.Loader; using ColdMint.scripts.debug; using ColdMint.scripts.utils; +using Godot; namespace ColdMint.scripts.mod; @@ -18,8 +20,16 @@ public class ModLoader /// private static AssemblyLoadContext? _assemblyLoadContext; - private static string[] _requiredDllList = new[] { Config.SolutionName }; + private static readonly string[] RequiredDllList = [Config.SolutionName]; + /// + /// Initializes the mod loader + /// 初始化模组加载器 + /// + /// + ///This exception is thrown if the built-in dll file cannot be found when it is loaded. + ///如果加载内置dll文件时,找不到文件,则抛出此异常。 + /// public static void Init() { //Initialize the context. @@ -39,7 +49,7 @@ public class ModLoader return; } - foreach (var requiredDll in _requiredDllList) + foreach (var requiredDll in RequiredDllList) { var dllPath = Path.Join(dllFolder, requiredDll + ".dll"); //Load the necessary dll files. @@ -49,85 +59,214 @@ public class ModLoader //When the dll that must be loaded does not exist, an error is reported immediately. //当必须加载的dll不存在时,立即报错。 LogCat.LogErrorWithFormat("dll_not_exist", LogCat.LogLabel.ModLoader, true, dllPath); - throw new NullReferenceException("dll not exist:" + dllPath); + throw new FileNotFoundException("dll not exist:" + dllPath); } - //Load the dll. - //加载dll。 - LogCat.LogWithFormat("load_dll", LogCat.LogLabel.ModLoader, true, dllPath); - try - { - _assemblyLoadContext.LoadFromAssemblyPath(dllPath); - } - catch (ArgumentNullException argumentNullException) - { - //The assemblyPath parameter is null. - //assemblyPath参数为空。 - LogCat.LogErrorWithFormat("load_dll_argument_null_exception", LogCat.LogLabel.ModLoader, true, dllPath); - LogCat.WhenCaughtException(argumentNullException, LogCat.LogLabel.ModLoader); - return; - } - catch (ArgumentException argumentException) - { - //Not an absolute path. - //不是绝对路径 - LogCat.LogErrorWithFormat("load_dll_argument_exception", LogCat.LogLabel.ModLoader, true, dllPath); - LogCat.WhenCaughtException(argumentException, LogCat.LogLabel.ModLoader); - return; - } - catch (FileLoadException fileLoadException) - { - //A file that was found could not be loaded. - //无法加载找到的文件。 - LogCat.LogErrorWithFormat("load_dll_file_load_exception", LogCat.LogLabel.ModLoader, true, dllPath); - LogCat.WhenCaughtException(fileLoadException, LogCat.LogLabel.ModLoader); - return; - } - catch (BadImageFormatException badImageFormatException) - { - //assemblyPath is not a valid assembly. - //assemblyPath不是有效的程序集。 - LogCat.LogErrorWithFormat("load_dll_bad_image_format_exception", LogCat.LogLabel.ModLoader, true, - dllPath); - LogCat.WhenCaughtException(badImageFormatException, LogCat.LogLabel.ModLoader); - return; - } - - //Loading the dll succeeded. - //加载dll成功。 - LogCat.LogWithFormat("load_dll_success", LogCat.LogLabel.ModLoader, true, dllPath); + LoadDllFile(dllPath); } } /// - /// Load a module for a directory - /// 加载某个目录的模组 + /// Load Dll file + /// 加载Dll文件 + /// + /// + ///dll file path + ///dll的文件路径 + /// + /// + ///Throw this error if the assemblyLoadContext has not been initialized. + ///如果assemblyLoadContext尚未初始化,那么抛出此错误。 + /// + private static void LoadDllFile(string dllPath) + { + if (_assemblyLoadContext == null) + { + throw new NullReferenceException("assemblyLoadContext is null."); + } + + //Load the dll. + //加载dll。 + LogCat.LogWithFormat("load_dll", LogCat.LogLabel.ModLoader, true, dllPath); + try + { + _assemblyLoadContext.LoadFromAssemblyPath(dllPath); + } + catch (ArgumentNullException argumentNullException) + { + //The assemblyPath parameter is null. + //assemblyPath参数为空。 + LogCat.LogErrorWithFormat("load_dll_argument_null_exception", LogCat.LogLabel.ModLoader, true, dllPath); + LogCat.WhenCaughtException(argumentNullException, LogCat.LogLabel.ModLoader); + return; + } + catch (ArgumentException argumentException) + { + //Not an absolute path. + //不是绝对路径 + LogCat.LogErrorWithFormat("load_dll_argument_exception", LogCat.LogLabel.ModLoader, true, dllPath); + LogCat.WhenCaughtException(argumentException, LogCat.LogLabel.ModLoader); + return; + } + catch (FileLoadException fileLoadException) + { + //A file that was found could not be loaded. + //无法加载找到的文件。 + LogCat.LogErrorWithFormat("load_dll_file_load_exception", LogCat.LogLabel.ModLoader, true, dllPath); + LogCat.WhenCaughtException(fileLoadException, LogCat.LogLabel.ModLoader); + return; + } + catch (BadImageFormatException badImageFormatException) + { + //assemblyPath is not a valid assembly. + //assemblyPath不是有效的程序集。 + LogCat.LogErrorWithFormat("load_dll_bad_image_format_exception", LogCat.LogLabel.ModLoader, true, + dllPath); + LogCat.WhenCaughtException(badImageFormatException, LogCat.LogLabel.ModLoader); + return; + } + + //Loading the dll succeeded. + //加载dll成功。 + LogCat.LogWithFormat("load_dll_success", LogCat.LogLabel.ModLoader, true, dllPath); + } + + /// + /// Load all mods + /// 加载全部模组 + /// + /// + ///This method scans the incoming subfolders and loads them as module folders. + ///此方法会将扫描传入的子文件夹,并将其子文件夹看作模组文件夹加载。 + /// + /// + ///Mod folder + ///模组文件夹 + /// + /// + ///If the given folder does not exist, throw this exception. + ///如果给定的文件夹不存在,则抛出此异常。 + /// + public static void LoadAllMods(string modFolder) + { + if (!Directory.Exists(modFolder)) + { + //The mod directory does not exist. + //模组目录不存在。 + throw new DirectoryNotFoundException("mod folder not exist:" + modFolder); + } + + var directoryInfo = new DirectoryInfo(modFolder); + foreach (var directory in directoryInfo.GetDirectories()) + { + LoadSingleMod(directory.FullName); + } + } + + /// + /// Load a single mod + /// 加载单个模组 /// /// ///Mod path ///模组路径 /// - public static void LoadMod(string modFolderPath) + /// + /// If the given directory does not exist, throw this exception. + ///如果给定的目录不存在,那么抛出此异常。 + /// + /// + ///Throw this exception if the manifest file creation deserialization fails. + ///如果清单文件创建反序列化失败,则抛出此异常。 + /// + private static void LoadSingleMod(string modFolderPath) { if (!Directory.Exists(modFolderPath)) { //The module folder does not exist. //模组文件夹不存在。 - LogCat.LogErrorWithFormat("mod_folder_does_not_exist", LogCat.LogLabel.ModLoader, true, modFolderPath); - return; + throw new DirectoryNotFoundException("Mod folder does not exist:" + modFolderPath); } - try + var modManifestPath = Path.Join(modFolderPath, Config.ModManifestFileName); + var modManifest = + ModManifest.CreateModManifestFromPath(modManifestPath); + if (modManifest == null) { - var modManifest = - ModManifest.CreateModManifestFromPath(Path.Join(modFolderPath, Config.ModManifestFileName)); + throw new NullReferenceException("mod manifest is null:" + modManifestPath); } - catch (FileNotFoundException fileNotFoundException) + + var dllList = modManifest.DllList; + if (dllList == null || dllList.Length == 0) { - //Do not continue to load the file when it does not exist. - //当文件不存在时就不要继续加载了。 - LogCat.WhenCaughtException(fileNotFoundException, LogCat.LogLabel.ModLoader); - return; + //The module does not contain a dll file. + //模组不包含dll文件。 + LogCat.LogWarningWithFormat("mod_not_contain_dll", true, LogCat.LogLabel.ModLoader, modFolderPath); + } + else + { + //The module contains dll files, load the dll files. + //包含dll文件,加载dll文件。 + foreach (var dll in dllList) + { + var dllPath = Path.GetFullPath(dll, modFolderPath); + LoadDllFile(dllPath); + } + } + + var pakList = modManifest.PckList; + if (pakList == null || pakList.Length == 0) + { + //The module does not contain a pck file. + //模组不包含pck文件。 + LogCat.LogWarningWithFormat("mod_not_contain_pck", true, LogCat.LogLabel.ModLoader, modFolderPath); + } + else + { + //The module contains pck files, load the pck files. + //包含pck文件,加载pck文件。 + foreach (var pak in pakList) + { + var pakPath = Path.GetFullPath(pak, modFolderPath); + LoadPckFile(pakPath); + } + } + } + + /// + /// Load the Pck file + /// 加载Pck文件 + /// + /// + ///Pck path + ///Pck路径 + /// + /// + ///If the given path does not exist, throw this exception. + ///如果给定的路径不存在,那么抛出此异常。 + /// + /// + ///Throw this exception if the pck package fails to load. + ///如果pck包加载失败了,抛出此异常。 + /// + private static void LoadPckFile(string pckPath) + { + if (!File.Exists(pckPath)) + { + throw new FileNotFoundException("pck file not exist:" + pckPath); + } + + var success = ProjectSettings.LoadResourcePack(pckPath); + if (success) + { + LogCat.LogWithFormat("load_pck_success", LogCat.LogLabel.ModLoader, true, pckPath); + } + else + { + LogCat.LogErrorWithFormat("load_pck_failed", LogCat.LogLabel.ModLoader, true, pckPath); + //Throw a suitable exception here for handling at the caller. + //为这里抛出合适的异常,以便在调用方处理。 + throw new DataException("load pck failed:" + pckPath); } } } \ No newline at end of file diff --git a/scripts/mod/ModManifest.cs b/scripts/mod/ModManifest.cs index 4892f9d..2b26c2c 100644 --- a/scripts/mod/ModManifest.cs +++ b/scripts/mod/ModManifest.cs @@ -25,12 +25,20 @@ public class ModManifest /// Dll path list of the mod /// 模组的Dll路径列表 /// + /// + ///Allow relative paths, such as:... / Points to the parent directory. + ///允许使用相对路径,例如: ../指向上级目录。 + /// public string[]? DllList { get; set; } /// /// Pck path list of mod /// 模组的Pck路径列表 /// + /// + ///Allow relative paths, such as:... / Points to the parent directory. + ///允许使用相对路径,例如: ../指向上级目录。 + /// public string[]? PckList { get; set; } /// @@ -50,7 +58,7 @@ public class ModManifest ///当给定的路径不存在时,抛出此异常。 /// /// - public static ModManifest CreateModManifestFromPath(string filePath) + public static ModManifest? CreateModManifestFromPath(string filePath) { if (!filePath.EndsWith(Config.ModManifestFileName)) { diff --git a/scripts/serialization/YamlSerialization.cs b/scripts/serialization/YamlSerialization.cs index ff05b83..a08fb58 100644 --- a/scripts/serialization/YamlSerialization.cs +++ b/scripts/serialization/YamlSerialization.cs @@ -51,8 +51,10 @@ public static class YamlSerialization /// /// /// - public static T Deserialize(string yaml) + // ReSharper disable ReturnTypeCanBeNotNullable + public static T? Deserialize(string yaml) { return YamlDeserializer.Deserialize(yaml); } + // ReSharper restore ReturnTypeCanBeNotNullable } \ No newline at end of file diff --git a/scripts/utils/ExplorerUtils.cs b/scripts/utils/ExplorerUtils.cs index 473da55..ec3aafa 100644 --- a/scripts/utils/ExplorerUtils.cs +++ b/scripts/utils/ExplorerUtils.cs @@ -58,6 +58,7 @@ public static class ExplorerUtils case Config.OsEnum.Macos: case Config.OsEnum.Ios: case Config.OsEnum.Web: + case Config.OsEnum.Editor: default: throw new ArgumentOutOfRangeException(); } diff --git a/scripts/utils/ResUtils.cs b/scripts/utils/ResUtils.cs index 423b514..728873d 100644 --- a/scripts/utils/ResUtils.cs +++ b/scripts/utils/ResUtils.cs @@ -23,20 +23,22 @@ public static class ResUtils public static string? GetSelfDllFolder() { var currentDirectory = Environment.CurrentDirectory; - if (Config.GetOs() == Config.OsEnum.Editor) + var osEnum = Config.GetOs(true); + if (osEnum == Config.OsEnum.Editor) { return Path.Join(currentDirectory, ".godot", "mono", "temp", "bin", "Debug"); } - if (Config.GetOs() == Config.OsEnum.Windows) + if (osEnum == Config.OsEnum.Windows) { return Path.Join(currentDirectory, "data_" + Config.SolutionName + "_windows_x86_64"); } - if (Config.GetOs() == Config.OsEnum.Linux) + if (osEnum == Config.OsEnum.Linux) { return Path.Join(currentDirectory, "data_" + Config.SolutionName + "_linuxbsd_x86_64"); } + return null; }