using System; using System.Collections.Generic; using System.Linq; using Cysharp.Threading.Tasks; namespace ShrinkModFramework { /// 默认模组组件根;目录变更只提交完整的新组合,失败时由 Host 恢复旧组合。 public static class ShrinkModCordisRuntime { private static ShrinkModContextHost _host; public static bool IsInitialized => _host != null; public static ShrinkModContextHost Host => _host ?? throw new InvalidOperationException( "ShrinkModCordisRuntime has not been initialized."); public static IReadOnlyDictionary Mods => _host?.Mods ?? EmptyMods; private static readonly IReadOnlyDictionary EmptyMods = new Dictionary(); public static async UniTask> ApplyDiscoveredAsync( ShrinkModFrameworkSettings settings = null, IEnumerable authorizedDllPaths = null) { settings ??= ShrinkModFrameworkSettings.Instance; var verboseLogging = settings == null || settings.verboseLogging; if (_host == null) { _host = new ShrinkModContextHost( settings == null || settings.enableHarmonyPatching, verboseLogging); } var revisionState = ShrinkExternalModAssemblyLoader.CaptureState(); ShrinkModNetworkManager.Configure(settings == null || settings.enableNetworkSync, verboseLogging); try { var authorizedPaths = authorizedDllPaths?.ToArray() ?? Array.Empty(); var sources = ShrinkModComponentDiscovery.DiscoverCurrent( settings, verboseLogging, authorizedPaths); await _host.ApplyAsync(sources, () => ShrinkExternalModAssemblyLoader.RestoreState(revisionState)); } catch { // DLL revision discovery is part of the same composition transaction: // a failing replacement must not make the failed assembly current. ShrinkExternalModAssemblyLoader.RestoreState(revisionState); throw; } if (verboseLogging) UnityEngine.Debug.Log($"[ShrinkModFramework] Cordis 模组组合已提交,共 {_host.Mods.Count} 个。"); return _host.Mods; } /// 提交宿主组合;生命周期或发现失败时恢复之前的程序集 revision 和组件。 public static async UniTask> ApplyComposedAsync( ShrinkModFrameworkSettings settings, IReadOnlyList sources) { if (sources == null) throw new ArgumentNullException(nameof(sources)); settings ??= ShrinkModFrameworkSettings.Instance; var verbose = settings != null && settings.verboseLogging; if (_host == null) _host = new ShrinkModContextHost(settings == null || settings.enableHarmonyPatching, verbose); var previous = ShrinkExternalModAssemblyLoader.CaptureState(); try { var states = sources.Where(source => source != null && source.DiscoveryState != null) .Select(source => source.DiscoveryState).Distinct().ToArray(); var composedState = new ShrinkExternalModAssemblyLoader.RevisionState(); foreach (var state in states) { foreach (var pair in state.Current) { if (composedState.Current.TryGetValue(pair.Key, out var other) && other.Revision != pair.Value.Revision) throw new InvalidOperationException($"Conflicting discovered revisions for {pair.Key}."); composedState.Current[pair.Key] = pair.Value; } foreach (var pair in state.Known) composedState.Known[pair.Key] = pair.Value; } ShrinkExternalModAssemblyLoader.RestoreState(composedState); ShrinkModNetworkManager.Configure(settings == null || settings.enableNetworkSync, verbose); await _host.ApplyAsync(sources, () => ShrinkExternalModAssemblyLoader.RestoreState(previous)); } catch { ShrinkExternalModAssemblyLoader.RestoreState(previous); throw; } return _host.Mods; } public static IReadOnlyShrinkModRegistry GetOrCreateRegistry(string name) => Host.GetOrCreateRegistry(name); internal static void ResetForDomainReload() { _host = null; } internal static void ResetForTesting() { if (_host != null) _host.ShutdownAsync().GetAwaiter().GetResult(); _host = null; } } }