Skip to content

Rizline 移动端资源获取 ​

国服 Android 资源走 CDN + Unity Addressables。离线脚本见 rizline-assets-get 根目录 main.py。

WARNING

本文只说明客户端/CDN 的资源拉取与解包流程,不保证接口长期可用。请勿二次分发官方资源。

TIP

对照仓库:

下文 {base} = resourceBaseUrl,{ver} = resourceVersion。实测日期 2026-09-19,客户端 2.7.1,{ver} = v141_2_7_1_3c13bbff2bP。

总览 ​

游戏启动不会一次性下完 catalog 里所有远程文件。启动只保证 predownload;其余按歌曲 / 插画 / 谱面按需拉取。离线脚本则会尽量把 catalog 全部远程项拉完再解包。

APK 内几乎没有完整资源包。当前 APK assets/aa/Android/ 通常只有 crilocaldata_assets_all.bundle;其余 bundle / acb 都在 CDN 的版本目录里。

脚本用法 ​

仓库根目录:

bash
pip install requests colorama UnityPy
python main.py

.acb 转 wav 需要 vgmstream-cli。Windows 可把可执行文件放到仓库 vgmstream-cli/;构建参考 Virace/vgmstream-cli-build。

WARNING

清空 ./download 会删掉已下的 bundle / acb。全量远程大约一千多个文件,确认后再选 y。

脚本会询问是否清空 ./download,再询问是否下载全部远程资源。下载完成后自动:

  1. parse_level() 扫描 bundle,选出最新 Default 关卡表
  2. parse_catalog() 解析 Addressables catalog
  3. export_all_resources() 按关卡 / 分类导出到 ./output

1. 版本配置 API ​

IL2CPP 入口:GetServerConfig、PigeonSDKGameConfig、GameStart。GetServerConfig.Start() 先 ping,再拉配置。脚本对应 getRizlineVersion()。

请求 ​

GET https://rizserver.pigeongames.net/game/server_api/v1/dis

无 query、无 body。Content-Length: 0。

请求头必填值说明
game_id是pigeongames.rizline缺了 / 错了都是 400 Invalid game_id
channel_id否11国服渠道;不带头或改 0 现网正文相同
i18n否zh-CN改 en / ja 现网 JSON 相同
host自动rizserver.pigeongames.net客户端会显式带
user-agent否BestHTTP/2 v2.6.3游戏用 BestHTTP
accept-encoding否gzip, identity
python
import requests

DIS = "https://rizserver.pigeongames.net/game/server_api/v1/dis"
headers = {
    "game_id": "pigeongames.rizline",
    "channel_id": "11",
    "i18n": "zh-CN",
    "user-agent": "BestHTTP/2 v2.6.3",
}
resp = requests.get(DIS, headers=headers, timeout=20)
resp.raise_for_status()
data = resp.json()

成功时 200,Content-Type: application/json; charset=utf-8。

TIP

game_id 是唯一必填头。不带 channel_id、不带 i18n,只要 game_id: pigeongames.rizline,现网同样 200。

WARNING

game_id 缺、空、写错都是 400,正文 Invalid game_id(text/html,不是 JSON)。POST 直接 404。

返回字段 ​

根对象对应 ServerConfig:

字段类型含义
configsVersionConfig[]按客户端版本列出的配置,通常取 [0]
minimalVersionstring最低可进游戏的客户端版本
maintenanceInfoobject?维护公告;维护时可能只有这一项,没有 configs

configs[] 对应 VersionConfig:

字段类型含义
versionstring客户端版本,如 2.7.1
resourceUrlstring当前资源目录,通常等于 {resourceBaseUrl}/{resourceVersion},可为空串
resourceBaseUrlstring资源根,国服现为 https://rizlineasset.pigeongames.net/versions
resourceVersionstring资源目录名,如 v141_2_7_1_3c13bbff2bP。末尾 P 表示热更版
platformstring?dump 里有,现网常省略
expiredbool?true 时弹资源过期,不能继续下
inReviewbool?审核包配置
maintenanceInfoobject?该版本自己的维护文案

maintenanceInfo:

字段语言
zhHans简中
zhHant繁中
en英文
ja日文

旧格式(约 1.0.10–2.0.7)只有 version + resourceUrl,维护文案还带 time,正文里的 {TIME} 会被替换。更早则是根上的 underMaintenance / maintenanceNotice*。

解析 ​

python
if not data.get("configs"):
    raise ValueError("configs 为空,可能在维护或走了备份地址")
cfg = data["configs"][0]
if cfg.get("expired"):
    raise ValueError("资源已过期")
