Traveller/scripts/mod/ModLoader.cs
Cold-Mint d95515fdb7
ModLoader now support calling entry functions.
模组加载器现在支持调用入口函数了。
2024-07-27 22:06:10 +08:00

347 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Data;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Text;
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 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);
}
}
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", LogCat.LogLabel.ModLoader, LogCat.UploadFormat,
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);
}
}
}
/// <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);
}
}
}