Skip to content

上游游戏后端 API 清单

这一页描述的不是我们自己的服务端

资源分发服务端带认证的代理壳子,真正的游戏后端(Totentanz / 官方 magica API)不由我们掌控。 本页记录的是上游那套 API 的形状,来源是社区留存的历史流量抓包。

它的用途只有一个:万一将来要自建游戏后端(而不是继续代理),这份清单就是规格基线—— API 服务端 走的正是这条路。我们自己的 /client/*/account/* 契约与本页完全无关, 不要混淆——那些契约见 客户端握手协议

数据来源与可信度

来源社区公开发布的历史流量归档(puella-historia.tsv.xz,1.03 GB 压缩 / 86.4 GiB 明文)
时间跨度2024-06-05 — 2024-07-30
请求条数293,217(另有 2,192 行字段数不符,计为 malformed 丢弃)
独立设备190
响应体总量92.3 GB
归一化端点205

这是一次性、不可再生的资料:官方服务端已不可达,抓包没了就没了。所以本页与 spec/upstream-api/ 的机器可读规格一并入库,而不是只活在某次分析的临时目录里。

归一化与隐私处理

  • 路径归一化:路径里嵌的 ID 会被折叠,否则端点清单会被稀释成几万条—— UUID → {uuid},纯数字段 → {n},24 位以上 hex → {hex}。 例:/magica/api/friend/user/0127be0d-…/magica/api/friend/user/{uuid}
  • 入库的是结构不是数据spec/upstream-api/ 里只有键路径、类型、数组长度区间、 数值区间,以及标识符型短字符串的枚举值(HEAL / CONNECT / RANK_1 这类)。 自由文本(台词、技能描述)一律不收集——它既不是 schema 的一部分,也会把玩家可 关联的内容带进来。UUID 与 32 位 hex 显式排除在枚举之外(设备与会话标识长这样)。
  • 原始响应体不入库。抓包里含真实玩家的 User-Id-… / Client-Session-Id, 完整响应体只在离线分析时用,仓库里不留。

端点分布:96% 的流量压在 93 个 /page/*

类别端点数调用数流量
/magica/api/page/*93152,96988.7 GB(96%)
其它 /magica/api/*111119,4763.4 GB(4%)
/search/friend_search/_search(Elasticsearch)120,772

/page/* 是"把渲染这个界面所需的全部状态一次性吐给我"的聚合端点。流量前几名:

端点调用平均响应最大响应
page/SupportSelect14,5271.98 MB13.2 MB
page/MyPage4,8822.54 MB7.2 MB
page/ShopTop52810.1 MB22.7 MB
page/GachaHistory3831.2 MB79.4 MB

这是"存档单向膨胀"的根源

/magica/api/quest/native/resume/check 平均响应 5.3 MB、最大 13 MB。拆开一次 3.5 MB 的响应看:

userQuestBattleList   2,316,055 bytes   list[1730]   ← 每关通关记录,只增不减
userSectionList         403,192 bytes   list[376]
userCharaList           399,432 bytes   list[41]
userCardList            263,771 bytes   list[77]

每次续战检查都把 1,730 条关卡记录整包重传,没有分页也没有增量。玩得越久越大, 且这是服务端侧的设计,不只是客户端存储问题。

若将来自建后端,/page/* 这 93 个端点是唯一值得重新设计的地方——届时前端也由我们 自己写,可以改成分页 / 增量契约,不必继承这个包袱。

战斗定义是逐场下发

/magica/api/quest/native/get(24,116 次调用,平均 39 KB)返回的是这一场解析完的 战斗定义,而不是让客户端去查表:

json
{"artId":200400401,"code":"HEAL",     "target":"CONNECT","sub":"HP",                   "effect":300,"growPoint":20}
{"artId":700218701,"code":"OTHER",    "target":"SELF",   "sub":"FORMATION_DEFENSE_UP","effect":100,"growPoint":0}
{"artId":200100601,"code":"RESURRECT","target":"ONE",                                  "effect":200,"growPoint":10}

响应顶层结构:

artList[]                 本场用得到的全部 art,数值已按等级解析完
magiaList[].artList       只是 artId 引用,如 [100300101, 200100601]
connectList[].artList     同上
waveList[].enemyList[]    每波敌人的 hpStart / mpStart / miniMagiaId
playerList[]              每个出战位的 cardId / hpStart / mpStart / magiaId
scenario, doppelList, memoriaList, formationJsonObjectList,
noDamagedEnemies, limitMp, isHalfSkill, canTripleSpeed, continuable

含义:客户端只跑状态机,所有数值由服务端算好推下来。自建后端必须生成这个 payload——它本质上是 master data(cards / pieces / enemies)的一次 join,不是开放式 逆向;而且有 24,116 份真实调用把 schema 钉死了。

code / sub / target 用的是与 master data 的 verbCode / effectCode / targetId 同一套有界词汇表。在全部 293k 行上聚合后(不是单次抓包的样本)实测:

字段种类取值
code15ATTACK BUFF BUFF_DIE BUFF_DYING BUFF_HPMAX BUFF_PARTY_DIE CONDITION_BAD CONDITION_GOOD DEBUFF ENCHANT HEAL IGNORE OTHER RESURRECT REVOKE
target7ALL CONNECT LIMITED ONE RANDOM5 SELF TARGET
sub60ACCEL BLAST CHARGE CRITICAL CURSE GUTS POISON PROVOKE STUN SURVIVE ATTACK_DARK/FIRE/TIMBER/WATER … 见规格文件

这就是战斗引擎的规模上界:15 × 60 × 7 是个可穷举的状态机,重写它是一份规格明确 的活,不是开放式逆向。注意属性专用效果(ATTACK_DARK 等)只有聚合后才看得到, 单次抓包会严重低估词汇表。

机器可读规格

规格放在 magirecocn-resource-server 仓库根的 spec/upstream-api/(205 个端点 / 64,961 条响应侧键路径 / 11 MB),不在本文档 仓库内——它有十几 MB,放进文档站会被打包进构建产物,而它的读者是写代码的人不是看站的人。 格式约定与使用注意见 spec/upstream-api/README.md

spec/upstream-api/
  index.json              端点索引:路径 / 调用次数 / 方法 / 键路径条数
  schemas/<endpoint>.json 每个端点的请求与响应结构

单个 schema 文件的形状:

json
{
 "_meta":    { "path": "...", "calls": 24116, "methods": ["POST"], "request_headers": [...] },
 "request":  { "request.userQuestBattleResultId": { "types": {"str": 24116} } },
 "response": { "response.artList[].code": { "types": {"str": 118432},
                                            "values": ["ATTACK","BUFF","DEBUFF","HEAL", "..."] } }
}

键路径约定:

写法含义
response.foo.bar对象字段
response.list[]数组元素(每次调用只采样前 6 个,同构数组够用)
response.map.{key}以 ID 为键的字典(排行榜按玩家 id 之类)
response.slotN#同级多个「同基名 + 数字后缀」字段折叠而成,实际下标见 indices

最后一条值得解释:opponentUserArenaBattleInfo1/2/3 是三棵完全相同的深子树,各约 1,700 条键路径;placeSkill1..9missionStatus1..3userCardId1..4 同理。不折叠 的话规格文件会膨胀数倍且完全不可读,折叠后语义不丢(下标记在 indices 里)。

已知局限

  • 只是一个 55 天的窗口:期间没出现的端点、没触发的分支不在清单里。205 这个数字是 下界
  • 枚举同理是下界:标了 "values_open": true 的字段表示取值种类超过采集上限, 只留了样例,不可当作完整取值集。
  • 190 个设备的行为不覆盖全部玩法:低频端点(如 user/delete 只有 1 次)的 schema 仅由极少数样本得出,字段可选性判断不可靠。
  • 抓包时间是 2024 年年中,与最终服务端版本可能有出入。

文档正文以 CC BY-NC-SA 4.0 授权 · 代码部分以 GPLv3 开源 · 本项目仅作学习研究使用,与版权方无任何关联