Skip to content

附录 E · 复现指南与待验证清单 ​

本系列反复强调"结论尽量给出出处"。这一页是那份承诺的兑现方式:教你把这套结论自己重做一遍,并把目前还没验证清楚的问题列出来。

如果你在本页发现哪一条已经被验证或推翻,欢迎到 CCB-Team.github.io 开 Issue 或 PR——纠错比补充新内容更有价值。

一、四条取证路径 ​

任何一条协议结论,最终都来自下面四条路径之一。看清"这条结论是靠什么得出的",就知道它的可信度边界在哪。

路径工具能确定什么局限
抓包mitmproxy / Fiddler / 自建代理线上真实的字段名、大小写、顺序、类型看不到"客户端为什么这么判"
UHT 转储UE4SS 的转储功能客户端的结构体与枚举定义(字段类型的权威来源)只有类型,没有行为;且不含 server_options 这类"字符串里的 JSON"键
蓝图反编译KismetDecompiler客户端行为:判定条件、配置键、默认值字面量可能丢失;有渲染上限(见第四节)
参考实现对照fyserver(C#)· kards-server-go(Go)可运行验证:这套字段客户端真的能过实现本身可能有历史包袱

三者的关系是:UHT 给类型,反编译给行为,抓包给事实,实现给交叉验证。四者不一致时,以能实际跑通客户端的为准,并把差异记下来(本页第三节就是这么来的)。

二、复现步骤 ​

1) 抓包 ​

bash
# 方式一:把客户端指到本地(改 IPRedirection 的两个字符串槽,见第 10 章)
#   两个槽必须【等长替换】,否则二进制会损坏
#   主槽  "https://kards.live.1939api.com/"
#   次槽  "https://kards.live.1939api.com/config"

# 方式二:不解包,直接在本机起最小服务端,用 hosts 或代理把域名指过来
mitmproxy -p 8080        # 或 mitmweb

抓包时优先看这四处,它们是最容易踩坑的地方:

观察点为什么重要
Authorization: JWT <token>前缀是 JWT ,不是 Bearer ;两个都兼容的写法更稳
Content-Type 是否带 charset官服自己就不统一:/config 带 charset=utf-8,/ 与 /session 不带 → 客户端能接受 charset(附录 F 实测)
字段名的大小写线上是 status,UHT 里是 Status,说明匹配是宽容的
未匹配时的响应体是字符串 null 而不是 JSON null——这一条最容易在抓包里看出来

2) UHT 转储 ​

转储产物按模块分目录,本系列的引用都来自这两处:

<游戏>\KARDS\Binaries\Win64\ue4ss\UHTHeaderDump\
├── kards\Public\          ← 协议层:会话、端点、对局、卡牌
└── KardsCore\Public\      ← 基础枚举:阵营、稀有度、卡集

它是纯头文件,所以用 grep 而不是通读:

bash
rg -n 'struct FMatch2' -A 30 kards/Public/Match2.h
rg -n 'enum class EFactionEnum' -A 14 KardsCore/Public/EFactionEnum.h

3) 蓝图反编译 ​

先把蓝图资产导出成可读格式,再交给反编译器批量处理:

bash
KismetDecompiler --input  <蓝图导出目录> \
                 --usmap  <m.usmap> \
                 --uhtdump <UHTHeaderDump> \
                 --inline --opt \
                 --out    Decompiled/kards
# --inline 把 dispatch 体内联回事件函数;--opt 做临时量值流内联
# --uhtdump 会额外输出每个函数的原生签名注释,读伪代码时非常有用

产物是"每资产一个 .cpp",函数按 // Function: <名字> 分段。读法请先看附录 B 第 0 节,那里列了 Kismet 伪代码的常见形态(return 之后仍有代码、NotEqual_ByteByte 链等价于 switch、out 参数等)——不了解这些形态会误判产物是坏的。

4) 最小验证闭环 ​

任何一条新结论都套用这个循环,它是本系列最常用、也最省时间的方法:

改一个字段 / 换一个常量
        │
        ▼
跑客户端 → 走到那个界面
        │
        ├─ 正常:记下响应,作为"已知良好样本"
        └─ 报错:二分删字段,直到能过

在线状态与断线判负这类"时序相关"的问题,用 安全 QA 的镜像与回放工具 做离线复现比反复开客户端快得多。

三、待验证清单 ​

下面这些点,本系列已经明确标注为推断或未定。它们不是"待办",而是诚实的边界——把边界写出来,比假装全都清楚更有用。

大部分已在 2026-09-25 用官服实测回答

其中 1、2、3、4、5、6、7、9 已被附录 F · 官服实测对照与附录 H · 真实客户端抓包实录确认(含两处纠正:library 的主键是 card_type 而非 id/deck_id;请求 provider 是 device_id 而 claim 才是 device)。仅 8、10 仍需客户端实测或走商店链路;11–13 是新增的未解项,都以真实抓包为背景。

