using HybridCLR; using Sirenix.Utilities; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.ResourceProviders; using UnityEngine.SceneManagement; public class GameLauncher : MonoBehaviour { private class MethodExecutionInfo { public MethodInfo method; public int sequnence; public MethodExecutionInfo(MethodInfo method, int sequnence) { this.method = method; this.sequnence = sequnence; } } const string START_SCENE_NAME = "Assets/Scenes/StartScene.unity"; const string META_DATA_DLL_PATH = "Assets/HotUpdateDlls/MetaDataDll/"; const string HOT_UPDATE_DLL_PATH = "Assets/HotUpdateDlls/HotUpdateDll/"; const string PHXH_DLL_NAME = "Phxh.dll"; const string FRAMEWORK_DLL_NAME = "FrameWork.dll"; const string GAMEPLAY_DLL_NAME = "GamePlay.dll"; const string FRAMEWORK_ASSEMBLY_NAME = "FrameWork"; const string AOT_GENERIC_REFERENCES_TYPE = "AOTGenericReferences"; const string PATCHED_AOT_ASSEMBLY_LIST = "PatchedAOTAssemblyList"; private bool _hasNewVersion; private Coroutine _launchCoroutine; private byte[] _dllBytes; private Dictionary _allHotUpdateAssemblies = new(); //version update private AsyncOperationHandle _downloadOP; private UI_VersionUpdate _versionUpdateUI; private Type _versionUpdaterType; private List _updateKeys = new(); private readonly List _phxhDependencyDlls = new List() { }; private readonly List _frameWorkDependencyDlls = new List() { //不要随意改顺序! }; private readonly List _gamePlayDependencyDlls = new List() { //不要随意改顺序! "BehaviorDesigner.Runtime.dll", "BestHTTP.dll", "DOTweenCSharp.dll", }; //所有使用了RuntimeInitializeOnLoadMethod attribute的程序集 private readonly List _hasRuntimeInitializeOnLoadMethodAssemblies = new List() { "BehaviorDesigner.Runtime", "BestHTTP", "GamePlay", }; public bool enableHotUpdate = true; private void Start() { _launchCoroutine = StartCoroutine(Launch()); } private void OnDestroy() { StopCoroutine(_launchCoroutine); _launchCoroutine = null; } private IEnumerator Launch() { #if UNITY_EDITOR enableHotUpdate = false; #endif Debug.Log($"Launch Game! enableHotUpdate:{enableHotUpdate}"); yield return VersionCheck(); if (_hasNewVersion) yield return VersionUpdate(); if (enableHotUpdate) yield return LoadAssemblies(); yield return EnterGame(); } private IEnumerator VersionCheck() { //todo 如果包体版本不一致则提示用户去app store重新下载 var checkUpdateOP = Addressables.CheckForCatalogUpdates(false); yield return checkUpdateOP; if (checkUpdateOP.Status == AsyncOperationStatus.Succeeded) { var catalogUpdateIDs = checkUpdateOP.Result; if (catalogUpdateIDs != null && catalogUpdateIDs.Count > 0) { _hasNewVersion = true; var updateCatalogOP = Addressables.UpdateCatalogs(catalogUpdateIDs, false); yield return updateCatalogOP; if (updateCatalogOP.Status == AsyncOperationStatus.Succeeded) { foreach (var resourceLocator in updateCatalogOP.Result) { _updateKeys.AddRange(resourceLocator.Keys); } } else { Debug.LogError($"Update catalog failed!exception:{updateCatalogOP.OperationException.Message}"); } Addressables.Release(updateCatalogOP); } } else { Debug.LogError($"CheckUpdate failed!exception:{checkUpdateOP.OperationException.Message}"); } Addressables.Release(checkUpdateOP); yield return ReloadAddressableCatalog(); Debug.Log($"版本检查结束,是否有新版本:{_hasNewVersion}"); } private IEnumerator VersionUpdate() { yield return OpenVersionUpdateUI(); yield return Download(); Debug.Log($"版本更新结束!"); } private IEnumerator LoadAssemblies() { yield return LoadPhxhDependencyAssemblies(); yield return LoadPhxhAssembies(); yield return LoadFrameworkDependencyAssemblies(); yield return LoadFrameWorkAssemblies(); yield return LoadMetadataForAOTAssemblies(); yield return LoadGamePlayDependencyAssemblies(); yield return LoadGamePlayAssemblies(); yield return ReloadAddressableCatalog(); ExecuteRuntimeInitializeOnLoadMethodAttribute(); Debug.Log("加载程序集结束!"); yield return null; } //补充元数据 private IEnumerator LoadMetadataForAOTAssemblies() { var frameworkAssembly = GetAssembly(FRAMEWORK_ASSEMBLY_NAME); var aotGenericRefType = frameworkAssembly.GetType(AOT_GENERIC_REFERENCES_TYPE); if (aotGenericRefType == null) { Debug.LogError($"LoadMetadataForAOTAssemblies failed, can't find type:{AOT_GENERIC_REFERENCES_TYPE}"); yield break; } var listFieldInfo = aotGenericRefType.GetField(PATCHED_AOT_ASSEMBLY_LIST, BindingFlags.Public | BindingFlags.Static); if (listFieldInfo == null) { Debug.LogError($"LoadMetadataForAOTAssemblies failed, can't find field:{PATCHED_AOT_ASSEMBLY_LIST}"); yield break; } var list = listFieldInfo.GetValue(null); if (list == null) { Debug.LogError($"LoadMetadataForAOTAssemblies failed, can't find list object"); yield break; } List aotAssemblies = list as List; foreach (var aotDllName in aotAssemblies) { var path = $"{META_DATA_DLL_PATH}{aotDllName}.bytes"; yield return ReadDllBytes(path); if (_dllBytes != null) { var err = HybridCLR.RuntimeApi.LoadMetadataForAOTAssembly(_dllBytes, HomologousImageMode.SuperSet); Debug.Log($"LoadMetadataForAOTAssembly:{aotDllName}. ret:{err}"); } } Debug.Log("补充元数据结束!"); } private IEnumerator LoadPhxhDependencyAssemblies() { foreach (var dllName in _phxhDependencyDlls) { yield return LoadSingleHotUpdateAssembly(dllName); } Debug.Log("加载Phxh依赖程序集结束!"); } private IEnumerator LoadPhxhAssembies() { yield return LoadSingleHotUpdateAssembly(PHXH_DLL_NAME); Debug.Log("加载Phxh程序集结束!"); } //加载FrameWork依赖的第三方热更序集 private IEnumerator LoadFrameworkDependencyAssemblies() { foreach (var dllName in _frameWorkDependencyDlls) { yield return LoadSingleHotUpdateAssembly(dllName); } Debug.Log("加载Framework依赖程序集结束!"); } //加载Framework程序集 private IEnumerator LoadFrameWorkAssemblies() { yield return LoadSingleHotUpdateAssembly(FRAMEWORK_DLL_NAME); Debug.Log("加载Framework程序集结束!"); } //通过加载场景打开版本更新界面 private IEnumerator OpenVersionUpdateUI() { _versionUpdateUI = FindObjectOfType(); if (_versionUpdateUI == null) { Debug.LogError("cant find UI_VersionUpdate"); } return null; } private IEnumerator CloseVersionUpdateUI() { return null; } //下载资源 private IEnumerator Download() { var downloadSizeOp = Addressables.GetDownloadSizeAsync((IEnumerable)_updateKeys); yield return downloadSizeOp; Debug.Log($"download size:{downloadSizeOp.Result / (1024f * 1024f)}MB"); if (downloadSizeOp.Result > 0) { _downloadOP = Addressables.DownloadDependenciesAsync((IEnumerable)_updateKeys, Addressables.MergeMode.Union, false); _versionUpdateUI.SetParams(_downloadOP); yield return _downloadOP; if (_downloadOP.Status != AsyncOperationStatus.Succeeded) { Debug.LogError( $"Download Update Content Failed! exception:{_downloadOP.OperationException.Message} \r\n {_downloadOP.OperationException.StackTrace}"); } Addressables.Release(_downloadOP); } Addressables.Release(downloadSizeOp); } //加载GamePlay依赖的第三方程序集 private IEnumerator LoadGamePlayDependencyAssemblies() { foreach (var dllName in _gamePlayDependencyDlls) { yield return LoadSingleHotUpdateAssembly(dllName); } Debug.Log("加载GamePlay依赖程序集结束!"); } //加载GamePlay程序集 private IEnumerator LoadGamePlayAssemblies() { yield return LoadSingleHotUpdateAssembly(GAMEPLAY_DLL_NAME); Debug.Log("加载GamePlay程序集结束!"); } /// /// Addressable初始化时热更新代码所对应的ScriptableObject的类型会被识别为System.Object,需要在热更新dll加载完后重新加载一下Addressable的Catalog /// https://hybridclr.doc.code-philosophy.com/docs/help/commonerrors /// /// private IEnumerator ReloadAddressableCatalog() { var op = Addressables.LoadContentCatalogAsync($"{Addressables.RuntimePath}/catalog.json"); yield return op; if (op.Status != AsyncOperationStatus.Succeeded) { Debug.LogError( $"load content catalog failed, exception:{op.OperationException.Message} \r\n {op.OperationException.StackTrace}"); } } /// /// 反射执行被RuntimeInitializeOnLoadMethod attribute标注的函数,HybirdCLR不支持该attribute /// private void ExecuteRuntimeInitializeOnLoadMethodAttribute() { var runtimeInitializedAttribute = typeof(RuntimeInitializeOnLoadMethodAttribute); List runtimeMethods = new(); foreach (var assemblyName in _hasRuntimeInitializeOnLoadMethodAssemblies) { var assembly = GetAssembly(assemblyName); if (assembly == null) { Debug.LogError($"找不到使用过RuntimeInitializeOnLoadMethod的assembly,name:{assemblyName}"); continue; } foreach (var type in assembly.GetTypes()) { type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).ForEach(method => { if (!method.IsStatic) return; var attri = method.GetCustomAttribute(runtimeInitializedAttribute); if (attri == null) return; var sequence = (int)(attri as RuntimeInitializeOnLoadMethodAttribute).loadType; var methodInfo = new MethodExecutionInfo(method, sequence); runtimeMethods.Add(methodInfo); }); } } runtimeMethods.Sort((a, b) => b.sequnence.CompareTo(a.sequnence)); foreach (var methodInfo in runtimeMethods) { Debug.Log($"call method methodName:{methodInfo.method.Name} sequnence:{methodInfo.sequnence}"); methodInfo.method.Invoke(null, null); } Debug.Log("调用RuntimeInitializeOnLoadMethod结束!"); } private IEnumerator ReadDllBytes(string path) { var op = Addressables.LoadAssetAsync(path); yield return op; if (op.Status == AsyncOperationStatus.Succeeded) { _dllBytes = op.Result.bytes; } else { Debug.LogError( $"cant load dll,exception:{op.OperationException.Message}\r\n{op.OperationException.StackTrace}"); _dllBytes = null; yield break; } } private IEnumerator LoadSingleHotUpdateAssembly(string dllName) { var path = $"{HOT_UPDATE_DLL_PATH}{dllName}.bytes"; yield return ReadDllBytes(path); if (_dllBytes != null) { var assembly = Assembly.Load(_dllBytes); _allHotUpdateAssemblies.Add(assembly.FullName, assembly); Debug.Log($"Load Assembly success,assembly Name:{assembly.FullName}"); } yield return null; } private IEnumerator EnterGame() { var op = Addressables.LoadSceneAsync(START_SCENE_NAME, LoadSceneMode.Single); yield return op; if (op.Status != AsyncOperationStatus.Succeeded) { Debug.LogError( $"load scene failed,exception:{op.OperationException.Message} \r\n {op.OperationException.StackTrace}"); yield break; } if (_hasNewVersion) yield return CloseVersionUpdateUI(); } private Assembly GetAssembly(string assemblyName) { IEnumerable allAssemblies = enableHotUpdate ? _allHotUpdateAssemblies.Values : AppDomain.CurrentDomain.GetAssemblies(); return allAssemblies.First(assembly => assembly.FullName.Contains(assemblyName)); } }