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

20 KiB
Raw Blame History

舞萌DX(maimai DX)扫码登录协议技术规范

来源文档:

  • docs-old/舞萌DX_aimedb调用链分析.md —— 完整调用链、二维码格式、ChimeLib/aimedb 关系
  • docs-old/chimelib协议逆向.md —— 纯网络实现细节、签名算法、抓包验证、关键坑点

适用场景:纯网络(纯 HTTP)复刻舞萌DX国服微信扫码登录,获取 userId 与 token 协议路径:路径 A —— 微信扫码(HTTPS + JSON,chimelib_dll.dll + WinHTTP) 非适用:路径 B —— 传统 Aime 物理卡(TCP 二进制,amdaemon_api.dll + IPC)


一、架构总览

1.1 两条 aimedb 通信路径

路径 名称 协议 核心组件 你实际用的
A 微信扫码 HTTPS + JSON chimelib_dll.dll (WinHTTP) ✅ 是
B 传统 Aime 物理卡 TCP 二进制 amdaemon_api.dll (IPC) ❌ 否

1.2 调用链(路径 A)

游戏客户端 Assembly-CSharp
    │ ChimeReaderManager (驱动扫码、取结果)
    ▼
ChimeLib.NET.dll (托管封装)
    │ Chime / ChimeUnit / ChimeResult / CCommGetUserData / CCommServAliveCheck
    ▼
chimelib_dll.dll (x64 原生,62KB,静态链接 WinHTTP)
    │ CCommGetUserData_Create/Execute/IsEnd/GetUserID/GetToken
    │ CCommServAliveCheck_Create/StartCheck/Execute/GetIndex/IsAlive
    ▼
aimedb 服务器集群
    │ https://ai.sys-all.cn/ (主)
    │ https://ai.sys-allnet.cn/ (备)
    ▼
POST /wc_aime/api/get_data  →  返回 userID + token
POST /wc_aime/api/alive_check  →  返回 "alive"

1.3 关键数据流向

二维码串(SGWCMAID...) 
    → ChimeLib.NET.CCommQRCode.IsValid() 校验
    → EraseHashIdentifier() 取 [20:] 64位载荷
    → CCommGetUserData_Create(strGameID="MAID", strChipID, strCommonKey="", strQRData=载荷, strTitleKey="SDGB", serverURLIndex)
    → WinHTTP HTTPS POST /wc_aime/api/get_data
    → 响应 {errorID, userID, token, key, timestamp}
    → ChimeResult.GetChimeId() → userId (uint)
    → ChimeResult.GetToken() → token (string)
    → OperationManager.SetToken(userId, token) 缓存
    → 后续游戏服务器 API 携带 token

二、二维码格式规范

2.1 完整二维码字符串(84 字符)

结构:SGWC(4) + MAID(4) + timestamp(12) + payload(64 hex) = 84 字符

偏移    长度    字段           说明                    示例
0–3     4       微信标识        常量 "SGWC"              SGWC
4–7     4       GameID         "MAID"=舞萌DX             MAID
8–19    12      时间戳          yymmddHHMMss (UTC+9)    260613133715
20–83   64      载荷/哈希       64位十六进制(疑似SHA-256) 77503FD2...427759BCA5

2.2 校验规则(CCommQRCode.IsValid())

def is_valid(qr: str) -> bool:
    return len(qr) == 84 and qr[0:4] == "SGWC" and qr[4:8] == "MAID"

2.3 传给原生层的数据(EraseHashIdentifier())

def erase_hash_identifier(qr: str) -> str:
    """去掉前 20 字符,返回 64 位载荷"""
    return qr[20:]  # 即 payload 部分

2.4 二维码 URL ⇄ 字符串互转

URL 格式:

https://wq.wahlap.net/qrcode/req/{GameID+timestamp+payload}.html?l=...&t=...&d=...
                                      └──────────── 80 字符 ────────────┘

提取规则:

from urllib.parse import urlparse
import re

def url_to_qr_string(url: str) -> str:
    """URL → 完整 84 字符二维码串"""
    path = urlparse(url).path
    match = re.search(r"/qrcode/req/([^/]+)\.html", path)
    if not match:
        raise ValueError("URL 路径格式不符")
    return "SGWC" + match.group(1)  # 前加 SGWC 前缀

def qr_string_to_url_body(qr: str) -> str:
    """完整二维码串 → URL 路径段(80字符,无 SGWC)"""
    return qr[4:]  # 去掉 SGWC

