using System; using System.CodeDom.Compiler; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.IO.MemoryMappedFiles; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using BITKit.IO; using BITKit.UX; using Cysharp.Threading.Tasks; using Microsoft.CSharp; using Microsoft.Extensions.Logging; using Newtonsoft.Json; namespace BITKit.Mod { /// /// Mod接口,需要实现所有方法 /// public interface IMod { string FolderPath { get; set; } /// /// 唯一Id /// public Guid Id => new Guid("3E5AF780-FAB1-40B7-B8EF-62938F2340CB"); /// /// Mod名称,可以重复 /// public string Name { get; } /// /// 包名,不可重复 /// public string PackageName { get; } /// /// 描述,可为空 /// public string Description { get; } /// /// 版本,调试时Mod版本需要高于以前的版本或者为0.0.0 /// public string Version { get; } /// /// 开发者 /// public string Author { get; } /// /// 联系邮箱,或者为其他联系方式 /// public string Email { get; } /// /// Mod主页 /// public string Url { get; } /// /// Mod标签,通常用于过滤和搜索 /// public string[] Tags { get; } /// /// Mod属性,例如图标,背景或者其他资产包 /// public object[] Properties { get; } /// /// 初始化时 /// void OnInitialize(); /// /// 初始化时的异步方法 /// /// UniTask OnInitializedAsync(CancellationToken cancellationToken); /// /// 初始化完成后 /// void OnInitialized(); /// /// 被释放时 /// void OnDispose(); /// /// 被释放时的异步方法 /// /// UniTask OnDisposeAsync(CancellationToken cancellationToken); /// /// 被释放后 /// void OnDisposed(); } public class MyMod : IMod { public string FolderPath { get; set; } public virtual string Name { get; set; } = nameof(MyMod); // ReSharper disable once StringLiteralTypo public virtual string PackageName { get; set; } = "com.bitkit.mymod"; public virtual string Description{ get; set; } = "Empty mod for test function"; public virtual string Version { get; set; } = "initial 0.0.1"; public virtual string Author { get; set; } = nameof(BITKit); public virtual string Email { get; set; } = "root@bitfall.icu"; public virtual string Url { get; set; } = "https://bitfall.icu"; public virtual string[] Tags { get; set; } = Array.Empty(); public virtual object[] Properties{ get; set; } = Array.Empty(); public virtual void OnInitialize() { } public virtual UniTask OnInitializedAsync(CancellationToken cancellationToken) { return UniTask.CompletedTask; } public virtual void OnInitialized() { } public virtual void OnDispose() { } public virtual UniTask OnDisposeAsync(CancellationToken cancellationToken) { return UniTask.CompletedTask; } public virtual void OnDisposed() { } } public partial class ModService { public static async UniTask SearchPackages() { var list=new List(); var path = Path.Combine(Environment.CurrentDirectory, "Mods"); try { var dir = new DirectoryInfo(path); dir.Create(); foreach (var x in dir.GetDirectories()) { var file = Path.Combine(x.FullName,ModPackage.DefaultFileName); if(File.Exists(file) is false)continue; var package = JsonConvert.DeserializeObject(await File.ReadAllTextAsync(file))!; package.PackagePath = file; package.WorkDirectory = x.FullName; list.Add(package); } } catch (Exception e) { BIT4Log.LogException(e); } return list.ToArray(); } public static async UniTask Reload() { OnReload?.Invoke(); var mods = Mods; foreach (var x in Mods) { UnLoad(x); UnInstall(x); } foreach (var x in await SearchPackages()) { var path = x.PackagePath; if (File.Exists(path) is false) { BIT4Log.Warning($"未找到{x.PackageName}的描述文件:{path}"); continue; } try { await LoadFromPackage(path); } catch (Exception e) { BIT4Log.LogException(e); } } OnReloaded?.Invoke(); } public static IMod[] Mods { get; private set; }=Array.Empty(); public static readonly ValidHandle IsBusy = new(); public static event Action OnPackageLoad; public static event Action OnPackageLoaded; public static event Action OnModLoad; public static event Action OnModLoaded; public static event Action OnModUnLoad; public static event Action OnModUnLoaded; public static event Action OnModInstalled; public static event Action OnModUnInstalled; public static event Action OnReload; public static event Action OnReloaded; public static event Func OnModLoadAsync; public static event Func OnModUnloadAsync; private static CancellationTokenSource _CancellationTokenSource; private static readonly ConcurrentDictionary _InstalledMods=new(); public static async UniTask Initialize(ILogger logger=null) { logger?.LogInformation("Mod服务已启动"); _CancellationTokenSource = new CancellationTokenSource(); try { try { var modPath = Path.Combine(Environment.CurrentDirectory, @"Mods\"); PathHelper.EnsureDirectoryCreated(modPath); var directoryInfo = new DirectoryInfo(modPath); foreach (var fileInfo in directoryInfo.GetFiles()) { switch (fileInfo.Extension) { case ".dll": { var assembly = Assembly.LoadFile(fileInfo.FullName); await Load(assembly); continue; } case ".cs": { var code = await File.ReadAllTextAsync(fileInfo.FullName); var assembly = BITSharp.Compile(code); await Load(assembly, fileInfo.DirectoryName); continue; } } } } catch (Exception e) { logger?.LogWarning("自动加载Mod失败"); BIT4Log.LogException(e); } } catch (Exception e) { logger?.LogWarning("Mod服务遇到了错误,已停止"); BIT4Log.LogException(e); } } public static void Dispose() { _CancellationTokenSource.Cancel(); try { Mods = Array.Empty(); _InstalledMods.Clear(); } catch (Exception e) { BIT4Log.LogException(e); } } public static UniTask Load(Assembly assembly,string folderPath=null) { BIT4Log.Log($"加载程序集:{assembly.FullName}"); try { //_ModDomain.Load(assembly.FullName); } catch (Exception e) { BIT4Log.LogException(e); return UniTask.CompletedTask; } var types = new List(); try { types.AddRange(assembly.GetTypes()); } catch (Exception e) { BIT4Log.Warning($"{assembly.FullName}遇到了错误"); BIT4Log.LogException(e); } foreach (var type in types) { //if (type.IsAssignableFrom(typeof(IMod)) is false) if(typeof(IMod).IsAssignableFrom(type) is false) { //BIT4Log.Log($"跳过类型:{type.FullName}"); continue; } var mod = Activator.CreateInstance(type).As(); //DI.Inject(mod); mod.FolderPath =folderPath; if(_InstalledMods.ContainsKey(mod.PackageName)) { BIT4Log.Log($"Mod已安装,跳过加载:{mod.PackageName}"); continue; } BIT4Log.Log($"加载Mod:{mod.GetType().FullName},Folder:{folderPath}"); Install(mod); Load(mod); } BIT4Log.Log($"程序集加载完成:{assembly.FullName}"); return UniTask.CompletedTask; } public static async UniTask LoadFromPackage(string path) { await UniTask.Yield(); if(File.Exists(path) is false) throw new FileNotFoundException(path); var package = JsonConvert.DeserializeObject(await File.ReadAllTextAsync(path))!; BIT4Log.Log($"加载Mod包:{package.PackageName}"); if(package.EntryPoint is null) throw new InvalidOperationException("空入口,无法识别类型"); path = Path.Combine(Path.GetDirectoryName(path)!, package.EntryPoint); if(File.Exists(path) is false) throw new InvalidOperationException($"未找到入口文件:{path}"); OnPackageLoad?.Invoke(package); foreach (var name in package.Dlls) { } var fileInfo = new FileInfo(path); switch (fileInfo.Extension) { case ".dll": { var assembly = Assembly.LoadFile(fileInfo.FullName); await Load(assembly, fileInfo.DirectoryName); break; } #if UNITY_5_3_OR_NEWER case ".cs": { var code = await File.ReadAllTextAsync(fileInfo.FullName); var assembly = BITSharp.Compile(code); await Load(assembly, fileInfo.DirectoryName); break; } #endif } OnPackageLoaded?.Invoke(package); } public static async UniTask Load(IMod mod) { await IsBusy; using var _ = IsBusy.GetHandle(); mod.OnInitialize(); OnModLoad?.Invoke(mod); BIT4Log.Log($"加载Mod:{mod.GetType().FullName}"); } public static async UniTask UnLoad(IMod mod) { mod.OnDispose(); OnModUnLoad?.Invoke(mod); await mod.OnDisposeAsync(_CancellationTokenSource.Token); foreach (var x in OnModUnloadAsync.CastAsFunc()) { await x.Invoke(mod); } mod.OnDisposed(); OnModUnLoaded?.Invoke(mod); BIT4Log.Log($"卸载Mod:{mod.GetType().FullName}"); } public static async UniTask Install(IMod mod) { await IsBusy; using var _ = IsBusy.GetHandle(); if (_InstalledMods.ContainsKey(mod.PackageName)) { throw new ArgumentException("Mod已安装"); } _InstalledMods.TryAdd(mod.PackageName,mod); Mods = _InstalledMods.Values.ToArray(); OnModInstalled?.Invoke(mod); await mod.OnInitializedAsync(_CancellationTokenSource.Token); foreach (var x in OnModLoadAsync.CastAsFunc()) { await x.Invoke(mod); } mod.OnInitialized(); OnModLoaded?.Invoke(mod); } public static async UniTask UnInstall(IMod mod) { await IsBusy; using var _ = IsBusy.GetHandle(); if(_InstalledMods.ContainsKey(mod.PackageName) is false) return; _InstalledMods.TryRemove(mod.PackageName); Mods = _InstalledMods.Values.ToArray(); OnModUnInstalled?.Invoke(mod); } } }