using HotUpdate.UI; using Cysharp.Threading.Tasks; using PhxhSDK.AOT; using PhxhSDK.AOT.SimpleLoader; using PhxhSDK.AOT.VersionUpdate; using System; #if UNITY_IOS && !UNITY_EDITOR using System.Runtime.InteropServices; #endif using PhxhSDK.AOT.PreConfig; using UnityEngine; using UnityEngine.SceneManagement; using Assets.PhxhSDK.AOT.BI; #if USE_OBFUZ using Obfuz.EncryptionVM; using Obfuz; #endif using static ProgressBarData; #if PLATFORM_ANDROID || UNITY_EDITOR using UnityEngine.Android; #endif public class GameLauncher : MonoBehaviour { #if USE_OBFUZ private static bool _isSetUpStaticSecret = false; /// /// 初始化EncryptionService后被混淆的代码才能正常运行, /// 因此尽可能地早地初始化它。 /// [ObfuzIgnore] [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] private static void _SetUpStaticSecretKey() { if (_isSetUpStaticSecret) { return; } _isSetUpStaticSecret = true; Debug.Log("[Obfz] SetUpStaticSecret begin"); var loadedAsset = Resources.Load("us5af47s54fsa1z90sf"); if (loadedAsset == null) { Debug.LogError("[Obfz] SetUpStaticSecret failed: could not load static secret asset"); return; } EncryptionService.Encryptor = new GeneratedEncryptionVirtualMachine(loadedAsset.bytes); Debug.Log("[Obfz] SetUpStaticSecret end"); } private static void _SetUpDynamicSecret(byte[] bytes) { Debug.Log("[Obfz] SetUpDynamicSecret begin"); EncryptionService.Encryptor = new GeneratedEncryptionVirtualMachine(bytes); Debug.Log("[Obfz] SetUpDynamicSecret end"); } #endif private const string START_SCENE_NAME = "Assets/Scenes/StartScene.unity"; #if USE_OBFUZ private const string DYNAMIC_SECRET_PATH = "Assets/Config/Obfuz/defaultDynamicSecretKey.bytes"; #endif //最大能容忍的下载卡住时长,一旦超过则重新开始下载 // private const float DOWNLOAD_STUCK_TIME_MAX = 10; #region 进度条优化 /// /// 进度条 /// private ProgressBarData progressBarData; /// /// 假进度 /// private float fakeProgess; /// /// 真进度 /// private float realProgess; /// /// 假进度计时 /// private float fakeProgessTimer; /// /// 假进度下一次增长的值 /// private float fakeNextAddValue; #endregion /// /// 最终进度 /// public float FinalProgess { get { return Mathf.Max(realProgess, fakeProgess); } } #if USE_HCLR private HybridCLRHelper _hybridCLRHelper; #endif private VersionUpdateHandle _versionUpdateHandle; private ISimpleLoader _simpleLoader; private IVersionUpdateUI versionUpdateUI; public bool enableHotUpdate = true; private void Awake() { #if USE_OBFUZ _SetUpStaticSecretKey(); #endif Screen.sleepTimeout = SleepTimeout.NeverSleep; // 保持屏幕常亮 fakeProgess = 0f; realProgess = 0f; fakeProgessTimer = 0f; progressBarData = Resources.Load("ProgressBarData"); } private void Start() { #if SDK_ADJUST var YourAppToken = "shw96lfwisqo"; // 替换为你的Adjust App Token AdjustInitTool.Instance.InitAdjustSDK(YourAppToken); #endif #if SDK_FIREBASE Firebase.Analytics.FirebaseAnalytics.LogEvent("First_open"); #endif versionUpdateUI = new TempUIHandle(); versionUpdateUI.HideUpdateInfo(); bool agreement = PlayerPrefs.GetInt("PrivacyAgreement", 0) == 1; //BIToolinAOT.Instance.TrackEventInAOT("app_open", null); if (!agreement) // 弹隐私弹窗 { TempUpdateUI.Instance.PrivacyAgreeCallback = () => { TempUpdateUI.Instance.m_ProgressObj.SetActive(true); Launch(); }; TempUpdateUI.Instance.m_PrivacyDialogObj.SetActive(true); } else { TempUpdateUI.Instance.m_ProgressObj.SetActive(true); Launch(); } } private void Update() { if (null == versionUpdateUI) return; versionUpdateUI.UpdateProgress(FinalProgess); if (realProgess >= 1f) return; if (null == _versionUpdateHandle) return; if (!_versionUpdateHandle.IsUpdating) return; if (null == progressBarData) return; if (0 >= progressBarData.ListProgressDatas.Count) // 没有配置数量 return; ProgressData lastProgressData = progressBarData.ListProgressDatas[^1]; if (fakeProgess >= lastProgressData.Target) // 进度满了 return; if (fakeProgessTimer <= 0f) { foreach (ProgressData progressData in progressBarData.ListProgressDatas) { if (fakeProgess >= progressData.Target) continue; fakeProgessTimer += UnityEngine.Random.Range(progressData.MinInterval, progressData.MaxInterval); fakeNextAddValue = UnityEngine.Random.Range(progressData.MinAddValue, progressData.MaxAddValue); break; } } fakeProgessTimer -= Time.deltaTime; if (fakeProgessTimer <= 0f) { fakeProgess += fakeNextAddValue; if (fakeProgess > lastProgressData.Target) fakeProgess = lastProgressData.Target; } } private async void Launch() { #if UNITY_EDITOR enableHotUpdate = false; #endif #if PLATFORM_ANDROID && !SDK_BILIBILI //B站在隐私协议之后才申请权限 if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite)) { Debug.Log("申请权限: " + Permission.ExternalStorageWrite); Permission.RequestUserPermission(Permission.ExternalStorageWrite); } #endif realProgess = 0f; versionUpdateUI.UpdateProgressText(LocalizeMono.Instance.GetLocalizeText("NetworkConnection")); // 尝试网络连接中... _versionUpdateHandle = new VersionUpdateHandle(versionUpdateUI, SetRealProgress); VersionUpdateInfo versionUpdateInfo = _versionUpdateHandle.GetVersionInfo(); PackDataInst.CreateInst(); UpdateLocalInfo localInfo = PackDataInst.Inst.localInfo; string acquiring = LocalizeMono.Instance.GetLocalizeText("Acquiring"); // 获取中... string displayLocalVersion = VersionDisplayFormatter.Format(localInfo.localFullVersion, "[LTRes]GameLauncher"); Debug.Log("[LTRes]GameLauncher - ShowVersion关键日志 stage=LaunchInit, fullOldVersion=" + localInfo.localFullVersion + ", fullNewVersion=" + acquiring + ", displayOldVersion=" + displayLocalVersion + ", displayNewVersion=" + acquiring + ", serverName=" + acquiring); versionUpdateUI.ShowVersion(Application.version, localInfo.workSpace, displayLocalVersion, acquiring, acquiring); BIToolinAOT.Instance.Init();//TalkingData初始化 try { DeviceIdHandle.Instance.Initialize();//设备ID获取初始化 #if UNITY_IOS && !UNITY_EDITOR Debug.Log("开始获取iOS标识:"); DeviceIdHandle.Instance.IDFV = FetchIDFV(); FetchIDFA(); Debug.Log("结束获取iOS标识:"); #endif } catch (Exception e) { Debug.LogError($"DeviceIdHandle Initialize Error:{e}"); } //PlayerPrefs.SetString("BI_Platform", TalkingDataInitTool.channel); #region 更新AppConfig bool downloadResult; do { try { versionUpdateUI.UpdateProgressText(LocalizeMono.Instance.GetLocalizeText("RequestConfiguration")); // 开始请求APPConfig BIToolinAOT.Instance.TrackEventInAOT("app_config_request", null); downloadResult = await PreConfig.instance.DownloadConfig(); versionUpdateUI.UpdateProgressText(LocalizeMono.Instance.GetLocalizeText("RequestConfigurationComplete")); // 请求APPConfig结束 string displayLocalVersionAfterConfig = VersionDisplayFormatter.Format(localInfo.localFullVersion, "[LTRes]GameLauncher"); Debug.Log("[LTRes]GameLauncher - ShowVersion关键日志 stage=ConfigReady, fullOldVersion=" + localInfo.localFullVersion + ", fullNewVersion=" + acquiring + ", displayOldVersion=" + displayLocalVersionAfterConfig + ", displayNewVersion=" + acquiring + ", serverName=" + PreConfig.instance.ServerName); versionUpdateUI.ShowVersion(Application.version, localInfo.workSpace, displayLocalVersionAfterConfig, acquiring, PreConfig.instance.ServerName); BIToolinAOT.Instance.TrackEventInAOT("app_config_response", new System.Collections.Generic.Dictionary(){ {"result", downloadResult? 0 : 1 } }); } catch (Exception) { downloadResult = false; } } while (!downloadResult && await versionUpdateUI.ShowDialog(LocalizeMono.Instance.GetLocalizeText("Tip"), LocalizeMono.Instance.GetLocalizeText("DownloadConfigErrorRetry"))); // 下载配置文件失败,是否重试? if (!downloadResult) TempUpdateUI.Instance.OnClickCancel(); #endregion if (!PreConfig.instance.isConfigValid) { // 配置错误,没有读取到ResUrl Debug.LogError("配置文件错误"); await versionUpdateUI.ShowDialog(LocalizeMono.Instance.GetLocalizeText("Tip"), LocalizeMono.Instance.GetLocalizeText("ConfigError")); // 配置文件错误 TempUpdateUI.Instance.OnClickCancel(); return; } await YooAssetHelper.Init(); _simpleLoader = new SimpleLoader_YooAsset(); Debug.Log($"Launch Game! enableHotUpdate:{enableHotUpdate}"); bool result = await _versionUpdateHandle.CheckUpdate();//这里会打点资源检测更新事件 if (!result) { Debug.LogError("更新失败!,无法进入游戏"); return; } if (enableHotUpdate) { #if USE_HCLR #if USE_OBFUZ byte[] dynamicSecret = await _simpleLoader.LoadDllBytes(DYNAMIC_SECRET_PATH); _SetUpDynamicSecret(dynamicSecret); await _simpleLoader.UnloadDll(DYNAMIC_SECRET_PATH); #endif Debug.Log("需要热更新,开始加载程序集"); _hybridCLRHelper = new HybridCLRHelper(); _hybridCLRHelper.SetDllList(AOTGenericReferences.PatchedAOTAssemblyList); _hybridCLRHelper.RegisterLoader(_simpleLoader); await _hybridCLRHelper.LoadAssemblies(); Debug.Log("加载程序集结束!"); #endif } await _simpleLoader.WarmUpShader(); await EnterGame(); } private async UniTask EnterGame() { Debug.Log("开始加载游戏场景"); SimulationProgress(versionUpdateUI.GetCurProgress()); try { Scene scene = await _simpleLoader.LoadSceneAsync(START_SCENE_NAME, LoadSceneMode.Additive); foreach (GameObject go in scene.GetRootGameObjects()) { if ("EventSystem".Equals(go.name)) { Destroy(go); break; } } } catch (Exception e) { Debug.LogError(e); } finally { await LoadOver(); Debug.Log("加载游戏场景结束"); } } private async void SimulationProgress(float startProgress) { versionUpdateUI.UpdateProgressText(LocalizeMono.Instance.GetLocalizeText("InitGaming")); // 初始化游戏... realProgess = startProgress; float step = (1f - startProgress) * 0.02f; while (startProgress + step <= 0.99f) { await UniTask.Delay(20); startProgress += step; realProgess = startProgress; } } private async UniTask LoadOver() { while (true) { await UniTask.Yield(); // Debug.Log($"校验是否加载完:{IVersionUpdateUI.IsLoadOver}"); if (StartSceneLoadingManager.IsLoadOver) { realProgess = 1f; versionUpdateUI.UpdateProgressText(LocalizeMono.Instance.GetLocalizeText("Complete")); // 完成 await UniTask.Delay(200); versionUpdateUI.CloseAll(); await SceneManager.UnloadSceneAsync(0); return; } } } /// /// 设置进度 /// /// private void SetRealProgress(float progress) { realProgess = progress; } #region iOS_DeviceHelper #if UNITY_IOS && !UNITY_EDITOR [DllImport("__Internal")] private static extern void RequestIDFA(); [DllImport("__Internal")] private static extern string GetIDFV(); #endif public Action OnIDFAReceived; // 调用这个方法请求 IDFA(iOS 14+ 会弹授权) // 调用 FetchIDFA() → 弹授权框 → 回调给 Unity public void FetchIDFA(Action callback = null) { #if UNITY_IOS && !UNITY_EDITOR OnIDFAReceived = callback; RequestIDFA(); // 调用 Xcode 端方法 #else callback?.Invoke("Editor_IDFA"); #endif } // 调用这个方法直接获取 IDFV public string FetchIDFV() { #if UNITY_IOS && !UNITY_EDITOR var idfv = GetIDFV(); Debug.Log("ios 获取到 IDFV: " + idfv); return idfv; #else return "Editor_IDFV"; #endif } // Xcode 会通过 UnitySendMessage 回调这个方法 public void OnIDFAResult(string idfa) { Debug.Log("ios 获取到 IDFA: " + idfa); DeviceIdHandle.Instance.IDFA = idfa; OnIDFAReceived?.Invoke(idfa); } #endregion }