348 lines
13 KiB
C#
348 lines
13 KiB
C#
using System;
|
||
using System.Data;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Runtime.Loader;
|
||
using ColdMint.scripts.debug;
|
||
using ColdMint.scripts.utils;
|
||
using Godot;
|
||
|
||
namespace ColdMint.scripts.mod;
|
||
|
||
/// <summary>
|
||
/// <para>Mod Loader</para>
|
||
/// <para>模组加载器</para>
|
||
/// </summary>
|
||
public class ModLoader
|
||
{
|
||
/// <summary>
|
||
/// <para>AssemblyLoadContext</para>
|
||
/// <para>装配加载上下文</para>
|
||
/// </summary>
|
||
private static AssemblyLoadContext? _assemblyLoadContext;
|
||
|
||
private static readonly string[] RequiredDllList = [Config.SolutionName];
|
||
|
||
/// <summary>
|
||
/// <para>Initializes the mod loader</para>
|
||
/// <para>初始化模组加载器</para>
|
||
/// </summary>
|
||
/// <exception cref="FileNotFoundException">
|
||
///<para>This exception is thrown if the built-in dll file cannot be found when it is loaded.</para>
|
||
///<para>如果加载内置dll文件时,找不到文件,则抛出此异常。</para>
|
||
/// </exception>
|
||
public static void Init()
|
||
{
|
||
//Initialize the context.
|
||
//初始化上下文环境。
|
||
LogCat.Log("initialize_the_context", LogCat.LogLabel.ModLoader);
|
||
_assemblyLoadContext = AssemblyLoadContext.GetLoadContext(typeof(Godot.Bridge.ScriptManagerBridge).Assembly);
|
||
if (_assemblyLoadContext == null)
|
||
{
|
||
LogCat.LogError("initialize_the_context_failed", LogCat.LogLabel.ModLoader);
|
||
return;
|
||
}
|
||
|
||
var dllFolder = ResUtils.GetSelfDllFolder();
|
||
if (dllFolder == null)
|
||
{
|
||
LogCat.LogError("get_dll_folder_failed", LogCat.LogLabel.ModLoader);
|
||
return;
|
||
}
|
||
|
||
foreach (var requiredDll in RequiredDllList)
|
||
{
|
||
var dllPath = Path.Join(dllFolder, requiredDll + ".dll");
|
||
//Load the necessary dll files.
|
||
//加载必须的dll文件。
|
||
if (!File.Exists(dllPath))
|
||
{
|
||
//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 FileNotFoundException("dll not exist:" + dllPath);
|
||
}
|
||
|
||
LoadDllFile(dllPath);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// <para>Load Dll file</para>
|
||
/// <para>加载Dll文件</para>
|
||
/// </summary>
|
||
/// <param name="dllPath">
|
||
///<para>dll file path</para>
|
||
///<para>dll的文件路径</para>
|
||
/// </param>
|
||
/// <exception cref="NullReferenceException">
|
||
///<para>Throw this error if the assemblyLoadContext has not been initialized.</para>
|
||
///<para>如果assemblyLoadContext尚未初始化,那么抛出此错误。</para>
|
||
/// </exception>
|
||
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
|
||
{
|
||
var assembly = _assemblyLoadContext.LoadFromAssemblyPath(dllPath);
|
||
var assemblyName = assembly.GetName().Name;
|
||
if (assemblyName == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
LogCat.LogWithFormat("dll_name", LogCat.LogLabel.ModLoader, true, assemblyName);
|
||
//If the load is not its own Dll file.
|
||
//如果加载的不是自身的Dll文件.
|
||
if (assemblyName == Config.SolutionName)
|
||
{
|
||
return;
|
||
}
|
||
|
||
//Call the method of the entry class.
|
||
//调用入口类的方法
|
||
var exportedTypes = assembly.GetExportedTypes();
|
||
LogCat.LogWarningWithFormat("dll_type_length", LogCat.LogLabel.ModLoader, LogCat.UploadFormat, dllPath,
|
||
exportedTypes.Length);
|
||
var modLifecycleHandlerType =
|
||
FindTypeInTypeArray(exportedTypes, Config.ModLifecycleHandlerName);
|
||
if (modLifecycleHandlerType == null)
|
||
{
|
||
//The module does not register a lifecycle processor.
|
||
//模组没有注册生命周期处理器。
|
||
LogCat.LogWarningWithFormat("dll_does_not_register_lifecycle_processor", LogCat.LogLabel.ModLoader,
|
||
LogCat.UploadFormat,
|
||
dllPath, Config.ModLifecycleHandlerName);
|
||
return;
|
||
}
|
||
|
||
var constructor = modLifecycleHandlerType.GetConstructor(Type.EmptyTypes);
|
||
if (constructor == null)
|
||
{
|
||
//No parameterless constructor found.
|
||
//未找到无参构造方法。
|
||
LogCat.LogWarningWithFormat("dll_no_parameterless_constructor", LogCat.LogLabel.ModLoader,
|
||
LogCat.UploadFormat,
|
||
dllPath);
|
||
return;
|
||
}
|
||
|
||
var modLifecycleHandler = constructor.Invoke(null);
|
||
var methodInfo =
|
||
modLifecycleHandlerType.GetMethod(nameof(IModLifecycleHandler.OnModLoaded));
|
||
if (methodInfo == null)
|
||
{
|
||
LogCat.LogWarningWithFormat("mod_lifecycle_handler_not_implement_interface",
|
||
LogCat.LogLabel.ModLoader,
|
||
LogCat.UploadFormat, dllPath);
|
||
return;
|
||
}
|
||
|
||
methodInfo.Invoke(modLifecycleHandler, null);
|
||
}
|
||
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);
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// <para>Find a specific type by class name</para>
|
||
/// <para>通过类名查找特定的类型</para>
|
||
/// </summary>
|
||
/// <param name="types">
|
||
///<para>TypeArray</para>
|
||
///<para>类型数组</para>
|
||
/// </param>
|
||
/// <param name="className">
|
||
///<para>ClassName</para>
|
||
///<para>类名</para>
|
||
/// </param>
|
||
/// <returns></returns>
|
||
public static Type? FindTypeInTypeArray(Type[] types, string className)
|
||
{
|
||
return types.FirstOrDefault(type => type.Name == className);
|
||
}
|
||
|
||
/// <summary>
|
||
/// <para>Load all mods</para>
|
||
/// <para>加载全部模组</para>
|
||
/// </summary>
|
||
/// <remarks>
|
||
///<para>This method scans the incoming subfolders and loads them as module folders.</para>
|
||
///<para>此方法会将扫描传入的子文件夹,并将其子文件夹看作模组文件夹加载。</para>
|
||
/// </remarks>
|
||
/// <param name="modFolder">
|
||
///<para>Mod folder</para>
|
||
///<para>模组文件夹</para>
|
||
/// </param>
|
||
/// <exception cref="DirectoryNotFoundException">
|
||
///<para>If the given folder does not exist, throw this exception.</para>
|
||
///<para>如果给定的文件夹不存在,则抛出此异常。</para>
|
||
/// </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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// <para>Load a single mod</para>
|
||
/// <para>加载单个模组</para>
|
||
/// </summary>
|
||
/// <param name="modFolderPath">
|
||
///<para>Mod path</para>
|
||
///<para>模组路径</para>
|
||
/// </param>
|
||
/// <exception cref="DirectoryNotFoundException">
|
||
/// <para>If the given directory does not exist, throw this exception.</para>
|
||
///<para>如果给定的目录不存在,那么抛出此异常。</para>
|
||
/// </exception>
|
||
/// <exception cref="NullReferenceException">
|
||
///<para>Throw this exception if the manifest file creation deserialization fails.</para>
|
||
///<para>如果清单文件创建反序列化失败,则抛出此异常。</para>
|
||
/// </exception>
|
||
private static void LoadSingleMod(string modFolderPath)
|
||
{
|
||
if (!Directory.Exists(modFolderPath))
|
||
{
|
||
//The module folder does not exist.
|
||
//模组文件夹不存在。
|
||
throw new DirectoryNotFoundException("Mod folder does not exist:" + modFolderPath);
|
||
}
|
||
|
||
var modManifestPath = Path.Join(modFolderPath, Config.ModManifestFileName);
|
||
var modManifest =
|
||
ModManifest.CreateModManifestFromPath(modManifestPath);
|
||
if (modManifest == null)
|
||
{
|
||
throw new NullReferenceException("mod manifest is null:" + modManifestPath);
|
||
}
|
||
|
||
var pckList = modManifest.PckList;
|
||
if (pckList == null || pckList.Length == 0)
|
||
{
|
||
//The module does not contain a pck file.
|
||
//模组不包含pck文件。
|
||
LogCat.LogWarningWithFormat("mod_not_contain_pck", LogCat.LogLabel.ModLoader, LogCat.UploadFormat,
|
||
modFolderPath);
|
||
}
|
||
else
|
||
{
|
||
//The module contains pck files, load the pck files.
|
||
//包含pck文件,加载pck文件。
|
||
foreach (var pck in pckList)
|
||
{
|
||
var pckPath = Path.GetFullPath(pck, modFolderPath);
|
||
LoadPckFile(pckPath);
|
||
}
|
||
}
|
||
|
||
var dllList = modManifest.DllList;
|
||
if (dllList == null || dllList.Length == 0)
|
||
{
|
||
//The module does not contain a dll file.
|
||
//模组不包含dll文件。
|
||
LogCat.LogWarningWithFormat("mod_not_contain_dll", LogCat.LogLabel.ModLoader, LogCat.UploadFormat,
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// <para>Load the Pck file</para>
|
||
/// <para>加载Pck文件</para>
|
||
/// </summary>
|
||
/// <param name="pckPath">
|
||
///<para>Pck path</para>
|
||
///<para>Pck路径</para>
|
||
/// </param>
|
||
/// <exception cref="FileNotFoundException">
|
||
///<para>If the given path does not exist, throw this exception.</para>
|
||
///<para>如果给定的路径不存在,那么抛出此异常。</para>
|
||
/// </exception>
|
||
/// <exception cref="Exception">
|
||
///<para>Throw this exception if the pck package fails to load.</para>
|
||
///<para>如果pck包加载失败了,抛出此异常。</para>
|
||
/// </exception>
|
||
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);
|
||
}
|
||
}
|
||
} |