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
}