TH1/Unity/Assets/Scripts/TH1_Logic/Editor/HybridCLR/TH1HybridCLRBuildTools.cs
2026-06-11 16:18:42 +08:00

343 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using TH1_Logic.Hotfix;
using UnityEditor;
using UnityEditor.Build;
using UnityEngine;
namespace TH1_Logic.Editor.HybridCLR
{
public static class TH1HybridCLRBuildTools
{
private const string HybridClrInstallerMenu = "HybridCLR/Installer...";
private const string HybridClrGenerateAllMenu = "HybridCLR/Generate/All";
public const long MinimumHotfixDllSize = 64 * 1024;
[MenuItem("Tools/TH1/iOS Migration/HybridCLR/1. Run HybridCLR Installer")]
public static void RunHybridClrInstaller()
{
ExecuteHybridClrMenu(HybridClrInstallerMenu);
}
[MenuItem("Tools/TH1/iOS Migration/HybridCLR/2. Configure TH1 Hotfix Settings")]
public static void ConfigureHotfixSettings()
{
var settingsType = FindType("HybridCLR.Editor.Settings.HybridCLRSettings");
if (settingsType == null)
{
Debug.LogWarning("[TH1.HybridCLR] HybridCLR package is not available yet. Let Unity resolve packages, then run this menu again.");
return;
}
var instance = settingsType.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static)?.GetValue(null);
if (instance == null)
{
Debug.LogWarning("[TH1.HybridCLR] HybridCLRSettings.Instance is unavailable.");
return;
}
SetBoolField(settingsType, instance, "enable", true);
AddStringToArrayField(settingsType, instance, "hotUpdateAssemblies", HotfixManifest.HotfixAssemblyName);
AddStringToArrayField(settingsType, instance, "patchAOTAssemblies", "mscorlib");
AddStringToArrayField(settingsType, instance, "patchAOTAssemblies", "System");
AddStringToArrayField(settingsType, instance, "patchAOTAssemblies", "System.Core");
AddStringToArrayField(settingsType, instance, "patchAOTAssemblies", "MemoryPack");
AddStringToArrayField(settingsType, instance, "patchAOTAssemblies", "System.Runtime.CompilerServices.Unsafe");
settingsType.GetMethod("Save", BindingFlags.Public | BindingFlags.Static)?.Invoke(null, null);
Debug.Log("[TH1.HybridCLR] Configured HybridCLRSettings for TH1.Hotfix.");
}
[MenuItem("Tools/TH1/iOS Migration/HybridCLR/3. Generate All")]
public static void GenerateAll()
{
using (TH1MigrationBuildValidationGate.Suppress())
{
EnsureHybridClrInstalled();
InvokeHybridClrGenerateAll();
EnsureAotMetadataSourceFiles(EditorUserBuildSettings.activeBuildTarget);
}
}
[MenuItem("Tools/TH1/iOS Migration/HybridCLR/4. Compile Hotfix Dll")]
public static void CompileHotfixDll()
{
TryCompileHotfixDll(EditorUserBuildSettings.development);
}
[MenuItem("Tools/TH1/iOS Migration/HybridCLR/5. Copy Hotfix Artifacts To StreamingAssets")]
public static void CopyHotfixArtifactsToStreamingAssets()
{
BuildAndCopyHotfixArtifacts(EditorUserBuildSettings.development);
}
public static bool BuildAndCopyHotfixArtifacts(bool developmentBuild)
{
if (!TryCompileHotfixDll(developmentBuild))
{
Debug.LogError("[TH1.HybridCLR] Compile hotfix dll failed. Skip copying hotfix artifacts.");
return false;
}
var copiedHotfixDll = CopyHotfixDll();
var copiedAotMetadata = CopyAotMetadataDlls();
AssetDatabase.Refresh();
return copiedHotfixDll && copiedAotMetadata;
}
[MenuItem("Tools/TH1/iOS Migration/HybridCLR/6. Test Load StreamingAssets Hotfix In Editor")]
public static void TestLoadHotfixInEditor()
{
var loaded = HotfixBootstrap.InitializeFromStreamingAssets(true);
Debug.Log($"[TH1.HybridCLR] Test load result: {loaded}, assembly: {HotfixBootstrap.LoadedAssemblyFullName}");
}
public static string GetHotfixDllSourcePath(BuildTarget buildTarget)
{
return Path.Combine("HybridCLRData", "HotUpdateDlls", buildTarget.ToString(), HotfixManifest.HotfixAssemblyName + ".dll");
}
public static string GetStreamingHotfixDllPath()
{
return Path.Combine(
Application.streamingAssetsPath,
HotfixManifest.RootFolderName,
HotfixManifest.HotfixDllFolderName,
HotfixManifest.HotfixAssemblyFileName);
}
public static string GetAotMetadataSourceDir(BuildTarget buildTarget)
{
return Path.Combine("HybridCLRData", "AssembliesPostIl2CppStrip", buildTarget.ToString());
}
public static string GetStreamingAotMetadataDir()
{
return Path.Combine(Application.streamingAssetsPath, HotfixManifest.RootFolderName, HotfixManifest.AotMetadataFolderName);
}
public static bool IsUsableHotfixDll(string path, out string message)
{
if (!File.Exists(path))
{
message = $"缺失: {path}";
return false;
}
var file = new FileInfo(path);
if (file.Length < MinimumHotfixDllSize)
{
message = $"文件过小,通常表示还是旧的空壳 DLL{FormatBytes(file.Length)}: {path}";
return false;
}
message = $"OK{FormatBytes(file.Length)}{file.LastWriteTime:yyyy-MM-dd HH:mm:ss}";
return true;
}
private static bool CopyHotfixDll()
{
var source = GetHotfixDllSourcePath(EditorUserBuildSettings.activeBuildTarget);
if (File.Exists(source) && new FileInfo(source).Length < MinimumHotfixDllSize)
{
Debug.LogError($"[TH1.HybridCLR] hotfix dll is too small and looks stale: {source}");
return false;
}
var destination = GetStreamingHotfixDllPath();
return CopyFile(source, destination, "hotfix dll");
}
private static bool CopyAotMetadataDlls()
{
var sourceDir = GetAotMetadataSourceDir(EditorUserBuildSettings.activeBuildTarget);
var destinationDir = GetStreamingAotMetadataDir();
if (!Directory.Exists(sourceDir))
{
Debug.LogError($"[TH1.HybridCLR] AOT metadata source missing: {sourceDir}. Run HybridCLR Installer, then HybridCLR/Generate/All after an IL2CPP target is selected.");
return false;
}
var copiedAll = true;
Directory.CreateDirectory(destinationDir);
foreach (var fileName in HotfixManifest.AotMetadataAssemblyFileNames)
{
var source = Path.Combine(sourceDir, fileName.Replace(".bytes", ""));
var destination = Path.Combine(destinationDir, fileName);
if (!File.Exists(source))
{
copiedAll = false;
Debug.LogError($"[TH1.HybridCLR] AOT metadata dll missing: {source}");
continue;
}
File.Copy(source, destination, true);
Debug.Log($"[TH1.HybridCLR] Copied AOT metadata: {destination}");
}
return copiedAll;
}
private static bool ExecuteHybridClrMenu(string menuPath)
{
if (EditorApplication.ExecuteMenuItem(menuPath))
{
return true;
}
Debug.LogWarning($"[TH1.HybridCLR] Unity menu not found: {menuPath}. Make sure the HybridCLR package has been resolved.");
return false;
}
private static void EnsureHybridClrInstalled()
{
var installerType = FindType("HybridCLR.Editor.Installer.InstallerController");
if (installerType == null)
{
throw new BuildFailedException("[TH1.HybridCLR] InstallerController was not found. Make sure the HybridCLR package has been resolved.");
}
var controller = Activator.CreateInstance(installerType);
var method = installerType.GetMethod("HasInstalledHybridCLR", BindingFlags.Public | BindingFlags.Instance);
if (method == null)
{
throw new BuildFailedException("[TH1.HybridCLR] InstallerController.HasInstalledHybridCLR was not found.");
}
var installed = method.Invoke(controller, null) is bool value && value;
if (!installed)
{
throw new BuildFailedException("[TH1.HybridCLR] HybridCLR is not initialized. Open Tools/TH1/iOS Migration/HybridCLR/1. Run HybridCLR Installer, click Install in the installer window, then run Prepare Player Build again.");
}
}
private static void InvokeHybridClrGenerateAll()
{
var commandType = FindType("HybridCLR.Editor.Commands.PrebuildCommand");
if (commandType == null)
{
throw new BuildFailedException("[TH1.HybridCLR] PrebuildCommand was not found. Make sure the HybridCLR package has been resolved.");
}
var method = commandType.GetMethod("GenerateAll", BindingFlags.Public | BindingFlags.Static);
if (method == null)
{
throw new BuildFailedException("[TH1.HybridCLR] PrebuildCommand.GenerateAll was not found.");
}
try
{
method.Invoke(null, null);
}
catch (TargetInvocationException e)
{
var root = e.InnerException ?? e;
throw new BuildFailedException($"[TH1.HybridCLR] Generate All failed: {root.Message}\n{root}");
}
}
private static void EnsureAotMetadataSourceFiles(BuildTarget buildTarget)
{
var sourceDir = GetAotMetadataSourceDir(buildTarget);
if (!Directory.Exists(sourceDir))
{
throw new BuildFailedException($"[TH1.HybridCLR] Generate All did not create AOT metadata directory: {sourceDir}");
}
var missing = HotfixManifest.AotMetadataAssemblyFileNames
.Select(fileName => fileName.Replace(".bytes", ""))
.Where(fileName => !File.Exists(Path.Combine(sourceDir, fileName)))
.ToArray();
if (missing.Length > 0)
{
throw new BuildFailedException($"[TH1.HybridCLR] Generate All did not create required AOT metadata files under {sourceDir}: {string.Join(", ", missing)}");
}
}
private static bool CopyFile(string source, string destination, string label)
{
if (!File.Exists(source))
{
Debug.LogWarning($"[TH1.HybridCLR] {label} source missing: {source}");
return false;
}
Directory.CreateDirectory(Path.GetDirectoryName(destination));
File.Copy(source, destination, true);
Debug.Log($"[TH1.HybridCLR] Copied {label}: {destination}");
return true;
}
private static bool TryCompileHotfixDll(bool developmentBuild)
{
var compileCommandType = FindType("HybridCLR.Editor.Commands.CompileDllCommand");
if (compileCommandType == null)
{
Debug.LogWarning("[TH1.HybridCLR] CompileDllCommand was not found. Make sure the HybridCLR package has been resolved.");
return false;
}
var compileMethod = compileCommandType.GetMethod(
"CompileDll",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { typeof(BuildTarget), typeof(bool) },
null);
if (compileMethod == null)
{
Debug.LogWarning("[TH1.HybridCLR] CompileDll(BuildTarget, bool) was not found.");
return false;
}
try
{
compileMethod.Invoke(null, new object[] { EditorUserBuildSettings.activeBuildTarget, developmentBuild });
return true;
}
catch (Exception e)
{
var root = e is TargetInvocationException && e.InnerException != null ? e.InnerException : e;
Debug.LogError($"[TH1.HybridCLR] Compile hotfix dll failed:\n{root}");
return false;
}
}
private static string FormatBytes(long length)
{
if (length >= 1024 * 1024) return $"{length / 1024f / 1024f:F2} MB";
if (length >= 1024) return $"{length / 1024f:F1} KB";
return $"{length} B";
}
private static Type FindType(string fullName)
{
return AppDomain.CurrentDomain.GetAssemblies()
.Select(assembly => assembly.GetType(fullName, false))
.FirstOrDefault(type => type != null);
}
private static void SetBoolField(Type type, object instance, string fieldName, bool value)
{
type.GetField(fieldName, BindingFlags.Public | BindingFlags.Instance)?.SetValue(instance, value);
}
private static void AddStringToArrayField(Type type, object instance, string fieldName, string value)
{
var field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.Instance);
if (field == null) return;
var values = ((string[])field.GetValue(instance) ?? Array.Empty<string>()).ToList();
if (!values.Contains(value))
{
values.Add(value);
field.SetValue(instance, values.ToArray());
}
}
}
}