查询参数(l/t/d):不参与机台协议,仅为微信侧生成二维码时的元数据。


三、服务器端点与网络协议

3.1 服务器地址

环境 域名 IP 端口 TLS
主 ai.sys-all.cn 81.71.193.236 443 TLSv1.2
备 ai.sys-allnet.cn (同解析) 443 TLSv1.2

3.2 API 端点

端点 Method Content-Type User-Agent 用途
/wc_aime/api/alive_check POST application/json (body {}) 无 探活,返回 200 alive
/qrcode/api/alive_check POST 无(空body) WC_AIME_LIB 探活变体,返回 200 alive
/wc_aime/api/get_data POST 无 WC_AIME_LIB 核心登录接口

3.3 请求头(关键:无 Content-Type)

POST /wc_aime/api/get_data HTTP/1.1
Host: ai.sys-allnet.cn
User-Agent: WC_AIME_LIB
Connection: Keep-Alive
Content-Length: 243

{"chipID":"...","openGameID":"MAID","key":"...","qrCode":"...","timestamp":"...","titlekey":"SDGB"}

⚠️ 严禁添加 Content-Type: application/json,否则服务端返回 errorID=57(HTTP 200 + 合法 JSON,极易误判为签名错误)。

3.4 请求体 JSON 字段

字段 类型 来源 说明
chipID string Keychip ID 机台唯一标识,如 XXXX-XXXXXXXXXXX
openGameID string 固定 "MAID"
key string 签名计算 SHA-256 大写 hex,核心签名字段
qrCode string 二维码[20:] 64 位载荷,非完整 84 字符串
timestamp string 本机本地时间 yymmddHHMMss 格式
titlekey string 固定 "SDGB"

3.5 响应体 JSON 字段

字段 类型 说明
errorID int 0=成功,非0=失败
userID int 成功时为正整数(如 12345678),失败时 -1
token string 成功时 20 位临时凭据,失败时空串
key string 服务端返回的签名(可忽略)
timestamp string 服务端时间戳

四、签名算法(核心)

4.1 算法规范

import hashlib

SIGN_KEY = "XcW5FW4cPArBXEk4vzKz3CIrMuA5EVVW"  # 32字符,含前导 X

def calculate_key(chip_id: str, timestamp: str) -> str:
    """
    key = SHA-256(chipID + timestamp + SIGN_KEY).hexdigest().upper()
    
    参数:
        chip_id: 如 "XXXX-XXXXXXXXXXX"
        timestamp: 如 "261001004115" (yymmddHHMMss)
    
    返回:64字符大写十六进制
    """
    sign_input = chip_id + timestamp + SIGN_KEY
    return hashlib.sha256(sign_input.encode()).hexdigest().upper()

4.2 技术细节

项目 值 验证来源
哈希算法 SHA-256 Hook CryptCreateHash ALGID=0x800C (CALG_SHA_256)
密钥串 XcW5FW4cPArBXEk4vzKz3CIrMuA5EVVW .rdata UTF-16 @0x00B040,含前导 X
拼接顺序 chipID + timestamp + KEY 不含 qrCode、不含 titlekey
输出编码 大写十六进制 hex 表 0123456789ABCDEF @0x00A440

4.3 抓包验证向量

输入:
  chipID   = "XXXX-XXXXXXXXXXX"
  timestamp = "261001004115"
  SIGN_KEY = "XcW5FW4cPArBXEk4vzKz3CIrMuA5EVVW"

SHA256("XXXX-XXXXXXXXXXX" + "261001004115" + "XcW5FW4cPArBXEk4vzKz3CIrMuA5EVVW")
= 67015F28CFC3C7FA5E629BECE54893B581A6DBD153426AE5E6BA1681C6D8E327

与 mitmproxy 抓包完全一致 ✓


五、完整请求流程(纯 Python 实现)

5.1 最小可用代码

import hashlib, http.client, json, ssl
from datetime import datetime

SIGN_KEY = "XcW5FW4cPArBXEk4vzKz3CIrMuA5EVVW"
CHIP_ID  = "XXXX-XXXXXXXXXXX"  # 可配置
HOST     = "ai.sys-allnet.cn"
PATH     = "/wc_aime/api/get_data"

