#插件开发指南
把游戏里发生的事,变成现实设备的反馈。这份文档带你从零做出一个能上架的插件。
#1. 先搞清楚数据是怎么流的
你的插件 本机 GameHub 客户端 设备
┌──────────────┐ HTTP/WS ┌────────────────────┐ IM/蓝牙 ┌────────┐
│ 读游戏状态 │ ─────────▶ │ 127.0.0.1:43002 │ ─────────▶ │ EMS │
│ 翻译成事件 │ │ 冷却 / 波形 / 分发 │ │ 飞机杯 │
└──────────────┘ └────────────────────┘ └────────┘
你只负责一件事:如实描述「刚刚发生了什么」。
强度多大、用哪个波形、发给哪台设备、要不要冷却 —— 全部由客户端根据用户的配置决定, 插件不碰这些。这条边界很重要:它让用户能用同一套设备配置玩所有游戏, 也让你不必为每种设备写一遍适配。
#2. 五分钟做出第一个插件
一个插件最少只要两个文件:
my-plugin.zip
├─ manifest.json ← 必须在压缩包根目录
└─ USAGE.md ← 给玩家看的教程
manifest.json:
{
"name": "My Game Link",
"author": "你的名字",
"version": "1.0.0",
"description": "把《我的游戏》的受伤和死亡事件发送到 GameHub。",
"yokonex": {
"source": "mygame",
"displayName": "我的游戏",
"adapter": "BepInEx 插件",
"tutorial": "USAGE.md",
"events": [
{ "name": "玩家受伤", "eventKey": "mygame.player_hurt", "commandId": "player-hurt" },
{ "name": "玩家死亡", "eventKey": "mygame.player_death", "commandId": "player-death" }
]
}
}
发一个事件,就这么一行:
curl -X POST http://127.0.0.1:43002/v1/events \
-H 'content-type: application/json' \
-d '{"source":"mygame","eventKey":"mygame.player_hurt","commandId":"player-hurt","occurredAt":"2026-09-10T04:00:00.000Z"}'
设备就动了。剩下的工作全是「怎么从游戏里可靠地拿到这个事件」。
#3. manifest.json 规范
| 字段 | 必填 | 说明 |
|---|---|---|
name |
✅ | 插件名,≤100 字 |
author |
✅ | 署名,≤60 字。可以与账号名不同 |
version |
✅ | 1.0.0 或 1.0.0-beta.1。新版本必须严格大于所有历史版本 |
description |
≤2000 字,市场列表页展示 | |
yokonex.source |
✅ | 插件的身份证,见下 |
yokonex.displayName |
✅ | 市场和客户端里展示的名字,≤60 字 |
yokonex.adapter |
✅ | 说清事件是怎么采集的,审核员会看 |
yokonex.tutorial |
包内教程文件的相对路径,强烈建议填 | |
yokonex.events |
✅ | 1~200 个事件 |
#source —— 最需要想清楚的字段
它同时是三样东西:
- 本机网关的路由段:
/v1/game-integrations/<source>/adapter-config - 市场里的唯一标识
- 一旦被你占用,别人永远无法向它发版
规则:小写字母、数字、下划线、连字符,2~64 位,字母或数字开头。
保留字(admin/api/app/client/gateway/system/v1/www/yokonex/ycygame 等)不能用。
建议直接用游戏的通行简称:cs2、stardew、7dtd、minecraft。
#eventKey —— 必须带 source 前缀
✅ mygame.player_hurt
❌ player_hurt (没前缀,会被拒)
❌ cs2.player_hurt (source 是 mygame 却用 cs2 前缀,会被拒)
强制前缀不是洁癖:它保证一个插件无法声明别家的事件键, 客户端按 eventKey 路由时也就不会串台。
#commandId
用户在客户端里把 commandId 绑到具体的设备指令和波形上。
manifest 里写的是默认值,用户可以改。
建议用可读的短横线命名:player-hurt、boss-killed。
#4. 本机网关协议
#4.1 发送事件
POST http://127.0.0.1:43002/v1/events
Content-Type: application/json
{
"source": "mygame",
"eventKey": "mygame.player_hurt",
"commandId": "player-hurt",
"occurredAt": "2026-09-10T04:00:00.0000000Z",
"sessionId": "mygame-3f8a...",
"eventId": "c91b...",
"data": { "damage": 23, "healthPercent": 61 }
}
| 字段 | 必填 | 说明 |
|---|---|---|
source |
✅ | 与 manifest 一致 |
eventKey |
✅ | 与 manifest 里声明的某一条一致 |
commandId |
✅ | 优先用网关下发的映射,见 4.2 |
occurredAt |
✅ | ISO-8601 UTC。事件发生的时刻,不是发送时刻 |
sessionId |
一次游戏会话内保持不变,便于排错 | |
eventId |
唯一 id,用于去重 | |
data |
任意附加信息,客户端会展示,未来可用于强度分级 |
#4.2 拉取用户配置
GET http://127.0.0.1:43002/v1/game-integrations/<source>/adapter-config
{
"enabled": true,
"endpoint": "ws://127.0.0.1:43002/mygame",
"mappings": { "mygame.player_hurt": "player-hurt" }
}
建议每 5 秒拉一次,并遵守:
enabled为false→ 用户关掉了联动,不要发送任何事件mappings里有这个 eventKey → 用它给出的commandId(用户改过绑定)mappings有内容但没这个 key → 用户禁用了这个事件,不要发- 返回 404 → 网关还不认识你的 source,用 manifest 里的默认
commandId继续发
最后一条让插件在客户端还没加上你的游戏时就能用,用户不必等。
#5. 事件设计:三条必须遵守的规矩
这一节比协议本身更重要。设备接的是人的身体,事件设计不当会直接变成糟糕甚至危险的体验。
#5.1 一定要做冷却
尸潮里「玩家受伤」每秒能触发十几次。没有冷却,设备会被刷成近乎常通。
参考值:
| 事件类型 | 建议冷却 |
|---|---|
| 高频(受伤、击杀、命中) | 1.0 ~ 1.5 秒 |
| 中频(流血、击晕、体力耗尽) | 4 ~ 12 秒 |
| 低频(感染、中毒、濒死) | 15 ~ 30 秒 |
| 罕见(血月、升级、通关) | 2 ~ 10 秒 |
#5.2 一定要做总量限流
光有单事件冷却不够 —— 十种事件各自不超频,加起来照样能把设备刷满。 再加一道全局闸门:10 秒窗口内最多 10~15 条。
#5.3 过期事件必须丢弃
网关没开、机器卡顿时事件会积压。恢复的瞬间把攒下的几十条一次性灌出去, 设备会为几秒前发生的事情连续动作 —— 这既莫名其妙又不安全。
入队时记下时间戳,出队时超过 3 秒就直接丢掉。
参考实现:
yokonex-7dtd插件的GatewayClient.cs,三道闸门都在里面, 且游戏线程只做入队、绝不做 IO。
#5.4 强度不归你管,但请写清楚
役次元官方建议电击强度设在 15~40。 插件不设置强度,但你应该在 USAGE.md 里提醒用户按官方设备说明设上限。
#6. 采集游戏事件的几条路子
| 方式 | 适用 | 风险 |
|---|---|---|
| 官方 Mod API | 有 Mod 支持的游戏(7DTD、Minecraft、星露谷、RimWorld…) | 无。首选 |
| 官方遥测接口 | CS2 的 GSI、LOL 的 Live Client、战争雷霆的 8111 端口、模拟飞行的 SimConnect | 无。首选 |
| BepInEx / MelonLoader | Unity 单机游戏 | 单机无风险 |
| 日志文件监听 | 会写详细日志的游戏 | 无风险,但延迟较高 |
| 画面 / 音频识别 | 上面全都没有时的最后手段 | 无封号风险,但延迟高、准确率一般 |
| 读内存 / 注入 | —— | 带反作弊的游戏绝对不要做,会封号 |
带内核反作弊的游戏(无畏契约的 Vanguard、逃离塔科夫的 BattlEye、 三角洲行动的 ACE 等)只能走画面/音频识别。不要读内存,不要注入,不要 hook。
7 Days to Die 这类游戏还要注意:开着 EasyAntiCheat 时游戏会跳过所有 DLL Mod, 必须让用户以 No EAC 方式启动,并在 USAGE.md 里写清楚。
#7. 打包
压缩包根目录必须直接是 manifest.json,不能套一层文件夹:
✅ my-plugin.zip
├─ manifest.json
└─ USAGE.md
❌ my-plugin.zip
└─ my-plugin/
├─ manifest.json
└─ USAGE.md
Windows 上右键「压缩」文件夹会得到错误的那种。请进入文件夹全选内容再压缩。
另外:
- ZIP 条目路径必须用正斜杠。PowerShell 的
Compress-Archive写的是反斜杠, 会被拒收 —— 用ZipFile.CreateEntry手动写,或用 7-Zip。 - 不接受加密压缩包、符号链接、
..路径、绝对路径。 - 压缩方法只接受 store 和 deflate。
#8. 上传前自检(强烈建议)
node scripts/check-plugin.mjs my-plugin.zip
这个命令调用的就是服务端那份校验代码,不是近似实现。 这里过了上传就一定过;这里报什么错,服务器就报同样的错。 不需要靠反复上传试探规则。
它同时会输出你上传时要用的 SHA-256。
#9. 发版
# 1. 注册(首次)
curl -X POST https://developer.ycygame.org/api/developer/register \
-H 'content-type: application/json' \
-d '{"username":"yourname","password":"至少十位的密码","email":"you@example.com"}' \
-c cookies.txt
# 2. 上传。x-content-sha256 必填,值来自第 8 步
curl -X POST https://developer.ycygame.org/api/developer/plugins/mygame/versions \
-b cookies.txt \
-H 'content-type: application/zip' \
-H "x-content-sha256: $(sha256sum my-plugin.zip | cut -d' ' -f1)" \
--data-binary @my-plugin.zip
上传成功后版本状态是 pending,审核通过才会出现在市场和客户端里。
#审核看什么
adapter写的采集方式是否属实、是否安全(不接受读内存/注入带反作弊的游戏)- 包内可执行文件(
.exe/.dll/.bat/.ps1)的来源和用途 - USAGE.md 是否说清了安装步骤和风险
- 事件设计是否做了冷却和限流
带 DLL 不会被拒 —— 游戏 Mod 本来就要带。但审核员会看它是干什么的, 所以附上源码和构建脚本能显著加快审核。
#10. 错误对照表
| 错误码 | 含义 | 怎么改 |
|---|---|---|
missing_sha256 |
没带 x-content-sha256 头 |
跑一遍第 8 步拿到哈希 |
sha256_mismatch |
声明的哈希与服务端算的不符 | 文件在传输中被改了,重传 |
manifest.not_at_root |
manifest.json 套了一层目录 | 见第 7 节 |
manifest.missing |
根目录没有 manifest.json | 同上 |
manifest.invalid |
字段校验不过 | 响应的 detail 里逐条列了问题 |
manifest.tutorial_missing |
声明的教程文件不在包里 | 检查路径拼写 |
source_mismatch |
URL 里的 source 与包内不一致 | 对齐两者 |
zip.backslash |
条目路径用了反斜杠 | 换打包工具,见第 7 节 |
zip.zip_bomb |
某个文件压缩比超过 200:1 | 通常是打包了巨大的空白/重复文件 |
zip.duplicate_entry |
包里有同名条目 | 重新打包 |
version_exists |
这个版本号已经发过 | 换新版本号 |
version_not_newer |
版本号不比历史版本大 | 版本号只能往上走 |
duplicate_content |
这个文件已经上传过了 | 内容没变就不用发新版 |
forbidden + source 已占用 |
这个 source 是别人的 | 换一个 source |
payload_too_large |
单次上传超过 100MB | 用分片上传接口 |
#11. 参考实现
yokonex-7dtd(七日杀) —— 完整可读的样板:
- 17 个事件,覆盖受伤/死亡/击杀/中毒/断肢/血月/昼夜
- 三道闸门(每事件冷却 + 10 秒窗口限流 + 3 秒过期丢弃)全部实现
- 游戏线程只入队、后台线程做 IO,网关没开也不卡游戏
- 网关 404 时回落到内置默认 commandId,装了就能用
- 刻意不用 Harmony:能从玩家状态读出来的,就不要打补丁