fix(ncm §30): 真机四连症状——JSON unescape + 封面流式 + 歌单豆腐/分隔符 tofu + 首屏诊断日志
症状/根因/修法(0 真实 NCM 业务请求;宿主+qemu 全绿):
- ③ LRC 挤一行/时间戳未解析:ncm_json_get_str_at 不做 JSON 转义还原,
lrc.lyric 的 \n 转义原样进串 → lrc_parse 无真实换行整段一行。修:ncm_json.c 加
json_unescape(RFC8259 全转义 + \uXXXX/代理对),get_str_at 返回前还原。
- ⑤ 封面全 failed→give up:cover.c 静态 g_buf 288KB < 封面 1.34MB → NCM_ERR_FULL。
设备 RAM 仅 56MB。修:cover.c 改流式写文件(ncm_http_download cb→ncm_cache_write +
meow_img_load_file 临时文件),NCM_COVER_MAX 8MB 改下载上限(非缓冲,bss 未涨)。
- ① 162 首歌单所有条目「三个豆腐」+「加载中」:占位符 ···(U+00B7)在 default 字体缺
该字形 → 3 豆腐(font.idx glyph 扫实证 U+00B7 MISSING)。修:占位 ···→...、全 App
· 分隔符→/;playlist_worker 首屏打 first-screen 诊断日志。
- ② 返回首页歌单列表 1 秒方框:字体缺 U+00B7/特殊字形(◢◤/Kannada)tofu;瞬态疑 glyph
atlas 冷缓存(SDK 内部未深挖)。修:·→/。
验证:test_ncm_core(+test_json_unescape, host+qemu) / listvirt44 / dispatch / box42 /
scroll17 / anim32 / widgets / overlay_suppress / sdk_input_keys / theme / vol / ime_fix /
pinyin / notif_gesture / notif_open / sdk_fixes / sb_collapse / ui_audio_host / notify_ipc /
notify_integration + playlist_race_sim / window_sim / search_sim / cache_evict_sim(1-9) /
player_flow_sim(P1-12) / stream_sim(16+2resume) 全 PASS;mipsel -Wall -Wextra 0 warning。
新 md5:ncm d91cd906、ncm_host b731654a、test_ncm_core_host 7a38194c。
(症状④ VOL_DOWN×3 杀 App 的 launcher 源修复 app_launcher.c 门控 !a->framework 已随
并发 commit 54a6e82 存入 HEAD;launcher 二进制 03dfcf76 留主会话/固件打包。)
注:build_ncm_host.sh 补 SDK 292/295 的 anim/box/scroll 源(宿主链接)。
This commit is contained in:
1 parent
54a6e828b7
commit
3397a702aa
15 files changed
+384
-88
No files matched your search
@@ -97,6 +97,83 @@ int ncm_json_arr_len(const ncm_json_t *j, int tok) {
|
||||
return k;
|
||||
}
|
||||
|
||||
/* JSON 字符串转义还原(jsmn 只分词、不做 escape 还原)。输入 [s, s+len) 是 token 的
|
||||
* 原文串体(去引号,仍含 \" \\ \/ \n \r \t \b \f \uXXXX 等字面转义),还原为实际字符
|
||||
* 写入 out,返回写入字节数。语义(RFC 8259 §7):
|
||||
* \" → " \\ → \ \/ → / \b → 0x08 \f → 0x0C
|
||||
* \n → 0x0A \r → 0x0D \t → 0x09
|
||||
* \uXXXX → UTF-16 码元 → UTF-8;\uD800-\uDBFF + \uDC00-\uDFFF 代理对合成 >U+FFFF 码点。
|
||||
* 输出长度恒 ≤ 输入长度(最坏 \uXXXX 6 字节 → ≤4 字节 UTF-8),在 out_sz 内必然放得下。
|
||||
*
|
||||
* 背景(REPORT §30 网易云真机四连症状):/api/song/lyric 返回的 lrc.lyric 把整段 LRC 的
|
||||
* 换行写成 JSON 转义 \n。旧实现 memcpy 原文 → 歌词串里是字面反斜杠+n(无真实换行),
|
||||
* lrc_parse 扫不到行尾 → 整段歌词挤成一行、时间戳全没解析(真机「LRC 挤一行」)。
|
||||
* 本函数做真正的 unescape,\n 还原成真实换行,lrc_parse 按行正确切分。
|
||||
* 也顺手修好所有走 get_str_at 的 URL/名字里被转义的 \/、\uXXXX(避免字面序列显形)。 */
|
||||
static size_t json_unescape(const char *s, size_t len, char *out, size_t out_sz)
|
||||
{
|
||||
size_t oi = 0, si = 0;
|
||||
while (si < len && oi < out_sz - 1) {
|
||||
unsigned char c = (unsigned char)s[si];
|
||||
if (c != '\\' || si + 1 >= len) { out[oi++] = (char)c; si++; continue; }
|
||||
char e = s[si + 1];
|
||||
switch (e) {
|
||||
case '"': out[oi++] = '"'; si += 2; break;
|
||||
case '\\': out[oi++] = '\\'; si += 2; break;
|
||||
case '/': out[oi++] = '/'; si += 2; break;
|
||||
case 'b': out[oi++] = '\b'; si += 2; break;
|
||||
case 'f': out[oi++] = '\f'; si += 2; break;
|
||||
case 'n': out[oi++] = '\n'; si += 2; break;
|
||||
case 'r': out[oi++] = '\r'; si += 2; break;
|
||||
case 't': out[oi++] = '\t'; si += 2; break;
|
||||
case 'u': {
|
||||
if (si + 6 > len) { out[oi++] = '\\'; si++; break; } /* 不完整 \u:原样保留 */
|
||||
unsigned cp = 0;
|
||||
int ok = 1;
|
||||
for (int k = 0; k < 4; k++) {
|
||||
unsigned char h = (unsigned char)s[si + 2 + k];
|
||||
unsigned d;
|
||||
if (h >= '0' && h <= '9') d = h - '0';
|
||||
else if (h >= 'a' && h <= 'f') d = h - 'a' + 10;
|
||||
else if (h >= 'A' && h <= 'F') d = h - 'A' + 10;
|
||||
else { ok = 0; break; }
|
||||
cp = (cp << 4) | d;
|
||||
}
|
||||
if (!ok) { out[oi++] = '\\'; si++; break; } /* 非十六进制:宽松保留 */
|
||||
si += 6;
|
||||
/* 高代理对 \uD800..\uDBFF 后紧跟 \uDC00..\uDFFF → 合成一个码点 */
|
||||
if (cp >= 0xD800 && cp <= 0xDBFF && si + 6 <= len &&
|
||||
s[si] == '\\' && s[si + 1] == 'u') {
|
||||
unsigned lo = 0; int ok2 = 1;
|
||||
for (int k = 0; k < 4; k++) {
|
||||
unsigned char h = (unsigned char)s[si + 2 + k];
|
||||
unsigned d;
|
||||
if (h >= '0' && h <= '9') d = h - '0';
|
||||
else if (h >= 'a' && h <= 'f') d = h - 'a' + 10;
|
||||
else if (h >= 'A' && h <= 'F') d = h - 'A' + 10;
|
||||
else { ok2 = 0; break; }
|
||||
lo = (lo << 4) | d;
|
||||
}
|
||||
if (ok2 && lo >= 0xDC00 && lo <= 0xDFFF) {
|
||||
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
||||
si += 6;
|
||||
}
|
||||
}
|
||||
if (cp < 0x80) { out[oi++] = (char)cp; }
|
||||
else if (cp < 0x800) { out[oi++] = (char)(0xC0 | (cp >> 6)); out[oi++] = (char)(0x80 | (cp & 0x3F)); }
|
||||
else if (cp < 0x10000) { out[oi++] = (char)(0xE0 | (cp >> 12)); out[oi++] = (char)(0x80 | ((cp >> 6) & 0x3F)); out[oi++] = (char)(0x80 | (cp & 0x3F)); }
|
||||
else { out[oi++] = (char)(0xF0 | (cp >> 18)); out[oi++] = (char)(0x80 | ((cp >> 12) & 0x3F)); out[oi++] = (char)(0x80 | ((cp >> 6) & 0x3F)); out[oi++] = (char)(0x80 | (cp & 0x3F)); }
|
||||
break;
|
||||
}
|
||||
default: /* 未知转义(宽松):保留反斜杠 + 字面 */
|
||||
out[oi++] = '\\'; si++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
out[oi] = 0;
|
||||
return oi;
|
||||
}
|
||||
|
||||
const char *ncm_json_get_str_at(const ncm_json_t *j, int tok, char *out, size_t out_sz) {
|
||||
if (tok < 0 || tok >= j->ntoks) return NULL;
|
||||
int type = j->toks[tok].type;
|
||||
@@ -110,8 +187,8 @@ const char *ncm_json_get_str_at(const ncm_json_t *j, int tok, char *out, size_t
|
||||
if (end < start) return NULL;
|
||||
size_t len = (size_t)(end - start);
|
||||
if (len >= out_sz) return NULL;
|
||||
memcpy(out, j->text + start, len);
|
||||
out[len] = 0;
|
||||
/* 真正还原 JSON 转义(旧实现直接 memcpy,\n/\/ 等字面序列未还原 → 歌词一行/URL 损坏) */
|
||||
json_unescape(j->text + start, len, out, out_sz);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user