def parse_qr_login(qr_input: str, chip_id: str = CHIP_ID) -> tuple[int, str]:
    """
    输入:二维码串(84字符)或 URL
    返回:(user_id: int, token: str)
    """
    # 1. 统一输入 → 标准 84 字符串
    qr_string = normalize_qr_input(qr_input)
    
    # 2. 校验格式
    validate_qr_string(qr_string)
    
    # 3. 构造请求体
    ts = datetime.now().strftime("%y%m%d%H%M%S")
    key = calculate_key(chip_id, ts)
    qr_code = qr_string[20:]  # 仅 64 位载荷
    
    body_dict = {
        "chipID": chip_id,
        "openGameID": "MAID",
        "key": key,
        "qrCode": qr_code,
        "timestamp": ts,
        "titlekey": "SDGB",
    }
    body_bytes = json.dumps(body_dict, separators=(",", ":")).encode()
    
    # 4. 发送请求(关键:无 Content-Type)
    ctx = ssl.create_default_context()
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE
    
    conn = http.client.HTTPSConnection(HOST, 443, context=ctx, timeout=20)
    try:
        conn.request("POST", PATH, body=body_bytes, headers={
            "Connection": "Keep-Alive",
            "User-Agent": "WC_AIME_LIB",
            "Content-Length": str(len(body_bytes)),
            # ★ 绝不加 Content-Type
        })
        resp = conn.getresponse()
        data = json.loads(resp.read().decode())
    finally:
        conn.close()
    
    # 5. 解析响应
    if data.get("errorID") != 0 or data.get("userID") == -1:
        raise RuntimeError(f"登录失败: errorID={data.get('errorID')}, userID={data.get('userID')}")
    
    return data["userID"], data["token"]

def normalize_qr_input(qr_input: str) -> str:
    """URL 或字符串 → 标准 84 字符二维码串"""
    qr = qr_input.strip()
    if qr.startswith("http://") or qr.startswith("https://"):
        qr = url_to_qr_string(qr)
    return qr

def validate_qr_string(qr: str) -> None:
    """校验二维码格式,不通过抛 ValueError"""
    if len(qr) != 84:
        raise ValueError(f"长度应为84,实际{len(qr)}")
    if not qr.startswith("SGWC"):
        raise ValueError("必须以 SGWC 开头")
    if qr[4:8] != "MAID":
        raise ValueError(f"GameID 必须为 MAID,实际 {qr[4:8]}")
    # 可选:校验 payload 为 64 位 hex
    if not all(c in "0123456789ABCDEF" for c in qr[20:]):
        raise ValueError("载荷必须为大写 hex")

5.2 关键实现要点清单

要点 正确做法 错误做法后果
qrCode 字段 qr_string[20:] (64位) 传完整 84 字符 → 签名不匹配
签名拼接 chipID + timestamp + KEY 加入 qrCode/titlekey → 签名错误
密钥串 含前导 X (32字符) 漏掉 X → 签名完全不同
请求头 无 Content-Type 加上 → errorID=57
timestamp 本机本地时间即可 纠结时区 → 其实服务端不强校验
SSL 验证 关闭验证 开启 → 证书链可能不通过

六、错误码对照

errorID 含义 处理建议
0 成功 正常返回 userID + token
1-8 原生 ChimeErrorId 读卡器/服务器/二维码无效等通用错误
57 二维码已失效/已被使用/请求头不符 检查:二维码是否过期(~10分钟)、是否重复提交、请求头是否含 Content-Type
userID:-1 失败标志 配合 errorID 判断具体原因

★ 实测补充(2026-10-06)

本工程用同一套参数实测到的失败响应是 errorID=2(不是 57):

{"errorID": 2, "key": "B744ADD5...", "timestamp": "261006125706",
 "userID": -1, "token": ""}

触发条件是「二维码过期」,不是「用过一次」。

二维码生成时刻(timestamp 内嵌) 使用时刻 结果
261006012047(01:20:47) 01:21:55(+68s) ✅ 成功
261006124637(12:46:37) 12:52:22(+5m45s) ✅ 成功
261006130621(13:06:21) 13:10:42(+4m21s) ✅ 成功
上面那个 12:46:37 的二维码 13:0x(+约 15min) ❌ errorID=2

即:二维码是「有时效」的,不是「按次数计量」的。

有效期 = 10 分钟(实测至少 5 分 45 秒内均可用,超时后返回 errorID=2)。 所以报错时不要怀疑「用过了」,重新生成一个即可。


七、时间戳处理细节

时间戳位置 时区 格式 说明
二维码内 (偏移8-19) UTC+9 (日本时间) yymmddHHMMss 微信侧生成,约 10 分钟有效期
请求体 timestamp 本机本地时间 yymmddHHMMss 实测 UTC+8 与 DLL 行为一致,服务端不强校验时区
响应 timestamp 服务端时间 yymmddHHMMss 仅作参考

