Files
Workspace/Assets/Modules/ShrinkModFramework/Runtime/Context/ShrinkModComponentDiscovery.cs
T

123 lines
4.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
namespace ShrinkModFramework
{
/// <summary>
/// 把工程内程序集和外部 DLL 的当前 revision 投影为延迟组件源。
/// 同一路径的旧外部程序集无法从 Mono AppDomain 卸载,但不会再进入候选集合。
/// </summary>
internal static class ShrinkModComponentDiscovery
{
public static IReadOnlyList<ShrinkModComponentSource> Discover(ShrinkModFrameworkSettings settings,
bool verboseLogging)
{
ShrinkExternalModAssemblyLoader.ScanExternalAssemblyRevisions(settings, verboseLogging);
var results = new List<ShrinkModComponentSource>();
var prefixes = settings?.assemblyNamePrefixes;
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (ShrinkExternalModAssemblyLoader.IsExternalAssembly(assembly) &&
!ShrinkExternalModAssemblyLoader.TryGetCurrentRevision(assembly, out _))
{
continue;
}
if (!ShouldScanAssembly(assembly, prefixes))
continue;
var assemblyRevision = GetAssemblyRevision(assembly);
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(
$"[ShrinkModFramework] 类型 {type.FullName} 标记了 ShrinkMod,但没有实现 IShrinkMod,已跳过。");
continue;
}
if (type.GetConstructor(Type.EmptyTypes) == null)
throw new InvalidOperationException($"模组 {type.FullName} 缺少无参构造函数,无法实例化。");
var dependencies = type
.GetCustomAttributes<ShrinkModDependencyAttribute>(false)
.Select(attribute => new ShrinkModDependency(
attribute.ModId, attribute.MinimumVersion, attribute.Optional))
.ToArray();
var info = new ShrinkModInfo(
modAttribute.ModId,
modAttribute.DisplayName,
modAttribute.Version,
modAttribute.LoadOrder,
type,
modAttribute.AutoApplyHarmonyPatches,
dependencies);
var revision = $"{assemblyRevision}:{type.FullName}";
results.Add(new ShrinkModComponentSource(info, revision,
() => (IShrinkMod)Activator.CreateInstance(type)));
}
}
return results;
}
private static string GetAssemblyRevision(Assembly assembly)
{
if (ShrinkExternalModAssemblyLoader.TryGetCurrentRevision(assembly, out var externalRevision))
return externalRevision;
try
{
return assembly.ManifestModule.ModuleVersionId.ToString("N");
}
catch
{
return assembly.FullName ?? assembly.GetName().Name ?? "unknown";
}
}
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;
return prefixes.Any(prefix => !string.IsNullOrWhiteSpace(prefix) &&
name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
}
private static IEnumerable<Type> GetTypesSafely(Assembly assembly)
{
try
{
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException exception)
{
return exception.Types.Where(type => type != null);
}
catch
{
return Array.Empty<Type>();
}
}
}
}