diff --git a/Api/Utility/EntityModuleConfig.cs b/Api/Utility/EntityModuleConfig.cs new file mode 100644 index 0000000..c16fcb3 --- /dev/null +++ b/Api/Utility/EntityModuleConfig.cs @@ -0,0 +1,107 @@ +using Milimoe.FunGame.Core.Entity; +using Milimoe.FunGame.Core.Library.Constant; + +namespace Milimoe.FunGame.Core.Api.Utility +{ + /// + /// 简易的实体模组配置文件生成器 + /// 仅支持继承了 的实体类型,每个 仅保存一种实体类型的数据 + /// 文件会保存为:程序目录/configs//.json + /// + /// + /// 新建一个配置文件,文件会保存为:程序目录/configs//.json + /// + /// + /// + public class EntityModuleConfig(string module_name, string file_name) : Dictionary where T : BaseEntity + { + /// + /// 模组的名称 + /// + public string ModuleName { get; set; } = module_name; + + /// + /// 配置文件的名称(后缀将是.json) + /// + public string FileName { get; set; } = file_name; + + /// + /// 使用索引器给指定key赋值,不存在key会新增 + /// + /// + /// + public new T this[string key] + { + get => base[key]; + set + { + if (value != null) Add(key, value); + } + } + + /// + /// 获取指定key的value + /// + /// + /// + public T? Get(string key) + { + if (TryGetValue(key, out T? value) && value != null) + { + return value; + } + return null; + } + + /// + /// 添加一个配置,如果已存在key会覆盖 + /// + /// + /// + public new void Add(string key, T value) + { + if (value != null) + { + if (TryGetValue(key, out _)) base[key] = value; + else base.Add(key, value); + } + } + + /// + /// 从配置文件中读取配置。 + /// + public void LoadConfig() + { + string dpath = $@"{AppDomain.CurrentDomain.BaseDirectory}configs/{ModuleName}"; + string fpath = $@"{dpath}/{FileName}.json"; + if (Directory.Exists(dpath) && File.Exists(fpath)) + { + string json = File.ReadAllText(fpath, General.DefaultEncoding); + Dictionary dict = NetworkUtility.JsonDeserialize>(json) ?? []; + Clear(); + foreach (string key in dict.Keys) + { + T obj = dict[key]; + base.Add(key, obj); + } + } + } + + /// + /// 将配置保存到配置文件。调用此方法会覆盖原有的.json,请注意备份 + /// + public void SaveConfig() + { + string json = NetworkUtility.JsonSerialize((Dictionary)this); + string dpath = $@"{AppDomain.CurrentDomain.BaseDirectory}configs/{ModuleName}"; + string fpath = $@"{dpath}/{FileName}.json"; + if (!Directory.Exists(dpath)) + { + Directory.CreateDirectory(dpath); + } + using StreamWriter writer = new(fpath, false, General.DefaultEncoding); + writer.WriteLine(json); + writer.Flush(); + } + } +} diff --git a/Api/Utility/GameModuleLoader.cs b/Api/Utility/GameModuleLoader.cs index e05a694..323a928 100644 --- a/Api/Utility/GameModuleLoader.cs +++ b/Api/Utility/GameModuleLoader.cs @@ -42,6 +42,11 @@ namespace Milimoe.FunGame.Core.Api.Utility /// public Dictionary AssociatedServers { get; } = []; + /// + /// 已加载的模组DLL名称对应的路径 + /// + public static Dictionary ModuleFilePaths => new(AddonManager.ModuleFilePaths); + private GameModuleLoader() { } /// diff --git a/Api/Utility/General.cs b/Api/Utility/General.cs index 3f924f6..26aa4cb 100644 --- a/Api/Utility/General.cs +++ b/Api/Utility/General.cs @@ -340,50 +340,63 @@ namespace Milimoe.FunGame.Core.Api.Utility public class Encryption { /// - /// 使用HMACSHA512算法加密 + /// 使用 HMAC-SHA512 算法对文本进行加密 /// - /// 需要加密的值 - /// 秘钥 - /// - internal static string HmacSha512(string Message, string Key) + /// 需要加密的文本 + /// 用于加密的秘钥 + /// 加密后的 HMAC-SHA512 哈希值 + public static string HmacSha512(string text, string key) { - byte[] MessageBytes = General.DefaultEncoding.GetBytes(Message); - Key = Convert.ToBase64String(General.DefaultEncoding.GetBytes(Key)); - byte[] KeyBytes = General.DefaultEncoding.GetBytes(Key); - HMACSHA512 Hmacsha512 = new(KeyBytes); - byte[] Hash = Hmacsha512.ComputeHash(MessageBytes); - string Hmac = BitConverter.ToString(Hash).Replace("-", ""); - return Hmac.ToLower(); + byte[] text_bytes = General.DefaultEncoding.GetBytes(text); + key = Convert.ToBase64String(General.DefaultEncoding.GetBytes(key)); + byte[] key_bytes = General.DefaultEncoding.GetBytes(key); + HMACSHA512 hmacsha512 = new(key_bytes); + byte[] hash_bytes = hmacsha512.ComputeHash(text_bytes); + string hmac = BitConverter.ToString(hash_bytes).Replace("-", ""); + return hmac.ToLower(); } /// - /// 使用RSA算法加密 + /// 计算文件的 SHA-256 哈希值 /// - /// 明文 - /// 公钥 - /// - public static string RSAEncrypt(string PlainText, string PublicKey) + /// 要计算哈希值的文件路径 + /// 文件的 SHA-256 哈希值 + public static string FileSha512(string file_path) { - byte[] Plain = Encoding.UTF8.GetBytes(PlainText); - using RSACryptoServiceProvider RSA = new(); - RSA.FromXmlString(PublicKey); - byte[] Encrypted = RSA.Encrypt(Plain, false); - return Convert.ToBase64String(Encrypted); + using SHA256 sha256 = SHA256.Create(); + using FileStream stream = File.OpenRead(file_path); + byte[] hash = sha256.ComputeHash(stream); + return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); } /// - /// 使用RSA算法解密 + /// 使用 RSA 算法加密 /// - /// 密文 - /// 私钥 + /// 明文 + /// 公钥 /// - public static string RSADecrypt(string SecretText, string PrivateKey) + public static string RSAEncrypt(string plain_text, string plublic_key) { - byte[] Encrypted = Convert.FromBase64String(SecretText); - using RSACryptoServiceProvider RSA = new(); - RSA.FromXmlString(PrivateKey); - byte[] Decrypted = RSA.Decrypt(Encrypted, false); - return Encoding.UTF8.GetString(Decrypted); + byte[] plain = Encoding.UTF8.GetBytes(plain_text); + using RSACryptoServiceProvider rsa = new(); + rsa.FromXmlString(plublic_key); + byte[] encrypted = rsa.Encrypt(plain, false); + return Convert.ToBase64String(encrypted); + } + + /// + /// 使用 RSA 算法解密 + /// + /// 密文 + /// 私钥 + /// + public static string RSADecrypt(string secret_text, string private_key) + { + byte[] secret = Convert.FromBase64String(secret_text); + using RSACryptoServiceProvider rsa = new(); + rsa.FromXmlString(private_key); + byte[] decrypted = rsa.Decrypt(secret, false); + return Encoding.UTF8.GetString(decrypted); } } @@ -393,14 +406,15 @@ namespace Milimoe.FunGame.Core.Api.Utility public static class StringExtension { /// - /// 使用HMACSHA512算法加密 + /// 使用 HMAC-SHA512 算法对文本进行加密 + /// 注意:此方法会先将 转为小写并计算两次哈希。 /// - /// 需要加密的值 - /// 秘钥 - /// - public static string Encrypt(this string msg, string key) + /// 需要加密的文本 + /// 用于加密的秘钥 + /// 加密后的 HMAC-SHA512 哈希值 + public static string Encrypt(this string text, string key) { - return Encryption.HmacSha512(msg, Encryption.HmacSha512(msg, key.ToLower())); + return Encryption.HmacSha512(text, Encryption.HmacSha512(text, key.ToLower())); } } diff --git a/Api/Utility/PluginConfig.cs b/Api/Utility/PluginConfig.cs index 894871f..5db7f8c 100644 --- a/Api/Utility/PluginConfig.cs +++ b/Api/Utility/PluginConfig.cs @@ -9,7 +9,7 @@ namespace Milimoe.FunGame.Core.Api.Utility /// 文件会保存为:程序目录/configs//.json /// /// - /// 新建一个配置文件,文件会保存为:程序目录/configs//.json + /// 新建一个配置文件,文件会保存为:程序目录/configs//.json /// /// /// diff --git a/Api/Utility/PluginLoader.cs b/Api/Utility/PluginLoader.cs index 40875a5..a17aaf2 100644 --- a/Api/Utility/PluginLoader.cs +++ b/Api/Utility/PluginLoader.cs @@ -13,6 +13,11 @@ namespace Milimoe.FunGame.Core.Api.Utility /// public Dictionary Plugins { get; } = []; + /// + /// 已加载的插件DLL名称对应的路径 + /// + public static Dictionary PluginFilePaths => new(AddonManager.PluginFilePaths); + private PluginLoader() { diff --git a/Api/Utility/TextReader.cs b/Api/Utility/TextReader.cs index 5521d04..21832f5 100644 --- a/Api/Utility/TextReader.cs +++ b/Api/Utility/TextReader.cs @@ -130,7 +130,7 @@ namespace Milimoe.FunGame.Core.Api.Utility /// /// 读取TXT文件内容 /// - /// 文件名 + /// 文件名(需要包含扩展名) /// 相对路径 /// 内容 public static string ReadTXT(string filename, string path = "") @@ -154,13 +154,14 @@ namespace Milimoe.FunGame.Core.Api.Utility } /// - /// 写入TXT文件内容(如不存在文件会创建,反之新起一行追加) + /// 写入TXT文件内容(如不存在文件会创建) + /// 选项用于覆盖或追加文本 /// - /// - /// 文件名 + /// + /// 文件名(需要包含扩展名) /// 相对路径 - /// 内容 - public static void WriteTXT(string message, string filename, string path = "") + /// 是否覆盖 + public static void WriteTXT(string content, string filename, string path = "", bool overwrite = false) { if (path.Trim() != "") { @@ -170,11 +171,19 @@ namespace Milimoe.FunGame.Core.Api.Utility } else path = $@"{AppDomain.CurrentDomain.BaseDirectory}{filename}"; // 写入内容 - StreamWriter writer = File.Exists(path) ? new(path, true, General.DefaultEncoding) : new(path, false, General.DefaultEncoding); - writer.WriteLine(message); + StreamWriter writer = File.Exists(path) ? new(path, !overwrite, General.DefaultEncoding) : new(path, false, General.DefaultEncoding); + writer.WriteLine(content); writer.Close(); } + /// + /// 写入并覆盖TXT文件内容 + /// + /// + /// 文件名(需要包含扩展名) + /// 相对路径 + public static void OverwriteTXT(string content, string filename, string path = "") => WriteTXT(content, filename, path, true); + /// /// 追加错误日志 默认写入logs文件夹下的当日日期.log文件 /// diff --git a/Service/AddonManager.cs b/Service/AddonManager.cs index af13f15..01777e6 100644 --- a/Service/AddonManager.cs +++ b/Service/AddonManager.cs @@ -8,6 +8,16 @@ namespace Milimoe.FunGame.Core.Service { internal class AddonManager { + /// + /// 已加载的插件DLL名称对应的路径 + /// + internal static Dictionary PluginFilePaths { get; } = []; + + /// + /// 已加载的模组DLL名称对应的路径 + /// + internal static Dictionary ModuleFilePaths { get; } = []; + /// /// 从plugins目录加载所有插件 /// @@ -28,7 +38,7 @@ namespace Milimoe.FunGame.Core.Service foreach (Type type in assembly.GetTypes().AsEnumerable().Where(type => type.IsSubclassOf(typeof(Plugin)))) { - AddAddonInstances(type, plugins, (instance) => + if (AddAddonInstances(type, plugins, (instance) => { if (instance.Load(otherobjs)) { @@ -36,7 +46,10 @@ namespace Milimoe.FunGame.Core.Service return true; } return false; - }); + })) + { + AddDictionary(PluginFilePaths, assembly, dll); + } } } @@ -65,9 +78,11 @@ namespace Milimoe.FunGame.Core.Service foreach (Type type in assembly.GetTypes().AsEnumerable().Where(type => typeof(IAddon).IsAssignableFrom(type))) { + bool isAdded = false; + if (type.IsSubclassOf(typeof(GameModule))) { - AddAddonInstances(type, modules, (instance) => + isAdded = AddAddonInstances(type, modules, (instance) => { if (instance.Load(otherobjs)) { @@ -79,15 +94,20 @@ namespace Milimoe.FunGame.Core.Service } else if (type.IsSubclassOf(typeof(CharacterModule))) { - AddAddonInstances(type, characters, (instance) => instance.Load(otherobjs)); + isAdded = AddAddonInstances(type, characters, (instance) => instance.Load(otherobjs)); } else if (type.IsSubclassOf(typeof(SkillModule))) { - AddAddonInstances(type, skills, (instance) => instance.Load(otherobjs)); + isAdded = AddAddonInstances(type, skills, (instance) => instance.Load(otherobjs)); } else if (type.IsSubclassOf(typeof(ItemModule))) { - AddAddonInstances(type, items, (instance) => instance.Load(otherobjs)); + isAdded = AddAddonInstances(type, items, (instance) => instance.Load(otherobjs)); + } + + if (isAdded) + { + AddDictionary(ModuleFilePaths, assembly, dll); } } } @@ -118,9 +138,11 @@ namespace Milimoe.FunGame.Core.Service foreach (Type type in assembly.GetTypes().AsEnumerable().Where(type => typeof(IAddon).IsAssignableFrom(type))) { + bool isAdded = false; + if (type.IsSubclassOf(typeof(GameModule))) { - AddAddonInstances(type, modules, (instance) => + isAdded = AddAddonInstances(type, modules, (instance) => { if (instance.Load(otherobjs)) { @@ -132,7 +154,7 @@ namespace Milimoe.FunGame.Core.Service } else if (type.IsSubclassOf(typeof(GameModuleServer))) { - AddAddonInstances(type, servers, (instance) => + isAdded = AddAddonInstances(type, servers, (instance) => { if (instance.Load(otherobjs)) { @@ -144,15 +166,20 @@ namespace Milimoe.FunGame.Core.Service } else if (type.IsSubclassOf(typeof(CharacterModule))) { - AddAddonInstances(type, characters, (instance) => instance.Load(otherobjs)); + isAdded = AddAddonInstances(type, characters, (instance) => instance.Load(otherobjs)); } else if (type.IsSubclassOf(typeof(SkillModule))) { - AddAddonInstances(type, skills, (instance) => instance.Load(otherobjs)); + isAdded = AddAddonInstances(type, skills, (instance) => instance.Load(otherobjs)); } else if (type.IsSubclassOf(typeof(ItemModule))) { - AddAddonInstances(type, items, (instance) => instance.Load(otherobjs)); + isAdded = AddAddonInstances(type, items, (instance) => instance.Load(otherobjs)); + } + + if (isAdded) + { + AddDictionary(ModuleFilePaths, assembly, dll); } } } @@ -178,7 +205,10 @@ namespace Milimoe.FunGame.Core.Service foreach (Type type in assembly.GetTypes().AsEnumerable().Where(type => type.IsSubclassOf(typeof(GameMap)))) { - AddAddonInstances(type, maps, (instance) => instance.Load(objs)); + if (AddAddonInstances(type, maps, (instance) => instance.Load(objs))) + { + AddDictionary(ModuleFilePaths, assembly, dll); + } } } @@ -192,8 +222,9 @@ namespace Milimoe.FunGame.Core.Service /// 循环程序集的类型 /// 实例的字典 /// 加载时触发的检查方法,返回false不添加 - private static void AddAddonInstances(Type type, Dictionary dictionary, Func? isadd = null) where T : IAddon + private static bool AddAddonInstances(Type type, Dictionary dictionary, Func? isadd = null) where T : IAddon { + bool isAdded = false; T? instance = (T?)Activator.CreateInstance(type); if (instance != null) { @@ -201,8 +232,16 @@ namespace Milimoe.FunGame.Core.Service if (!string.IsNullOrWhiteSpace(name) && (isadd == null || isadd(instance))) { dictionary.TryAdd(name.Trim(), instance); + isAdded = true; } } + return isAdded; + } + + private static void AddDictionary(Dictionary dict, Assembly assembly, string dllpath) + { + string filename = assembly.GetName().Name?.Trim() ?? ""; + if (filename != "") dict.TryAdd(filename, dllpath); } } }