# 官方资源 Release 布局与资源侧契约 - **更新时间**:2026-07-27 - **用途**:冻结日服官方资源在本地发布根上的布局、URL 映射、seed 规则、`bat`/`bat-api` 关系,以及 `bat-api` 分发 path 的 1:1 对应关系。 - **范围**:资源发现 / 清单 / 落盘 / 只读分发(**不是**完整游戏业务 API)。 - **权威代码**: - URL / 平台 / seed:`adapters/src/official/yostar_jp.rs` - inventory 抽取:`adapters/src/official/inventory.rs` - 落盘与 manifest:`infrastructure/src/official_download.rs`(`destination_for_url`) - 发布布局:`infrastructure/src/official_update.rs` - 分发:`cmd/bat-api` + `internal/api`(见 `docs/reports/GO_STATUS.md`) --- ## 1. 产品边界(资源侧) | 角色 | 组件 | 职责 | |---|---|---| | 同步 / 运维(近乎全自动) | Rust `bat` | auto-discover、拉取、校验、发布、watch/daemon、RPC 后端 | | 资源 bootstrap / 只读分发 | Go `bat-api` | 同环境经 `bat.sock` 发现已发布版本和 `resource_root`,提供 `/v1/bootstrap`、server-info 改写和官方 CDN path 字节 | | 试验 CLI | Go `cmd/bat` → `bin/bat-go` | 非产品;禁止与 Rust `bat` 重名 | **禁止**:把已安装客户端目录或 `/home/wanye/D/BlueArchive` 当作生产输入;真实全量样本优先服务器 release 或 `/tmp` 隔离目录。 --- ## 2. 发布根布局(L1) ```text / # 官方原版资源发布根(--output / BAT_OUTPUT) current -> versions/ # 原子 symlink,生产读侧 versions// # 已发布 versioned release(= resource_root) official-download-manifest.json official-parse-cache.json # 校验后派生解析缓存,不是汉化产物 official-textunit-index.json # TextUnit 明细与解析错误索引,不是汉化产物 official-textunit-tasks.json # 翻译任务候选派生队列,不发 Crowdin 网络请求 crowdin-textunit-queue.json # Crowdin worker 离线输入队列 official-sync-snapshot.json # 常在 active root / current 下 official-launcher-bootstrap.json # 官方 launcher 引导链版本化产物 prod-clientpatch.bluearchiveyostar.com/ / TableBundles/ TableCatalog.bytes TableCatalog.hash # e.g. ExcelDB.db, Excel.zip Windows_PatchPack/ BundlePackingInfo.bytes BundlePackingInfo.hash catalog_StandaloneWindows64.zip catalog_StandaloneWindows64.hash FullPatch_NNN.zip Android_PatchPack/ BundlePackingInfo.bytes BundlePackingInfo.hash catalog_Android.zip catalog_Android.hash FullPatch_NNN.zip MediaResources-Windows/ Catalog/MediaCatalog.bytes Catalog/MediaCatalog.hash GameData/... Prologue/... MediaResources/ # Android Catalog/MediaCatalog.bytes Catalog/MediaCatalog.hash ... yostar-serverinfo.bluearchiveyostar.com/ # 若曾下载 server-info .json .staging// # 未发布写侧(失败可复用) official-version-state.json # 发布根级版本状态 official-bootstrap-cache.json # auto-discover 缓存 official-launcher-bootstrap.pending.json # 维护期 launcher 已前进但资源未开放时的待处理证据 / # 汉化产物发布根(--localized-output / BAT_LOCALIZED_OUTPUT) current -> versions/ # 已汉化后才切换;未汉化状态不发布 versions// # 与官方相对路径一致的汉化资源 localized-version-state.json # 预留:后续 Patch 发布阶段维护,官方同步阶段不写入 ``` 官方资源发布和汉化发布是两个独立状态: - `not_localized`:官方原版资源已经完成下载、校验和发布,汉化资源尚未发布;这是官方同步完成后的默认状态。 - `localized`:同一官方版本的原版资源和汉化资源都已发布,生产侧可以同时提供两套资源。 ### 2.1 读侧 vs 写侧 | 阶段 | 根目录 | |---|---| | 下载写入 | `/.staging/` | | 发布完成 | rename 到 `versions/`,再切换 `current` | | 生产读取 / bat-api | RPC 给出的 `version.resource_root`;通常等价于 `current` 解析后的 versioned 目录 | --- ## 3. URL → 磁盘映射(核心不变量) 实现:`OfficialResourcePullService::destination_for_url`。 ```text https://{host}/{path...} → /{host}/{path...} ``` 规则: 1. 仅 `https://` 2. host 必须是官方 JP 资源 host(见下节) 3. path 分段不得为 `.` / `..` 4. **禁止** query / fragment(否则直接拒绝,避免同路径覆盖) 5. 分段经 sanitize 后 join;结果必须在 `resource_root` 内 ### 3.1 官方 host | Host | 用途 | |---|---| | `prod-clientpatch.bluearchiveyostar.com` | Addressables / Table / Media / PatchPack 内容 | | `yostar-serverinfo.bluearchiveyostar.com` | server-info JSON | (launcher 包 CDN 属于启动器链,**不是**默认资源 release 主体。Rust `bat` 会把启动器链中与资源发现相关的 launcher metadata、CDN config、remote manifest 文件列表、选中的 `resources.assets` 来源和 `GameMainConfig` 摘要写入 `official-launcher-bootstrap.json`,供后续 `bat-api` / 自研客户端在 Rust 侧完成前继续以 versioned release 为权威来源。) ### 3.2 bat-api 对外 path(1:1) ```text GET {public-base-url}/prod-clientpatch.bluearchiveyostar.com//... ≡ 磁盘 /prod-clientpatch.bluearchiveyostar.com//... ``` 默认仅服务 **download manifest 索引内且 Present + size 匹配** 的文件。 ### 3.3 launcher 资源引导兼容 只读分析本机样本时可见两类启动器形态: | 目录形态 | 说明 | |---|---| | `AllResources/YostarGames/BlueArchive_JP_Gamelauncher` | 官方 Electron 启动器目录 | | `Localized` / `Localized_Official` | 汉化或改造启动器目录 | | `AllResources/YostarGames/BlueArchive_JP` | 官方安装后的游戏客户端目录,含 `game-launcher-config.json`、`manifest.json` 和 `BlueArchive_Data/StreamingAssets/catalog_Remote.*` | 这些目录只作为开发期样本;生产链路不得依赖 `/home/wanye/D/BlueArchive` 或任何已安装客户端目录。 官方启动器样本中与资源发现相关的 HTTP path: | Host / path | 资源侧意义 | |---|---| | `https://api-launcher-jp.yo-star.com/api/launcher/game/config` | 返回 launcher 观察到的最新客户端版本和包路径 | | `https://api-launcher-jp.yo-star.com/api/launcher/game/config/json?version=...&file_path=...` | 返回远端 package manifest URL | | `https://api-launcher-jp.yo-star.com/api/launcher/advanced/game/download/cdn` | 返回 launcher package CDN primary / backup | `bat-api` 的兼容范围是**资源引导**,不是完整启动器更新服务: | bat-api path | 行为 | |---|---| | `/v1/launcher/bootstrap` | 返回资源引导聚合视图:已发布 release、launcher metadata、GameMainConfig 摘要、server-info URL、client-patch base、改写后的 Addressables root | | `/api/launcher/game/config` | 返回 `{code,message,data}` envelope,字段来自 Rust `bat` snapshot/RPC 中的 `launcher_metadata`,并附带 `resource_bootstrap_url` | | `/api/launcher/game/config/json` | 返回指向 `/api-launcher-jp.yo-star.com/api/launcher/resource/bootstrap.json` 的资源引导 JSON URL,显式标记 `package_update_manifest=false` | | `/api/launcher/advanced/game/download/cdn` | 返回 `public-base-url` 作为资源引导 CDN 根,显式标记 `package_update_manifest=false` | | `/api-launcher-jp.yo-star.com/...` | 与上面裸 path 等价,便于反向代理或 hosts 映射保持官方 host 形状 | 数据来源只能是 Rust `bat` 已发布状态;当前 Go `bat-api` 仍主要消费 snapshot/RPC 摘要,后续字段统一与联调时应把 versioned launcher artifact 纳入 contract fixture: 1. `catalog.status` / `official-sync-snapshot.json` 中的 `launcher_metadata`。 2. `catalog.status` / `official-sync-snapshot.json` 中的 `game_main_config_bootstrap`。 3. `official-launcher-bootstrap.json` 中的官方 launcher bootstrap versioned artifact。 4. `resource.manifest` 和磁盘 Present/size 检查得到的当前 release 索引。 `bat-api` 不下载 launcher 包、不生成官方 PC package update manifest、不执行启动器签名/鉴权链、不仿造登录、账号、网关或游戏业务 API。需要真实资源拉取时,仍由 Rust `bat --auto-discover` 在隔离 staging 中通过官方 HTTP metadata 完成,并把已发布结果通过 RPC 暴露给 `bat-api`。 --- ## 4. `official-download-manifest.json` | 字段 | 类型 | 说明 | |---|---|---| | `version` | u32 | 当前为 `1` | | `entries` | map URL → entry | 按完整官方 URL 为键(有序 BTreeMap) | 每条 entry: | 字段 | 说明 | |---|---| | `url` | 官方 https URL | | `destination` | 相对 resource_root 的路径(`host/path...`) | | `bytes` | 文件大小 | | `blake3` | 本地 BLAKE3 hex | **权威清单**:拉取闭环写入的 manifest;`bat-api` / RPC `resource.manifest` 以此为应有集合,再以磁盘校验 Present。 --- ## 5. 发现与 seed URL 规则(L2) 常量根: - server-info:`https://yostar-serverinfo.bluearchiveyostar.com` - client-patch:`https://prod-clientpatch.bluearchiveyostar.com` `AddressablesCatalogUrlRoot` 形如: ```text https://prod-clientpatch.bluearchiveyostar.com/ ``` 默认平台:`Windows` + `Android`。 ### 5.1 平台目录名 | 平台 | Patch 目录 | Media 目录 | Addressables catalog zip | |---|---|---|---| | Windows | `Windows_PatchPack` | `MediaResources-Windows` | `catalog_StandaloneWindows64.zip` | | Android | `Android_PatchPack` | `MediaResources` | `catalog_Android.zip` | ### 5.2 Seed 端点模板 共享(非平台): ```text {CLIENT_PATCH}/{token}/TableBundles/TableCatalog.bytes {CLIENT_PATCH}/{token}/TableBundles/TableCatalog.hash ``` 每平台: ```text {CLIENT_PATCH}/{token}/{PatchDir}/BundlePackingInfo.bytes {CLIENT_PATCH}/{token}/{PatchDir}/BundlePackingInfo.hash {CLIENT_PATCH}/{token}/{PatchDir}/{catalog_zip} {CLIENT_PATCH}/{token}/{PatchDir}/{catalog_base}.hash {CLIENT_PATCH}/{token}/{MediaDir}/Catalog/MediaCatalog.bytes {CLIENT_PATCH}/{token}/{MediaDir}/Catalog/MediaCatalog.hash ``` ### 5.3 Content URL 模板 | 类型 | 模板 | |---|---| | Table 文件 | `{CLIENT_PATCH}/{token}/TableBundles/{Name}` | | Patch pack | `{CLIENT_PATCH}/{token}/{PatchDir}/{FullPatch_NNN.zip}` | | Media 文件 | `{CLIENT_PATCH}/{token}/{MediaDir}/{relative_path}` | `relative_path` 示例:`GameData/Audio/VOC_JP/JP_Airi.zip`、`Prologue/Scenario/Event/10000_Title_Sound.ogg`。 ### 5.4 校验分层 | 对象 | 算法 / 规则 | |---|---| | seed `.bytes` + `.hash` | 官方 `.hash` 为 **xxHash32(seed=0)** 的十进制文本;强校验 | | 一般已下载文件 | 本地 manifest **size + BLAKE3** | | `.zip` | 另加 ZIP central/local 结构校验 | | `catalog_*.hash` | Addressables/SBP **Hash128 文本标记**,**不是** seed 的 xxHash32 规则 | --- ## 6. Inventory 抽取规则(L3,当前实现) 实现:`adapters/src/official/inventory.rs`(**可打印串启发式**,非完整 schema 反序列化)。 | Catalog | 抽取逻辑 | 风险 | |---|---|---| | `BundlePackingInfo.bytes` | 可打印串中扩展名为 `zip` 且匹配 `FullPatch_NNN.zip`(总长 17,中间 3 位数字) | 漏抽非 FullPatch 包名(当前有意只 FullPatch) | | `TableCatalog.bytes` | 可打印串中 `.db`/`.zip` 文件名;**出现次数 ≥ 2** 才收录 | 依赖「双份列表」启发式;形态变化会漏/多 | | `MediaCatalog.bytes` | 可打印串中相对路径,扩展名 zip/mp4/png/jpg/jpeg/ogg/wav | 路径须 `is_plausible_relative_path` | **R2 待真机核对**:用服务器全量 seed 字节跑抽取,与 manifest 中 content URL 集合 diff;有未解释差异再改 inventory + fixture。 仓库内已有:`adapters/tests/fixtures`、`infrastructure/tests/fixtures/official_regression`;**不能替代**全量 release 实勘。 --- ## 7. 客户端资源请求假设(R3,服务 bat-api) | 面 | 假设(当前工程) | bat-api 行为 | |---|---|---| | 启动前资源发现 | 客户端/补丁器需要知道当前资源版本、server-info 和 client-patch 根 | `GET /v1/bootstrap` 返回 `bat` RPC 健康、release 摘要、server-info URL、client-patch base 和改写后的 Addressables root | | 服务就绪 | 运维需要区分进程存活和 release 是否可分发 | `GET /healthz` 返回 liveness + RPC refresh 诊断;`GET/HEAD /readyz` 无可分发 release 时返回 `503` | | client-patch 内容 | GET 官方 path;无业务鉴权头(资源 CDN) | `GET/HEAD /prod-clientpatch.../...` 原样字节,支持 Range | | server-info | GET JSON;字段 PascalCase(`ConnectionGroups` 等) | 可选加载并**只改** `AddressablesCatalogUrlRoot` 指向 `{public-base}/prod-clientpatch.../{token}` | | launcher bootstrap | 启动器链会先查 launcher metadata,再找到 server-info / Addressables root | `/v1/launcher/bootstrap` 与 `/api/launcher/...` 只输出资源引导兼容信息,来源是 Rust snapshot/RPC | | seed `.hash` | 纯文本十进制(可含空白) | 原样分发 | | Range / 断点 | 官方客户端下载器用 Range;bat 用 curl `.part` | `ServeContent` 支持 Range / `206` / `416` / `If-Range` | | 缓存 / 条件请求 | 资源位于 versioned root;manifest 有 BLAKE3 | ETag 优先使用 manifest BLAKE3;返回 Last-Modified、Accept-Ranges、长期 Cache-Control | | 业务 ApiUrl/Gateway | 游戏协议 | **不改写、不仿造** | Addressables 改写后客户端拼接: ```text {rewritten_root}/TableBundles/TableCatalog.bytes ≡ {public-base}/prod-clientpatch.../{token}/TableBundles/TableCatalog.bytes ``` 与磁盘映射一致。 --- ## 8. RPC 与分发发现顺序 `bat-api`(及任何 Go 服务层)发现当前 release,并由 `/v1/bootstrap` 组织为启动前资源入口: 1. `daemon.status` 2. `daemon.doctor` 3. `catalog.status`(`version.resource_root`、`addressables_root`、app/bundle) 4. `resource.manifest` 分页(url / destination / bytes / blake3) 5. 在 `resource_root` 上 Lstat 校验 Present / size **不读** `bat-status.json` / `bat-tasks.json` 作为常规路径。 生产配置:`--socket` / `BAT_API_SOCKET`,`bat-api` 与 `bat` 在同服务器、同容器或同共享文件系统环境内运行。`--resource-root` 只用于本地 fixture 或应急只读诊断,不作为生产资源根配置。`BAT_API_REFRESH_INTERVAL` 控制 bat-api 周期重读 RPC,以跟随 Rust `bat` 发布新 release。见 `cmd/bat-api/.env.example`。 --- ## 9. issue #2 / #3 样本索引(R4/R5 预置) 在服务器 release 上优先采集到 `/tmp` 隔离目录(**不入库大文件**): | 用途 | 建议路径模式 | |---|---| | Addressables(#2) | `{PatchDir}/catalog_*.zip` 解压后的 JSON/bin + 旁路 `.hash` | | UnityFS(#3) | `FullPatch_*.zip` 内抽样 `.bundle`,或已解包 bundle | | seed 加固(R2) | 各平台 `TableCatalog` / `BundlePackingInfo` / `MediaCatalog` 的 `.bytes`+`.hash` | 字段目标(#2,已有 `m_Crc` 部分):继续扩大 hash/size/CRC/依赖等可校验字段覆盖。 结构目标(#3):header / block / directory / metadata / object table 引擎级解析。 --- ## 10. 服务器实勘清单(R1,等 SSH) 连接信息到位后只读执行: 1. `readlink current` → version id 2. 顶层是否仅有官方 host 目录 + manifest/snapshot 3. manifest 条目数 vs 磁盘抽样 size 4. RPC 四步(status → doctor → catalog.status → manifest 首页) 5. 将结论写入 `docs/reports/resource-server-survey-YYYYMMDD.md`(无凭据) 所需: ```text SSH: user@host -p PORT 资源目录: .../official bat.sock 或 state-dir: ... ``` --- ## 11. 相关文档 - `docs/reports/GO_STATUS.md` — Go 边界与进度 - `docs/architecture/official-resource-backend.md` — 拉取后端总览 - `docs/reference/rpc-backend-api.md` — RPC 契约 - `docs/guides/official-resource-test-pull.md` — 用户向运行说明 - `docs/reports/CURRENT_GAPS.md` — G-009 / #2 / #3 --- ## 12. 变更纪律 1. 改 URL 模板或落盘规则 → **必须**同步本文 + 相关单测。 2. 改 inventory 启发式 → 说明覆盖的真实风险并补 fixture。 3. 真机实勘若发现与本文冲突 → **以真机为准** 修代码与本文,禁止静默分叉。