feat(cordis): 接入上下文组合与模组事务热替换

This commit is contained in:
2026-08-16 23:20:40 +08:00
commit ad256f109b
676 changed files with 52168 additions and 0 deletions
@@ -0,0 +1,187 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using UnityEngine;
namespace ShrinkModFramework
{
internal static class ShrinkExternalModAssemblyLoader
{
internal sealed class ExternalAssemblyRevision
{
public string Path;
public string Revision;
public Assembly Assembly;
}
private static readonly Dictionary<string, ExternalAssemblyRevision> CurrentAssemblyRevisions =
new(StringComparer.OrdinalIgnoreCase);
private static readonly HashSet<Assembly> ExternalAssemblyHistory = new();
private static readonly Dictionary<string, string> KnownAssemblyFiles = new(StringComparer.OrdinalIgnoreCase);
private static readonly object ResolveLock = new();
private static bool _resolveRegistered;
public static IReadOnlyList<Assembly> LoadExternalAssemblies(ShrinkModFrameworkSettings settings, bool verboseLogging)
{
return ScanExternalAssemblyRevisions(settings, verboseLogging)
.Select(revision => revision.Assembly)
.ToArray();
}
internal static IReadOnlyList<ExternalAssemblyRevision> ScanExternalAssemblyRevisions(
ShrinkModFrameworkSettings settings, bool verboseLogging)
{
if (settings != null && !settings.enableExternalDllMods)
{
CurrentAssemblyRevisions.Clear();
KnownAssemblyFiles.Clear();
return Array.Empty<ExternalAssemblyRevision>();
}
#if ENABLE_IL2CPP && !UNITY_EDITOR
Debug.LogWarning("[ShrinkModFramework] IL2CPP 运行时不支持外部 DLL 热加载,已跳过外部模组扫描。");
return Array.Empty<ExternalAssemblyRevision>();
#else
var modsDirectory = GetExternalModsDirectory(settings);
if (settings == null || settings.autoCreateExternalModsDirectory)
Directory.CreateDirectory(modsDirectory);
RegisterAssemblyResolve();
var dllPaths = Directory.GetFiles(modsDirectory, "*.dll", SearchOption.AllDirectories)
.Select(Path.GetFullPath)
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase)
.ToArray();
var presentPaths = new HashSet<string>(dllPaths, StringComparer.OrdinalIgnoreCase);
foreach (var dllPath in dllPaths
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase))
{
var assemblyName = Path.GetFileNameWithoutExtension(dllPath);
KnownAssemblyFiles[assemblyName] = dllPath;
try
{
var bytes = File.ReadAllBytes(dllPath);
var revision = ComputeSha256(bytes);
if (CurrentAssemblyRevisions.TryGetValue(dllPath, out var current) &&
string.Equals(current.Revision, revision, StringComparison.Ordinal))
{
continue;
}
var pdbPath = Path.ChangeExtension(dllPath, ".pdb");
var assembly = File.Exists(pdbPath)
? Assembly.Load(bytes, File.ReadAllBytes(pdbPath))
: Assembly.Load(bytes);
var loadedRevision = new ExternalAssemblyRevision
{
Path = dllPath,
Revision = revision,
Assembly = assembly
};
CurrentAssemblyRevisions[dllPath] = loadedRevision;
ExternalAssemblyHistory.Add(assembly);
if (verboseLogging)
Debug.Log($"[ShrinkModFramework] 已加载外部模组程序集:{assembly.GetName().Name} ({revision[..12]})");
}
catch (Exception ex)
{
Debug.LogError($"[ShrinkModFramework] 加载外部 DLL 失败:{dllPath}\n{ex.Message}");
}
}
foreach (var missingPath in CurrentAssemblyRevisions.Keys
.Where(path => !presentPaths.Contains(path))
.ToArray())
{
CurrentAssemblyRevisions.Remove(missingPath);
}
return CurrentAssemblyRevisions.Values
.OrderBy(revision => revision.Path, StringComparer.OrdinalIgnoreCase)
.ToArray();
#endif
}
internal static bool IsExternalAssembly(Assembly assembly) =>
assembly != null && ExternalAssemblyHistory.Contains(assembly);
internal static bool TryGetCurrentRevision(Assembly assembly, out string revision)
{
foreach (var current in CurrentAssemblyRevisions.Values)
{
if (ReferenceEquals(current.Assembly, assembly))
{
revision = current.Revision;
return true;
}
}
revision = null;
return false;
}
public static string GetExternalModsDirectory(ShrinkModFrameworkSettings settings)
{
var folderName = settings != null && !string.IsNullOrWhiteSpace(settings.externalModsFolderName)
? settings.externalModsFolderName.Trim()
: "Mods";
return Path.Combine(Application.persistentDataPath, folderName);
}
internal static void ResetForTesting()
{
CurrentAssemblyRevisions.Clear();
ExternalAssemblyHistory.Clear();
KnownAssemblyFiles.Clear();
if (_resolveRegistered)
{
AppDomain.CurrentDomain.AssemblyResolve -= OnAssemblyResolve;
_resolveRegistered = false;
}
}
private static string ComputeSha256(byte[] bytes)
{
using var sha256 = SHA256.Create();
return BitConverter.ToString(sha256.ComputeHash(bytes)).Replace("-", string.Empty);
}
private static void RegisterAssemblyResolve()
{
lock (ResolveLock)
{
if (_resolveRegistered)
return;
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
_resolveRegistered = true;
}
}
private static Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
{
var requestedName = new AssemblyName(args.Name).Name;
if (string.IsNullOrWhiteSpace(requestedName))
return null;
if (!KnownAssemblyFiles.TryGetValue(requestedName, out var path) || !File.Exists(path))
return null;
try
{
return Assembly.Load(File.ReadAllBytes(path));
}
catch
{
return null;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bd2c1a02b62d790449c129e226cde40b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
namespace ShrinkModFramework
{
internal static class ShrinkHarmonyPatchService
{
private sealed class EmptyLease : IDisposable
{
public static readonly EmptyLease Instance = new();
public void Dispose() { }
}
private sealed class HarmonyLease : IDisposable
{
private readonly Type _harmonyType;
private readonly object _harmony;
private readonly string _harmonyId;
private readonly bool _verboseLogging;
private bool _disposed;
public HarmonyLease(Type harmonyType, object harmony, string harmonyId, bool verboseLogging)
{
_harmonyType = harmonyType;
_harmony = harmony;
_harmonyId = harmonyId;
_verboseLogging = verboseLogging;
}
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
try
{
var unpatchSelf = _harmonyType.GetMethod("UnpatchSelf",
BindingFlags.Public | BindingFlags.Instance,
null,
Type.EmptyTypes,
null);
if (unpatchSelf != null)
{
unpatchSelf.Invoke(_harmony, null);
}
else
{
var unpatchAll = _harmonyType.GetMethod("UnpatchAll",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { typeof(string) },
null);
if (unpatchAll == null)
throw new MissingMethodException("Harmony.UnpatchSelf()/UnpatchAll(string) 不存在。");
unpatchAll.Invoke(null, new object[] { _harmonyId });
}
if (_verboseLogging)
Debug.Log($"[ShrinkModFramework] 已撤回 Harmony 补丁:{_harmonyId}");
}
catch (Exception ex)
{
Debug.LogError($"[ShrinkModFramework] 撤回 Harmony 补丁失败:{_harmonyId}\n{ex.Message}");
}
finally
{
AppliedHarmonyIds.Remove(_harmonyId);
}
}
}
private static readonly HashSet<string> AppliedHarmonyIds = new(StringComparer.Ordinal);
public static void ApplyPatchesIfNeeded(ShrinkModInfo modInfo, bool enableHarmonyPatching, bool verboseLogging)
{
_ = ApplyPatchesCore(modInfo, enableHarmonyPatching, verboseLogging, reversible: false);
}
public static IDisposable AcquirePatchesIfNeeded(ShrinkModInfo modInfo, bool enableHarmonyPatching,
bool verboseLogging)
{
return ApplyPatchesCore(modInfo, enableHarmonyPatching, verboseLogging, reversible: true);
}
private static IDisposable ApplyPatchesCore(ShrinkModInfo modInfo, bool enableHarmonyPatching,
bool verboseLogging, bool reversible)
{
if (!enableHarmonyPatching || !modInfo.AutoApplyHarmonyPatches)
return EmptyLease.Instance;
var harmonyType = Type.GetType("HarmonyLib.Harmony, 0Harmony");
if (harmonyType == null)
{
if (verboseLogging)
Debug.Log($"[ShrinkModFramework] 未检测到 Harmony,跳过模组 {modInfo.ModId} 的补丁自动应用。");
return EmptyLease.Instance;
}
var harmonyId = $"shrink.mod.{modInfo.ModId}";
if (!AppliedHarmonyIds.Add(harmonyId))
return EmptyLease.Instance;
try
{
var harmony = Activator.CreateInstance(harmonyType, harmonyId);
var patchAll = harmonyType.GetMethod("PatchAll", new[] { typeof(Assembly) });
if (patchAll == null)
throw new MissingMethodException("Harmony.PatchAll(Assembly) 不存在。");
patchAll.Invoke(harmony, new object[] { modInfo.EntryType.Assembly });
if (verboseLogging)
Debug.Log($"[ShrinkModFramework] 已应用 Harmony 补丁:{modInfo.ModId}");
return reversible
? new HarmonyLease(harmonyType, harmony, harmonyId, verboseLogging)
: EmptyLease.Instance;
}
catch (Exception ex)
{
AppliedHarmonyIds.Remove(harmonyId);
throw new InvalidOperationException($"模组 {modInfo.ModId} 应用 Harmony 补丁失败:{ex.Message}", ex);
}
}
internal static void ResetForTesting() => AppliedHarmonyIds.Clear();
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c1bf43d28651dea4fa7d5e5c56361da4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,459 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
namespace ShrinkModFramework
{
public static class ShrinkModLoader
{
private sealed class DiscoveredMod
{
public ShrinkModInfo Info;
public Type EntryType;
}
private static readonly Dictionary<string, ShrinkModHandle> LoadedMods = new(StringComparer.Ordinal);
private static readonly List<ShrinkModHandle> LoadSequence = new();
private static readonly ShrinkModRegistryManager RegistryManager = new();
public static bool IsLoaded { get; private set; }
public static IReadOnlyDictionary<string, ShrinkModHandle> Mods =>
ShrinkModCordisRuntime.IsInitialized ? ShrinkModCordisRuntime.Mods : LoadedMods;
public static event Action<ShrinkModHandle> OnModReady;
public static event Action<IReadOnlyDictionary<string, ShrinkModHandle>> OnAllModsReady;
public static IReadOnlyDictionary<string, ShrinkModHandle> LoadAll(ShrinkModFrameworkSettings settings = null)
{
settings ??= ShrinkModFrameworkSettings.Instance;
if (IsLoaded)
{
Debug.Log("[ShrinkModLoader] 模组已装载,跳过重复装载。");
return Mods;
}
if (settings == null || settings.useContextHost)
return ApplyContextComposition(settings);
var verboseLogging = settings == null || settings.verboseLogging;
try
{
ShrinkModNetworkManager.Configure(settings == null || settings.enableNetworkSync, verboseLogging);
ShrinkExternalModAssemblyLoader.LoadExternalAssemblies(settings, verboseLogging);
var discovered = DiscoverMods(settings);
var ordered = ResolveLoadOrder(discovered, allowExistingLoadedDependencies: true);
foreach (var discoveredMod in ordered)
{
var handle = InstantiateAndTrack(discoveredMod, settings, verboseLogging);
LoadSequence.Add(handle);
}
RunLifecycle(LoadSequence, verboseLogging);
IsLoaded = true;
OnAllModsReady?.Invoke(LoadedMods);
Debug.Log($"[ShrinkModLoader] 模组装载完成,共 {LoadedMods.Count} 个。");
return LoadedMods;
}
catch
{
ResetForTesting();
throw;
}
}
public static IReadOnlyDictionary<string, ShrinkModHandle> LoadNewExternalMods(ShrinkModFrameworkSettings settings = null)
{
settings ??= ShrinkModFrameworkSettings.Instance;
if (settings == null || settings.useContextHost)
return ApplyContextComposition(settings);
if (!IsLoaded)
return LoadAll(settings);
var verboseLogging = settings == null || settings.verboseLogging;
ShrinkExternalModAssemblyLoader.LoadExternalAssemblies(settings, verboseLogging);
var discovered = DiscoverMods(settings)
.Where(mod => !LoadedMods.ContainsKey(mod.Info.ModId))
.ToList();
if (discovered.Count == 0)
return LoadedMods;
ValidateNewMods(discovered);
var ordered = ResolveLoadOrder(discovered, allowExistingLoadedDependencies: true);
var newHandles = new List<ShrinkModHandle>(ordered.Count);
try
{
foreach (var discoveredMod in ordered)
{
var handle = InstantiateAndTrack(discoveredMod, settings, verboseLogging);
LoadSequence.Add(handle);
newHandles.Add(handle);
}
RunLifecycle(newHandles, verboseLogging);
return LoadedMods;
}
catch
{
foreach (var handle in newHandles)
{
LoadedMods.Remove(handle.Info.ModId);
LoadSequence.Remove(handle);
}
throw;
}
}
public static bool TryGetMod(string modId, out ShrinkModHandle handle)
{
if (!string.IsNullOrWhiteSpace(modId))
return Mods.TryGetValue(modId, out handle);
handle = null;
return false;
}
public static ShrinkModRegistry<T> GetOrCreateRegistry<T>(string name)
=> ShrinkModCordisRuntime.IsInitialized
? ShrinkModCordisRuntime.GetOrCreateRegistry<T>(name)
: RegistryManager.GetOrCreateRegistry<T>(name);
internal static void ResetForTesting()
{
ShrinkModCordisRuntime.ResetForTesting();
LoadedMods.Clear();
LoadSequence.Clear();
RegistryManager.Clear();
IsLoaded = false;
OnModReady = null;
OnAllModsReady = null;
ShrinkModNetworkManager.ResetForDomainReload();
ShrinkExternalModAssemblyLoader.ResetForTesting();
ShrinkHarmonyPatchService.ResetForTesting();
}
internal static void ResetForDomainReload()
{
ShrinkModCordisRuntime.ResetForDomainReload();
LoadedMods.Clear();
LoadSequence.Clear();
RegistryManager.Clear();
IsLoaded = false;
OnModReady = null;
OnAllModsReady = null;
ShrinkModNetworkManager.ResetForDomainReload();
ShrinkExternalModAssemblyLoader.ResetForTesting();
ShrinkHarmonyPatchService.ResetForTesting();
}
private static IReadOnlyDictionary<string, ShrinkModHandle> ApplyContextComposition(
ShrinkModFrameworkSettings settings)
{
var previousGenerations = Mods.ToDictionary(
pair => pair.Key,
pair => pair.Value.Generation,
StringComparer.Ordinal);
var result = ShrinkModCordisRuntime.ApplyDiscoveredAsync(settings).GetAwaiter().GetResult();
IsLoaded = true;
foreach (var pair in result.OrderBy(pair => pair.Key, StringComparer.Ordinal))
{
if (!previousGenerations.TryGetValue(pair.Key, out var generation) ||
generation != pair.Value.Generation)
{
OnModReady?.Invoke(pair.Value);
}
}
OnAllModsReady?.Invoke(result);
return result;
}
private static ShrinkModHandle InstantiateAndTrack(DiscoveredMod discoveredMod,
ShrinkModFrameworkSettings settings, bool verboseLogging)
{
var instance = CreateInstance(discoveredMod);
var handle = new ShrinkModHandle(discoveredMod.Info, instance);
LoadedMods.Add(handle.Info.ModId, handle);
ShrinkHarmonyPatchService.ApplyPatchesIfNeeded(
handle.Info,
settings == null || settings.enableHarmonyPatching,
verboseLogging);
return handle;
}
private static List<DiscoveredMod> DiscoverMods(ShrinkModFrameworkSettings settings)
{
var results = new List<DiscoveredMod>();
var prefixes = settings?.assemblyNamePrefixes;
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (!ShouldScanAssembly(assembly, prefixes))
continue;
foreach (var type in GetTypesSafely(assembly))
{
if (type == null || type.IsAbstract || type.IsInterface)
continue;
var modAttribute = type.GetCustomAttribute<ShrinkModAttribute>(false);
if (modAttribute == null)
continue;
if (!typeof(IShrinkMod).IsAssignableFrom(type))
{
Debug.LogWarning($"[ShrinkModLoader] 类型 {type.FullName} 标记了 ShrinkMod,但没有实现 IShrinkMod,已跳过。");
continue;
}
if (type.GetConstructor(Type.EmptyTypes) == null)
throw new InvalidOperationException($"模组 {type.FullName} 缺少无参构造函数,无法实例化。");
var dependencies = type
.GetCustomAttributes<ShrinkModDependencyAttribute>(false)
.Select(attr => new ShrinkModDependency(attr.ModId, attr.MinimumVersion, attr.Optional))
.ToArray();
results.Add(new DiscoveredMod
{
EntryType = type,
Info = new ShrinkModInfo(
modAttribute.ModId,
modAttribute.DisplayName,
modAttribute.Version,
modAttribute.LoadOrder,
type,
modAttribute.AutoApplyHarmonyPatches,
dependencies)
});
}
}
ValidateDiscoveredMods(results);
return results;
}
private static void ValidateDiscoveredMods(List<DiscoveredMod> discovered)
{
var seen = new HashSet<string>(StringComparer.Ordinal);
foreach (var mod in discovered)
{
if (!seen.Add(mod.Info.ModId))
throw new InvalidOperationException($"发现重复模组 ID{mod.Info.ModId}");
}
var map = discovered.ToDictionary(x => x.Info.ModId, x => x, StringComparer.Ordinal);
foreach (var mod in discovered)
{
foreach (var dependency in mod.Info.Dependencies)
{
if (!map.TryGetValue(dependency.ModId, out var target))
{
if (!dependency.Optional && !LoadedMods.ContainsKey(dependency.ModId))
throw new InvalidOperationException($"模组 {mod.Info.ModId} 缺少必选依赖 {dependency.ModId}");
continue;
}
ValidateDependencyVersion(mod.Info, dependency, target.Info.Version);
}
}
}
private static void ValidateNewMods(List<DiscoveredMod> newMods)
{
var seen = new HashSet<string>(LoadedMods.Keys, StringComparer.Ordinal);
foreach (var mod in newMods)
{
if (!seen.Add(mod.Info.ModId))
throw new InvalidOperationException($"发现重复模组 ID{mod.Info.ModId}");
}
var map = newMods.ToDictionary(x => x.Info.ModId, x => x, StringComparer.Ordinal);
foreach (var mod in newMods)
{
foreach (var dependency in mod.Info.Dependencies)
{
if (map.TryGetValue(dependency.ModId, out var newTarget))
{
ValidateDependencyVersion(mod.Info, dependency, newTarget.Info.Version);
continue;
}
if (LoadedMods.TryGetValue(dependency.ModId, out var loadedTarget))
{
ValidateDependencyVersion(mod.Info, dependency, loadedTarget.Info.Version);
continue;
}
if (!dependency.Optional)
throw new InvalidOperationException($"模组 {mod.Info.ModId} 缺少必选依赖 {dependency.ModId}");
}
}
}
private static void ValidateDependencyVersion(ShrinkModInfo owner, ShrinkModDependency dependency, string actualVersion)
{
if (!string.IsNullOrEmpty(dependency.MinimumVersion) &&
ShrinkVersionUtility.Compare(actualVersion, dependency.MinimumVersion) < 0)
{
throw new InvalidOperationException(
$"模组 {owner.ModId} 依赖 {dependency.ModId} >= {dependency.MinimumVersion},但当前只有 {actualVersion}");
}
}
private static List<DiscoveredMod> ResolveLoadOrder(List<DiscoveredMod> discovered, bool allowExistingLoadedDependencies)
{
var map = discovered.ToDictionary(x => x.Info.ModId, x => x, StringComparer.Ordinal);
var result = new List<DiscoveredMod>();
var visiting = new HashSet<string>(StringComparer.Ordinal);
var visited = new HashSet<string>(StringComparer.Ordinal);
foreach (var mod in discovered.OrderBy(x => x.Info.LoadOrder).ThenBy(x => x.Info.ModId, StringComparer.Ordinal))
Visit(mod, map, visiting, visited, result, allowExistingLoadedDependencies);
return result;
}
private static void Visit(DiscoveredMod current, Dictionary<string, DiscoveredMod> map,
HashSet<string> visiting, HashSet<string> visited, List<DiscoveredMod> result,
bool allowExistingLoadedDependencies)
{
if (visited.Contains(current.Info.ModId))
return;
if (!visiting.Add(current.Info.ModId))
throw new InvalidOperationException($"检测到模组循环依赖,涉及 {current.Info.ModId}");
foreach (var dependency in current.Info.Dependencies.OrderBy(x => x.ModId, StringComparer.Ordinal))
{
if (map.TryGetValue(dependency.ModId, out var target))
{
Visit(target, map, visiting, visited, result, allowExistingLoadedDependencies);
continue;
}
if (allowExistingLoadedDependencies && LoadedMods.ContainsKey(dependency.ModId))
continue;
if (!dependency.Optional)
throw new InvalidOperationException($"模组 {current.Info.ModId} 缺少必选依赖 {dependency.ModId}");
}
visiting.Remove(current.Info.ModId);
visited.Add(current.Info.ModId);
result.Add(current);
}
private static IShrinkMod CreateInstance(DiscoveredMod discovered)
{
try
{
return (IShrinkMod)Activator.CreateInstance(discovered.EntryType);
}
catch (Exception ex)
{
throw new InvalidOperationException($"实例化模组 {discovered.Info.ModId} 失败:{ex.Message}", ex);
}
}
private static void RunLifecycle(IEnumerable<ShrinkModHandle> handles, bool verboseLogging)
{
var stagedHandles = handles.ToList();
RunPhase(stagedHandles, ShrinkModState.Constructed, verboseLogging, (mod, context) => mod.OnConstruct(context), "构造");
RunPhase(stagedHandles, ShrinkModState.ContentRegistered, verboseLogging,
(mod, context) => mod.OnRegisterContent(context), "注册内容");
RegisterOptionalRuntimeIntegrations(stagedHandles, verboseLogging);
RunPhase(stagedHandles, ShrinkModState.Initialized, verboseLogging,
(mod, context) => mod.OnInitialize(context), "初始化");
RunPhase(stagedHandles, ShrinkModState.Ready, verboseLogging, (mod, context) => mod.OnReady(context), "完成就绪");
}
private static void RegisterOptionalRuntimeIntegrations(IEnumerable<ShrinkModHandle> handles, bool verboseLogging)
{
foreach (var handle in handles)
ShrinkModOptionalRuntimeIntegration.RegisterModInstance(handle.Instance, handle.Info, verboseLogging);
ShrinkModOptionalRuntimeIntegration.RefreshGlobalBindings(verboseLogging);
}
private static void RunPhase(IEnumerable<ShrinkModHandle> handles, ShrinkModState targetState, bool verboseLogging,
Action<IShrinkMod, ShrinkModContext> callback, string phaseName)
{
foreach (var handle in handles)
{
var context = new ShrinkModContext(handle.Info, RegistryManager, LoadedMods, verboseLogging);
try
{
callback(handle.Instance, context);
handle.State = targetState;
if (verboseLogging)
Debug.Log($"[ShrinkModLoader] {handle.Info.ModId} 已完成阶段:{phaseName}");
if (targetState == ShrinkModState.Ready)
OnModReady?.Invoke(handle);
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"模组 {handle.Info.ModId} 在阶段 '{phaseName}' 执行失败:{ex.Message}", ex);
}
}
}
private static bool ShouldScanAssembly(Assembly assembly, string[] prefixes)
{
var name = assembly.GetName().Name;
if (string.IsNullOrEmpty(name))
return false;
if (name.StartsWith("Unity", StringComparison.Ordinal) ||
name.StartsWith("System", StringComparison.Ordinal) ||
name.StartsWith("mscorlib", StringComparison.Ordinal) ||
name.StartsWith("netstandard", StringComparison.Ordinal))
return false;
if (prefixes == null || prefixes.Length == 0)
return true;
for (var i = 0; i < prefixes.Length; i++)
{
var prefix = prefixes[i];
if (!string.IsNullOrWhiteSpace(prefix) &&
name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
return true;
}
return false;
}
private static IEnumerable<Type> GetTypesSafely(Assembly assembly)
{
try
{
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
return ex.Types.Where(t => t != null);
}
catch
{
return Array.Empty<Type>();
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3f1310a972c773f479f8e35265c63535
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: