422 lines
11 KiB
C#
422 lines
11 KiB
C#
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 Newtonsoft.Json;
|
|
|
|
namespace BITKit.Mod
|
|
{
|
|
/// <summary>
|
|
/// Mod接口,需要实现所有方法
|
|
/// </summary>
|
|
public interface IMod
|
|
{
|
|
string FolderPath { get; set; }
|
|
/// <summary>
|
|
/// 唯一Id
|
|
/// </summary>
|
|
public Guid Id => new Guid("3E5AF780-FAB1-40B7-B8EF-62938F2340CB");
|
|
/// <summary>
|
|
/// Mod名称,可以重复
|
|
/// </summary>
|
|
public string Name { get; }
|
|
/// <summary>
|
|
/// 包名,不可重复
|
|
/// </summary>
|
|
public string PackageName { get; }
|
|
/// <summary>
|
|
/// 描述,可为空
|
|
/// </summary>
|
|
public string Description { get; }
|
|
/// <summary>
|
|
/// 版本,调试时Mod版本需要高于以前的版本或者为0.0.0
|
|
/// </summary>
|
|
public string Version { get; }
|
|
/// <summary>
|
|
/// 开发者
|
|
/// </summary>
|
|
public string Author { get; }
|
|
/// <summary>
|
|
/// 联系邮箱,或者为其他联系方式
|
|
/// </summary>
|
|
public string Email { get; }
|
|
/// <summary>
|
|
/// Mod主页
|
|
/// </summary>
|
|
public string Url { get; }
|
|
/// <summary>
|
|
/// Mod标签,通常用于过滤和搜索
|
|
/// </summary>
|
|
public string[] Tags { get; }
|
|
/// <summary>
|
|
/// Mod属性,例如图标,背景或者其他资产包
|
|
/// </summary>
|
|
public object[] Properties { get; }
|
|
/// <summary>
|
|
/// 初始化时
|
|
/// </summary>
|
|
void OnInitialize();
|
|
/// <summary>
|
|
/// 初始化时的异步方法
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
UniTask OnInitializedAsync(CancellationToken cancellationToken);
|
|
/// <summary>
|
|
/// 初始化完成后
|
|
/// </summary>
|
|
void OnInitialized();
|
|
/// <summary>
|
|
/// 被释放时
|
|
/// </summary>
|
|
void OnDispose();
|
|
/// <summary>
|
|
/// 被释放时的异步方法
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
UniTask OnDisposeAsync(CancellationToken cancellationToken);
|
|
/// <summary>
|
|
/// 被释放后
|
|
/// </summary>
|
|
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<string>();
|
|
public virtual object[] Properties{ get; set; } = Array.Empty<object>();
|
|
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<ModPackage[]> SearchPackages()
|
|
{
|
|
|
|
var list=new List<ModPackage>();
|
|
var path = Path.Combine(Environment.CurrentDirectory, "Mods");
|
|
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<ModPackage>(await File.ReadAllTextAsync(file))!;
|
|
package.PackagePath = file;
|
|
package.WorkDirectory = x.FullName;
|
|
list.Add(package);
|
|
}
|
|
|
|
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<ModService>($"未找到{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<IMod>();
|
|
|
|
public static readonly ValidHandle IsBusy = new();
|
|
|
|
public static event Action<ModPackage> OnPackageLoad;
|
|
public static event Action<ModPackage> OnPackageLoaded;
|
|
|
|
public static event Action<IMod> OnModLoad;
|
|
public static event Action<IMod> OnModLoaded;
|
|
public static event Action<IMod> OnModUnLoad;
|
|
public static event Action<IMod> OnModUnLoaded;
|
|
public static event Action<IMod> OnModInstalled;
|
|
public static event Action<IMod> OnModUnInstalled;
|
|
|
|
public static event Action OnReload;
|
|
public static event Action OnReloaded;
|
|
|
|
public static event Func<IMod,UniTask> OnModLoadAsync;
|
|
public static event Func<IMod,UniTask> OnModUnloadAsync;
|
|
|
|
private static CancellationTokenSource _CancellationTokenSource;
|
|
private static readonly ConcurrentDictionary<string,IMod> _InstalledMods=new();
|
|
|
|
public static async UniTask Initialize()
|
|
{
|
|
BIT4Log.Log<ModService>("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;
|
|
}
|
|
#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);
|
|
continue;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
BIT4Log.Warning<ModService>("自动加载Mod失败");
|
|
BIT4Log.LogException(e);
|
|
}
|
|
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
BIT4Log.LogException(e);
|
|
BIT4Log.Warning<ModService>("Mod服务遇到了错误,已停止");
|
|
}
|
|
|
|
|
|
}
|
|
|
|
public static void Dispose()
|
|
{
|
|
_CancellationTokenSource.Cancel();
|
|
try
|
|
{
|
|
Mods = Array.Empty<IMod>();
|
|
_InstalledMods.Clear();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
BIT4Log.LogException(e);
|
|
}
|
|
|
|
}
|
|
|
|
public static UniTask Load(Assembly assembly,string folderPath=null)
|
|
{
|
|
BIT4Log.Log<ModService>($"加载程序集:{assembly.FullName}");
|
|
|
|
try
|
|
{
|
|
//_ModDomain.Load(assembly.FullName);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
BIT4Log.LogException(e);
|
|
return UniTask.CompletedTask;
|
|
}
|
|
|
|
var types = new List<Type>();
|
|
|
|
try
|
|
{
|
|
types.AddRange(assembly.GetTypes());
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
BIT4Log.Warning<ModService>($"{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<ModService>($"跳过类型:{type.FullName}");
|
|
continue;
|
|
}
|
|
var mod = Activator.CreateInstance(type).As<IMod>();
|
|
//DI.Inject(mod);
|
|
mod.FolderPath =folderPath;
|
|
if(_InstalledMods.ContainsKey(mod.PackageName))
|
|
{
|
|
BIT4Log.Log<ModService>($"Mod已安装,跳过加载:{mod.PackageName}");
|
|
continue;
|
|
}
|
|
BIT4Log.Log<ModService>($"加载Mod:{mod.GetType().FullName},Folder:{folderPath}");
|
|
|
|
Install(mod);
|
|
Load(mod);
|
|
}
|
|
|
|
BIT4Log.Log<ModService>($"<color=green>程序集加载完成:{assembly.FullName}</color>");
|
|
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<ModPackage>(await File.ReadAllTextAsync(path))!;
|
|
BIT4Log.Log<ModService>($"加载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)
|
|
{
|
|
mod.OnInitialize();
|
|
OnModLoad?.Invoke(mod);
|
|
BIT4Log.Log<ModService>($"加载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<ModService>($"卸载Mod:{mod.GetType().FullName}");
|
|
}
|
|
|
|
public static async void 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 void UnInstall(IMod mod)
|
|
{
|
|
using var _ = IsBusy.GetHandle();
|
|
|
|
|
|
if(_InstalledMods.ContainsKey(mod.PackageName) is false) return;
|
|
_InstalledMods.TryRemove(mod.PackageName);
|
|
Mods = _InstalledMods.Values.ToArray();
|
|
OnModUnInstalled?.Invoke(mod);
|
|
}
|
|
}
|
|
}
|
|
|