239 lines
8.0 KiB
C#
239 lines
8.0 KiB
C#
using System;
|
|
using System.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.AddressableAssets;
|
|
using UnityEngine.AddressableAssets.ResourceLocators;
|
|
using UnityEngine.Networking;
|
|
using UnityEngine.ResourceManagement.AsyncOperations;
|
|
using UnityEngine.UI;
|
|
using TMPro;
|
|
|
|
public class BootstrapUpdater : MonoBehaviour
|
|
{
|
|
public const string ContentReadyPrefsKey = "Ota.ContentReady";
|
|
|
|
[Header("Host")]
|
|
[Tooltip("Apache folder that serves version.json and the Addressables catalog. Example: https://kkapi.playpoolstudios.com/ota")]
|
|
[SerializeField] string otaBaseUrl = "https://kkapi.playpoolstudios.com/ota";
|
|
[Tooltip("Opened when version.json has no storeUrl. Same role as the login screen Telegram link.")]
|
|
[SerializeField] string fallbackStoreUrl = "https://t.me/+dfUhy4gmGXk0ZDRl";
|
|
|
|
[Header("Content")]
|
|
[SerializeField] string introSceneAddress = AddressableSceneLoader.IntroScene;
|
|
[SerializeField] string gameContentLabel = "game-content";
|
|
|
|
[Header("UI")]
|
|
[SerializeField] TMP_Text statusText;
|
|
[SerializeField] Slider progressSlider;
|
|
[SerializeField] TMP_Text progressText;
|
|
[SerializeField] GameObject retryButton;
|
|
[SerializeField] GameObject updateButton;
|
|
|
|
string pendingStoreUrl = "";
|
|
bool updateRunning;
|
|
|
|
void Start()
|
|
{
|
|
SetRetryVisible(false);
|
|
SetUpdateVisible(false);
|
|
SetProgress(0f, "");
|
|
if (!updateRunning)
|
|
StartCoroutine(RunUpdate());
|
|
}
|
|
|
|
public void Retry()
|
|
{
|
|
if (updateRunning)
|
|
return;
|
|
SetRetryVisible(false);
|
|
SetUpdateVisible(false);
|
|
StartCoroutine(RunUpdate());
|
|
}
|
|
|
|
public void OpenStore()
|
|
{
|
|
string url = string.IsNullOrWhiteSpace(pendingStoreUrl) ? fallbackStoreUrl : pendingStoreUrl;
|
|
if (!string.IsNullOrWhiteSpace(url))
|
|
Application.OpenURL(url.Trim());
|
|
}
|
|
|
|
IEnumerator RunUpdate()
|
|
{
|
|
updateRunning = true;
|
|
SetStatus("Checking for updates");
|
|
|
|
OtaVersionInfo version = null;
|
|
bool versionFetched = false;
|
|
yield return FetchVersion(info =>
|
|
{
|
|
version = info;
|
|
versionFetched = info != null;
|
|
});
|
|
|
|
if (!versionFetched)
|
|
{
|
|
#if UNITY_EDITOR
|
|
Debug.LogWarning("OTA version.json was not reachable. Editor play continues into " + introSceneAddress + ".");
|
|
#else
|
|
if (PlayerPrefs.GetInt(ContentReadyPrefsKey, 0) != 1)
|
|
{
|
|
Fail("Check your connection and try again.");
|
|
yield break;
|
|
}
|
|
SetStatus("Offline. Loading saved game");
|
|
#endif
|
|
}
|
|
else if (OtaVersionInfo.IsClientBelowRequiredVersion(version.minPlayerVersion))
|
|
{
|
|
pendingStoreUrl = version.storeUrl;
|
|
string message = string.IsNullOrWhiteSpace(version.message)
|
|
? "A new version is required."
|
|
: version.message.Trim();
|
|
SetStatus(message);
|
|
SetUpdateVisible(true);
|
|
updateRunning = false;
|
|
yield break;
|
|
}
|
|
|
|
AsyncOperationHandle<IResourceLocator> init = Addressables.InitializeAsync(false);
|
|
yield return init;
|
|
bool initOk = init.IsValid() && init.Status == AsyncOperationStatus.Succeeded;
|
|
if (init.IsValid())
|
|
Addressables.Release(init);
|
|
if (!initOk)
|
|
{
|
|
Fail("Could not start content update.");
|
|
yield break;
|
|
}
|
|
|
|
AsyncOperationHandle<System.Collections.Generic.List<string>> check = Addressables.CheckForCatalogUpdates(false);
|
|
yield return check;
|
|
if (check.Status == AsyncOperationStatus.Succeeded && check.Result != null && check.Result.Count > 0)
|
|
{
|
|
SetStatus("Updating catalog");
|
|
AsyncOperationHandle<System.Collections.Generic.List<IResourceLocator>> catalogs = Addressables.UpdateCatalogs(check.Result, false);
|
|
yield return catalogs;
|
|
if (catalogs.Status != AsyncOperationStatus.Succeeded)
|
|
{
|
|
Addressables.Release(catalogs);
|
|
Addressables.Release(check);
|
|
Fail("Could not update the content catalog.");
|
|
yield break;
|
|
}
|
|
Addressables.Release(catalogs);
|
|
}
|
|
Addressables.Release(check);
|
|
|
|
long downloadBytes = 0;
|
|
AsyncOperationHandle<long> sizeHandle = Addressables.GetDownloadSizeAsync(gameContentLabel);
|
|
yield return sizeHandle;
|
|
if (sizeHandle.Status == AsyncOperationStatus.Succeeded)
|
|
downloadBytes = sizeHandle.Result;
|
|
Addressables.Release(sizeHandle);
|
|
|
|
if (downloadBytes > 0)
|
|
{
|
|
AsyncOperationHandle download = Addressables.DownloadDependenciesAsync(gameContentLabel, false);
|
|
while (!download.IsDone)
|
|
{
|
|
DownloadStatus status = download.GetDownloadStatus();
|
|
float percent = status.TotalBytes > 0
|
|
? (float)status.DownloadedBytes / status.TotalBytes
|
|
: download.PercentComplete;
|
|
SetProgress(percent, FormatMegabytes(status.DownloadedBytes) + " / " + FormatMegabytes(status.TotalBytes));
|
|
SetStatus("Downloading update " + Mathf.RoundToInt(percent * 100f) + "%");
|
|
yield return null;
|
|
}
|
|
|
|
if (download.Status != AsyncOperationStatus.Succeeded)
|
|
{
|
|
Addressables.Release(download);
|
|
Fail("Download failed. Check your connection and try again.");
|
|
yield break;
|
|
}
|
|
Addressables.Release(download);
|
|
}
|
|
|
|
PlayerPrefs.SetInt(ContentReadyPrefsKey, 1);
|
|
PlayerPrefs.Save();
|
|
SetStatus("Loading");
|
|
SetProgress(1f, "");
|
|
yield return AddressableSceneLoader.LoadRoutine(introSceneAddress);
|
|
updateRunning = false;
|
|
}
|
|
|
|
IEnumerator FetchVersion(Action<OtaVersionInfo> onResult)
|
|
{
|
|
string baseUrl = (otaBaseUrl ?? "").Trim().TrimEnd('/');
|
|
if (string.IsNullOrEmpty(baseUrl))
|
|
{
|
|
onResult?.Invoke(null);
|
|
yield break;
|
|
}
|
|
|
|
string url = baseUrl + "/version.json";
|
|
using (var request = UnityWebRequest.Get(url))
|
|
{
|
|
request.timeout = 20;
|
|
yield return request.SendWebRequest();
|
|
if (request.result != UnityWebRequest.Result.Success)
|
|
{
|
|
onResult?.Invoke(null);
|
|
yield break;
|
|
}
|
|
|
|
try
|
|
{
|
|
onResult?.Invoke(JsonUtility.FromJson<OtaVersionInfo>(request.downloadHandler.text));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogWarning("OTA version.json parse failed: " + e.Message);
|
|
onResult?.Invoke(null);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Fail(string message)
|
|
{
|
|
SetStatus(message);
|
|
SetRetryVisible(true);
|
|
updateRunning = false;
|
|
}
|
|
|
|
void SetStatus(string message)
|
|
{
|
|
if (statusText != null)
|
|
statusText.text = message;
|
|
}
|
|
|
|
void SetProgress(float normalized, string label)
|
|
{
|
|
if (progressSlider != null)
|
|
{
|
|
progressSlider.gameObject.SetActive(true);
|
|
progressSlider.value = Mathf.Clamp01(normalized);
|
|
}
|
|
if (progressText != null)
|
|
progressText.text = label ?? "";
|
|
}
|
|
|
|
void SetRetryVisible(bool visible)
|
|
{
|
|
if (retryButton != null)
|
|
retryButton.SetActive(visible);
|
|
}
|
|
|
|
void SetUpdateVisible(bool visible)
|
|
{
|
|
if (updateButton != null)
|
|
updateButton.SetActive(visible);
|
|
}
|
|
|
|
static string FormatMegabytes(long bytes)
|
|
{
|
|
float mb = bytes / (1024f * 1024f);
|
|
return mb.ToString("0.0", System.Globalization.CultureInfo.InvariantCulture) + " MB";
|
|
}
|
|
}
|