v0 plus附加功能
- 新增 unlockmusic.py:解锁(解禁)歌曲 + 解禁 Master / Re:Master 难度,整个流程复用 upsert.py 的 run() - 买票前置闸门 api/ChargeGuard.py:必须先跑过 GetUserCharge,且同进程只允许买一次 - buy_ticket.py 票源改为 GetUserCharge,不再调用 GetGameCharge - UpsertUserAll 与买票的冷却统一为 15 秒 - api/ 文件名统一 PascalCase(文件名 = 函数名) - 项目改名 Astral,README 加 Logo、鸣谢与开发者说明 - 自检 284 项
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
<h1 align="center">WaHappy</h1>
|
||||
<p align="center">
|
||||
<img src="icon/Astral.png" alt="Astral" width="220">
|
||||
</p>
|
||||
|
||||
<h1 align="center">Astral</h1>
|
||||
|
||||
<p align="center">
|
||||
<b>舞萌DX 的游戏助手</b><br>
|
||||
@@ -11,21 +15,22 @@
|
||||
|
||||
## 这是什么
|
||||
|
||||
WaHappy 是一个用 **Python 纯网络方式**与舞萌DX(SDGB 国服)游戏服务器交互的工具集。
|
||||
Astral 是一个用 **Python 纯网络方式**与舞萌DX(SDGB 国服)游戏服务器交互的工具集。
|
||||
|
||||
它不修改游戏、不注入进程,只是把游戏客户端做的事用脚本重做一遍:
|
||||
扫码登录 → 取数据 → 上传成绩 → 登出。
|
||||
扫码登录 → 取数据 → 上传成绩 / 解锁歌曲 → 登出。
|
||||
|
||||
> 协议实现全部来自反编译源码与真机网络日志的双向验证,
|
||||
> 每个关键常量在代码注释里都标了**源码行号**。
|
||||
|
||||
---
|
||||
|
||||
## 当前状态:v0
|
||||
## 当前状态:v0 plus 附加功能
|
||||
|
||||
| 脚本 | 状态 |
|
||||
|---|---|
|
||||
| **`upsert.py`** | ✅ **可用** —— 扫码登录 → 上传一局成绩 → 落库回查 → 登出 |
|
||||
| **`unlockmusic.py`** | ✅ **可用** —— 解锁(解禁)歌曲 / 难度(复用 `upsert.py` 整套流程) |
|
||||
| **`buy_ticket.py`** | ✅ **可用** —— 购买功能票 |
|
||||
| **`use_ticket.py`** | ✅ **可用** —— 消耗当前持有的功能票 |
|
||||
| **`reincarnate.py`** | 🔧 已实现(**未实跑**)—— 账号数据迁移(转生) |
|
||||
@@ -59,35 +64,44 @@ v0 的开发过程中,我反复把同一个目标拆成小段丢给 AI,结
|
||||
## 项目框架
|
||||
|
||||
```
|
||||
WaHappy/
|
||||
Astral/
|
||||
├── config.py 运行时可配置项(机台标识 / 歌曲数据 / 日志级别)★ 需自建
|
||||
├── config.py.example 配置模板
|
||||
├── icon/ Logo
|
||||
├── upsert.py ★ 主脚本:扫码登录 → 上传成绩 → 登出
|
||||
├── unlockmusic.py 主脚本:解锁(解禁)歌曲(复用 upsert.py 的 run())
|
||||
├── buy_ticket.py 主脚本:购买功能票
|
||||
├── use_ticket.py 主脚本:消耗当前持有的功能票
|
||||
├── reincarnate.py 主脚本:账号数据迁移(转生)
|
||||
├── netdelivery.py 主脚本:查 / 下 .app / .opt 更新包
|
||||
├── selftest.py 零网络自检(249 项,判据 = 源码 + 真机日志)
|
||||
├── selftest.py 零网络自检(284 项,判据 = 源码 + 真机日志)
|
||||
│
|
||||
├── api/ ★ 所有与服务器交互的模块(一个接口一个脚本)
|
||||
│ ├── __init__.py 统一导出 + 日志自动跟踪
|
||||
│ ├── maimai_net.py 网络核心(AES / deflate / URL 混淆 / Cookie)
|
||||
│ ├── log_setup.py 日志(INFO / DEBUG 双模式)
|
||||
│ ├── upsert_user_all.py 上传成绩(36 节点 / 110 字段 / 差分)
|
||||
│ ├── get_need_user_data.py 结算快照编排 + 落盘 data/<userId>.json
|
||||
│ ├── MaimaiNet.py 网络核心(AES / deflate / URL 混淆 / Cookie)
|
||||
│ ├── LogSetup.py 日志(INFO / DEBUG 双模式)
|
||||
│ ├── ChargeGuard.py 买票前置闸门(必须先跑过 GetUserCharge)
|
||||
│ ├── UpsertUserAll.py 上传成绩 / 解锁歌曲(36 节点 / 110 字段 / 差分)
|
||||
│ ├── GetNeedUserData.py 结算快照编排 + 落盘 data/<userId>.json
|
||||
│ └── ... 40+ 个接口脚本
|
||||
│
|
||||
├── data/ 每账号一份用户数据快照(<userId>.json)★ 不入库
|
||||
├── download/ netdelivery.py 下载的 .app / .opt ★ 不入库
|
||||
├── logs/ 日志输出(latest.log + 日期.log)★ 不入库
|
||||
└── source/ 反编译源码 ★ 不入库,需自备
|
||||
└── logs/ 日志输出(latest.log + 日期.log)★ 不入库
|
||||
```
|
||||
|
||||
**设计约定**:`api/` 里每个脚本 = 一个接口,对外暴露一个**能传参的函数**。
|
||||
主脚本只负责编排,日志和协议细节都在 `api/` 内部。
|
||||
|
||||
**命名约定**:服务端 API 名里带 `Get` 的,**脚本名与函数名一律带 `get` / `Get`**
|
||||
(不要模棱两可)。所以取数脚本全是 `get_*.py`,函数是 `GetXxx`。
|
||||
**命名约定**:**文件名 = 函数名**,都按首字母大写的驼峰切分,一一对应:
|
||||
|
||||
| 服务端接口 | 脚本 | 函数 |
|
||||
|---|---|---|
|
||||
| `GetUserDataApi` | `api/GetUserData.py` | `GetUserData()` |
|
||||
| `UpsertUserAllApi` | `api/UpsertUserAll.py` | `UpsertUserAll()` |
|
||||
| `UserLoginApi` | `api/UserLogin.py` | `UserLogin()` |
|
||||
|
||||
服务端 API 名里带 `Get` 的,脚本名与函数名一律带 `Get`(不要模棱两可)。
|
||||
|
||||
### 分支说明
|
||||
|
||||
@@ -97,7 +111,10 @@ WaHappy/
|
||||
| **`docs`** | 孤儿分支 —— 只有 `docs/`,开发文档与踩坑记录 |
|
||||
|
||||
两个分支**没有共同的提交历史**,各自独立。
|
||||
`source/` 是反编译源码,**需要自备**。
|
||||
|
||||
> 🛠 **给开发者**:协议实现对照的反编译源码(`Assembly-CSharp` / `AMDaemon.NET` 等)
|
||||
> 不入库,需要自己准备一份放在 `source/`;代码注释里的行号都指向它。
|
||||
> 只是用工具的话**完全不需要**这个目录。
|
||||
|
||||
---
|
||||
|
||||
@@ -136,7 +153,7 @@ copy config.py.example config.py # Windows
|
||||
### 3. 跑自检
|
||||
|
||||
```bash
|
||||
python selftest.py # 零网络自检,应该 249 项全绿(不依赖你的 config)
|
||||
python selftest.py # 零网络自检,应该 284 项全绿(不依赖你的 config)
|
||||
```
|
||||
|
||||
### 主脚本一览
|
||||
@@ -144,6 +161,7 @@ python selftest.py # 零网络自检,应该 249 项全绿(不依赖
|
||||
| 脚本 | 干什么 | 副作用 | 需要二维码 |
|
||||
|---|---|---|---|
|
||||
| `upsert.py` | 上传一局成绩 | ★ 有 | ✅ |
|
||||
| `unlockmusic.py` | 解锁(解禁)歌曲 / 难度 | ★ 有 | ✅ |
|
||||
| `buy_ticket.py` | 购买功能票 | ★ 有 | ✅ |
|
||||
| `use_ticket.py` | 消耗当前持有的功能票 | ★ 有 | ✅ |
|
||||
| `reincarnate.py` | 账号数据迁移(转生) | ★ 有 | ✅(目标号;源号可选) |
|
||||
@@ -168,8 +186,9 @@ python upsert.py "<二维码>" --send
|
||||
|
||||
# 常用开关
|
||||
python upsert.py "<二维码>" --send --debug # 日志里带完整收发报文
|
||||
python upsert.py "<二维码>" --send --cooldown 0 # 跳过发包前 60 秒冷却
|
||||
python upsert.py "<二维码>" --send --cooldown 0 # 跳过发包前 15 秒冷却
|
||||
python upsert.py "<二维码>" --send --no-verify # 跳过上传后的落库回查
|
||||
python unlockmusic.py "<二维码>" --send --music 11414 # 解锁歌曲 / 解禁难度
|
||||
```
|
||||
|
||||
**换歌 / 换成绩只改 `config.py` 的 `PLAY_*`,不用动任何代码。**
|
||||
@@ -199,7 +218,7 @@ python upsert.py "<二维码>" --send --extra-data extra.json
|
||||
| `1` | **新增** —— 旧数据里**没有**同主键的条目 | INSERT |
|
||||
| `2` | **无变化** —— 只有 `userFavoriteList` 用,其他节点不会出现 | 跳过 |
|
||||
|
||||
主键见 `api/upsert_user_all.py` 的 `DIFF_PRIMARY_KEYS`
|
||||
主键见 `api/UpsertUserAll.py` 的 `DIFF_PRIMARY_KEYS`
|
||||
(如 `userKaleidxScopeList` 用 `gateId`、`userMusicDetailList` 用 `musicId`+`level`)。
|
||||
|
||||
> ⚠️ **对已存在的条目填 `1` 会 HTTP 500** —— 服务端会去 INSERT 一条重复记录。
|
||||
@@ -210,10 +229,52 @@ python upsert.py "<二维码>" --send --extra-data extra.json
|
||||
|
||||
---
|
||||
|
||||
## unlockmusic.py —— 解锁(解禁)歌曲
|
||||
|
||||
```bash
|
||||
python unlockmusic.py "<二维码>" --music 11414 # 预检,不发包
|
||||
python unlockmusic.py "<二维码>" --music 11414 --send # ★ 真实解锁
|
||||
python unlockmusic.py "<二维码>" --music 1085,11414 --send # 多首,逗号分隔
|
||||
python unlockmusic.py "<二维码>" --music 11414 --send --no-unlock-remaster
|
||||
```
|
||||
|
||||
**它没有自己的协议实现** —— 整个流程直接复用 `upsert.py` 的 `run()`:
|
||||
扫码 → 预览 → 登录 → `UpsertUserAll` → 落库回查 → 登出,一模一样。
|
||||
唯一多出来的东西是「要解锁哪些歌」这个入参。
|
||||
|
||||
**解锁也不是单独的接口** —— 它是往 `userItemList` 里补几条 `UserItem`:
|
||||
|
||||
| itemKind | 解锁什么 |
|
||||
|---|---|
|
||||
| `5` | 歌曲本体(`MusicUnlockList`) |
|
||||
| `6` | Master 难度(`MusicUnlockMasterList`) |
|
||||
| `7` | Re:Master 难度(`MusicUnlockReMasterList`) |
|
||||
|
||||
条目形如 `{"itemKind":5,"itemId":1085,"stock":1,"isValid":true}`。
|
||||
|
||||
- **标准谱(`musicId < 10000`)的 Master / Re:Master 天生解锁**,只写 `kind5`;
|
||||
只有 DX 谱(`>= 10000`)才需要写 `kind6` / `kind7`。
|
||||
- 服务器上**已存在的条目会自动跳过** —— 重复插入会让服务端直接 HTTP 500。
|
||||
- Re:Master 还受活动开关约束,那是机台侧的事,脚本改不了;写了也可能进游戏仍是灰的。
|
||||
|
||||
| 参数 | 说明 |
|
||||
|---|---|
|
||||
| `--music ID[,ID...]` | 要解锁的 musicId,可重复、可逗号分隔(`--unlock-music` 也认) |
|
||||
| `--send` | 真正发包(不加就是预检,零副作用) |
|
||||
| `--no-unlock-master` | 不解禁 Master |
|
||||
| `--no-unlock-remaster` | 不解禁 Re:Master |
|
||||
| `--cooldown N` / `--no-verify` / `--debug` | 和 `upsert.py` 完全一样 |
|
||||
|
||||
> 依据反编译源码 `VOExtensions.cs:981-1008`(四个解锁列表并进 `userItemList`)
|
||||
> 与 `NotesListManager.cs:415/455`(`if (id < 10000) return true;`)。
|
||||
> 这部分**没有真机抓包可对照**(机台侧的解锁不上报),是按源码实现的。
|
||||
|
||||
---
|
||||
|
||||
## buy_ticket.py —— 购买功能票
|
||||
|
||||
```bash
|
||||
python buy_ticket.py "<二维码>" --list # 只列当前票券和可买的票
|
||||
python buy_ticket.py "<二维码>" --list # 登录后只列当前票券,不购买
|
||||
python buy_ticket.py "<二维码>" --charge-id 2 # 预检:只看报文
|
||||
python buy_ticket.py "<二维码>" --charge-id 2 --send # ★ 真实购买
|
||||
```
|
||||
@@ -228,10 +289,17 @@ python buy_ticket.py "<二维码>" --charge-id 2 --send # ★ 真实购买
|
||||
|
||||
### 它做的安全检查
|
||||
|
||||
1. 买之前查 `GetGameChargeApi` —— 目标票不在可购买清单里就**连登录都不做**直接退出
|
||||
2. 跑前查 `GetUserChargeApi` —— **账号已持有功能票时直接拒绝购买**(重复购买有封号风险)
|
||||
1. 跑前查 `GetUserChargeApi` —— **账号已持有功能票时直接拒绝购买**(重复购买有封号风险)
|
||||
2. 买票前 **15 秒等待**(贴近真机节奏)
|
||||
3. 跑后再查一次确认状态
|
||||
|
||||
> **票源只看 `GetUserChargeApi`**(账号自己的票券表),不再看 `GetGameChargeApi`
|
||||
> (那是机台本地的上架清单,跟账号状态无关)。
|
||||
>
|
||||
> `UpsertUserChargelog` 内部还有两道硬闸门:
|
||||
> **必须先跑过 `GetUserCharge`**(`api/ChargeGuard.py` 记账),
|
||||
> 以及**同一账号同一进程只允许买一次票** —— 都拦在发包之前。
|
||||
|
||||
---
|
||||
|
||||
## use_ticket.py —— 消耗当前持有的功能票
|
||||
@@ -372,7 +440,8 @@ if __name__ == "__main__":
|
||||
- ✅ 账号数据快照落盘(`data/<userId>.json`)
|
||||
- ✅ 账号数据迁移(转生)
|
||||
- ✅ 查 / 下 `.app` / `.opt` 更新包
|
||||
- ✅ 零网络自检 249 项
|
||||
- ✅ 解锁歌曲 / 解禁 Master 与 Re:Master 难度
|
||||
- ✅ 零网络自检 284 项
|
||||
- ✅ MelonLoader 抓包 Mod(`gamehook/`)
|
||||
|
||||
**将来可能会加**
|
||||
@@ -412,6 +481,7 @@ if __name__ == "__main__":
|
||||
- **志成🍥** —— 提供脱壳 DLL
|
||||
- [**WorkBuddy AI**](https://www.workbuddy.ai/)—— 提供 AI 模型
|
||||
- **DeepSeek V4.1 Flash** —— 主要代码编写
|
||||
- **GPT-Image-2** —— Logo 设计
|
||||
- [**NVIDIA NIM**](https://build.nvidia.com/models)、
|
||||
[**AMD**](https://developer.amd.com.cn/radeon/tokenfactory)、
|
||||
[**Kilo**](https://kilo.ai/)—— 提供 AI 模型
|
||||
|
||||
Reference in New Issue
Block a user