base = cfg["resourceBaseUrl"]
ver = cfg["resourceVersion"]
url = cfg.get("resourceUrl") or f"{base}/{ver}"

客户端用 cfg["version"] 对当前安装版本;对不上或 expired 就停。脚本只取 version / resourceUrl / resourceBaseUrl / resourceVersion / minimalVersion。

WARNING

configs 为空或只有 maintenanceInfo 时不要继续拼资源 URL。expired=true 客户端会弹资源过期。

TIP

resourceUrl 一般等于 {resourceBaseUrl}/{resourceVersion},但并不保证永远如此。解析时优先用这两个字段分别拼后续路径。

实测 2026-09-19 ​

正常返回:

json
{
  "configs": [{
    "version": "2.7.1",
    "resourceUrl": "https://rizlineasset.pigeongames.net/versions/v141_2_7_1_3c13bbff2bP",
    "resourceBaseUrl": "https://rizlineasset.pigeongames.net/versions",
    "resourceVersion": "v141_2_7_1_3c13bbff2bP"
  }],
  "minimalVersion": "2.7.1"
}

响应头有 etag、broadcast-expires: true、access-control-allow-origin: *。

情况结果
完整头 / 只带 game_id200,正文相同
无 game_id / 空 / 错(如 pigeongames.phigros)400,text/html,正文 Invalid game_id
channel_id=0、i18n=en/ja200,和 zh-CN 相同
POST404 HTML:Cannot POST /server_api/v1/dis

备份 / 审核配置 ​

不带头也能 GET。这不是启动主路径,内容也不一定是当前热更。

用途URL实测
备份https://rizlineasset.pigeongames.net/configs/game_config_cn.json200,configs: []
审核https://rizlineasset.pigeongames.net/configs/review_config_cn_MfBdz4mYo8UpYGbG31LZ4Tey5AVPouMR.json404 OSS NoSuchKey

备份 configs 为空,不能当现网 {ver} 用。要以 /v1/dis 为准。

WARNING

game_config_cn.json 等备份经常是空 configs,审核配置还可能 404。不要用它们当启动配置。

2. 热更补丁链 API ​

类:GameStart.LoadResourcePatches、LoadResourcePatchMetadata。脚本对应 getPatchMetadatas() / collect_patch_chain()。

请求 ​

GET {base}/{ver}/patch_metadata

无自定义头。CDN(OSS)直接给文件。HEAD 和 GET 都能 200。

python
url = f"{base}/{ver}/patch_metadata"
resp = requests.get(url, timeout=20)
if resp.status_code != 200 or resp.text.lstrip().startswith(("<Error", "<?xml")):
    prev, files = None, []  # 链在这里断
else:
    lines = [ln.strip() for ln in resp.text.splitlines() if ln.strip()]
    prev, files = lines[0], lines[1:]
状态含义
200这一版是热更,正文是补丁表
404这一版没有 metadata,链断在这里。目录往往还在,基线文件就放这儿

200 时 Content-Type 常是 application/octet-stream,不是 JSON。UTF-8,LF(\n),文件末尾也有换行。XML / <Error> 当失败。

WARNING

不要当 JSON 解析。OSS 404 的 body 是 XML <Error><Code>NoSuchKey</Code>...,状态码和正文都要认。

TIP

路径里不要加 Android/。正确是 {base}/{ver}/patch_metadata,写成 {base}/{ver}/Android/patch_metadata 会 404。

正文结构 ​