注意:本机时区非 UTC+8 时,请求体 timestamp 仍用本地时间,但需确保系统时间准确(NTP 同步)。


八、探活接口(可选,纯 Python 可独立实现)

8.1 探活请求

def check_alive(host: str = "ai.sys-allnet.cn") -> bool:
    ctx = ssl.create_default_context()
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE
    
    conn = http.client.HTTPSConnection(host, 443, context=ctx, timeout=10)
    try:
        conn.request("POST", "/wc_aime/api/alive_check", body=b"{}", headers={
            "Content-Type": "application/json",  # 探活需加 Content-Type
            "Connection": "Keep-Alive",
        })
        resp = conn.getresponse()
        return resp.status == 200 and resp.read().decode().strip() == "alive"
    finally:
        conn.close()

8.2 行为差异

端点 Content-Type User-Agent 响应
/wc_aime/api/alive_check application/json 无 200 alive
/qrcode/api/alive_check 无 WC_AIME_LIB 200 alive
/wc_aime/api/get_data 无 WC_AIME_LIB 业务响应

DLL 实测:CCommServAliveCheck 探活阶段不发真实网络请求(WinHTTP hook 不触发),纯 Python 可直接调用 alive_check 验证服务器存活。


九、原生 DLL 接口签名(供 P/Invoke 参考)

9.1 CCommServAliveCheck 系列

// 创建探活会话
HANDLE CCommServAliveCheck_Create();

// 设置连接权限
void CCommServAliveCheck_setConnectPermission(HANDLE h, BOOL permit);

// 开始探活
void CCommServAliveCheck_startCheck(HANDLE h);

// 执行轮询
void CCommServAliveCheck_execute(HANDLE h);

// 是否忙碌
BOOL CCommServAliveCheck_isBusy(HANDLE h);

// 获取存活服务器索引
int CCommServAliveCheck_getIndex(HANDLE h);

// 是否存活
BOOL CCommServAliveCheck_isAlive(HANDLE h);

// 销毁
void CCommServAliveCheck_Destroy(HANDLE h);

9.2 CCommGetUserData 系列

// 创建登录会话
// strGameID="MAID", strChipID=KeychipID, strCommonKey="", strQRData=二维码[20:], strTitleKey="SDGB", serverURLIndex=探活返回索引
HANDLE CCommGetUserData_Create(
    LPCSTR strGameID,
    LPCSTR strChipID,
    LPCSTR strCommonKey,
    LPCSTR strQRData,
    LPCSTR strTitleKey,
    int serverURLIndex
);

// 执行请求
void CCommGetUserData_execute(HANDLE h);

// 是否结束
BOOL CCommGetUserData_isEnd(HANDLE h);

// 获取错误码 (0=成功)
int CCommGetUserData_getErrorID(HANDLE h);

// 获取 userID (uint)
unsigned int CCommGetUserData_getUserID(HANDLE h);

// 获取 token (ANSI 字符串)
LPCSTR CCommGetUserData_getToken(HANDLE h);

// 销毁
void CCommGetUserData_Destroy(HANDLE h);

9.3 托管层调用序列(ChimeUnit.Execute 状态机)

// 1. 探活
var alive = CCommServAliveCheck_Create();
CCommServAliveCheck_setConnectPermission(alive, true);
CCommServAliveCheck_startCheck(alive);
while (CCommServAliveCheck_isBusy(alive)) CCommServAliveCheck_execute(alive);
int urlIndex = CCommServAliveCheck_getIndex(alive);
bool isAlive = CCommServAliveCheck_isAlive(alive);
CCommServAliveCheck_Destroy(alive);

// 2. 登录
var login = CCommGetUserData_Create(
    "MAID",                    // gameID
    keychipId,                 // chipID (如 XXXX-XXXXXXXXXXX)
    "",                        // commonKey (托管层传空,原生层内部处理)
    qrString.Substring(20),    // qrData (64位载荷)
    "SDGB",                    // titleKey
    urlIndex                   // 探活返回的服务器索引
);
while (!CCommGetUserData_isEnd(login)) CCommGetUserData_execute(login);
if (CCommGetUserData_getErrorID(login) == 0) {
    uint userId = CCommGetUserData_getUserID(login);
    string token = CCommGetUserData_getToken(login);  // PtrToStringAnsi
}
CCommGetUserData_Destroy(login);

十、常量汇总表

