using System; using System.Collections; using AOT.Boot; using AOT.UI; using UnityEngine; using YooAsset; namespace AOT.Patch { /// /// 启动补丁:version 门控 → YooAsset 初始化/更新 → 回调。 /// public sealed class PatchRunner { readonly MonoBehaviour _host; readonly BootSettings _settings; readonly BootPatchView _view; public PatchRunner(MonoBehaviour host, BootSettings settings, BootPatchView view) { _host = host; _settings = settings; _view = view; } public IEnumerator Run(Action onDone) { _view?.SetStatus("检查版本…"); _view?.SetProgress(0.05f); VersionInfo version = null; string versionError = null; yield return new VersionClient(_settings.VersionUrl, _settings.AllowStreamingVersionFallback) .Fetch((info, err) => { version = info; versionError = err; }); if (version == null) { // 无 CDN 时仍允许编辑器直通;真机 Host/Web 则失败。 if (!CanContinueWithoutVersion()) { onDone?.Invoke(null, $"获取 version.json 失败: {versionError}"); yield break; } version = new VersionInfo { appVersionMin = _settings.AppVersion, appVersionMax = _settings.AppVersion, resVersion = "local", cdnRoot = _settings.DefaultCdnRoot, packages = new[] { _settings.PackageName }, }; _view?.SetStatus($"版本回退本地 ({versionError})"); } else { _view?.SetStatus($"远程版本 {version.resVersion}"); } if (VersionInfo.CompareVersion(_settings.AppVersion, version.appVersionMin) < 0) { onDone?.Invoke(null, $"壳版本过低,请更新微信小游戏包(当前 {_settings.AppVersion} < 最低 {version.appVersionMin})"); yield break; } string cdnRoot = string.IsNullOrEmpty(version.cdnRoot) ? _settings.DefaultCdnRoot : version.cdnRoot; var ctx = new BootContext { AppVersion = _settings.AppVersion, ResVersion = version.resVersion, CdnRoot = cdnRoot, PackageName = _settings.PackageName, WsUrlTemplate = _settings.WsUrlTemplate, }; #if UNITY_EDITOR if (_settings.EditorDirectHotUpdate) { _view?.SetStatus("编辑器直通热更(跳过资源补丁)"); _view?.SetProgress(0.9f); onDone?.Invoke(ctx, null); yield break; } #endif _view?.SetStatus("初始化资源系统…"); _view?.SetProgress(0.15f); if (!YooAssets.IsInitialized) YooAssets.Initialize(); if (!YooAssets.TryGetPackage(_settings.PackageName, out var package)) package = YooAssets.CreatePackage(_settings.PackageName); ctx.Package = package; var playMode = ResolvePlayMode(); var initOp = CreateInitializeOperation(package, playMode, cdnRoot); if (initOp == null) { onDone?.Invoke(null, $"不支持的 PlayMode: {playMode}"); yield break; } yield return initOp; if (initOp.Status != EOperationStatus.Succeeded) { onDone?.Invoke(null, $"YooAsset 初始化失败: {initOp.Error}"); yield break; } _view?.SetStatus("请求资源版本…"); _view?.SetProgress(0.35f); var versionOp = package.RequestPackageVersionAsync(); yield return versionOp; if (versionOp.Status != EOperationStatus.Succeeded) { onDone?.Invoke(null, $"请求 PackageVersion 失败: {versionOp.Error}"); yield break; } _view?.SetStatus($"更新清单 {versionOp.PackageVersion}"); _view?.SetProgress(0.5f); var manifestOp = package.LoadPackageManifestAsync(new LoadPackageManifestOptions(versionOp.PackageVersion, 60)); yield return manifestOp; if (manifestOp.Status != EOperationStatus.Succeeded) { onDone?.Invoke(null, $"更新清单失败: {manifestOp.Error}"); yield break; } _view?.SetStatus("下载资源…"); var downloader = package.CreateResourceDownloader(new ResourceDownloaderOptions(10, 3)); if (downloader.TotalDownloadCount > 0) { downloader.DownloadProgressChanged += info => { _view?.SetProgress(0.5f + info.Progress * 0.4f); _view?.SetStatus($"下载 {info.CurrentDownloadCount}/{info.TotalDownloadCount}"); }; downloader.StartDownload(); yield return downloader; if (downloader.Status != EOperationStatus.Succeeded) { onDone?.Invoke(null, $"下载失败: {downloader.Error}"); yield break; } } _view?.SetProgress(0.95f); _view?.SetStatus("资源就绪"); onDone?.Invoke(ctx, null); } bool CanContinueWithoutVersion() { #if UNITY_EDITOR return _settings.EditorDirectHotUpdate; #else return false; #endif } EPlayMode ResolvePlayMode() { #if UNITY_EDITOR return _settings.PlayMode; #elif UNITY_WEBGL return EPlayMode.WebPlayMode; #else return _settings.PlayMode == EPlayMode.EditorSimulateMode ? EPlayMode.HostPlayMode : _settings.PlayMode; #endif } InitializePackageOperation CreateInitializeOperation(ResourcePackage package, EPlayMode playMode, string cdnRoot) { if (playMode == EPlayMode.EditorSimulateMode) { #if UNITY_EDITOR var buildResult = EditorSimulateBuildInvoker.Build(_settings.PackageName, (int)EBundleType.VirtualAssetBundle); var options = new EditorSimulateModeOptions { EditorFileSystemParameters = FileSystemParameters.CreateDefaultEditorFileSystemParameters(buildResult.PackageRootDirectory) }; return package.InitializePackageAsync(options); #else return null; #endif } if (playMode == EPlayMode.OfflinePlayMode) { var options = new OfflinePlayModeOptions { BuiltinFileSystemParameters = FileSystemParameters.CreateDefaultBuiltinFileSystemParameters() }; return package.InitializePackageAsync(options); } if (playMode == EPlayMode.HostPlayMode) { var remote = new CdnRemoteService(cdnRoot); var options = new HostPlayModeOptions { BuiltinFileSystemParameters = FileSystemParameters.CreateDefaultBuiltinFileSystemParameters(), CacheFileSystemParameters = FileSystemParameters.CreateDefaultSandboxFileSystemParameters(remote) }; options.BuiltinFileSystemParameters.AddParameter(EFileSystemParameter.CopyBuiltinPackageManifest, true); return package.InitializePackageAsync(options); } if (playMode == EPlayMode.WebPlayMode) { // 微信小游戏正式接入时替换为 WechatFileSystem;M0 先用默认 WebNetwork。 var remote = new CdnRemoteService(cdnRoot); var options = new WebPlayModeOptions { WebNetworkFileSystemParameters = FileSystemParameters.CreateDefaultWebNetworkFileSystemParameters(remote, true) }; return package.InitializePackageAsync(options); } return null; } } }