Files
Astral/docs/项目总览.md
T
Txiaochen 41909b9169 docs: WaHappy 开发文档
孤儿分支,只含 docs/。内容包括:
- 项目总览 / 主脚本编写指南 / 日志指南
- 上传成绩开发文档 / UpsertUserAll 踩坑清单(28 条)
- 真机 Hook 对照报告 / 实跑验证记录
- 购买功能票流程(含被服务端拒绝的排查过程)
  · 扫码登录协议技术规范
2026-10-06 14:42:32 +08:00

12 KiB
Raw Blame History

舞萌DX 纯网络脚本工程 · 项目总览

项目名 WaHappy —— 舞萌DX 的游戏助手。 面向使用者的说明见根目录 README.md; 本文件是开发向的主文档,开工前先读它;遇到「该放哪、该怎么写」的问题以本文件为准。


一、这个工程是什么

用 Python 脚本,通过纯网络(纯 HTTP) 形式直接与舞萌DX(SDGB 国服)的游戏服务器交互, 复刻游戏客户端的协议行为。

对象 是什么 对应反编译源码
游戏服务器 玩家数据读写:扫码登录、取数、上传成绩、登出 source/Assembly-CSharp/
纯网络形式 脚本直接收发 HTTP 报文(AES 加密 JSON),不依赖运行中的游戏客户端 source/Assembly-CSharp/Net/
扫码登录 微信二维码换 userId + token,走 aimedb source/ChimeLib.NET/

二、目录结构

wahappy-v0-goal/
├── config.py              ★ 运行时可配置项(密钥/设备/区域/歌曲数据/日志级别)
├── upsert.py                ★ 主脚本范例:扫码登录 → 上传成绩 → 登出
├── buy_ticket.py          ★ 主脚本范例:扫码登录 → 购买功能票 → 登出
├── selftest.py            零网络自检(220 项,判据=源码+真机日志+真机抓包)
│
├── api/                   ★ 所有与服务器交互的脚本(一个 api 一个脚本)
│   ├── __init__.py        统一导出 + 日志跟踪登记
│   ├── log_setup.py       统一日志(INFO / DEBUG 双模式)
│   ├── maimai_net.py      网络核心(AES / deflate / URL 混淆 / Cookie)
│   ├── user_getter_common.py  取数脚本公共底座
│   ├── chime.py           扫码登录
│   ├── upsert_user_all.py ★ 上传成绩
│   └── ...                其余见第六节
│
├── source/                ★ 反编译源码 + 网络日志,只读查证用
├── docs/                  ★ 文档
└── logs/                  ★ 日志输出(latest.log + 日期.log)

三、api/ 脚本契约

api/ 里的每个脚本是一个功能模块,对外暴露函数;函数能接收参数, 内部完成「组包 → 发包 → 解析 → 返回结果」的全过程。

def 某Api函数(必填参数, *, 可选参数...) -> 结果:
    """一句话说明(会出现在日志的 [接口说明] 里)"""
    ...

参数形态规律:

  • 业务数据(user_id / token / 二维码 / playlog_id)→ 位置参数
  • 配置项(client_id / region_id / timeout_ms)→ 关键字参数,可省略走默认值

四、日志

4.1 两种模式

模式 输出内容
INFO(默认) 只展示 api 脚本的运行情况和结果
DEBUG 在 INFO 基础上,额外展示向 api 发送的数据和api 返回的数据

切换只改 config.py 一行:

LOG_LEVEL = "info"     # 只看流程
LOG_LEVEL = "debug"    # 连收发数据一起看

4.2 运行提示

每个 api 函数工作时会提示开始,结束时提示结束:

=== ParseQRCode 开始运行 ===
  [接口说明] 解析二维码并换取 userId 与 token。
  [位置参数] 'SGWCMAID2610022219463A297AF4CA...'
  [运行结果] 成功 tuple(2 项)(耗时 412 ms)
=== ParseQRCode 运行结束 ===

DEBUG 模式额外输出:

-> 请求 GetGameSettingApi
      URL    : https://maimai-gm.wahlap.com:42081/Maimai2Servlet/mE2s3Jhd/83a5...
      请求头 : {"Content-Type":"application/json","Mai-Encoding":"1.55"}
      请求体 :
      {
        "placeId": 1000,
        "clientId": "TESTCLIENT1"
      }

<- 响应 GetGameSettingApi
      状态码 : 200
      耗时   : 312 ms
      响应体 :
      {
        "isAouAccession": false,
        "gameSetting": { ... }
      }

4.3 日志文件(两种形式同时存在、同时写入)