# ===== 签名与密钥 =====
SIGN_KEY = "XcW5FW4cPArBXEk4vzKz3CIrMuA5EVVW"  # 含前导 X,32字符

# ===== 默认机台标识 =====
DEFAULT_CHIP_ID = "XXXX-XXXXXXXXXXX"

# ===== 服务器 =====
PRIMARY_HOST = "ai.sys-all.cn"
BACKUP_HOST  = "ai.sys-allnet.cn"
PORT = 443
LOGIN_PATH = "/wc_aime/api/get_data"
ALIVE_PATH = "/wc_aime/api/alive_check"

# ===== 固定请求字段 =====
OPEN_GAME_ID = "MAID"
TITLE_KEY = "SDGB"
USER_AGENT = "WC_AIME_LIB"

# ===== 二维码常量 =====
QR_PREFIX = "SGWC"
QR_GAME_ID = "MAID"
QR_PREFIX_LEN = 20   # SGWC(4) + MAID(4) + timestamp(12)
QR_PAYLOAD_LEN = 64
QR_TOTAL_LEN = 84

# ===== HTTP 头(登录接口) =====
LOGIN_HEADERS = {
    "Connection": "Keep-Alive",
    "User-Agent": USER_AGENT,
    # "Content-Type": "application/json",  # ★ 绝不加这一行
}

十一、抓包调试方法论

11.1 强制走代理

// Hook WinHttpOpen
// dwAccessType = 3 (WINHTTP_ACCESS_TYPE_NAMED_PROXY)
// pwszProxy = "127.0.0.1:8890"

11.2 mitmproxy CA 证书安装

# 必须用 X509Store 静默导入,Import-Certificate 会因 UI 限制失败
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store("Root", "CurrentUser")
$store.Open("ReadWrite")
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("mitmproxy-ca-cert.pem")
$store.Add($cert)
$store.Close()

11.3 落盘与读取

mitmdump -w flow.mitm
# Python 读取
from mitmproxy.io import FlowReader
with open("flow.mitm", "rb") as f:
    for flow in FlowReader(f).stream():
        print(flow.request.pretty_url)
        print(flow.request.content)
        print(flow.response.content)

11.4 捕获签名输入

// Hook CryptHashData 导出入口
// 捕获 pbData 指针指向的字节数组 → 即签名原文
// 对比:chipID + timestamp + KEY

11.5 常见 Hook 坑点

坑点 原因 解决
IAT hook 全失效 pefile imp.address 已是完整 VA,再加 ImageBase 导致地址错误 直接用 imp.address + ASLR_delta
IAT 写入后还原 CrackProof 驱动写保护页面 改用全局 inline hook:改写 advapi32 导出函数入口 + capstone 精确 trampoline
x64 trampoline 崩 指令切碎不完整 capstone 逐条解析,累计 >=14 字节再跳转
误判加壳 Hook 全失效以为加壳 实测:.text 熵 6.2、465 个 FF 15、入口点可正常反汇编 → 未加壳

十二、实测验证记录

# 二维码时间戳 userID token (20位) 备注
1 260925205225 12345678 TESTTOKEN0000000000 DLL 验证成功
2 260930235204 12345678 e1Tsql1aP1SHLUthRdYI
3 261001001304 12345678 rI48n34Ttne1qR9FDFnY 同一时间戳多次请求 token 各异
4 261001001304 12345678 znoWwYW1bMpwlNUzcTpX
5 261001001304 12345678 8Cp6IztEGvLlYt5SRjxw

结论:

  • userID 固定(同一账号)
  • token 每次请求生成新的 20 位临时凭据(一次性)
  • 探活+登录总耗时 ~800ms(网络延迟主导)

十三、最小复刻检查清单

实现纯网络扫码登录,必须覆盖:

  • 二维码输入归一化(URL → 84字符串)
  • 格式校验(长度84、前缀SGWC、GameID=MAID、载荷64位hex)
  • 载荷提取(qr_string[20:])
  • 时间戳生成(本地时间 %y%m%d%H%M%S)
  • 签名计算(SHA256(chipID+timestamp+KEY) 大写hex)
  • 请求体构造(紧凑JSON,字段顺序固定)
  • HTTPS POST(无Content-Type,UA=WC_AIME_LIB,Keep-Alive)
  • SSL 不验证证书
  • 响应解析(errorID=0、userID>0、token非空)
  • 错误处理(errorID=57→请求头问题,errorID=2→二维码过期)

本文档仅包含技术协议实现细节,不含项目结构、代码规范、文件路径等工程化内容。