using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using HybridCLR; using UnityEngine; namespace TH1_Logic.Hotfix { public static class HotfixBootstrap { private static bool _initialized; private static bool _aotMetadataLoaded; private static readonly HashSet InvokedEntryAssemblies = new HashSet(); public static bool IsLoaded { get; private set; } public static Assembly LoadedAssembly { get; private set; } public static string LoadedAssemblyFullName { get; private set; } public static bool InitializeFromStreamingAssets(bool requireHotfix, bool invokeEntry = true) { try { var root = Path.Combine(Application.streamingAssetsPath, HotfixManifest.RootFolderName); LoadAotMetadata(root); if (_initialized) return IsLoaded; var hotfixPath = Path.Combine(root, HotfixManifest.HotfixDllFolderName, HotfixManifest.HotfixAssemblyFileName); if (!File.Exists(hotfixPath)) { if (requireHotfix) { Debug.LogWarning($"[TH1.Hotfix] Hotfix dll missing: {hotfixPath}"); } return false; } var assembly = Assembly.Load(File.ReadAllBytes(hotfixPath)); LoadedAssembly = assembly; LoadedAssemblyFullName = assembly.FullName; RefreshRuntimeTypeCaches(); if (invokeEntry) { InvokeHotfixEntry(assembly); } _initialized = true; IsLoaded = true; Debug.Log($"[TH1.Hotfix] Loaded {LoadedAssemblyFullName}"); return true; } catch (Exception e) { Debug.LogError($"[TH1.Hotfix] Initialize failed: {e}"); return false; } } public static void EnsureAotMetadataLoaded() { try { var root = Path.Combine(Application.streamingAssetsPath, HotfixManifest.RootFolderName); LoadAotMetadata(root); } catch (Exception e) { Debug.LogWarning($"[TH1.Hotfix] Ensure AOT metadata failed: {e.Message}"); } } public static void InvokeHotfixEntry(Assembly assembly) { if (assembly == null) return; var assemblyKey = assembly.FullName ?? assembly.GetName().Name; if (!InvokedEntryAssemblies.Add(assemblyKey)) return; InvokeHotfixEntryInternal(assembly); } private static void LoadAotMetadata(string root) { if (_aotMetadataLoaded) return; var aotDir = Path.Combine(root, HotfixManifest.AotMetadataFolderName); var allLoaded = true; foreach (var fileName in HotfixManifest.AotMetadataAssemblyFileNames) { var path = Path.Combine(aotDir, fileName); if (!File.Exists(path)) { allLoaded = false; Debug.LogWarning($"[TH1.Hotfix] AOT metadata missing: {path}"); continue; } var result = RuntimeApi.LoadMetadataForAOTAssembly(File.ReadAllBytes(path), HomologousImageMode.SuperSet); Debug.Log($"[TH1.Hotfix] Load AOT metadata {fileName}: {result}"); } _aotMetadataLoaded = allLoaded; } private static void InvokeHotfixEntryInternal(Assembly assembly) { var entryType = assembly.GetType(HotfixManifest.EntryTypeName); if (entryType == null) { Debug.LogWarning($"[TH1.Hotfix] Entry type not found: {HotfixManifest.EntryTypeName}"); return; } var method = entryType.GetMethod(HotfixManifest.EntryMethodName, BindingFlags.Public | BindingFlags.Static); if (method == null) { Debug.LogWarning($"[TH1.Hotfix] Entry method not found: {HotfixManifest.EntryMethodName}"); return; } method.Invoke(null, null); } private static Type FindType(string fullName) { return AppDomain.CurrentDomain.GetAssemblies() .Select(assembly => assembly.GetType(fullName, false)) .FirstOrDefault(type => type != null); } public static void RefreshRuntimeTypeCaches() { InvokeStaticNoArg("ParadoxNotion.ReflectionTools", "FlushMem"); InvokeStaticNoArg("ParadoxNotion.Serialization.JSONSerializer", "FlushMem"); } private static void InvokeStaticNoArg(string typeName, string methodName) { var type = FindType(typeName); var method = type?.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static); method?.Invoke(null, null); } } }