{上一版 resourceVersion}
Android/{hash}.bundle
Android/catalog_catalog.hash
Android/catalog_catalog.json
Android/cridata_assets_criaddressables/{name}.acb={checksum}
iOS/{hash}.bundle
...
行含义
第 1 行上一版 {ver},后面当下一跳去拉
其余行本版相对上一版改过的文件。路径按字典序
Android/... / iOS/...成对出现,内容不必完全同一组 hash
catalog_catalog.json / .hashcatalog 本身也算热更文件
cridata_assets_criaddressables/*.acb=*热更音频,没有末尾 .bundle

国服脚本只收 Android/,并跳过 catalog_catalog.json / catalog_catalog.hash:

python
SKIP = {"catalog_catalog.json", "catalog_catalog.hash"}
android = [
    ln for ln in files
    if ln.startswith("Android/") and ln.split("/", 1)[-1] not in SKIP
]

链怎么走 ​

从当前 {ver} 开始:

  1. GET {base}/{ver}/patch_metadata
  2. 200:本版 files 全部记进 resourcePatchMap[path] = ver(已有的不覆盖,所以保留最后写入 / 最新的那一版);ver = 第 1 行 prev,继续
  3. 404 或正文无效:停。最后一次成功 metadata 里的 prev 就是 resourceBaseVersion

不是「还能拉到 metadata 的最旧版」,是「最后一份成功表写下的 prev」。404 的那一版目录仍可能完整存在。

WARNING

patch_metadata 404 ≠ 该版本目录不存在。基线 bundle / acb 往往就在这个 404 的 {ver} 下面。

TIP

resourceVersion 末尾带 P 的是热更版,会有 patch_metadata。基线目录通常不带 P,例如现网 v138_2_7_0_d9b31649c6。

resourcePatchMap:相对路径 → 最后一次出现在 files[] 里的版本。catalog 远程项对不上任何一版 files[] 的,走基线目录。

静态字段:resourceBaseUrl、resourceBaseVersion、resourcePatchMap、internalIdURLMap。

实测 2026-09-19 ​

请求状态结果
.../v141_...P/patch_metadata200prev=v140_...P,Android 21 / iOS 21(bundle 17 + catalog×2 + acb 2)
.../v140_...P/patch_metadata200prev=v139_...P,Android 7(bundle 5 + catalog×2)
.../v139_...P/patch_metadata200prev=v138_2_7_0_d9b31649c6,Android 30(bundle 26 + catalog×2 + acb 2)
.../v138_2_7_0_d9b31649c6/patch_metadata404链终点,resourceBaseVersion = v138_...
假版本 / 空版本 / {ver}/Android/patch_metadata404OSS NoSuchKey
对 v141 HEAD200文件存在

所以现网基线目录是 v138_2_7_0_d9b31649c6(无末尾 P)。

3. Catalog API ​

GET {base}/{当前ver}/Android/catalog_catalog.json
GET {base}/{当前ver}/Android/catalog_catalog.hash

iOS 把路径里的 Android 换成 iOS。无自定义头。catalog 必须从当前 ver拉,不要用基线目录里可能过期的那份。

python
catalog = requests.get(
    f"{base}/{ver}/Android/catalog_catalog.json", timeout=60
).json()
catalog_hash = requests.get(
    f"{base}/{ver}/Android/catalog_catalog.hash", timeout=20
).text.strip()

200,JSON。这是 Unity Addressables ContentCatalogData。APK 里还有 assets/aa/catalog.json,运行时以 CDN 当前版为准。.hash 用来判断 catalog 要不要更新,和 JSON 里的 m_BuildResultHash 不是同一个值。

WARNING

catalog 必须从当前 {ver} 拉。基线目录里的 catalog_catalog.json 也能 200,但是旧表,对不上当前热更文件。

TIP

必须带平台目录:Android/ 或 iOS/。少这一层,或写成 StandaloneWindows64/,都是 404。

JSON 顶层 ​

字段类型含义
m_LocatorIdstring一般是 AddressablesMainContentCatalog
m_BuildResultHashstringcatalog 构建 hash,和 .hash 文件不是同一个
m_InstanceProviderData / m_SceneProviderDataobjectProvider:m_Id、m_ObjectType、m_Data
m_ResourceProviderDataarray全部 ResourceProvider
m_ProviderIdsstring[]如 AssetBundleProvider
m_InternalIdsstring[]资源内部 ID 表,下载只关心这里的 http:// 项
m_KeyDataStringstringBase64,key 二进制
m_BucketDataStringstringBase64,bucket 二进制
m_EntryDataStringstringBase64,entry 二进制
m_ExtraDataStringstringBase64,下载选项等
m_resourceTypesarray{ m_AssemblyName, m_ClassName },注意这个字段是小写 r
m_InternalIdPrefixesarray现网常为空

实测 2026-09-19 ​

v141 m_LocatorId = AddressablesMainContentCatalog,m_BuildResultHash = deb7b24656391e54b21a2a0430144df6,.hash 文件 = e4ff79cb97ddf7a7edc01bd29e7a62ab。

请求状态说明
{ver}/Android/catalog_catalog.json200约 380 KB,m_InternalIds 3052 条、无重复
{ver}/iOS/catalog_catalog.json200同样 3052 条
{ver}/Android/catalog_catalog.hash20032 位 hex 文本
{resourceBaseVersion}/Android/catalog_catalog.json200基线目录里也有,但是旧 catalog,不要用
{ver}/catalog_catalog.json(无平台目录)404
{ver}/StandaloneWindows64/catalog_catalog.json404PC 不走这套 CDN

m_InternalIds 分类 ​

v141 共 3052 条、无重复:

形态条数要不要下 CDN
http://.../*.bundle1331要,远程 bundle
http://.../*.acb=...158要,远程音频
32 位 hex1330否,内部 id
Assets/ 等本地路径159否
{UnityEngine.AddressableAssets.Addressables.RuntimePath}/...1APK 内 crilocaldata
其它逻辑名73否,如 ApplicationVariables

远程项一律是占位前缀,这个主机不能访问(HEAD 502 / 断连):

http://rizastcdn.pigeongames.cn/default/Android/{hash}.bundle
http://rizastcdn.pigeongames.cn/default/Android/cridata_assets_criaddressables/{name}.acb={checksum}.bundle

真实主机是 {base}。加载后由 InternalIdTransformFunc 改写。

WARNING

http://rizastcdn.pigeongames.cn/default/ 只是 catalog 占位前缀,实测 HEAD 为 502。直接请求会失败。

脚本剥前缀得到相对路径;若含 .acb= 且以 .bundle 结尾,再去掉末尾 .bundle,否则音频 404。

python
PLACEHOLDER = "http://rizastcdn.pigeongames.cn/default"

def catalog_relative_path(internal_id: str) -> str:
    rel = internal_id
    if rel.startswith(PLACEHOLDER + "/"):
        rel = rel[len(PLACEHOLDER) + 1 :]
    if ".acb=" in rel and rel.endswith(".bundle"):
        rel = rel[: -len(".bundle")]
    return rel
# bundle: Android/{hash}.bundle
# acb:    Android/cridata_assets_criaddressables/{name}.acb={checksum}

key → bundle 映射 ​

m_KeyDataString / m_BucketDataString / m_EntryDataString 都是小端二进制,Base64 后塞进 JSON。解包用 parse_unity_catalog():

  1. Key:uint32 count,然后每条 uint8 type + 内容。0 UTF-8 长度字符串,1 UTF-16LE 长度字符串,4 int32
  2. Bucket:uint32 count,每条 key_offset + entry_count + entry_index[]
  3. Entry:定长 28 字节 × 7 个 int32:internalId / provider / depBucket / crc / extra / siblings / type
  4. 把 bucket 的 key 和 entry 的依赖 / InternalId 对上,得到 chart.xxx → abcd....bundle

build_bundle_map() 只留 value 以 .bundle 结尾的项。

4. 资源 URL 拼法 ​

客户端对每个 InternalId:

  1. 已在 internalIdURLMap → 用缓存 URL
  2. 去掉 http://rizastcdn.pigeongames.cn/default/
  3. 相对路径在 resourcePatchMap → {base}/{mappedVer}/{相对路径}
  4. 不在 map 里(基线) → {base}/{resourceBaseVersion}/{相对路径}

游戏不会从新到旧试 404。基线一次指向链终点那个完整目录。离线脚本会先试 mapped / 基线,失败再扫历史版本,用来兜 CDN 个别缺文件。

python
def resolve_file_version(rel_path: str, patch_map: dict, base_version: str) -> str:
    for key in (rel_path, rel_path.removesuffix(".bundle"), rel_path + ".bundle"):
        if key in patch_map:
            return patch_map[key]
    return base_version

def build_download_url(base: str, version: str, rel_path: str) -> str:
    return f"{base}/{version}/{rel_path}"
文件方法URL
版本配置GEThttps://rizserver.pigeongames.net/game/server_api/v1/dis
补丁表GET{base}/{ver}/patch_metadata
CatalogGET{base}/{当前ver}/Android/catalog_catalog.json
Catalog hashGET{base}/{当前ver}/Android/catalog_catalog.hash
热更 bundleGET{base}/{最后写入它的 ver}/Android/{hash}.bundle
基线 bundleGET{base}/{链终点 prev}/Android/{hash}.bundle
热更 / 基线音频GET{base}/{对应 ver}/Android/cridata_assets_criaddressables/{name}.acb={checksum}

音频必须保留 cridata_assets_criaddressables/,并且去掉 catalog 末尾的 .bundle。带 .bundle 或少子目录都是 404。

WARNING

catalog 里的 acb InternalId 带末尾 .bundle,CDN 上的真实路径没有。热更文件只存在写入它的那一版;基线文件放当前 ver 会 404。

TIP

游戏不会从新到旧试版本。命中 resourcePatchMap 用 mapped ver,否则一次指向 resourceBaseVersion。

bundle / acb 都是无鉴权 GET/HEAD,响应体就是文件。脚本按 URL 末段存到 download/bundles/ 或 download/acb/。

实测 2026-09-19 ​

以 v141 热更文件 Android/1bed5685....bundle、热更 acb ヘーブンリースカイ....acb=3225e0、基线 Android/00108990....bundle 为例:

情况状态说明
热更 bundle 放当前 ver200约 95 MB
同一热更 bundle 放基线 v138404只活在写入它的那一版
热更 acb 去掉 .bundle、保留子目录200约 3.2 MB
热更 acb 仍带 catalog 的 .bundle404
热更 acb 去掉 cridata_assets_criaddressables/404
catalog 占位主机 rizastcdn.../default/...502不能直接下
基线 bundle 放当前 ver404
基线 bundle 放 resourceBaseVersion(v138)20042407 字节

5. 启动下载 vs 按需下载 ​

GameStart.DownloadResources(isRetry):

  1. key:predownload、predownload_ + i18n(如 predownload_zh-CN)
  2. GetDownloadSizeAsync 算差量
  3. Addressables.DownloadDependenciesAsync(key)

LoadOver() 后进主界面。点歌曲 / 插画 / 谱面再 LoadAssetAsync,按改写后的 URL 拉剩余文件。

离线脚本会把 catalog 全部远程 http 项尽量全部拉完,再额外并入热更链里出现过、但当前 catalog 未列出的路径。

TIP

启动只下 predownload / predownload_zh-CN。谱面、HiRes 曲绘、歌曲 acb 都是点进对应界面才拉。

6. 解包 ​

下载目录:

download/
├── catalog_catalog.json
├── fileList.json          # catalog key → bundle 映射
├── bundles/*.bundle
└── acb/*.acb

关卡表 ​

扫描全部 bundle 的 MonoBehaviour,找出名为 Default 的 typetree。候选按来源 bundle 更新时间、是否末曲 temp、关卡数、谱面数、activityTime 排序,自动写成 output/default.json。

关卡来源是 levels + discOLevels。

Catalog 映射 ​

parse_unity_catalog() 解码 m_KeyDataString / m_BucketDataString / m_EntryDataString,得到 key → InternalId。build_bundle_map() 只保留 value 以 .bundle 结尾的项。

分类导出 ​

按 catalog key 前缀分类:

key 前缀类型输出
chart.谱面 TextAssetcharts/.../*.json
chart.challenge.挑战谱面charts/challenge/
illustration.曲绘illustrations/,含 .HiRes / .cn
altIllustration.替代曲绘alt_illustrations/
layout.铺面背景layouts/
seriesBanner. / seriesPoster.系列图series/banners、series/posters
avatar.头像avatars/
banner.活动 Bannerbanners/
banner.bannerListBanner 配置banners/bannerList.json
local. / predownload_本地化localization/
D00视频videos/
Assets/Rizline/、Assets/Le TaiUIui/

谱面 key 可能带 .mr / .cn 分段,会进对应子目录。.mat / .prefab 跳过。

每个关卡会先导出到 output/charts/<discName>/<歌曲名>/:谱面 json、曲绘(含 HiRes / cn)、chartInfo.json,以及用 vgmstream 转出的 wav。catalog 里已随关卡导出的谱面不会再重复放到分类目录。未进关卡的 acb 转到 output/audio/。

7. 输出结构 ​

output/
├── default.json
├── charts/<Disc>/<歌曲>/
│   ├── chart.<id>.<diff>.json
│   ├── illustration.<id>.png
│   ├── <musicId>.wav
│   └── chartInfo.json
├── illustrations/
├── alt_illustrations/
├── layouts/
├── series/
├── avatars/
├── banners/
├── localization/
├── videos/
├── ui/
└── audio/

谱面 json 格式见 Rizline谱面格式说明。

8. 关键类 ​

类作用
PigeonSDKGameConfig配置 URL 常量
ServerConfig / VersionConfig版本 JSON
GetServerConfig启动拉配置
GameStart补丁链、catalog、predownload
GameStart.ResourcePatchMetadataversion + files
Addressables.DownloadDependenciesAsync按 key 下依赖

字符串:server_api/v1/dis、/patch_metadata、predownload、predownload_、http://rizastcdn.pigeongames.cn/default/、game-resource-expired、game-resource-ver。

与 PC 的区别 ​

  • 从 CDN 下 bundle / acb,bundle 不加密
  • catalog:{base}/{ver}/Android/catalog_catalog.json
  • 音频:.acb + vgmstream
  • 需要网络
  • 文档:资源 / 存档