Appearance
游戏存档
gameSaves 可以查询某款 Steam 游戏的存档、同步并等待结果,并把存档文件交给小程序。受支持游戏可按需读取后端已有的结构化数据;如果需要原始内容,开发者仍可下载 ZIP 自行解压、解析。
权限声明
在 package.json 中声明:
json
{
"heybox": {
"permissions": {
"gameSaves": { "enabled": true }
}
}
}游戏存档 API 不弹出单独的用户授权确认,也不要求首次调用由点击触发。仍需登录小黑盒并声明 gameSaves。支持正式发布版本、具备访问资格的预览版本,以及通过有效开发启动票进入的本地调试会话;服务端按本次启动的版本或本地快照校验权限,并始终校验当前 Steam 账号及存档归属。
查询、同步和文件下载不要求额外声明 network 或 filesystem。如果开发者要把文件上传到自己的服务,仍需另外申请网络权限并告知用户。
三个顶层方法与文件对象
| 方法 | 用途 |
|---|---|
gameSaves.list(gameId) | 返回 Promise<GameSaveInfo[]>,无存档时为空数组;只查询文件清单 |
gameSaves.sync(gameId, { signal? }) | 返回 Promise<void>;成功正常完成,失败、取消或超时抛错 |
saves[i].files[j].downloadFile({ signal? }) | 下载一个文件,返回可调用 readBytes() 的只读 FileHandle |
gameSaves.getLatestStructuredData(gameId) | 按需读取已有结构化数据,返回 Promise<GameSaveStructuredData> |
gameId 使用 Steam AppID 字符串,例如黑神话悟空的 "2358720"。开发者只需传入 gameId,无需传入或保存 accountId;Runtime 向服务端解析当前 Steam 存档账号,并固定本轮同步使用的账号,服务端持续校验归属。sync() 可直接调用,无需先 list()。文件对象自动绑定查询时的游戏、账号、存档及文件,账号变化时旧对象的下载会被拒绝,需重新查询。切换小黑盒登录账号后,应关闭再打开小程序;既有客户端的页面账号快照不保证在当前页面实时更新。
读取文件
在握手成功后启用按钮,将下面的函数绑定到点击事件:
ts
import hbSDK from '@heybox/hb-sdk'
async function onReadSaveClick() {
const gameId = '2358720'
const saves = await hbSDK.gameSaves.list(gameId)
const save = saves[0]
const info = save?.files[0]
if (!save || !info) return // 页面提示用户先同步
const file = await info.downloadFile()
const zipBytes = await file.readBytes()
// 由开发者选择 ZIP 库解压,再交给游戏专用解析器。
console.log('已读取字节数', zipBytes.byteLength)
}同步由 Runtime 内部每 2 秒查询状态,最长等待 5 分钟。小程序不保存任务 ID,也不自行轮询:
ts
const controller = new AbortController()
await hbSDK.gameSaves.sync(gameId, {
signal: controller.signal,
})
const latest = await hbSDK.gameSaves.list(gameId)同步失败抛出原业务错误;取消返回 REQUEST_CANCELLED,超时返回 REQUEST_TIMEOUT。取消、超时或关闭页面只停止等待,不撤销已经创建的服务端任务;稍后可重新查询列表。服务端现有同步冷却规则仍然生效。
Runtime 会通过 Host 私有存储保存已收到的同步任务 ID,按小程序、黑盒用户和游戏隔离,并绑定 Steam 存档账号。取消、超时、关闭页面或暂时断网后,再次调用同一个游戏的 sync() 会先重新校验运行资格和当前账号,再查询原任务:仍在同步或上传则继续等待;已成功则正常完成 Promise;已失败则抛出原失败原因。任务结束后清理记录,下次主动同步重新走创建流程,不长期复用成功结果。
只有服务端明确返回任务不存在或已被替换时,才清理旧记录并尝试创建新任务;网络或服务临时故障保留记录并返回错误。任务有效性以服务端查询为准,不根据本地保存时间推断任务已结束。每次调用独立等待最多 5 分钟,不对外暴露内部任务阶段或进度回调。
恢复能力需要 Host 的持久存储可用;不支持或读写失败时降级为当前 Runtime 内记录,无法保证关闭页面后恢复。若页面在收到并保存 taskId 前关闭,前端也无法保证恢复;创建与重复请求仍受后端现有任务复用和冷却规则约束。同一 Runtime 内并发发起同一游戏的进行中同步会复用任务,各调用可独立取消等待;跨窗口的并发创建仍由后端处理。这项能力不包含 ZIP 下载断点续传,也不保存 Steam 凭证、授权结果或存档内容。
已接入登录恢复入口的 Mobile 宿主会在同步返回 STEAM_LOGIN_REQUIRED 时提示“去登录 Steam”。用户完成原生登录后,返回小程序重新点击同步;SDK 仍保留本次失败结果,不自动重试,也不返回 Steam 凭证。仅绑定 Steam 不代表登录会话一直有效。未接入恢复入口的宿主仍需由页面提示用户在黑盒中重新登录。
列表默认只返回元数据和文件操作对象,不预先下载 ZIP。调用 downloadFile() 时才下载,返回的 FileHandle 可重复读取。文件对象的元数据只读,修改查询参数或后续查询不会改变旧对象的下载目标;同步后重新 list() 获取最新文件对象。不要把列表序列化保存后当作可操作对象使用,JSON 不保留 downloadFile() 方法,恢复页面时应重新查询。
读取完整结构化数据
gameSaves.getLatestStructuredData(gameId) 独立读取当前 Steam 账号最新已有的完整结构化业务数据,无需先调用 list() 或 sync(),也不依赖文件清单是否为空。gameId 为必填的 Steam AppID 字符串;不接受 accountId。
ts
const gameId = '2358720'
const result = await hbSDK.gameSaves.getLatestStructuredData(gameId)
if (result.status === 'ready') {
// data 保留该游戏已有的业务字段,按请求时的 gameId 理解数据结构。
console.log(result.data)
console.log('角色等级', result.data.level)
}
// 需要原始文件时另行查询、下载。
const saves = await hbSDK.gameSaves.list(gameId)
const file = await saves[0]?.files[0]?.downloadFile()每次调用都独立请求,Runtime 通过轻量 check 获取并固定本次 Steam 账号,再请求完整业务数据;后端复核运行资格和账号。不缓存结果,不查询存档文件清单。后续调用会重新获取当前账号。
返回值为 Promise<GameSaveStructuredData>,按 status 区分结果。成功时只有 status 和 data,不再返回 game、schemaVersion 或 overview。data 的类型为 Record<string, unknown>,不同游戏的业务字段不同,使用字段前应按对应游戏的数据结构校验。
status | 返回字段 | 含义 |
|---|---|---|
ready | data: Record<string, unknown> | 已取得完整结构化业务数据 |
empty | 无其他字段 | 当前账号暂无可确认归属的结果 |
unsupported | 无其他字段 | 当前游戏尚未开放该能力 |
failed | error: { code, message } | 数据暂时读取失败,文件清单仍可使用 |
支持如下游戏,保留原有业务字段名,不统一不同游戏的数据结构:
| 游戏 / Steam AppID | data 内容 | 数据说明 |
|---|---|---|
黑神话:悟空 / 2358720 | 等级、周目、配装、收集及再战/连战记录 | 查看字段说明 |
任务栏英雄 / 3678970 | 关卡、英雄、符文、宠物、背包、仓库及物品实例 | 查看字段说明 |
影之刃零 / 4115450 | 全部已保存槽位、默认槽位及装备、任务、Boss、收集进度 | 查看字段说明 |
完整是指后端已经保存的业务结果,不表示 ZIP 中的所有原始字节,也不附带数据库文档 ID、黑盒用户 ID、SteamID、玩家资料或好友数据。安全整数使用数字,超出 JavaScript 安全整数范围的整数使用字符串以避免精度丢失;字段内容不做数量截断。一次调用返回完整对象,不是流式读取或分页。
该方法只读取已有结果,不现场解压、不触发新的处理任务,也不保证与 saves 中某个 ZIP 一一对应。sync() 成功只表示存档同步完成,不保证结构化数据已经更新。账号切换、运行资格失效和请求取消使本次读取抛错;普通读取故障返回 failed(STRUCTURED_DATA_UNAVAILABLE)。已有文件清单不受本次读取失败影响。
各游戏的字段说明包含结构示意、字段含义和关联方式。示意基于数据定义,不是真实账号响应;旧记录可能缺少字段或返回 null,请兼容未知字段,并保留原字段拼写和大小写。
首期范围
- 只开放现有新版 Steam 云同步链路保存的文件;游戏过滤规则仍然生效,不保证获得某游戏的全部原始文件或历史版本。
- 不读取本机游戏目录,不包含手动上传、收藏、公开存档和旧版存档记录。旧记录可通过重新同步生成新版结果。
- 每个文件为 ZIP,
originalPath是其中游戏文件的原始相对路径;列表大小是 ZIP 字节数。 - 文件保留在 Runtime 管理的沙箱缓存中,通过只读句柄按需读取;下载时不保留整份内存副本,也不向小程序暴露缓存路径。
- 单文件最多 8 MiB,当前会话通过文件对象的
downloadFile()累计获取最多 32 MiB;重复获取同一文件也计入额度,丢弃 JavaScript 对象不会归还额度,当前没有单句柄释放方法。应复用已获取的句柄,超限需关闭后重新打开小程序。返回句柄只读,不能写回或覆盖游戏存档。 - 每次读取或查询句柄属性仍需联网复核运行资格和账号归属;复核不会重新列举存档。账号变化、运行资格变化或关闭小程序后,句柄失效;已交给开发者的字节无法收回。
- 本能力由配套后端、SDK 与 H5/Runtime 实现,复用客户端已有的账号、下载和沙箱文件能力,不要求新增客户端接口。缺少原语或运行上下文时返回
METHOD_FORBIDDEN。真实本地联调需使用hb-sdk dev的手机或 PC 入口获取可信开发上下文,并部署支持开发/预览启动的后端。浏览器 Mock 不会读取真实 Steam 存档,不能替代真实 Host 验证。
常见结果
| 结果或错误码 | 页面处理 |
|---|---|
空列表 / NO_GAME_SAVE | 提示该账号暂无可获取的存档 |
STEAM_LOGIN_REQUIRED | 提示用户重新登录 Steam 后重试 |
STEAM_ACCOUNT_CHANGED / SYNC_TASK_CHANGED | 重新查询账号或发起新一轮同步 |
PARTIAL_SYNC_FAILED | 部分文件同步失败,保留上一份完整结果,提示重试 |
FILE_TOO_LARGE / FILE_QUOTA_EXCEEDED | 提示首期文件或会话容量限制 |
FILE_ACCESS_REVOKED | 丢弃旧句柄,重新打开小程序并查询下载 |
RECENT_SAVE_AVAILABLE | 五分钟内已有成功上传的存档,直接调用 list(gameId) 使用已有文件;需要结构化结果时调用 getLatestStructuredData(gameId),已有文件不保证已经解析 |
SYNC_REJECTED | 展示服务端原因,例如该游戏禁用云同步 |
列表 sizeBytes 来自既有存档记录,是估算值;准确字节数使用下载后 file.stat() 的 size。单文件限制按实际下载字节数执行。
调试示例
统一在 apps/h5/test/test_user_miniprogram 的“游戏存档”分类中测试查询、按需结构化数据、同步、停止等待、文件读取与只读限制;使用真实宿主与运行上下文,不再维护独立模拟小程序。