役次元 Yokonex Hub 开发文档

#插件开发指南

把游戏里发生的事,变成现实设备的反馈。这份文档带你从零做出一个能上架的插件。


#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.01.0.0-beta.1新版本必须严格大于所有历史版本
description ≤2000 字,市场列表页展示
yokonex.source 插件的身份证,见下
yokonex.displayName 市场和客户端里展示的名字,≤60 字
yokonex.adapter 说清事件是怎么采集的,审核员会看
yokonex.tutorial 包内教程文件的相对路径,强烈建议填
yokonex.events 1~200 个事件

#source —— 最需要想清楚的字段

它同时是三样东西:

  1. 本机网关的路由段:/v1/game-integrations/<source>/adapter-config
  2. 市场里的唯一标识
  3. 一旦被你占用,别人永远无法向它发版

规则:小写字母、数字、下划线、连字符,2~64 位,字母或数字开头。 保留字(admin/api/app/client/gateway/system/v1/www/yokonex/ycygame 等)不能用。

建议直接用游戏的通行简称:cs2stardew7dtdminecraft

#eventKey —— 必须带 source 前缀

✅ mygame.player_hurt
❌ player_hurt          (没前缀,会被拒)
❌ cs2.player_hurt      (source 是 mygame 却用 cs2 前缀,会被拒)

强制前缀不是洁癖:它保证一个插件无法声明别家的事件键, 客户端按 eventKey 路由时也就不会串台。

#commandId

用户在客户端里把 commandId 绑到具体的设备指令和波形上。 manifest 里写的是默认值,用户可以改。 建议用可读的短横线命名:player-hurtboss-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 秒拉一次,并遵守:

最后一条让插件在客户端还没加上你的游戏时就能用,用户不必等。


#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 上右键「压缩」文件夹会得到错误的那种。请进入文件夹全选内容再压缩。

另外:


#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审核通过才会出现在市场和客户端里

#审核看什么

带 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(七日杀) —— 完整可读的样板: