123 lines
4.9 KiB
C#
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>();
|
|
}
|
|
}
|
|
}
|
|
}
|