#问题目前掌握一分钟验证法
1✅ 已确认:是绝对地址官服 GET / 的 endpoints.* 与 /session 的 *_url 全部是含 host 的绝对 URL见 附录 F
2✅ 已确认:取第二段官服 root = https://kards.live.1939api.com,点分第二段正是 live见 附录 F
3✅ 已确认:两者都不是,是 card_type官服 library 外层是 {cards, new_cards}(与正文一致),条目主键是资产名 card_type,另有 count / gold_card_count / player_id / recently_crafted_count,没有 id 也没有 deck_id见 附录 F
4✅ 已确认:是对象,键随动作变真实客户端抓包:XStartOfGame → {"playerID":<id>};XActionStartOfTurn → {"side":"left","56":"20"};XActionEndOfTurn 再加 reason。左右侧的数字键不同(left 用 "56"、right 用 "75")见附录 H与第 13 章
5基本确认:不校验签名官服 token 用 RS256;私服手搓的 HS256/无签名令牌能用,说明客户端不验签。另:claim 里 provider 是 device,请求里才是 device_id见 附录 F
6✅ 已确认:取客户端项目版本官服 versions = ["Kards 1.60"],而登录 DTO 的 version 是 Kards 1.48.24871.launcher,该组合登录成功 → 闸门比的不是 DTO 的 version 字段见 附录 F
7✅ 已确认:同一份响应内就混用一份 /session 里 server_time 点分、last_logon_date ISO+6 位、new_player_login_reward.reset 空格分隔;且 GET / 的 server_time 是 ISO——同名键在两条接口格式不同见 附录 F
8部分确认:不拦登录探测时官服 xserver_closed 非空而 /session 仍返回 200 → 维护提示是客户端侧展示逻辑弹窗样式仍需客户端实测
9✅ 已确认:请求 device_id,claim deviceprovider: "device" 会被官服拒为 Unknown provider;/session 必填字段只有 provider + provider_details见 附录 F
10EKardsProvider 与登录 provider 的关系确认不是同一件事;官服 provider_details.payment_provider 实测为 XSOLLA,完整取值集合需走商店链路抓 provider_details.payment_provider 的取值范围

| 11 | ❓ 官服动作提交的额外门槛 | 按真实客户端的完整格式(含 local_subactions、保留 base64 填充、开着带 match_id 心跳的 WS、客户端同款请求头)提交,官服仍回 400 ACTION_ERROR。已排除:信封、动作名、action_data 形状、填充、包头部取值、WS 会话、请求头。唯一线索:官服开局载荷没有 local_subactions,而那台私服有 → 疑似能力协商导致两边格式不同 | 抓一份客户端 → 官服的动作提交(代理 + 证书拦截),解出请求体 | | 12 | ❓ 出牌/攻击类动作的字段 | 现有两局样本只覆盖 XStartOfGame / XActionStartOfTurn / XActionEndOfTurn / ActionEndMatch 四种 | 打一局更长的人机(对官服或对私服),把 POST /matches/v2/{id}/actions 的请求体全部留下 | | 13 | ❓ 动作流的保留上限 | 实测 min_action_id 是闭区间下界、5 条以内全量重放、无截断;但"一局几十条动作后是否截断"未知 | 找一局动作多的对局,用 min_action_id=1 轮询看返回条数 |

为什么要专门列一张"不知道"的表

协议逆向里最危险的不是"没搞懂",而是把推测当事实写进代码——这类错误会在很久之后以一个毫不相关的形式暴露出来。把不确定项集中列出,至少保证:读者知道哪里要自己验,以及怎么验。

四、已知工具局限 ​

同样的道理,工具的能力边界也写清楚:

局限表现绕过办法
部分蓝图无法反编译少数资产是裸导出数组、缺 NameMap/Imports,反编译器直接跳过(本次统计有 6 个,如 card_event_royal_research、card_unit_hampshire_regiment)换用其它 UAsset 工具重新导出,或从 .uexp 手工解析
巨型资产产物过大BP_BaseCard 的反编译产物达百 MB 级,整体阅读不现实先 rg -n '// Function:' 定位函数,再按行号读那一段
文本字面量丢失渲染为 UAssetAPI.Kismet.Bytecode.FScriptText(典型是 Format 的模板串)结合上下文推断,或看调用处的参数个数
数组下标/通配未解析渲染为 <ArrayVariable> / <ArrayIndex> / <Target>需要时回看原始字节码
工具链对 UAssetAPI 版本敏感游戏更新后资产版本变化可能导致解析失败固定工具与游戏版本,并记录版本号

五、参与与纠错 ​

  • 发现本文有误:直接开 Issue,附上抓包片段或复现步骤,比只给结论有用得多。
  • 补齐上面任一待验证项:同样欢迎,尤其是第 1、4、5 条——它们影响实现选择。
  • 本站内容的写作原则见团队介绍;协议相关结论的实测/推断界线,在各篇末尾都有交代。