TH1/Unity/Assets/Scripts/TH1_AOT/HotfixBootstrap.cs
2026-06-11 00:34:50 +08:00

168 lines
5.8 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEngine;
namespace TH1_Logic.Hotfix
{
public static class HotfixBootstrap
{
private static bool _initialized;
private static bool _aotMetadataLoaded;
private static readonly HashSet<string> InvokedEntryAssemblies = new HashSet<string>();
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 runtimeApiType = FindType("HybridCLR.RuntimeApi");
var modeType = FindType("HybridCLR.HomologousImageMode");
if (runtimeApiType == null || modeType == null)
{
return;
}
var method = runtimeApiType.GetMethod(
"LoadMetadataForAOTAssembly",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { typeof(byte[]), modeType },
null);
if (method == null)
{
Debug.LogWarning("[TH1.Hotfix] HybridCLR RuntimeApi.LoadMetadataForAOTAssembly not found.");
return;
}
var mode = Enum.Parse(modeType, "SuperSet");
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 = method.Invoke(null, new object[] { File.ReadAllBytes(path), mode });
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);
}
}
}