430 lines
18 KiB
C#
430 lines
18 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.IO;
|
|
using System.Threading;
|
|
using Cysharp.Threading.Tasks;
|
|
using UnityEngine;
|
|
using YooAsset;
|
|
|
|
namespace Game
|
|
{
|
|
public class ResourceUpdatePackageHandle
|
|
{
|
|
public EPlayMode playMode;
|
|
public string packageName;
|
|
public string buildPipeline;
|
|
|
|
public ResourceUpdatePackageHandle(EPlayMode playMode, string packageName, string buildPipeline)
|
|
{
|
|
this.playMode = playMode;
|
|
this.packageName = packageName;
|
|
this.buildPipeline = buildPipeline;
|
|
|
|
EventManager.Instance.Subscribe(UpdatePackageCallbackEventArgs.EventId, UpdatePackageCallbackEvent);
|
|
EventManager.Instance.Subscribe(EnterSingleModeEventArgs.EventId, EnterSingleModeEvent);
|
|
}
|
|
|
|
private void EnterSingleModeEvent(object sender, GameEventArgs e)
|
|
{
|
|
this.playMode = EPlayMode.OfflinePlayMode;
|
|
this.InitPackageAsync().Forget();
|
|
}
|
|
|
|
private void UpdatePackageCallbackEvent(object sender, GameEventArgs e)
|
|
{
|
|
var args = e as UpdatePackageCallbackEventArgs;
|
|
switch (args.callbackType)
|
|
{
|
|
case UpdatePackageCallbackType.再次初始化资源包:
|
|
this.InitPackageAsync().Forget();
|
|
break;
|
|
case UpdatePackageCallbackType.开始下载网络文件:
|
|
this.BeginDownloadAsync().Forget();
|
|
break;
|
|
case UpdatePackageCallbackType.再次更新静态版本:
|
|
this.UpdatePackageVersionAsync().Forget();
|
|
break;
|
|
case UpdatePackageCallbackType.再次更新补丁清单:
|
|
this.UpdateManifestAsync().Forget();
|
|
break;
|
|
case UpdatePackageCallbackType.再次下载网络文件:
|
|
this.CreateDownloaderAsync().Forget();
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
|
|
public async UniTask InitPackageAsync(CancellationToken token = default)
|
|
{
|
|
// 创建资源包裹类
|
|
var package = YooAssets.TryGetPackage(packageName);
|
|
if (package == null)
|
|
package = YooAssets.CreatePackage(packageName);
|
|
else
|
|
{
|
|
YooAssets.DestroyPackage(this.packageName);
|
|
package = YooAssets.CreatePackage(packageName);
|
|
}
|
|
|
|
// 编辑器下的模拟模式
|
|
InitializationOperation initializationOperation = null;
|
|
if (playMode == EPlayMode.EditorSimulateMode)
|
|
{
|
|
var createParameters = new EditorSimulateModeParameters();
|
|
createParameters.SimulateManifestFilePath = EditorSimulateModeHelper.SimulateBuild(buildPipeline, packageName);
|
|
initializationOperation = package.InitializeAsync(createParameters);
|
|
}
|
|
|
|
// 单机运行模式
|
|
if (playMode == EPlayMode.OfflinePlayMode)
|
|
{
|
|
var createParameters = new OfflinePlayModeParameters();
|
|
createParameters.DecryptionServices = new FileStreamDecryption();
|
|
initializationOperation = package.InitializeAsync(createParameters);
|
|
}
|
|
|
|
// 联机运行模式
|
|
if (playMode == EPlayMode.HostPlayMode)
|
|
{
|
|
string defaultHostServer = GetHostServerURL();
|
|
string fallbackHostServer = GetHostServerURL();
|
|
var createParameters = new HostPlayModeParameters();
|
|
createParameters.DecryptionServices = new FileStreamDecryption();
|
|
createParameters.BuildinQueryServices = new GameQueryServices();
|
|
createParameters.RemoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
|
|
initializationOperation = package.InitializeAsync(createParameters);
|
|
}
|
|
|
|
// WebGL运行模式
|
|
if (playMode == EPlayMode.WebPlayMode)
|
|
{
|
|
string defaultHostServer = GetHostServerURL();
|
|
string fallbackHostServer = GetHostServerURL();
|
|
var createParameters = new WebPlayModeParameters();
|
|
createParameters.DecryptionServices = new FileStreamDecryption();
|
|
createParameters.BuildinQueryServices = new GameQueryServices();
|
|
createParameters.RemoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
|
|
initializationOperation = package.InitializeAsync(createParameters);
|
|
}
|
|
|
|
await initializationOperation;
|
|
|
|
// 如果初始化失败弹出提示界面
|
|
if (initializationOperation.Status != EOperationStatus.Succeed)
|
|
{
|
|
Debug.LogWarning($"{initializationOperation.Error}");
|
|
// 初始化失败
|
|
EventManager.Instance.FireNow(InitializeFailedEventArgs.EventId, new InitializeFailedEventArgs());
|
|
}
|
|
else
|
|
{
|
|
var version = initializationOperation.PackageVersion;
|
|
Debug.Log($"Init resource package version : {version}");
|
|
await this.UpdatePackageVersionAsync(token);
|
|
}
|
|
}
|
|
|
|
#region InitHelper
|
|
|
|
/// <summary>
|
|
/// 获取资源服务器地址
|
|
/// </summary>
|
|
private string GetHostServerURL()
|
|
{
|
|
//string hostServerIP = "http://10.0.2.2"; //安卓模拟器地址
|
|
#if UNITY_EDITOR
|
|
string hostServerIP = "http://127.0.0.1:8080";
|
|
#else
|
|
string hostServerIP = "http://192.168.0.124:8080";
|
|
#endif
|
|
string appVersion = "v1.0";
|
|
#if UNITY_EDITOR
|
|
if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.Android)
|
|
return $"{hostServerIP}/CDN/Android/{appVersion}";
|
|
else if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.iOS)
|
|
return $"{hostServerIP}/CDN/IPhone/{appVersion}";
|
|
else if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.WebGL)
|
|
return $"{hostServerIP}/CDN/WebGL/{appVersion}";
|
|
else
|
|
return $"{hostServerIP}/CDN/PC/{appVersion}";
|
|
#else
|
|
if (Application.platform == RuntimePlatform.Android)
|
|
return $"{hostServerIP}/CDN/Android/{appVersion}";
|
|
else if (Application.platform == RuntimePlatform.IPhonePlayer)
|
|
return $"{hostServerIP}/CDN/IPhone/{appVersion}";
|
|
else if (Application.platform == RuntimePlatform.WebGLPlayer)
|
|
return $"{hostServerIP}/CDN/WebGL/{appVersion}";
|
|
else
|
|
return $"{hostServerIP}/CDN/PC/{appVersion}";
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// 远端资源地址查询服务类
|
|
/// </summary>
|
|
private class RemoteServices : IRemoteServices
|
|
{
|
|
private readonly string _defaultHostServer;
|
|
private readonly string _fallbackHostServer;
|
|
|
|
public RemoteServices(string defaultHostServer, string fallbackHostServer)
|
|
{
|
|
_defaultHostServer = defaultHostServer;
|
|
_fallbackHostServer = fallbackHostServer;
|
|
}
|
|
|
|
string IRemoteServices.GetRemoteMainURL(string fileName)
|
|
{
|
|
return $"{_defaultHostServer}/{fileName}";
|
|
}
|
|
|
|
string IRemoteServices.GetRemoteFallbackURL(string fileName)
|
|
{
|
|
return $"{_fallbackHostServer}/{fileName}";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 资源文件流加载解密类
|
|
/// </summary>
|
|
private class FileStreamDecryption : IDecryptionServices
|
|
{
|
|
/// <summary>
|
|
/// 同步方式获取解密的资源包对象
|
|
/// 注意:加载流对象在资源包对象释放的时候会自动释放
|
|
/// </summary>
|
|
AssetBundle IDecryptionServices.LoadAssetBundle(DecryptFileInfo fileInfo, out Stream managedStream)
|
|
{
|
|
BundleStream bundleStream = new BundleStream(fileInfo.FileLoadPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
|
managedStream = bundleStream;
|
|
return AssetBundle.LoadFromStream(bundleStream, fileInfo.ConentCRC, GetManagedReadBufferSize());
|
|
}
|
|
|
|
/// <summary>
|
|
/// 异步方式获取解密的资源包对象
|
|
/// 注意:加载流对象在资源包对象释放的时候会自动释放
|
|
/// </summary>
|
|
AssetBundleCreateRequest IDecryptionServices.LoadAssetBundleAsync(DecryptFileInfo fileInfo, out Stream managedStream)
|
|
{
|
|
BundleStream bundleStream = new BundleStream(fileInfo.FileLoadPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
|
managedStream = bundleStream;
|
|
return AssetBundle.LoadFromStreamAsync(bundleStream, fileInfo.ConentCRC, GetManagedReadBufferSize());
|
|
}
|
|
|
|
private static uint GetManagedReadBufferSize()
|
|
{
|
|
return 1024;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 资源文件偏移加载解密类
|
|
/// </summary>
|
|
private class FileOffsetDecryption : IDecryptionServices
|
|
{
|
|
/// <summary>
|
|
/// 同步方式获取解密的资源包对象
|
|
/// 注意:加载流对象在资源包对象释放的时候会自动释放
|
|
/// </summary>
|
|
AssetBundle IDecryptionServices.LoadAssetBundle(DecryptFileInfo fileInfo, out Stream managedStream)
|
|
{
|
|
managedStream = null;
|
|
return AssetBundle.LoadFromFile(fileInfo.FileLoadPath, fileInfo.ConentCRC, GetFileOffset());
|
|
}
|
|
|
|
/// <summary>
|
|
/// 异步方式获取解密的资源包对象
|
|
/// 注意:加载流对象在资源包对象释放的时候会自动释放
|
|
/// </summary>
|
|
AssetBundleCreateRequest IDecryptionServices.LoadAssetBundleAsync(DecryptFileInfo fileInfo, out Stream managedStream)
|
|
{
|
|
managedStream = null;
|
|
return AssetBundle.LoadFromFileAsync(fileInfo.FileLoadPath, fileInfo.ConentCRC, GetFileOffset());
|
|
}
|
|
|
|
private static ulong GetFileOffset()
|
|
{
|
|
return 32;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
string packageVersion; // 最新资源版本
|
|
|
|
private async UniTask UpdatePackageVersionAsync(CancellationToken token = default)
|
|
{
|
|
Debug.Log("获取最新的资源版本 !");
|
|
await new WaitForSecondsRealtime(0.5f);
|
|
|
|
// var packageName = (string)_machine.GetBlackboardValue("PackageName");
|
|
var package = YooAssets.GetPackage(packageName);
|
|
var operation = package.UpdatePackageVersionAsync();
|
|
await operation;
|
|
|
|
if (operation.Status != EOperationStatus.Succeed)
|
|
{
|
|
Debug.LogWarning(operation.Error);
|
|
// PatchEventDefine.PackageVersionUpdateFailed.SendEventMessage();
|
|
EventManager.Instance.FireNow(PackageVersionUpdateFailedEventArgs.EventId, new PackageVersionUpdateFailedEventArgs());
|
|
Debug.Log("获取最新的资源版本失败!");
|
|
}
|
|
else
|
|
{
|
|
// _machine.SetBlackboardValue("PackageVersion", operation.PackageVersion);
|
|
// _machine.ChangeState<FsmUpdatePackageManifest>();
|
|
|
|
this.packageVersion = operation.PackageVersion;
|
|
Debug.Log($"最新资源版本为:{operation.PackageVersion}");
|
|
await this.UpdateManifestAsync(token);
|
|
}
|
|
}
|
|
|
|
private async UniTask UpdateManifestAsync(CancellationToken token = default)
|
|
{
|
|
Debug.Log("更新资源清单!");
|
|
await new WaitForSecondsRealtime(0.5f);
|
|
|
|
// var packageName = (string)_machine.GetBlackboardValue("PackageName");
|
|
// var packageVersion = (string)_machine.GetBlackboardValue("PackageVersion");
|
|
var package = YooAssets.GetPackage(packageName);
|
|
bool savePackageVersion = true;
|
|
var operation = package.UpdatePackageManifestAsync(packageVersion, savePackageVersion);
|
|
await operation;
|
|
|
|
if (operation.Status != EOperationStatus.Succeed)
|
|
{
|
|
Debug.LogWarning(operation.Error);
|
|
// PatchEventDefine.PatchManifestUpdateFailed.SendEventMessage();
|
|
EventManager.Instance.FireNow(PatchManifestUpdateFailedEventArgs.EventId, new PatchManifestUpdateFailedEventArgs());
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// _machine.ChangeState<FsmCreatePackageDownloader>();
|
|
Debug.Log("更新资源清单完成!");
|
|
await this.CreateDownloaderAsync(token);
|
|
}
|
|
}
|
|
|
|
|
|
ResourceDownloaderOperation downloader;
|
|
|
|
private async UniTask CreateDownloaderAsync(CancellationToken token = default)
|
|
{
|
|
Debug.Log("创建补丁下载器!");
|
|
await new WaitForSecondsRealtime(0.5f);
|
|
|
|
// var packageName = (string)_machine.GetBlackboardValue("PackageName");
|
|
var package = YooAssets.GetPackage(packageName);
|
|
int downloadingMaxNum = 10;
|
|
int failedTryAgain = 3;
|
|
downloader = package.CreateResourceDownloader(downloadingMaxNum, failedTryAgain);
|
|
// _machine.SetBlackboardValue("Downloader", downloader);
|
|
|
|
if (downloader.TotalDownloadCount == 0)
|
|
{
|
|
Debug.Log("Not found any download files !");
|
|
// _machine.ChangeState<FsmUpdaterDone>();
|
|
await this.UpdaterDoneAsync(token);
|
|
}
|
|
else
|
|
{
|
|
// 发现新更新文件后,挂起流程系统
|
|
// TODO: 注意:开发者需要在下载前检测磁盘空间不足
|
|
int totalDownloadCount = downloader.TotalDownloadCount;
|
|
long totalDownloadBytes = downloader.TotalDownloadBytes;
|
|
// PatchEventDefine.FoundUpdateFiles.SendEventMessage(totalDownloadCount, totalDownloadBytes);
|
|
Debug.Log("发现新更新文件后,挂起流程系统");
|
|
EventManager.Instance.FireNow(DownloadProgressUpdateEventArgs.EventId, new FoundUpdateFilesEventArgs(totalDownloadCount, totalDownloadBytes));
|
|
// this.BeginDownloadAsync(token).Forget();
|
|
}
|
|
}
|
|
|
|
private async UniTask BeginDownloadAsync(CancellationToken token = default)
|
|
{
|
|
Debug.Log("开始下载补丁文件!");
|
|
downloader.OnDownloadErrorCallback = WebFileDownloadFailed;
|
|
downloader.OnDownloadProgressCallback = DownloadProgressUpdate;
|
|
downloader.BeginDownload();
|
|
await downloader;
|
|
|
|
// 检测下载结果
|
|
if (downloader.Status != EOperationStatus.Succeed)
|
|
return;
|
|
|
|
// _machine.ChangeState<FsmDownloadPackageOver>();
|
|
await this.DownloadPackageOverAsync(token);
|
|
|
|
void DownloadProgressUpdate(int totaldownloadcount, int currentdownloadcount, long totaldownloadbytes, long currentdownloadbytes)
|
|
{
|
|
EventManager.Instance.FireNow(DownloadProgressUpdateEventArgs.EventId, new DownloadProgressUpdateEventArgs(
|
|
totaldownloadcount, currentdownloadcount, totaldownloadbytes, currentdownloadbytes));
|
|
}
|
|
|
|
void WebFileDownloadFailed(string filename, string error)
|
|
{
|
|
Debug.Log(this.packageName + " : " + error);
|
|
EventManager.Instance.FireNow(WebFileDownloadFailedEventArgs.EventId, new WebFileDownloadFailedEventArgs(this.packageName, error));
|
|
}
|
|
}
|
|
|
|
private async UniTask DownloadPackageOverAsync(CancellationToken token = default)
|
|
{
|
|
Debug.Log("下载完成");
|
|
await UniTask.Yield();
|
|
await this.ClearPackageCacheAsync(token);
|
|
}
|
|
|
|
private async UniTask ClearPackageCacheAsync(CancellationToken token = default)
|
|
{
|
|
Debug.Log("清理未使用的缓存文件!");
|
|
await UniTask.Yield();
|
|
|
|
var package = YooAssets.GetPackage(packageName);
|
|
var operation = package.ClearUnusedCacheFilesAsync();
|
|
operation.Completed += Operation_Completed;
|
|
|
|
void Operation_Completed(YooAsset.AsyncOperationBase obj)
|
|
{
|
|
// _machine.ChangeState<FsmUpdaterDone>();
|
|
this.UpdaterDoneAsync(token).Forget();
|
|
}
|
|
}
|
|
|
|
private async UniTask UpdaterDoneAsync(CancellationToken token = default)
|
|
{
|
|
Debug.Log("流程更新完毕!");
|
|
await UniTask.Yield();
|
|
EventManager.Instance.FireNow(UpdaterDoneEventArgs.EventId, new UpdaterDoneEventArgs());
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 资源文件解密流
|
|
/// </summary>
|
|
public class BundleStream : FileStream
|
|
{
|
|
public const byte KEY = 64;
|
|
|
|
public BundleStream(string path, FileMode mode, FileAccess access, FileShare share) : base(path, mode, access, share)
|
|
{
|
|
}
|
|
|
|
public BundleStream(string path, FileMode mode) : base(path, mode)
|
|
{
|
|
}
|
|
|
|
public override int Read(byte[] array, int offset, int count)
|
|
{
|
|
var index = base.Read(array, offset, count);
|
|
for (int i = 0; i < array.Length; i++)
|
|
{
|
|
array[i] ^= KEY;
|
|
}
|
|
|
|
return index;
|
|
}
|
|
} |