Skip to content

游戏存档 ​

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返回字段含义
readydata: Record<string, unknown>已取得完整结构化业务数据
empty无其他字段当前账号暂无可确认归属的结果
unsupported无其他字段当前游戏尚未开放该能力
failederror: { code, message }数据暂时读取失败,文件清单仍可使用

支持如下游戏,保留原有业务字段名,不统一不同游戏的数据结构:

游戏 / Steam AppIDdata 内容数据说明
黑神话:悟空 / 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 的“游戏存档”分类中测试查询、按需结构化数据、同步、停止等待、文件读取与只读限制;使用真实宿主与运行上下文,不再维护独立模拟小程序。