文件 模式 说明
logs/latest.log 覆盖(w) 只包含当次主脚本运行产出的日志,下次运行被覆盖
logs/YYYY-MM-DD.log 追加(a) 存放当日产生的所有日志

每行都带 [YYYY-MM-DD HH:MM:SS] 时间戳。

4.4 主脚本怎么写

from api import setup_logging, teardown_logging, ParseQRCode, UserLogin

def main():
    setup_logging("我的主脚本")      # 开头一句;api 调用从此自动记录
    try:
        user_id, token = ParseQRCode(qr)
        login = UserLogin(user_id, token)
    finally:
        teardown_logging()            # 结尾一句(不写也会在退出时自动收尾)

不需要写装饰器,不需要自己埋点 —— api/__init__.py 在导入时就统一套好了日志外壳。


五、协议实现要点(全部有源码依据)

环节 依据 要点
URL 生成 Packet.cs:485 + NetQuery.cs:14 BASE_URL + md5(接口名 + "MaimaiChn" + "8bF76dE9")
请求封装 NetHttpClient.cs:262-300 JSON → 原始 deflate → 0x78 0x9C + Adler32(大端) → AES-256-CBC
响应解封装 NetHttpClient.cs:355-380 AES 解密 → 去 2 字节头 / 4 字节尾 → deflate → JSON
AES 参数 CipherAES.cs:11-12 Key 32B / IV 16B / CBC / PKCS7
请求头 NetHttpClient.cs:174-190 Mai-Encoding: 1.55、Content-Encoding: deflate、UA = 混淆名#userId
number 头 Packet.cs:187-189 + PingChecker.cs:227 = Ping 心跳的往返毫秒(GameManager.PingMsec),只由 Ping 刷新;除 Ping 外每个接口都带
Cookie Packet.cs:190-193,228-234 ★ UserLoginApi 响应会下发 Set-Cookie: JSESSIONID=...,之后每个带 userId 的请求都会带上;requests.Session 自动处理,但必须复用同一个 client(get_default_client())
playSpecial VOExtensions.cs:846-855 + GameManager.cs:332-378 位反转32(Random(1,1037933)*2069 + CalcSpecialNum);标准 ACA 机台 CalcSpecialNum = 1024 = 0x400
扫码签名 技术规范 §4 SHA256(chipID + timestamp + KEY) 大写;不带 Content-Type
loginDateTime PacketUserLogin.cs:29 「本地墙上时间当 UTC」= 标准 Unix 秒 + 时区偏移

★ 一个必须注意的坑:Accept-Encoding 必须为空

requests 默认发 Accept-Encoding: gzip, deflate。本协议响应体是 AES 密文(二进制), 服务端若据此做内容编码会返回空 body。C# 端不需要是因为 HttpWebRequest 默认 AutomaticDecompression = None。所以 maimai_net.py 固定置空该头。


六、API 清单

6.1 会话链路(6 个)

脚本 函数 对应接口
chime.py ParseQRCode(qr, *, chip_id) aimedb /wc_aime/api/get_data
gamesettings.py GetGameSettings(*, place_id, client_id) GetGameSettingApi
ping.py Ping(*, client_id) Ping
userpreview.py UserPreview(user_id, token, *, ...) GetUserPreviewApi
userlogin.py UserLogin(user_id, token, *, region_id, place_id) UserLoginApi
userlogout.py UserLogout(user_id, token, login_dt, *, logout_type) UserLogoutApi

6.2 上传成绩(2 个)

脚本 函数 说明
upsert_user_all.py UpsertUserAll(user_id, playlog_id, *, login_date_time, auth_date_time, token, snapshot, extra_data, play, ...) ★ 一个函数跑完整条结算链路:抓快照 → 组包 → 前置接口 → 自检 → 60 秒冷却 → 发包 → 落库回查。唯一有副作用的接口
upsert_user_all.py derive_play_characters(snapshot) 从服务器快照推「本局 5 只角色 + 游玩前等级」,免得手填的 config.PLAY_CHARACTERS 过期
upsert_user_all.py load_extra_data(extra) / merge_extra_data(user_all, extra) 把外部 JSON(dict / JSON 串 / 文件路径)自动匹配插入 36 节点
upsert_user_all.py verify_upsert_landing(user_id, snapshot, play) 发包后重新拉服务器数据,确认真的落库
upsert_snapshot.py fetch_upsert_snapshot(user_id, ...) 一键抓结算基线快照(UpsertUserAll 内部会按需自动调)
upsert_user_chargelog.py UpsertUserChargelog(user_id, charge_id, login_date_time, ...) ★ 购买功能票(UpsertUserChargelogApi):自动取当前票券/档案、算 validDate 与 price、闸门、发包
upsert_user_chargelog.py calc_valid_date(now) / ticket_price(id) / ticket_name(id) 有效期算法(-4h+90d)、票的 creditNum、票名

