宿主+qemu-mipsel 全链路 PASS(unikey实拿/801轮询/eapi往返/RSA对拍/缓存LRU); -Wall -Wextra 自有代码0warning 待联调: 803真扫码、真机 hw:0,0 播放链路
19 KiB
os.meow.ncm 技术栈预研 + 构建验证报告
日期:2026-08-31。工作区:
/home/Aska/Project/R3ProII,仅改动patch_v2/ncm_app/。 目标:为 MeowOS 网易云 App(v1 = 扫码登录 + 我的歌单 + 播放)在 HiBy R3 Pro II (MIPS32r2 单核、~14MB 可用内存、无 GPU、ALSA 直开 hw:0,0)上的纯 C 静态栈做选型与真机交叉编译验证。 工具链:.tools/zig-mipsel-cc(zig 包装 mipsel-linux-musleabi soft-float 静态交叉)。全程未 commit/push/刷机/动设备。
1. 选型结论表
| 能力 | 选型 | 版本 | 许可 | mipsel 体积(text) | 说明 |
|---|---|---|---|---|---|
| TLS + 加密 | mbedtls(裁剪) | 3.6.2 | Apache-2.0 | 链接 Demo ≈ 388 KB | TLS1.2 client + AES-128-CBC/ECB/GCM + MD5 + RSA(1024 raw) + Base64 + X509,单库全包 |
| TLS(备选) | BearSSL | master@7bea48e | MIT | TLS client 子集 ≈ 96 KB | 更省 flash,但无 base64、AES-ECB 需自行逐块、RSA raw 用 br_rsa_i31_public |
| JSON | jsmn | 1.1.0 | MIT | 2.2 KB | 分词器,无 malloc/无 DOM;cJSON 对比 20 KB |
| 二维码 | qrcodegen | nayuki@993ccbb | MIT | 10.8 KB | 纯 C 单文件,无图形依赖 |
| MP3 解码 | minimp3 | master@ea99364 | CC0 | 37.4 KB | minimp3_ex 流式/可 seek;dr_mp3 对比 40.7 KB |
HTTPS GET/POST 可达性:mbedtls 已实测连通(见 §2)。crypto 是否 mbedtls 自带就够:是——
AES-128-ECB/CBC、MD5、Base64、RSA(raw) 全有;RSA raw 用 mbedtls_rsa_public()(无 padding 纯模幂)已交叉校验与 pow(m,e,n) 完全一致(见 §3),这是文档标注最易踩坑处,已验证可行。
2. HTTPS 实测(宿主 = 本机 aarch64;另跑通 MIPS 版)
demo:demo/https_unikey.c(mbedtls TLS1.2 + eapi 加密 + jsmn)。
目标:POST https://interface.music.163.com/eapi/login/qrcode/unikey,body params=<eapi hex>。
deviceId=68DFA27096B7A5DC2CB393C77D8C194FDC0459B743081D091EBD
---- response body ----
{"code":200,"unikey":"33a1c6a6-28ad-4e1e-bc8e-ebe769953913"}
UNIKEY = 33a1c6a6-28ad-4e1e-bc8e-ebe769953913
- 宿主:
code:200+ unikey 顶层字段,与 NCM_API_NOTES §7 一致 ✅ - MIPS 版交叉编译后,在
qemu-mipsel下全链路跑通(DNS→TCP→TLS→POST→JSON 解析),同样拿到真实 unikey:{"code":200,"unikey":"6731b1ac-71a2-41eb-a704-a2de39d6d0b7"}。即 mipsel 静态产物的网络+crypto+TLS 路径正确。 - eapi 加密逻辑与原版
crypto::eapi一致:digest=md5("nobody"+url+"use"+json+"md5forencrypt"),plain=url+"-36cd479b6b5-"+json+"-36cd479b6b5-"+digest,params=HEX_UPPER(AES128ECB(plain,"e82ckenh8dichen8"));header(osver/deviceId/os/appver/versioncode/mobilename/buildver/resolution/__csrf/channel/requestId)与e_r:false均入加密体(§8 要求,去掉会 4xx)。
3. RSA raw(weapi 无 padding 模幂)校验
mbedtls_rsa_public(ctx, 128B输入右对齐16B明文, 128B输出),n=1024-bit、e=65537。
- mipsel(qemu) 与宿主输出一致,且与 Python
pow(m,e,n)逐一字节相同(256 hex 完全匹配):CT = 55B33E7E3229B8D977137D120B3C7A5B5FB8E933325CAA5BB8CEF6C132D57E5E...B2B83 - 结论:mbedtls_rsa_public() 可用作 weapi 的 textbook RSA,避开 PKCS#1 padding 陷阱。demo:
demo/rsa_raw_test.c。
4. 总体积账(mipsel,-Os,flash≈text)
选定的库合计(≈438 KB):
| 项 | text | 备注 |
|---|---|---|
| mbedtls TLS+crypto+x509(链接 demo) | 388 KB | 含 ~6 KB musl libc 基线;库文本合计 410 KB,gc-sections 未再降 |
| jsmn | 2.2 KB | |
| qrcodegen | 10.8 KB | |
| minimp3 | 37.4 KB | |
| 库合计 | ≈ 438 KB | 另需 App/UI/封面/音频输出层(未计入,自研) |
可选裁剪方向(能明显压 mbedtls):限制密码套件到 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(+1 个 CBC+SHA 兜底)、
ECP 只留 P-256、去掉 ECDSA(服务器给 RSA 证书)、MBEDTLS_SSL_IN/OUT_CONTENT_LEN=4096。估可降到 ~250 KB。
若固件预算仍紧,切 BearSSL(≈96KB) + 自带 base64/AES-ECB 例程,能再省 ~300 KB。
对比参考项体积:cJSON 20 KB、dr_mp3 40.7 KB、BearSSL 全库 310 KB(裁剪子集 96 KB)。空静态 mipsel 基线 text=5888。
5. 内存 / 性能估算(14MB 可用)
-
TLS 握手 buffer:mbedtls 默认
SSL_IN/OUT_CONTENT_LEN=16KB 各一,共 ~32KB;demo 二进制bss=26888(≈26KB)+堆内 SSL context/ECDH 临时密钥。对 14MB 无压力;建议调成 4096 更省。 -
JSON 解析:jsmn 无 malloc,token 数组按需分配(响应小,几百字节)。不要全量缓冲歌单详情,只取需要的键(文档 §4.4)。
-
解码:minimp3 流式,输入 ~每帧几 KB + 输出
MINIMP3_MAX_SAMPLES_PER_FRAME*2*ch,整体 <20KB。 -
封面:必须限幅——NCM 建议 5MB,MeowOS 应限 ~256KB 并降采样/调色板(无 GPU 只能 CPU 软缩放)。
-
二维码位图:41×41 符号渲染成 UI 尺寸(如 400×400,灰度 1B/px=160KB,1-bit 可 20KB)。
-
预算结论:TLS+解码+二维码+封面(256KB cap)+UI 帧缓冲,总工作集应在数百 KB 量级,14MB 富余;唯一要盯的是封面/歌单响应别全量驻留。
-
性能:
- MP3 解码实时性:宿主(aarch64) minimp3 = 568×RT、dr_mp3 = 627×RT;
qemu-mipsel(TCG 模拟,远慢于真机,通常 10–50×) 下 minimp3 = 4.8×RT、dr_mp3 = 4.7×RT。 → 真机 MIPS32 单核跑 44.1k/64kbps 解码余量很大(估计数十倍 RT),解码不是瓶颈。⚠️ 该数字为宿主/模拟估算,标注不准。 - TLS 握手:ECDHE-RSA-AES128-GCM,单核软 RSA 1024 校验 + ECDH P-256,估几十~几百 ms,可接受(仅首次/重连)。
- weapi RSA raw 模幂:1024-bit 软模幂每次约几 ms~几十 ms(只对 weapi 请求,频率低)。
- MP3 解码实时性:宿主(aarch64) minimp3 = 568×RT、dr_mp3 = 627×RT;
6. 风险与 fallback
| 风险 | 评估 | 对策/fallback |
|---|---|---|
| weapi RSA raw 最易踩坑 | ✅ 已交叉校验正确 | 保持 mbedtls_rsa_public()+左补零到 256 hex;用 rsa_raw_test 做回归 |
| deviceId/UA 不稳定触发风控 | 高 | deviceId(52 hex) 首生成后持久化;UA 按 eapi(iPhone)/weapi(Chrome) 分别固定;轮询≥1s、失败退避 |
| session(MUSIC_U)丢失即需重扫码 | 高 | cookie 持久化;启动用 /weapi/w/nuser/account/get 校验 |
eapi 的 header/e_r 缺失 → 4xx |
中 | demo 已带,必须保留(§8) |
| 无版权/VIP 曲目 url=null | 中 | song_url_v1 返回 null → 提示 + 降级到 song/enhance/player/url(br=320000) 兜底 |
| 音质策略(Hi-res 降级) | v1 只保 mp3 | 账号非免费档 level 被钳到免费档;v1 默认 standard(128k)/higher(192k);不引入 flac 解码(14MB 内存+单核不划算);后续再评估 dr_flac |
| 触发高频风控 | 中 | 请求限频、重试退避、必要时随机国内 IP(X-Real-IP/X-Forwarded-For) |
| mbedtls flash ~388KB 偏大 | 中 | 见 §4 裁剪;若预算吃紧切 BearSSL(≈96KB) |
7. 出入清单(只改 patch_v2/ncm_app/)
patch_v2/ncm_app/
├── vendor/ # 各库源码(上游 clone/tar + 许可证)
│ ├── mbedtls/ (3.6.2, 含 framework 子模块, 裁剪 config 在 config/mbedtls_config_ncm.h)
│ ├── bearssl/ (官方 git, 无语义版本)
│ ├── jsmn/ cjson/ qrcodegen/ minimp3/ dr_mp3/
├── libs_common.sh # 公共 zig/ar/size 环境
├── build_mbedtls.sh # 裁剪 config + 静态编 mipsel → out/mbedtls/mipsel/*.a
├── build_bearssl.sh # make lib → out/bearssl/mipsel/bm/libbearssl.a
├── build_cjson.sh build_jsmn.sh build_qrcodegen.sh build_minimp3.sh build_dr_mp3.sh
├── build_demo.sh # 宿主 + mipsel 的 HTTPS demo 复现
├── bench/ # minimp3/dr_mp3 基准(sample-15s.mp3 来自 samplelib)
├── demo/https_unikey.c # eapi + TLS1.2 + jsmn(打 unikey)
├── demo/rsa_raw_test.c # weapi RSA raw 校验
├── out/https_unikey_mipsel(. _s) # mipsel 静态 demo(qemu-mipsel 跑通)
├── out/mbedtls/{mipsel,host}/ # 库产物 out/bearssl/mipsel/bm/
├── VENDOR_NOTES.md # 版本/许可/裁剪/体积
└── REPORT.md # 本报告
未做/待主会话:真机 hw:0,0 播放链路、扫码后真实登录(803+set-cookie)、weapi 真实登录态请求、
CA bundle 内置方案、音质档位映射实测。这些属设备/账号侧联调,由主会话在设备上执行(本预研不动设备)。
8. 结论
- 可行:纯 C + mbedtls(裁剪) + jsmn + qrcodegen + minimp3,静态 mipsel 交叉编译全通,HTTPS POST 拿到真实 unikey,MIPS(qemu) 版全链路跑通。
- 推荐 TLS/加密一体用 mbedtls(单库一次解决 TLS + AES/MD5/RSA raw/Base64 + X509,规避 RNSS-raw 手写风险);BearSSL 为省 flash 备选。
- v1 体积增量 ≈ 438 KB(库)/ 只做 mp3,内存估计数百 KB 工作集,14MB 可行;主要优化点是 mbedtls 裁剪与封面限幅。
- 最关键风险(RSA raw)已验证;其余风险集中在风控(deviceId/UA 稳定持久化)与无版权降级策略。
9. 核心库批次(core/,纯 C 静态库,无 UI)
日期:2026-08-31。在 §1-8 预研(候选库/工具链已定型)基础上落地核心库:http/crypto/login/api/cache 五模块
- 公共/存储/JSON/请求构建支撑模块 + ncm_ctx 上下文。仅改动
patch_v2/ncm_app/。未 commit/push/刷机/动设备。
9.1 模块与公开 API(头文件签名)
| 模块 | 文件 | 主要公开函数 |
|---|---|---|
| 公共工具 | core/ncm_common.h/.c |
ncm_hex_encode/decode ncm_b64_encode/decode ncm_md5_hex ncm_sha256_hex ncm_url_encode/decode ncm_random_bytes ncm_now_ms 等 |
| JSON | core/ncm_json.h/.c |
ncm_json_parse ncm_json_obj_get ncm_json_arr_get/len ncm_json_path[_str/_int/_bool] ncm_json_get_str_at/get_int_at |
| HTTP | core/ncm_http.h/.c |
ncm_http_request(一次性 POST/GET,限幅)ncm_http_download(流式回调 + max_bytes)ncm_http_parse_set_cookie ncm_http_set_verify_default |
| 存储 | core/ncm_store.h/.c |
ncm_store_init ncm_store_get/set_device_id ncm_store_machine_key ncm_store_cookie_encrypt/decrypt ncm_store_save/load/clear_session ncm_store_write/read_file ncm_store_read_cfg_int |
| 加密 | core/ncm_crypto.h/.c |
ncm_crypto_eapi ncm_crypto_weapi ncm_crypto_linuxapi ncm_crypto_rsa_raw_selfcheck |
| 请求构建 | core/ncm_req.h/.c |
ncm_req_build(eapi/weapi/linux,注入 header/csrf_token/UA/Cookie)ncm_req_cookie_get |
| 登录 | core/ncm_login.h/.c |
ncm_login_qr_url ncm_login_unikey ncm_login_qr_matrix(qrcodegen)ncm_login_poll ncm_login_status_check ncm_login_save/load/clear_cookie |
| 歌单/播放 | core/ncm_api.h/.c |
ncm_api_user_playlist ncm_api_playlist_detail ncm_api_song_detail ncm_api_song_url ncm_api_song_url_auto ncm_api_lyric ncm_api_level_str |
| 缓存 | core/ncm_cache.h/.c |
ncm_cache_init ncm_cache_set_limit_gb/limit_gb ncm_cache_load_setting ncm_cache_lookup ncm_cache_open/write/commit/abort ncm_cache_total_bytes ncm_cache_evict ncm_cache_path |
| 上下文 | core/ncm.h + ncm_ctx.c |
ncm_ctx_t(deviceId/cookie/CA/默认音质/cache)ncm_ctx_init ncm_ctx_set/load/clear_cookie ncm_ctx_default_cache_dir |
关键常量:NCM_EAPI_KEY/NCM_WEAPI_PRESET/NCM_WEAPI_IV/NCM_LINUXAPI_KEY/NCM_RSA_DER_B64(ncm_crypto.h);
UA 按加密类型分流(eapi=iPhone NeteaseMusic 9.0.90/5038,weapi=Chrome/Edg 124),请求头注入对齐 CNMPlayer request.rs(deviceId/UA 稳定)。
9.2 关键实现取舍
- CA 方案(
ncm_http.h注释):verify_ca=1(默认)加载系统 CA bundle(或ca_path),失败即拒绝(防 MITM);verify_ca=0跳过校验(仅调试/受限网)。全局默认由ncm_http_set_verify_default设定,ncm_ctx_init以其verify_ca/ca_path设置。设备侧需内置 CA 根(VENDOR_NOTES §备注),否则接口 eapi/weapi 会被拒。 - RSA raw:用
mbedtls_rsa_public()无 padding 纯模幂,16 字节 secret 右对齐 128 字节(前导 0),输出 256 小写 hex——已与 Pythonpow(m,e,n)逐字节一致(见 9.4)。 - weapi:双层 AES-128-CBC(
paramsbase64,含+/=需 urlencode)+ RSA(raw) 加密随机 16 位 base62 密钥(RSA 前反转)。 - 会话cookie:加密落盘
MeowData/<pkg>/data/session.enc,密钥 =sha256(MAC + 盐);无 MAC(qemu/无网卡)回退到设备私有.mkey派生。仅为防裸读模糊,非真安全(头文件已注明)。 - 缓存:目录默认
MeowData/os.meow.ncm/cache/;上限默认 10GB(1~100 可配),读设置键ncm.cache_limit_gb(<data_dir>/ncm.cache_limit_gb.cfg,对齐 SDK meow_config);LRU 按文件 mtime,ncm_cache_lookup/commit刷新;命中即离线可播。 - 音质:
ncm_api_song_url_auto从 desired(默认 Hi-res)往下降级hires→lossless→exhigh→higher→standard,只返回可解码 mp3(url 非空且 type=mp3);无该档对当前歌曲降级。
9.3 构建
./build_core.sh → out/core-host/、core-mipsel/(自有代码对象 + vendor qrcodegen + jsmn 唯一实现 TU),链接 out/mbedtls/{host,mipsel}/*.a,产出:
test/test_ncm_core_host(宿主,可跑)out/test_ncm_core_mipsel(mipsel 静态)。size:text 462662 / data 212 / bss 27688(含测试 printf 基建 + mbedtls + qrcodegen;核心库本身链接更小)。- 自有代码
-Wall -Wextra0 warning;vendor 警告豁免。mipsel 用.tools/zig-mipsel-cc静态交叉。
9.4 验证记录(宿主 + qemu-mipsel 双向实测,响应已脱敏/截断)
- unikey 实拿(eapi):
{"code":200,"unikey":"<uuid>"}(宿主dc5bd090-…,qemu-mipselef9a5db2-…);二维码 URLhttps://music.163.com/login?codekey=<uuid>。 - 轮询(未扫码):
{"code":801,"message":"等待扫码"}→state=等待扫码(801),两平台一致。 - login_status(伪造 cookie):返回 weapi 可解析 JSON(code 200 但 profile null)→ 库判
NCM_ERR_AUTH(-13),证明 weapi 请求被服务端接受。 - RSA raw 交叉:
ncm_crypto_rsa_raw_selfcheck输出55b33e7e…b2b83,与 Pythonpow(m,65537,n)(§3 已交叉验证值)逐字节一致。weapi params 为合法 base64(RSA 封装的 16 字节随机密钥无法无私钥解密,其 AES-CBC 为标准用法)。 - eapi 往返:
ncm_crypto_eapi产物可用 AES-ECB 解回含url+json的明文(/api/login/qrcode/unikey+{"type":3})。 - cookie 加解密:38 字节串
MUSIC_U=…; __csrf=xyz; os=pc加密→解密完全一致(含一次 mbedtlscrypt_cbc就地改写 IV 的坑,已修)。 - 缓存:写入/命中/abort 不留档/强制小上限触发 LRU 淘汰(总字节降至 ≤ 上限)。
- 二维码:41×41 内矩阵生成,暗模块 >0。
- 无凭据错误处理:
user_playlist(uid=0,无cookie)优雅返回(公开数据或错误码,不崩溃);song_url(id=0)返回无 url。
9.5 md5(自有源码)
778847a8419325af2b95ad0ef7749781 core/ncm_api.h c07d246c9b64e4d3b9078b9e33d19fe4 core/ncm_cache.h
6e878566f6a4c4377c98e87e9967fdcc core/ncm_common.h 9056253bfb635d8224e7454c6b1796a2 core/ncm_crypto.h
157807f07a96f43249f6aeb9707d053f core/ncm.h 477dd9f5eab61e2940cb432d1b215b3a core/ncm_http.h
398e62a0b641f13bc80c02d5e42a0401 core/ncm_json.h 05fb336d3b65b041095bd8fb3c64bc3c core/ncm_login.h
96f423ef1a36bda7b5cb3961fbe22028 core/ncm_req.h 02c7ff38dd856455f32a4cc7bbc5edd5 core/ncm_store.h
f8f8e96464d3c4ebcf1898eb4212c55a core/jsmn_impl.c a00e9b9b1adf7c6cf924eb9ecfc90a67 core/ncm_common.c
e75c29217ef5dd3f0e23c740b79210c1 core/ncm_crypto.c 9ebfa83e31a530b4dc8fd7877c2f5fb9 core/ncm_ctx.c
319df449ecd7727f328e01adb518ba57 core/ncm_http.c d69ff302ec111a74f621571818da4622 core/ncm_json.c
a53e55f9cab828fd59b694330ec7371e core/ncm_login.c d81eda09640ff748b0a1c14607447d1b core/ncm_req.c
1ceda66d30d44a3d8850df2e876c0195 core/ncm_store.c 7cdb84fcf87384eb5125e5aae01eac84 test/test_ncm_core.c
94a4dd07fba98a6e7b70021aaeb8cb19 build_core.sh
产物:test/test_ncm_core_host md5 0e41d3d413819768d65d2e9ca9e082ed;out/test_ncm_core_mipsel md5 e05f1937e5fc49a2590b830c28ee6372。
9.6 出入清单(只改 patch_v2/ncm_app/)
新增:
core/ncm.h ncm_ctx.c // 伞头 + 上下文
core/ncm_common.h/.c // hex/base64/md5/sha256/urlencode/errcode
core/ncm_json.h/.c // jsmn 路径导航
core/ncm_crypto.h/.c // eapi/weapi/linuxapi + RSA raw
core/ncm_http.h/.c // mbedtls HTTPS POST/GET + CA取舍 + 超时 + chunked
core/ncm_store.h/.c // deviceId/MAC机器密钥/cookie加解密/配置读取
core/ncm_req.h/.c // 加密请求构建(md5/UA/cookie 注入)
core/ncm_login.h/.c // unikey/二维码矩阵/轮询状态机/cookie捕获/login_status
core/ncm_api.h/.c // 歌单/详情/歌曲/播放地址/歌词
core/ncm_cache.h/.c // LRU 缓存 + 离线可播
core/jsmn_impl.c // jsmn 唯一实现 TU(防多 TU 重复定义)
test/test_ncm_core.c // 宿主 + mipsel 综合实测
build_core.sh // 宿主 + mipsel 编译链接
修改:
REPORT.md // 追加本批记录
(未动 vendor/,未动其余目录)
9.7 未做/诚实出入
- 803 真登录未实测(需真实扫码):code 803 分支的 set-cookie 捕获/合并逻辑按 document 实现,但未真跑;联调时建议真扫码验证一次
MUSIC_U内容与login_status校验链路。 - weapi params 无法无 RSA 私钥解密核验其内部 AES 双层,但 RSA 封路径已逐字节交叉,AES-CBC 为标准用法(可用 CNMPlayer 同源实现比对)。
- 播放地址/缓存仅验证到“请求-解析-无版权返回 null”;未做真实音频下载落盘(缓存 open/write/commit 已由单元测试覆盖;真实 mp3 下载留给带 UI 的播放层用
ncm_http_download+ncm_cache_*串联)。 - 栈用量:单次 http 调用涉及 mbedtls ssl/净栈,峰值栈估几十 KB(与预研 demo 同级);App 线程栈需 ≥64KB,联调时以真机线程栈为准。
- JSON 响应限幅:歌单/歌曲详情走 1MB~2MB 堆缓冲 + token 计数后精确分配,不算常驻;封面/大 JSON 不整体驻留,封面下载另有
max_bytes限幅(建议 ~256KB,未在本次接线)。