docs: WaHappy 开发文档

孤儿分支,只含 docs/。内容包括:
- 项目总览 / 主脚本编写指南 / 日志指南
- 上传成绩开发文档 / UpsertUserAll 踩坑清单(28 条)
- 真机 Hook 对照报告 / 实跑验证记录
- 购买功能票流程(含被服务端拒绝的排查过程)
  · 扫码登录协议技术规范
This commit is contained in:
Txiaochen
2026-10-06 14:42:32 +08:00
commit 41909b9169
15 changed files with 34059 additions and 0 deletions
+175
View File
@@ -0,0 +1,175 @@
# UpsertUserAll 落库排查报告
> **背景**:`python upsert.py "<二维码>" --send` 修复闸门后能真实发包,
> 服务端返回 `returnCode=1`,但**数据未写入服务器**。
> 本文档记录已完成的全部排查与结论。
---
## 一、结论速览
| 项 | 状态 |
|---|---|
| 闸门放行 | 已修复,`returnCode=1` |
| 报文与真机对齐 | **非预期差异归零** |
| 数据落库 | **未落库**(`point`/`playCount` 回查不变) |
| 根因 | **未确定**,已排除 6 类可能 |
---
## 二、已修复的 5 个 Bug
### Bug 1:`upsert.py` 漏传 `confirm`(你遇到的直接原因)
`upsert.py` 原来只传 `dry_run=not opts["send"]`,但 `UpsertUserAll` 的规则是
`confirm=None` 时取 `config.UPSERT_CONFIRM`(默认 `False`),所以照样被拦。
```python
# 修复前
dry_run=not opts["send"],
# 修复后
dry_run=not opts["send"],
confirm=bool(opts["send"]), # 关键
```
### Bug 2:`userExtend` 缺 2 个字段
| | Get 侧 | Upsert 侧 |
|---|---|---|
| 键数 | 14 | **16** |
| 差 | — | `isPhotoAgree`、`isGotoCodeRead` |
这两个字段**只在 Upsert 侧出现**,必须补上。
### Bug 3:`userRating.udemae` 有 7 个冗余大写字段
服务器 Get 响应里 `npcWinNum` 与 `NpcWinNum` **并存**(23 键):
```json
// Get 侧(source/2026-10-05.log:87)
{ "npcWinNum": 0, ..., "NpcWinNum": 0, "NpcLoseNum": 0, ... } // 23 键
// Upsert 侧(source/2026-10-05.log:123)
{ "npcWinNum": 0, ... } // 16 键
```
源码 `Net.VO.Mai2/UserUdemae.cs` 只声明了**小写的 16 个**,所以大写那批是
服务端冗余输出,回传时必须裁掉。修复前请求体比真机大 119 字节。
### Bug 4:`userCharacterList` 排序错
| | 顺序 |
|---|---|
| `charaSlot` | [395, 101, 400402, 400401, 200401] |
| 真机 `userCharacterList` | **[101, 395, 200401, 400401, 400402]**(按 characterId 升序) |
回传时按 `characterId` 升序,不是按 `charaSlot` 顺序。
### Bug 5:扫码用的 chipID 形式错
| 用途 | 正确值 | 源码依据 |
|---|---|---|
| aimedb 扫码 | `XXXX-XXXXXXXXXXX`(**完整**) | `ChimeReaderManager.cs:250` `KeychipId.Value` |
| 游戏服务器 | `TESTCLIENT1`(短值) | `PacketGetGameSetting.cs:23` `KeychipId.ShortValue` |
我原来扫码也用了短值。已按源码修正为完整形式。
---
## 三、报文对齐最终状态
```
顶层键 : 7/7
36 节点形态 : 36/36
userData : 82/82 键
userPlaylog : 110/110 键
userExtend : 16/16 键
udemae : 16/16 键
非预期差异 : 0
请求体大小 : 17158 vs 17161 字节(差 3 字节,来自数值位数)
```
**深度递归对比(逐字段、逐元素)后,除「不同次登录必然不同」的值外,零差异。**
---
## 四、已排除的可能(6 类)
| # | 假设 | 验证方式 | 结果 |
|---|---|---|---|
| 1 | 时序问题(写入有延迟) | 发送后 T+0 / T+20s / T+45s 三次回查 | 都不变 |
| 2 | 差分节点为空导致「看不出变化」 | 直接改绝对值(`point` 4110→4111) | 仍不写 |
| 3 | 字段缺失/多余 | 深度递归键集合 + 值对比 | 已归零 |
| 4 | `number` 请求头缺失 | 补上 `Packet.cs:170` 要求的头 | 仍不写 |
| 5 | `playSpecial` 值不对 | 查源码 `CalcSpecialNum` = 硬件标志位 | 非根因 |
| 6 | 缺少 `GetUserNewItemList` 前置 | 完整复刻真机 121→123 流程 | 仍不写 |
### 关于第 2 项的探测细节
这是最关键的一次探测 —— 直接 `patch` 把 `userData.point` 从 4110 改成 4111 发上去:
```
baseline point = 4110
上传成绩成功:returnCode=1
T+ 0s point = 4110
T+20s point = 4110
T+45s point = 4110
```
**连最简单的整数字段都不写**,说明不是「数据不对」而是「整包没被接受」。
---
## 五、当前判断
服务端**收下了**请求(返回正确的 `apiName`),但**没有应用**。
可能的方向(未验证):
1. **账号/机台绑定校验** —— token 可能绑定了特定 keychip,而 `XXXX-XXXXXXXXXXX`
只是文档里的示例值,不是**你实际机台**的 keychip。
若服务端校验「token 对应的机台 ≠ 请求里的 chipID」,就会静默拒收。
2. **需要真实游玩产生的会话状态** —— 真机的 Upsert 发生在**真实游玩之后**,
服务端可能记录了「本次 loginId 下有一局真实游玩」才允许结算。
纯网络脚本没有这个前置状态。
3. **服务端侧的风控/审计** —— 真机与脚本的 TLS 指纹、请求节奏等特征不同。
---
## 六、需要你提供的信息
要继续定位,最有价值的是:
| # | 信息 | 为什么关键 |
|---|---|---|
| 1 | **你机台的真实 Keychip ID** | 若 `XXXX-XXXXXXXXXXX` 不是你的机台,方向 1 就成立 |
| 2 | 该机台在 `keychip.csv` 里的对应行 | 可确认 keychip 与店铺/区域的绑定关系 |
| 3 | 真机跑一局后立刻导出 Upsert 报文 | 可拿同账号同机台的最新报文做 1:1 对比 |
> `source/keychip.csv` 有 229KB 全量机台对照表,如果你能告诉我机台所在的
> 店铺 ID,我可以从中查出正确的 keychip。
---
## 七、本次排查的正面成果
即使尚未落库,这次排查也修掉了 **5 个真实 Bug**,其中 3 个(`userExtend` 缺字段、
`udemae` 冗余字段、`userCharacterList` 排序)是**只有做深度值对比才能发现**的:
- 修复前:报文与真机有 11 处非预期差异
- 修复后:**0 处**
自检 129/129 仍全绿。
---
## 八、复现方式
```powershell
# 1) 真实发送 + 落库验证(会写服务器,不可撤销)
python upsert.py "<二维码>" --send
# 2) 零网络自检
python selftest.py
```