6.3 取数(一个接口一个脚本)

脚本 函数 对应接口
userdata.py UserData GetUserDataApi
user_extend.py UserExtend GetUserExtendApi
user_option.py UserOption GetUserOptionApi
user_character.py UserCharacter GetUserCharacterApi
user_ghost.py UserGhost GetUserGhostApi
user_map.py UserMap / UserMapAll GetUserMapApi
user_login_bonus.py UserLoginBonus / UserLoginBonusAll GetUserLoginBonusApi
user_rating.py UserRating GetUserRatingApi
user_item.py UserItem / UserItemAll GetUserItemApi
user_music.py UserMusicDetail / UserMusicDetailAll GetUserMusicApi
user_course.py UserCourse / UserCourseAll GetUserCourseApi
user_charge.py UserCharge GetUserChargeApi
user_favorite.py UserFavorite / UserFavoriteAll GetUserFavoriteApi
user_activity.py UserActivity GetUserActivityApi
user_mission.py UserMissionData GetUserMissionDataApi
user_intimate.py UserIntimate GetUserIntimateApi
user_shop_stock.py UserShopStock GetUserShopStockApi
user_favorite_item.py UserFavoriteItem / UserFavoriteItemAll GetUserFavoriteItemApi
user_rival.py UserRivalData / UserRivalMusic(All) GetUserRivalDataApi / GetUserRivalMusicApi
user_friend_bonus.py UserFriendBonus GetUserFriendBonusApi
user_kaleidx_scope.py UserKaleidxScope / GameKaleidxScope GetUserKaleidxScopeApi / GetGameKaleidxScopeApi
user_region.py UserRegion GetUserRegionApi
user_recommend_music.py UserRecommendRateMusic / UserRecommendSelectMusic GetUserRecommendRateMusicApi / GetUserRecommendSelectMusicApi
user_new_item.py UserNewItemList GetUserNewItemListApi
game_ranking.py GetGameRanking GetGameRankingApi
game_event.py GetGameEvent GetGameEventApi
game_charge.py GetGameCharge GetGameChargeApi
game_weekly_data.py GetGameWeeklyData GetGameWeeklyDataApi
game_tournament_info.py GetGameTournamentInfo GetGameTournamentInfoApi
game_ng_music_id.py GetGameNgMusicId GetGameNgMusicIdApi

七、新增一个 api 要做什么

  1. 在 api/ 下新建脚本,定义一个能传参的函数
  2. 函数首行写 docstring(会变成日志的 [接口说明])
  3. 用 from .log_setup import log_info, log_request, log_response 记日志
  4. 用 from .maimai_net import request_dict 发包
  5. 在 api/__init__.py 里导入并加进 _API_FUNCS ← 漏了这步就不会有运行提示

八、相关文档

文件 内容
docs/上传成绩开发文档.md UpsertUserAll 的 36 节点 / 110 字段 / 差分语义 / 真机对照
docs/主脚本编写指南.md ★ 写自己的主脚本:三条铁律 / 最小骨架 / 四个流程模板 / 常见错误
docs/购买功能票流程.md ★ 登录 → 买票 → 登出:接口结构 / 源码流程 / validDate 算法 / 待抓包确认项
docs/UpsertUserAll踩坑清单.md ★ 28 条坑的逐条记录:现象 / 根因 / 源码依据 / 正确做法 / 自检项 + 发包前 Checklist
docs/实跑验证记录.md ★ 真实跑通记录:returnCode=1 + 回读确认 playCount 1260→1261,含完整时间线与落库证据
docs/真机Hook对照报告.md ★ MelonLoader Mod 真机抓包(70 请求)与本实现的逐项对照:number / Cookie / CalcSpecialNum / 剩余差异说明
docs/UpsertUserAll实跑对比.md 逐字段对比明细(110 字段 × 真机 vs 本工程)
docs/UpsertUserAll落库排查报告.md 「returnCode=1 但没落库」的根因分析
docs/日志与主脚本指南.md 日志模块完整参考 + 主脚本写法
docs/舞萌DX_扫码登录协议技术规范.md 扫码登录协议(上一代整理)
source/2026-10-05.log 真机网络日志(本工程最重要的判据)

新增 api 后请同步更新第六节清单。