DSH 游戏商店规范
版本 1。English
游戏商店是 DeepSeek Harness(DSH)的一个插件,包名 dsh-game-store。这份规范讲五件事:一个游戏包长什么样,商店能做什么,游戏怎么上架和下架,留言板这些社区功能怎么放在 GitHub 上,商店怎么保护玩家。
想直接动手,看怎么发布你的游戏。
1. 几个词
| 词 | 意思 |
|---|---|
| 游戏包 | 一个 npm 包,里面是一个游戏的全部文件 |
| 游戏清单 | 游戏包里的 game.json,写游戏的名字、介绍、入口、图片等 |
| 目录 | dsh-game-store 仓库里的 catalog/games/,每个游戏一个小文件 |
| 目录条目 | 目录里的一个文件,指向一个游戏包的一个确切版本 |
| 校验值 | npm 给每个版本算出的 sha512-…,文件变一个字节它就变 |
| 商店 | 装在 DSH 里的 dsh-game-store 插件 |
2. 游戏包
一个游戏就是一个 npm 包,建议命名 dsh-game-<名字>。它同时是一个 DSH 插件,所以 DSH 的插件管理器能安装、更新、卸载它。
游戏是本地网页游戏:HTML、JavaScript、图片、声音这些文件,打开入口页就能玩。
2.1 目录结构
dsh-game-<名字>/
├── package.json npm 包清单
├── game.json 游戏清单
├── cordis.patch.yml 标准文件,原样照抄
├── LICENSE 许可证
├── game/ 游戏本体,入口在这里面
│ └── index.html
├── store/ 商店里展示的内容
│ ├── icon.png 图标
│ ├── cover.png 封面
│ ├── shot-1.png 截图
│ ├── trailer.mp4 视频(可选)
│ ├── about.en.md 游戏说明(英文)
│ └── about.zh.md 游戏说明(中文)
└── manual/ 手册(可选)
└── index.html
只有 game/、manual/、store/ 三个文件夹里的文件会被商店打开。
2.2 package.json
{
"name": "dsh-game-my-game",
"version": "0.1.0",
"license": "MIT",
"repository": { "type": "git", "url": "git+https://github.com/you/dsh-game-my-game.git" },
"icon": "./store/icon.png",
"files": ["game", "store", "game.json", "cordis.patch.yml", "README.md", "LICENSE"],
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } },
"dshGame": "./game.json"
}
必须有:
name、version(x.y.z)。license:一个 SPDX 名字(如MIT),或SEE LICENSE IN <文件名>。包的根目录要有许可证文件。dsh.bundle.patch:固定写"./cordis.patch.yml"。dshGame:固定写"./game.json"。
不能有:
- 任何依赖(
dependencies、peerDependencies、optionalDependencies)。游戏用到的文件全部放进包里。 - 安装脚本(
preinstall、install、postinstall、prepare)。 main、module、exports、bin、browser、dsh.client。这些会让代码进到 DSH 里运行;游戏只在自己的页面里运行。"private": true。
2.3 cordis.patch.yml
所有游戏包用同一个文件,一个字都不改:
# dsh-game-store spec 1: a game adds no plugin rows.
[]
它告诉 DSH:这个包不往 DSH 里加任何插件代码。
2.4 游戏清单 game.json
{
"spec": 1,
"id": "my-game",
"name": { "en": "My Game", "zh": "我的游戏" },
"tagline": { "en": "A short line under the name.", "zh": "名字下面的一句话。" },
"summary": { "en": "One sentence about the game.", "zh": "一句话介绍。" },
"author": { "name": "Your Name", "url": "https://github.com/you" },
"entry": "game/index.html",
"icon": "store/icon.png",
"cover": "store/cover.png",
"screenshots": [
{ "src": "store/shot-1.png", "caption": { "en": "In play", "zh": "游戏画面" } }
],
"about": { "en": "store/about.en.md", "zh": "store/about.zh.md" },
"manual": { "zh": "manual/index.html" },
"languages": ["en", "zh-CN"],
"controls": ["keyboard", "mouse", "touch"],
"players": { "min": 1, "max": 1 },
"content": { "rating": "everyone" }
}
| 字段 | 必填 | 说明 |
|---|---|---|
spec | 是 | 固定写 1 |
id | 是 | 游戏在商店里的名字:小写字母、数字、连字符,最多 40 个字符。上架后固定 |
name | 是 | 游戏名,最多 40 个字符 |
tagline | 否 | 标语,最多 80 个字符 |
summary | 是 | 一句话介绍,最多 80 个字符 |
author.name | 是 | 作者 |
author.url | 否 | 作者主页,https:// 开头 |
entry | 是 | 入口页,game/ 里的一个 HTML 文件 |
icon | 是 | 图标:正方形,至少 128×128,最大 256 KB |
cover | 是 | 封面:16:9,至少 1280×720,最大 2 MB |
screenshots | 是 | 1 到 8 张截图,每张至少 960×540,最大 2 MB;caption 可选 |
videos | 否 | 最多 3 段视频,放在 store/ 里,MP4 或 WebM,每段最大 40 MB。写法:[{ "src": "store/trailer.mp4", "poster": "store/trailer.jpg", "caption": { "en": "Trailer", "zh": "预告片" } }]。poster(视频封面图)和 caption 可选 |
about | 是 | 游戏说明,每种语言一个 Markdown 文件,每个最大 64 KB |
manual | 否 | 手册,每种语言一个 manual/ 里的 HTML 文件 |
languages | 是 | 游戏本身支持的语言,如 ["en", "zh-CN", "ja"] |
controls | 是 | 操作方式,从 keyboard、mouse、touch、gamepad 里选 |
players | 是 | 人数,{ "min": 1, "max": 4 } |
content.rating | 是 | 内容分级:everyone(所有人)、teen(13 岁以上)、mature(18 岁以上) |
content.notes | 看情况 | 内容提示,最多 200 个字符。分级是 teen 或 mature 时必填,写清有什么内容 |
版本和许可证写在 package.json 里,清单里不再写一遍。
多语言文字:name、tagline、summary、caption、content.notes 可以写成一个字符串,也可以按语言写:{ "en": "…", "zh": "…", "ja": "…" }。语言代号用小写,en 必须有。商店按 DSH 的界面语言显示,找不到就用英文。
图片:PNG、JPEG 或 WebP。
2.5 游戏说明
每个游戏包自带说明,商店直接读它来展示。说明分三部分:
- 一句话介绍(
summary):出现在商店首页的卡片上。 - 游戏说明(
about):出现在游戏页。用 Markdown 写:标题、段落、列表、粗体、表格、https://链接。HTML 标签和图片会被当成普通文字,截图请放进screenshots。 - 手册(
manual,可选):一个完整的 HTML 页面,玩家装好游戏后在游戏页点“手册”打开。手册和游戏一样离线运行,用到的图片要放在manual/里或直接嵌进页面。
2.6 入口和运行方式
玩家点“开始游戏”,商店在本机给这个游戏开一个地址(http://127.0.0.1:<端口>/),把 entry 指的页面显示出来。游戏占满整个 DSH 窗口,同时进入系统全屏。
对游戏的要求:
- 占满窗口。窗口多大就画多大,窗口变了跟着变。页面不出现滚动条。
- 离线。游戏只能读自己包里的文件。字体、脚本、图片、声音全部放进
game/,用相对路径引用。 - 存档用
localStorage或IndexedDB。每个游戏有自己固定的地址,存档互不相通;游戏更新、卸载重装以后存档还在。 - 用相对路径。入口页在
/game/…下面,写src="main.js",别写src="/main.js"。 - 游戏在一个受限的框里运行:能运行脚本、锁定鼠标、读手柄、全屏;不能弹出新窗口,不能跳转到别的网页。
2.7 大小
解压后最大 300 MB。
3. 商店能做什么
| 功能 | 说明 |
|---|---|
| 浏览目录 | 第一屏是最新上架的游戏:封面、名字、一句话介绍、安装按钮。下面是全部游戏 |
| 看游戏页 | 每个游戏有自己的页面:视频、封面和截图、游戏说明、留言板、版本、作者、许可证、大小、语言、操作方式、人数、内容分级、npm 包、源码仓库、校验值 |
| 安装 | 一键安装目录里登记的那个版本 |
| 启动 | 游戏占满窗口并进入全屏。鼠标移到窗口顶边,出现一个小条:重新开始、全屏、退出游戏 |
| 看手册 | 游戏包带手册时,游戏页有“手册”按钮 |
| 更新 | 目录里的版本比已装的新,按钮变成“更新到 x.y.z” |
| 卸载 | 删掉游戏文件,存档保留 |
| 我的游戏 | 列出已安装的游戏 |
| 在网页上玩 | 网站 dsh-game-store.com 的每个游戏页有“在网页上玩”,不装 DSH 也能玩。游戏在自己的子域名 <id>.play.dsh-game-store.com 里运行,文件和游戏包里的一样 |
商店每天读一次目录,地址是 https://dsh-game-store.com/catalog.json。读不到时用上次保存的那份;一次都没读到过,就用插件自带的那份。
手动安装的游戏包(dsh plugin add …)也会出现在“我的游戏”里,带一条提示:目录里没有它,商店没有检查过它。
4. 上架:用 GitHub
目录放在 dsh-game-store 仓库的 catalog/games/ 里,每个游戏一个文件 <id>.json:
{
"id": "my-game",
"package": "dsh-game-my-game",
"version": "0.1.0",
"integrity": "sha512-…",
"repository": "https://github.com/you/dsh-game-my-game",
"addedAt": "2026-11-13"
}
| 字段 | 说明 |
|---|---|
id | 和文件名、游戏清单里的 id 一样 |
package | npm 包名 |
version | 一个确切的版本 |
integrity | 这个版本的校验值:npm view <包名>@<版本> dist.integrity |
repository | 游戏的 GitHub 仓库 |
addedAt | 上架日期 |
status、reason | 下架时由维护者填,见第 6 节 |
上架和更新都走同一条路:
- 开发者把游戏包发布到 npm。
- 开发者向
dsh-game-store仓库提一个 Pull Request,新增或修改自己游戏的那个文件。 - CI 自动检查(第 5.1 节)。
- 维护者审核(第 5.2 节),合并。
- 合并后网站自动重新生成,目录随之更新。玩家的商店最晚一天后看到,点“刷新目录”马上看到。
每个版本都要过一次 Pull Request。 目录登记的是确切的版本和校验值,商店只装这一个。开发者在 npm 上发了新版本,目录没改,玩家那边就什么都不会变。
为什么这样设计:
- 游戏的名字、介绍、截图都放在游戏包里,目录条目只有几行。开发者改介绍,发一个新版本就行,介绍和游戏永远对得上。
- 目录是 git 里的文件,谁在什么时候上架、更新、下架了什么,历史都在。
- 审核过的是哪些文件,玩家装到的就是哪些文件,靠校验值保证。
5. 审核标准
5.1 自动检查
CI 对每个条目做这些事,全部通过才能合并:
- 条目格式正确,
id和包名在目录里没有重复。 - npm 上有这个版本,校验值和条目里的一样。
- 下载游戏包(只下载,不运行其中任何东西),逐项对照第 2 节:
package.json的必须项和禁止项、标准的cordis.patch.yml、游戏清单的每个字段、入口文件存在、图片的格式尺寸大小、英文说明存在、有许可证文件、总大小在限制内、没有符号链接、没有可执行程序和脚本文件(.exe、.sh、.node等)。 - 把包装进一个空文件夹(关掉安装脚本),确认装得上、没有带进别的包。
游戏文件里出现从网上加载的地址时,CI 给出警告:商店会拦住这些请求,游戏在玩家那里会缺东西。
开发者在自己电脑上能跑同样的检查:npx dsh-game-store check <文件夹或包名@版本>。
5.2 人工审核
维护者会装上玩一下,确认:
- 能玩:能启动,能进到游戏里,占满窗口,离线能运行。
- 说明属实:介绍、截图、内容分级和实际内容一致。
- 有权发布:是作者自己的作品,或有授权。用到的素材、字体、代码符合各自的许可证。许可证不在常见列表里的,维护者会读一遍。
- 内容合适:没有违法内容,没有针对个人或群体的仇恨内容,没有真钱赌博。
- 守规矩:没有试图联网、收集玩家信息、挖矿、绕开商店的限制,代码没有为了躲避审查做混淆。压缩过的代码可以,但源码仓库里要有原样的源码或说明。
更新已上架的游戏,重点看这次改了什么。
6. 下架规则
下架由维护者改目录条目,同样走 Pull Request,留下记录。有两级:
| 状态 | 什么时候用 | 玩家那边 |
|---|---|---|
delisted 下架 | 作者要求;作者联系不上且游戏已经不能运行;版权方有合理的投诉;说明和实际内容不符且作者不改 | 商店里搜不到,不能再安装或更新。已经装了的还能玩,游戏页会显示下架原因 |
blocked 封禁 | 发现恶意行为:试图联网偷数据、绕开限制、攻击玩家的电脑 | 商店拒绝启动,游戏页显示原因并提示卸载 |
条目写成:
{ "…": "…", "status": "delisted", "reason": "作者要求下架" }
- 下架和封禁都要写
reason,玩家看得到。 - 作者可以随时要求下架自己的游戏,提 Pull Request 或开 issue。
- 除了恶意行为,维护者下架前先在游戏的仓库或 Pull Request 里联系作者,给 7 天时间处理。
- 作者不同意下架决定,在
dsh-game-store仓库开 issue 说明。问题解决后可以重新上架。 - 恶意行为立即封禁,同时向 npm 举报这个包。
7. 社区:都放在 GitHub 上
商店没有自己的服务器。网站是静态页面,目录是仓库里的文件,游戏包在 npm 上。玩家之间的内容也照这个办法:账号就是 GitHub 账号,内容存在 GitHub 仓库里。
7.1 游戏页
每个游戏有自己的页面,网站上是 https://dsh-game-store.com/games/<id>/,商店里点游戏就能打开。页面内容全部来自游戏包:视频、截图、游戏说明、手册、各项信息。开发者改页面,发一个新版本就行。
7.2 留言板
- 每个游戏页有一块留言板。一条留言是
dsh-game-store仓库里的一个 issue,带标签game:<id>。 - 留言用 GitHub 账号:点“用 GitHub 账号留言”,打开一张已经填好游戏
id的表单,写完提交。仓库里的 Actions 会自动打上标签。 - 回复就是这个 issue 下面的评论。
- 商店和网站通过 GitHub 的公开接口读留言,只显示文字。读取不需要登录;GitHub 对不登录的读取有次数限制(每个网络地址每小时 60 次),商店把结果保留 10 分钟。
- 管理用 GitHub 自带的功能:维护者可以关闭、锁定、隐藏留言,封禁账号。关闭的 issue 留言板里就看不到了。
7.3 计划中(还没有实现)
下面这些沿用同一个办法,写在这里是为了定下方向。实现以后会补进规范。
- 网站登录:用 GitHub 账号登录网站(会请你授权读取邮箱),登录后可以直接在游戏页留言、把存档同步到自己的 GitHub。这需要一个 GitHub OAuth 应用和一小段服务端代码来换取登录凭证。
- 创意工坊:玩家给游戏做的地图、模组、皮肤。每件作品一个 npm 包,登记在
catalog/workshop/<游戏 id>/<作品 id>.json,和游戏一样提 Pull Request、过 CI、核对校验值。游戏在清单里声明自己接受哪几类作品、放在哪个文件夹;商店安装后把文件交给游戏。 - 更多材料:音轨、设定集、攻略页。放在游戏包的
extras/里,在清单里列出来,游戏页多出相应的栏目,音轨可以直接在页面上播放。 - 评分和推荐:用留言 issue 上的 👍 反应计数。
8. 安全
安装别人的游戏,就是在自己电脑上运行别人写的代码。商店做了下面这些限制,也有做不到的地方,都写在这里。
8.1 商店做到的
装什么
- 商店只安装目录里登记的包和版本。安装前向 npm 查这个版本的校验值,和目录里的不一样就停下。
- 装完以后,商店读 pnpm 记录的校验值再比一次,并按第 2 节把装好的文件检查一遍。有一项不过,就把包删掉。
- 游戏包没有依赖,所以安装一个游戏只会多出这一个包。
- 游戏包没有安装脚本;DSH 用的 pnpm 默认也不运行依赖包的安装脚本,商店从不放行。
- 游戏包的
cordis.patch.yml是空的,没有main、exports、dsh.client。所以 DSH 本身(主机进程和页面)不加载游戏的任何代码。
在哪里运行
- 每个游戏有自己的本机地址(
127.0.0.1上的一个固定端口)。浏览器把它和 DSH、和别的游戏都当成不同的网站:游戏读不到 DSH 页面里的东西,读不到别的游戏的存档。 - 这个地址只对本机开放,只提供游戏包里
game/、manual/、store/三个文件夹的文件,只读。 - 游戏页面带着一条内容安全策略:脚本、图片、字体、声音、网络请求都只能来自游戏自己的地址。发往互联网和发往 DSH 接口的请求会被浏览器拦下。
- 游戏在受限的框(sandbox iframe)里:不能弹窗口,不能把 DSH 的页面带到别处,不能提交表单。
- 在网站上玩也是同样的隔离:每个游戏一个子域名,带同一条内容安全策略,只允许被
dsh-game-store.com的页面嵌入。网站自己的地址不提供游戏文件。 - 商店自己的接口只回应本机地址,写操作只接受 JSON 请求,别的网页没法替玩家点“安装”或“卸载”。
让玩家看得见
- 游戏页写明作者、许可证、npm 包、源码仓库和校验值。
留言板
- 留言来自 GitHub 的公开接口,商店只把它当文字显示,里面的链接、图片、HTML 都不会生效。点一条留言会打开它在 GitHub 上的页面。
- 手动安装、目录里没有的游戏,商店会标出来。
8.2 商店做不到的
- 商店不逐行审查游戏代码。 自动检查查的是包的结构,人工审核是装上玩一下、翻一翻源码。藏得深的问题可能看不出来。
- 游戏的 JavaScript 是真的在你的电脑上运行。 它被关在浏览器给网页的那个圈子里:碰不到你的文件,联不了网。但它可以占用 CPU 和内存;浏览器本身要是有安全漏洞,这个圈子也可能被突破。请保持 DSH 是最新版本。
- 页面跳转拦不住。 游戏可以让自己那个框去加载另一个地址(只能发出请求,读不到结果)。DSH 自己的接口要登录凭证,商店的写操作只收 JSON,所以这样做改不了什么;但本机上别的没有保护的服务,可能会收到这种请求。
- WebRTC 这类连接不归内容安全策略管。 游戏拿不到你的数据可以外传,但理论上能用它和外界通信。
- 目录本身是信任的起点。 商店相信
dsh-game-store.com上的目录(走 HTTPS)和 npm。目录仓库或网站被人攻破,登记进去的恶意版本就会被当成正常版本。 - 手动安装的包不受这些保护。 用
dsh plugin add装一个不在目录里的“游戏包”,它要是带了main或自己的 patch,就是一个普通插件,代码直接在 DSH 里运行。商店只会提醒你它不在目录里。 - 商店页面会直接联网两处:
dsh-game-store.com(目录、图片、视频)和 GitHub(留言和头像)。这两处能看到你的网络地址。游戏本身联不了网。 - 在网页上玩,存档在浏览器里。 它和 DSH 里的存档是两份,互不相通;清掉浏览器数据,网页上的存档就没了。
- 桌面版还没有全部测过。 上面的限制在 DSH 的网页版里实测过。桌面版用的是同一套浏览器内核,但还没有在里面逐项验证。
9. 版本
这是规范的第 1 版,游戏清单里写 "spec": 1。以后有不兼容的改动,会出第 2 版;商店会同时认新旧两版,给开发者留出改的时间。
对规范有意见,在 dsh-game-store 仓库开 issue。