dsh-mpkg-wallpaper 3.13.3 → 3.13.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +137 -10
- package/lib/pkg-extract.js +116 -10
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -169,6 +169,109 @@ function mpwTrace(ev, data) {
|
|
|
169
169
|
} catch (e) {}
|
|
170
170
|
}
|
|
171
171
|
/* ═══ MPW-BLOBRETRY-END ═══ */
|
|
172
|
+
/* ═══ MPW-WEJSON-BEGIN ═══ */
|
|
173
|
+
/* 官方随包 JSON 的**宽容解析**(WE 允许尾逗号;我们的 `JSON.parse` 不允许)。
|
|
174
|
+
*
|
|
175
|
+
* ── 为什么必须有(官方证据,2026-09-24 官方产物盘点;与渲染器仓同一轮定性)──────────────
|
|
176
|
+
* · 官方随包发布的 `wallpaper_engine/assets/effects/fluidsimulation/effect.json`(10,224 B)
|
|
177
|
+
* **自己就带尾逗号**:第 402 行 `"shaders/effects/fluidsimulation_normal.vert",` 后面紧跟 `}`
|
|
178
|
+
* ⇒ 标准 `JSON.parse` 报 `Expecting value: line 403 column 2`。WE 照发照用 ⇒ **官方容忍尾逗号**
|
|
179
|
+
* (引擎用 jsoncpp,二进制里带 `allowTrailingCommas`/`allowComments` 开关名)。
|
|
180
|
+
* · 后果是**静默**的:本仓凡是"读包内条目失败就 catch 成 null"的地方(project.json 属性表、
|
|
181
|
+
* web 壁纸的 general.properties…),遇到这种文件会**整份丢掉** —— 属性面板空白、`{user:…}`
|
|
182
|
+
* 绑定全部回落默认值,而日志里一行都不会有。
|
|
183
|
+
* · 渲染器仓的对应修复编号 **P-177**(`core/we-scene-bundle.js` 的 `export function parseWeJson`
|
|
184
|
+
* + `weJsonStats()`,同一轮把页面与 bundle 里"读容器条目文本"的解析点全部换过去)。本块与它
|
|
185
|
+
* **逐条同口径**(行为对照,不是代码搬运:渲染器仓是 GPL-3.0-or-later、本仓 MIT ⇒ 这里按同一
|
|
186
|
+
* 语义**独立实现**,`tools/we-json-tolerance-test.mjs` 拿同一组夹具把两边的读数逐条对拍)。
|
|
187
|
+
* 宿主侧 `lib/pkg-extract.js` 里的同名实现(Node 侧、同一语义)与本块在同一份判据里逐夹具对拍
|
|
188
|
+
* (防两处漂移)。
|
|
189
|
+
*
|
|
190
|
+
* ── 口径(只做"官方明确容忍"的那两件事,其余照旧抛)──────────────────────────────────
|
|
191
|
+
* ① 尾逗号:`,` 后面(跳过空白)紧跟 `}` / `]` ⇒ 丢掉这个逗号;**字符串内的逗号一律不动**
|
|
192
|
+
* (扫描器带 inString/escape 状态,不会碰 `"a,}"` 这种内容)。
|
|
193
|
+
* ② 注释:**只在**"去掉尾逗号后仍解析失败"时才尝试剥 `//` 与 `/* *\/`(同样字符串内不动),
|
|
194
|
+
* 并单独计数 —— 这样"到底有没有真用到"现场看得见,不是我们猜的。
|
|
195
|
+
* ③ BOM(U+FEFF)与官方一致地吃掉(`JSON.parse` 自己不认 BOM)。
|
|
196
|
+
* **健康文件逐位等价于 `JSON.parse`**:先走 `JSON.parse(text)`,成功就原样返回(plain 计数 +1);
|
|
197
|
+
* 只有失败才落到宽容路径。真坏 JSON **仍然抛** ⇒ **调用点的 try/catch 语义一个都不变**
|
|
198
|
+
* (原来抛的还抛、原来吞的还吞,只是吞的那几处现在要记账,见 `mpwWeJsonSwallowed`)。
|
|
199
|
+
* 计数:`mpwWeJsonStats()` 返回 `{ calls, plain, trailingComma, comments, failed, swallowed }`
|
|
200
|
+
* —— 前五项与渲染器 `weJsonStats()` 同名同义(两仓对拍用同一组读数);`swallowed` 是本仓
|
|
201
|
+
* 额外加的"被调用方吞掉"记账(渲染器那边是调用点自己记日志,没有这一项)。 */
|
|
202
|
+
const MPW_WEJSON = { calls: 0, plain: 0, trailingComma: 0, comments: 0, failed: 0, swallowed: 0 };
|
|
203
|
+
/** 只读快照(对外只给这个,避免调用方改计数)。 */
|
|
204
|
+
function mpwWeJsonStats() { return Object.assign({}, MPW_WEJSON) }
|
|
205
|
+
/** 文本整形:一趟扫描,按需去掉**字符串外**的尾逗号(`withComments=false`)或尾逗号 + 注释
|
|
206
|
+
* (`withComments=true`)。**字符串里的一律照抄**(`"a,}"` / `"//"` / `"/*"` 一个字都不动);
|
|
207
|
+
* 注释替换成等长空白,行号与列号都保持可读;尾逗号判定会**越过空白与注释**看下一个有效字符
|
|
208
|
+
* (所以 `{"a":1, /* c *\/ }` 与不带注释的写法同解)。 */
|
|
209
|
+
function mpwWeJsonRepair(src, withComments) {
|
|
210
|
+
let out = "", inStr = false, esc = false;
|
|
211
|
+
for (let i = 0; i < src.length; i++) {
|
|
212
|
+
const c = src[i], d = src[i + 1];
|
|
213
|
+
if (inStr) { // 字符串内:只跟 escape 状态,内容逐字照抄
|
|
214
|
+
out += c;
|
|
215
|
+
if (esc) esc = false;
|
|
216
|
+
else if (c === "\\") esc = true;
|
|
217
|
+
else if (c === '"') inStr = false;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (c === '"') { inStr = true; out += c; continue }
|
|
221
|
+
if (withComments && c === "/" && d === "/") { // `//` 行注释 ⇒ 空白
|
|
222
|
+
while (i < src.length && src[i] !== "\n") { out += " "; i++ }
|
|
223
|
+
out += "\n";
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (withComments && c === "/" && d === "*") { // `/* */` 块注释 ⇒ 空白
|
|
227
|
+
out += " "; i += 2;
|
|
228
|
+
while (i < src.length && !(src[i] === "*" && src[i + 1] === "/")) { out += (src[i] === "\n" ? "\n" : " "); i++ }
|
|
229
|
+
out += " "; i++;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (c === ",") { // 尾逗号:往后看下一个有效字符(可跨空白/注释)
|
|
233
|
+
let j = i + 1;
|
|
234
|
+
for (;;) {
|
|
235
|
+
while (j < src.length && (src[j] === " " || src[j] === "\t" || src[j] === "\n" || src[j] === "\r")) j++;
|
|
236
|
+
if (!withComments) break;
|
|
237
|
+
if (src[j] === "/" && src[j + 1] === "/") { while (j < src.length && src[j] !== "\n") j++; continue }
|
|
238
|
+
if (src[j] === "/" && src[j + 1] === "*") { j += 2; while (j < src.length && !(src[j] === "*" && src[j + 1] === "/")) j++; j += 2; continue }
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
if (src[j] === "}" || src[j] === "]") continue; // 尾逗号 ⇒ 丢掉这个逗号
|
|
242
|
+
}
|
|
243
|
+
out += c;
|
|
244
|
+
}
|
|
245
|
+
return out;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* 解析官方随包 JSON(project.json / scene.json / materials / effects / models / combo 定义…)。
|
|
249
|
+
* 与 `JSON.parse` 的差别只有两条:容忍尾逗号;再不行才容忍注释(外加吃掉 BOM)。
|
|
250
|
+
* **其余一律照旧抛**(调用方的 try/catch 语义不变)。
|
|
251
|
+
* @param {string} text
|
|
252
|
+
* @returns {any}
|
|
253
|
+
*/
|
|
254
|
+
function mpwParseWeJson(text) {
|
|
255
|
+
MPW_WEJSON.calls++;
|
|
256
|
+
const s0 = (typeof text === "string") ? text : String(text == null ? "" : text);
|
|
257
|
+
const s = s0.charCodeAt(0) === 0xFEFF ? s0.slice(1) : s0; // ① BOM:官方产物会带,JSON.parse 自己不认
|
|
258
|
+
try { const v = JSON.parse(s); MPW_WEJSON.plain++; return v } catch (e) { /* 健康文件到此为止(逐位等价 JSON.parse) */ }
|
|
259
|
+
try { const v = JSON.parse(mpwWeJsonRepair(s, false)); MPW_WEJSON.trailingComma++; return v } catch (e) { /* ② 尾逗号也不够 ⇒ 再试注释 */ }
|
|
260
|
+
try { const v = JSON.parse(mpwWeJsonRepair(s, true)); MPW_WEJSON.comments++; return v } // ③ 尾逗号 + 注释
|
|
261
|
+
catch (e) { MPW_WEJSON.failed++; throw e } // 真坏 JSON:照旧抛
|
|
262
|
+
}
|
|
263
|
+
/** 包内 JSON 解析失败、调用方**按原有语义吞掉**时的记账:计数 + 至少一行诊断(不静默)。
|
|
264
|
+
* (调用点的 try/catch 结构不动:这里只负责"吞了要说一声",返回值恒 null 方便 `catch { x = ... }` 写法。) */
|
|
265
|
+
function mpwWeJsonSwallowed(where, e) {
|
|
266
|
+
try { MPW_WEJSON.swallowed++ } catch (e2) {}
|
|
267
|
+
try {
|
|
268
|
+
if (typeof mpwErr === "function") mpwErr("包内 JSON 解析失败(按原语义吞掉): " + where, e);
|
|
269
|
+
else console.warn("[dsh-mpkg-wallpaper] 包内 JSON 解析失败(按原语义吞掉): " + where, e);
|
|
270
|
+
} catch (e2) {}
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
try { window.__mpwWeJson = { parse: mpwParseWeJson, stats: mpwWeJsonStats, swallowed: mpwWeJsonSwallowed }; } catch (e) {}
|
|
274
|
+
/* ═══ MPW-WEJSON-END ═══ */
|
|
172
275
|
// ①(P-104 2026-09-17 用户发布纪律②) **插件侧 localStorage 也要有上限**:
|
|
173
276
|
// 本插件只用固定几个键(STORE_KEY / mpw_settings_backup / mpwdiag / mpwwatch),键数量天然有界;
|
|
174
277
|
// 真正的风险在**单值**——STORE_KEY 里含背景 data URL(本文件注释自述 1MB+)。这里给单值硬顶
|
|
@@ -352,7 +455,7 @@ function mpwTrace(ev, data) {
|
|
|
352
455
|
// ①(MERGED-3 1.3) 诊断开关速查区离线副本(渲染器离线时用)。
|
|
353
456
|
// 在线数据源 = <渲染器>/diag-flags.json(由 we-scene-demo/diag-flag-check.mjs 脚本生成,勿手改);
|
|
354
457
|
// tools/panel-smoke.mjs 断言本副本 == diag-flags.json 的 common 集合 → 两边同步,勿单独改这里。
|
|
355
|
-
const MPW_DIAG_FLAGS_FALLBACK = '[{"name":"att","usage":"att=legacy"},{"name":"mcc","usage":"mcc=1"},{"name":"piv","usage":"piv=1"},{"name":"align","usage":"align=0"},{"name":"parallax","usage":"parallax=legacy"},{"name":"audio","usage":"audio=1"},{"name":"whitefallback","usage":"whitefallback=0"},{"name":"hier","usage":"hier=0"},{"name":"isolate","usage":"isolate=<层名>"},{"name":"audit","usage":"audit=3"}]';
|
|
458
|
+
const MPW_DIAG_FLAGS_FALLBACK = '[{"name":"att","usage":"att=legacy"},{"name":"mcc","usage":"mcc=1"},{"name":"piv","usage":"piv=1"},{"name":"align","usage":"align=0"},{"name":"parallax","usage":"parallax=legacy"},{"name":"gyro","usage":"gyro=1"},{"name":"audio","usage":"audio=1"},{"name":"whitefallback","usage":"whitefallback=0"},{"name":"hier","usage":"hier=0"},{"name":"isolate","usage":"isolate=<层名>"},{"name":"audit","usage":"audit=3"}]';
|
|
356
459
|
const DEFAULT_THINK_BG = false; // Deep diving 背景方框:默认取消(透明)
|
|
357
460
|
const DEFAULT_ENABLED = true;
|
|
358
461
|
const DEFAULT_CLOCK = false;
|
|
@@ -2343,9 +2446,10 @@ function mpwThumbFail(el, why) {
|
|
|
2343
2446
|
const proj = projJson ? null : mpkg.entries.find((e) => e.name === "project.json");
|
|
2344
2447
|
if (proj) {
|
|
2345
2448
|
try {
|
|
2346
|
-
|
|
2449
|
+
// ①(P-177 同口径) 包内 project.json 走宽容解析(官方允许尾逗号;严格 parse 会整份丢掉)
|
|
2450
|
+
const pj = mpwParseWeJson(new TextDecoder().decode(await readEntry(proj)));
|
|
2347
2451
|
propsMap = (pj.general && pj.general.properties) || null;
|
|
2348
|
-
} catch {}
|
|
2452
|
+
} catch (e) { mpwWeJsonSwallowed("handleVideoTexes:project.json", e) } // 原来就吞;现在计数 + 一行诊断
|
|
2349
2453
|
}
|
|
2350
2454
|
const key2 = file.name + "|" + file.size;
|
|
2351
2455
|
const prevS = readSection();
|
|
@@ -2576,7 +2680,8 @@ function mpwThumbFail(el, why) {
|
|
|
2576
2680
|
/** 解析 project.json 文本,提取标题 + 可调参数信息。 */
|
|
2577
2681
|
function extractProjectInfo(jsonText) {
|
|
2578
2682
|
try {
|
|
2579
|
-
|
|
2683
|
+
// ①(P-177 同口径) 包内 project.json 走宽容解析(官方允许尾逗号);健康文件与 JSON.parse 逐位等价
|
|
2684
|
+
const json = mpwParseWeJson(jsonText);
|
|
2580
2685
|
const props = json.general && json.general.properties;
|
|
2581
2686
|
const list = [];
|
|
2582
2687
|
if (props) {
|
|
@@ -2601,7 +2706,8 @@ function mpwThumbFail(el, why) {
|
|
|
2601
2706
|
}
|
|
2602
2707
|
return { title: json.title || "", properties: list };
|
|
2603
2708
|
} catch (e) {
|
|
2604
|
-
|
|
2709
|
+
// 原来就吞(bad project.json ⇒ 无标题/无属性);现在记账:计数 + 一行诊断(不静默)
|
|
2710
|
+
return mpwWeJsonSwallowed("extractProjectInfo:project.json", e);
|
|
2605
2711
|
}
|
|
2606
2712
|
}
|
|
2607
2713
|
/** ③(重做) 异步版:project.json 按需读取(424MB 大包不整体进内存)。 */
|
|
@@ -5186,7 +5292,13 @@ function mpwThumbFail(el, why) {
|
|
|
5186
5292
|
const dirBase = HOST_BASE + (info.src === "library"
|
|
5187
5293
|
? "/library-web/" + encodeURIComponent(info.id) + "/"
|
|
5188
5294
|
: "/custom-folder/" + encodeURIComponent(info.id) + "/");
|
|
5189
|
-
|
|
5295
|
+
// ①(P-177 同口径) 网页壁纸的包内 project.json:**先读文本再宽容解析**(原来是 r.json(),
|
|
5296
|
+
// 官方带尾逗号的 project.json 会在这里被静默丢掉 ⇒ general.properties 一个都推不下去)。
|
|
5297
|
+
// 原来的 `.catch(() => {})` 语义不变:解析失败仍吞,但走 mpwWeJsonSwallowed 记账。
|
|
5298
|
+
fetch(dirBase + "project.json")
|
|
5299
|
+
.then((r) => (r.ok ? r.text() : null))
|
|
5300
|
+
.then((txt) => { try { return txt ? mpwParseWeJson(txt) : null } catch (e) { return mpwWeJsonSwallowed("webShimPushProps:project.json", e) } })
|
|
5301
|
+
.then((pj) => {
|
|
5190
5302
|
const wire = mpwWebPropsToWire(pj);
|
|
5191
5303
|
try {
|
|
5192
5304
|
const edits = (readSection().propEdits || {})[info.key] || null;
|
|
@@ -17219,7 +17331,12 @@ body[data-mpw-modal] [role="dialog"] {
|
|
|
17219
17331
|
if (section.converted !== "web" || !section.mpkgKey || section.mpkgKey.indexOf("custom|") !== 0) return;
|
|
17220
17332
|
const name = section.mpkgKey.slice("custom|".length);
|
|
17221
17333
|
const url = HOST_BASE + "/custom-folder/" + encodeURIComponent(name) + "/loadJson.json";
|
|
17222
|
-
|
|
17334
|
+
// ①(P-177 同口径) 包内 loadJson.json(L2D 作者的 SettingModel):先读文本再宽容解析;
|
|
17335
|
+
// 原来的 `.catch(() => setWebCfg(null))` 语义不变,但解析失败要记账(不静默)。
|
|
17336
|
+
fetch(url)
|
|
17337
|
+
.then((r) => (r.ok ? r.text() : null))
|
|
17338
|
+
.then((txt) => { try { return txt ? mpwParseWeJson(txt) : null } catch (e) { return mpwWeJsonSwallowed("webCfg:loadJson.json", e) } })
|
|
17339
|
+
.then((d) => {
|
|
17223
17340
|
if (dead || !d || !d.SettingModel) { setWebCfg(null); return; }
|
|
17224
17341
|
const langs = d.AnimationsTexts ? Object.keys(d.AnimationsTexts) : [];
|
|
17225
17342
|
// ①(修正) 缓存完整 loadJson(含 EventInfos/AnimationsTexts),
|
|
@@ -17938,7 +18055,12 @@ body[data-mpw-modal] [role="dialog"] {
|
|
|
17938
18055
|
let projJson = null;
|
|
17939
18056
|
try {
|
|
17940
18057
|
const pj = await fetch(HOST_BASE + "/custom-folder/" + encodeURIComponent(w.name) + "/project.json");
|
|
17941
|
-
|
|
18058
|
+
// ①(P-177 同口径) 包内 project.json:**先读文本再宽容解析**(官方允许尾逗号;
|
|
18059
|
+
// 原来是 pj.json() ⇒ 带尾逗号的官方 project.json 在这里被静默丢掉、时段全走默认)。
|
|
18060
|
+
if (pj.ok) {
|
|
18061
|
+
const pjTxt = await pj.text();
|
|
18062
|
+
try { projJson = mpwParseWeJson(pjTxt) } catch (e) { mpwWeJsonSwallowed("customFolder:project.json(projJson)", e) }
|
|
18063
|
+
}
|
|
17942
18064
|
} catch {}
|
|
17943
18065
|
const ok = await handleVideoTexes({ entries: d.entries }, vtex, { name: w.title || w.name, size: 0 }, t, showError, readEntry, projJson, setHint, { kind: "host", token: d.token });
|
|
17944
18066
|
if (ok === "stale") { try { console.warn("[dsh-mpkg-wallpaper] 时段导入已过期(壁纸已切换),跳过后续链"); } catch {}; return; }
|
|
@@ -18421,10 +18543,11 @@ body[data-mpw-modal] [role="dialog"] {
|
|
|
18421
18543
|
let cfg = { enabled: true, morning: 4, day: 9, dusk: 17, night: 20 };
|
|
18422
18544
|
if (projIdx >= 0) {
|
|
18423
18545
|
try {
|
|
18424
|
-
|
|
18546
|
+
// ①(P-177 同口径) 宿主 /media 供来的包内 project.json 也走宽容解析
|
|
18547
|
+
const pj = mpwParseWeJson(new TextDecoder().decode(await readFull(projIdx)));
|
|
18425
18548
|
const pm = (pj.general && pj.general.properties) || null;
|
|
18426
18549
|
if (pm) cfg = timeConfigFromProps(pm, {});
|
|
18427
|
-
} catch {}
|
|
18550
|
+
} catch (e) { mpwWeJsonSwallowed("hostMedia:project.json(timeConfig)", e) } // 原来就吞;现在记账
|
|
18428
18551
|
}
|
|
18429
18552
|
const slot = slotForTime(cfg, new Date());
|
|
18430
18553
|
const active = vtex.find((v) => slotFromName(v.name) === slot) || vtex[0];
|
|
@@ -20625,6 +20748,8 @@ body[data-mpw-modal] [role="dialog"] {
|
|
|
20625
20748
|
"diagflag.align.d": "?align=0:复现旧对齐(origin 恒几何中心),用于对齐语义 A/B。",
|
|
20626
20749
|
"diagflag.parallax": "视差旧公式",
|
|
20627
20750
|
"diagflag.parallax.d": "?parallax=legacy:鼠标视差退回旧 lwe 近似式;默认官方公式。",
|
|
20751
|
+
"diagflag.gyro": "移动端姿态源",
|
|
20752
|
+
"diagflag.gyro.d": "?gyro=1 强制启用 / ?gyro=0 完全不监听(一个监听都不挂)/ 缺省 auto(有细指针不启用、触摸设备启用):deviceorientation 的 beta/gamma 按与鼠标同一套 u,v 喂进指针源 ⇒ 视差 g_ParallaxPosition、粒子、轨迹、脚本指针全链生效。",
|
|
20628
20753
|
"diagflag.audio": "开启声音",
|
|
20629
20754
|
"diagflag.audio.d": "?audio=1:播放场景 sound 层(每层一条流;浏览器策略可能需要先点一下页面)。",
|
|
20630
20755
|
"diagflag.whitefallback": "缺纹理回退",
|
|
@@ -21432,6 +21557,8 @@ body[data-mpw-modal] [role="dialog"] {
|
|
|
21432
21557
|
"diagflag.align.d": "?align=0: legacy alignment (origin always geometric center) for A/B.",
|
|
21433
21558
|
"diagflag.parallax": "Legacy parallax",
|
|
21434
21559
|
"diagflag.parallax.d": "?parallax=legacy: old lwe-approx mouse parallax; the official formula is default.",
|
|
21560
|
+
"diagflag.gyro": "Device-motion pointer source",
|
|
21561
|
+
"diagflag.gyro.d": "?gyro=1 force on / ?gyro=0 no deviceorientation listener at all / default auto (off when a fine pointer exists, on for touch devices): beta/gamma feed the same u,v pointer source as the mouse, so parallax g_ParallaxPosition, particles, trails and script pointers all follow.",
|
|
21435
21562
|
"diagflag.audio": "Enable audio",
|
|
21436
21563
|
"diagflag.audio.d": "?audio=1: play scene sound layers (one stream per layer; may need a user gesture).",
|
|
21437
21564
|
"diagflag.whitefallback": "Missing-texture fallback",
|
package/lib/pkg-extract.js
CHANGED
|
@@ -52,6 +52,108 @@ import { readFileSync, readdirSync, statSync, openSync, readSync, closeSync, exi
|
|
|
52
52
|
import { join, resolve, sep } from 'node:path';
|
|
53
53
|
import { deflateSync, inflateSync } from 'node:zlib';
|
|
54
54
|
|
|
55
|
+
/* ═══ MPW-WEJSON-BEGIN ═══ */
|
|
56
|
+
/* 官方随包 JSON 的**宽容解析**(WE 允许尾逗号;`JSON.parse` 不允许)——**宿主侧**实现。
|
|
57
|
+
*
|
|
58
|
+
* ── 为什么必须有(官方证据,2026-09-24 官方产物盘点;与渲染器仓同一轮定性)──────────────
|
|
59
|
+
* · 官方随包发布的 `wallpaper_engine/assets/effects/fluidsimulation/effect.json`(10,224 B)
|
|
60
|
+
* **自己就带尾逗号**:第 402 行 `"shaders/effects/fluidsimulation_normal.vert",` 后面紧跟 `}`
|
|
61
|
+
* ⇒ 标准 `JSON.parse` 报 `Expecting value: line 403 column 2`。WE 照发照用 ⇒ **官方容忍尾逗号**
|
|
62
|
+
* (引擎用 jsoncpp,二进制里带 `allowTrailingCommas`/`allowComments` 开关名)。
|
|
63
|
+
* · 宿主侧同样会**静默丢整份文件**:`scene.json`(音频 sound 层引用 / 主图提取 / 图层清单)、
|
|
64
|
+
* `model.json`、`material.json`(`access.readJson`)解析失败就是 `null` —— 少一层、少一条音轨、
|
|
65
|
+
* 回退预览图,日志一行都没有。
|
|
66
|
+
* · 渲染器仓的对应修复编号 **P-177**(`core/we-scene-bundle.js` 的 `export function parseWeJson`
|
|
67
|
+
* + `weJsonStats()`);插件**客户端**侧同一实现是 `lib/client.js` 的 `mpwParseWeJson`
|
|
68
|
+
* (`/* ═══ MPW-WEJSON-BEGIN ═══ *\/` 块)。两处是**同一语义的独立实现**(渲染器仓 GPL-3.0-or-later、
|
|
69
|
+
* 本仓 MIT ⇒ 只做行为对照,不搬运代码),且 `tools/we-json-tolerance-test.mjs` 把两处切片后
|
|
70
|
+
* 逐夹具对拍(解析值 + 计数增量全等)⇒ 防漂移。
|
|
71
|
+
*
|
|
72
|
+
* ── 口径(只做"官方明确容忍"的那两件事,其余照旧抛)──────────────────────────────────
|
|
73
|
+
* ① 尾逗号:`,` 后面(跳过空白)紧跟 `}` / `]` ⇒ 丢掉这个逗号;**字符串内的逗号一律不动**。
|
|
74
|
+
* ② 注释:**只在**"去掉尾逗号后仍解析失败"时才尝试剥 `//` 与 `/* *\/`(同样字符串内不动),单独计数。
|
|
75
|
+
* ③ BOM(U+FEFF)与官方一致地吃掉。
|
|
76
|
+
* **健康文件逐位等价于 `JSON.parse`**:先走 `JSON.parse(text)`,成功原样返回(plain +1);
|
|
77
|
+
* 真坏 JSON **仍然抛** ⇒ 调用点的 try/catch 语义与读量(如 audio-scan 规格的 sceneJsonRead 预算)不变,
|
|
78
|
+
* 只是失败的那几处现在记账(计数 + 一行 console.warn,见 `mpwWeJsonSwallowed`),不再静默。
|
|
79
|
+
* 计数:`mpwWeJsonStats()` 返回 `{ calls, plain, trailingComma, comments, failed, swallowed }`
|
|
80
|
+
* —— 前五项与渲染器 `weJsonStats()` / 客户端 `mpwWeJsonStats()` 同名同义。 */
|
|
81
|
+
const MPW_WEJSON = { calls: 0, plain: 0, trailingComma: 0, comments: 0, failed: 0, swallowed: 0 };
|
|
82
|
+
const MPW_WEJSON_SEEN = new Map(); // where|msg → 次数(诊断去重:同一个坏文件只吵一行)
|
|
83
|
+
/** 只读快照(对外只给这个,避免调用方改计数)。 */
|
|
84
|
+
function mpwWeJsonStats() { return Object.assign({}, MPW_WEJSON) }
|
|
85
|
+
/** 文本整形:一趟扫描,按需去掉**字符串外**的尾逗号(`withComments=false`)或尾逗号 + 注释
|
|
86
|
+
* (`withComments=true`)。**字符串里的一律照抄**(`"a,}"` / `"//"` / `"/*"` 一个字都不动);
|
|
87
|
+
* 注释替换成等长空白,行号与列号都保持可读;尾逗号判定会**越过空白与注释**看下一个有效字符
|
|
88
|
+
* (所以 `{"a":1, /* c *\/ }` 与不带注释的写法同解)。与客户端 `lib/client.js` 的同名函数同语义。 */
|
|
89
|
+
function mpwWeJsonRepair(src, withComments) {
|
|
90
|
+
let out = '', inStr = false, esc = false;
|
|
91
|
+
for (let i = 0; i < src.length; i++) {
|
|
92
|
+
const c = src[i], d = src[i + 1];
|
|
93
|
+
if (inStr) { // 字符串内:只跟 escape 状态,内容逐字照抄
|
|
94
|
+
out += c;
|
|
95
|
+
if (esc) esc = false;
|
|
96
|
+
else if (c === '\\') esc = true;
|
|
97
|
+
else if (c === '"') inStr = false;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (c === '"') { inStr = true; out += c; continue }
|
|
101
|
+
if (withComments && c === '/' && d === '/') { // `//` 行注释 ⇒ 空白
|
|
102
|
+
while (i < src.length && src[i] !== '\n') { out += ' '; i++ }
|
|
103
|
+
out += '\n';
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (withComments && c === '/' && d === '*') { // `/* */` 块注释 ⇒ 空白
|
|
107
|
+
out += ' '; i += 2;
|
|
108
|
+
while (i < src.length && !(src[i] === '*' && src[i + 1] === '/')) { out += (src[i] === '\n' ? '\n' : ' '); i++ }
|
|
109
|
+
out += ' '; i++;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (c === ',') { // 尾逗号:往后看下一个有效字符(可跨空白/注释)
|
|
113
|
+
let j = i + 1;
|
|
114
|
+
for (;;) {
|
|
115
|
+
while (j < src.length && (src[j] === ' ' || src[j] === '\t' || src[j] === '\n' || src[j] === '\r')) j++;
|
|
116
|
+
if (!withComments) break;
|
|
117
|
+
if (src[j] === '/' && src[j + 1] === '/') { while (j < src.length && src[j] !== '\n') j++; continue }
|
|
118
|
+
if (src[j] === '/' && src[j + 1] === '*') { j += 2; while (j < src.length && !(src[j] === '*' && src[j + 1] === '/')) j++; j += 2; continue }
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
if (src[j] === '}' || src[j] === ']') continue; // 尾逗号 ⇒ 丢掉这个逗号
|
|
122
|
+
}
|
|
123
|
+
out += c;
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 解析官方随包 JSON(scene.json / project.json / materials / effects / models / combo 定义…)。
|
|
129
|
+
* 与 `JSON.parse` 的差别只有两条:容忍尾逗号;再不行才容忍注释(外加吃掉 BOM)。
|
|
130
|
+
* **其余一律照旧抛**(调用方的 try/catch 语义不变)。
|
|
131
|
+
* @param {string} text
|
|
132
|
+
* @returns {any}
|
|
133
|
+
*/
|
|
134
|
+
function mpwParseWeJson(text) {
|
|
135
|
+
MPW_WEJSON.calls++;
|
|
136
|
+
const s0 = (typeof text === 'string') ? text : String(text == null ? '' : text);
|
|
137
|
+
const s = s0.charCodeAt(0) === 0xFEFF ? s0.slice(1) : s0; // ① BOM:官方产物会带,JSON.parse 自己不认
|
|
138
|
+
try { const v = JSON.parse(s); MPW_WEJSON.plain++; return v } catch (e) { /* 健康文件到此为止(逐位等价 JSON.parse) */ }
|
|
139
|
+
try { const v = JSON.parse(mpwWeJsonRepair(s, false)); MPW_WEJSON.trailingComma++; return v } catch (e) { /* ② 尾逗号也不够 ⇒ 再试注释 */ }
|
|
140
|
+
try { const v = JSON.parse(mpwWeJsonRepair(s, true)); MPW_WEJSON.comments++; return v } // ③ 尾逗号 + 注释
|
|
141
|
+
catch (e) { MPW_WEJSON.failed++; throw e } // 真坏 JSON:照旧抛
|
|
142
|
+
}
|
|
143
|
+
/** 包内 JSON 读取/解析失败、调用方**按原有语义吞掉**时的记账:计数 + 一行 console.warn(不静默)。
|
|
144
|
+
* (调用点的 try/catch 结构不动;返回值恒 null 方便 `catch { x = ... }` 写法。同 where+msg 只吵一次。) */
|
|
145
|
+
function mpwWeJsonSwallowed(where, e) {
|
|
146
|
+
try { MPW_WEJSON.swallowed++ } catch (e2) {}
|
|
147
|
+
try {
|
|
148
|
+
const key = where + '|' + String((e && e.message) || e);
|
|
149
|
+
const n = (MPW_WEJSON_SEEN.get(key) || 0) + 1;
|
|
150
|
+
MPW_WEJSON_SEEN.set(key, n);
|
|
151
|
+
if (n === 1) console.warn('[dsh-mpkg-wallpaper] 包内 JSON 读取/解析失败(按原语义吞掉): ' + where + ' — ' + String((e && e.message) || e));
|
|
152
|
+
} catch (e2) {}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
/* ═══ MPW-WEJSON-END ═══ */
|
|
156
|
+
|
|
55
157
|
/** Wallpaper Engine texture format ids (TEXI0001 header), per RePKG/lwe. */
|
|
56
158
|
const TexFormat = {
|
|
57
159
|
RGBA8888: 0,
|
|
@@ -587,8 +689,9 @@ function enumerateAudioTracks(src) {
|
|
|
587
689
|
if (sjEntry) {
|
|
588
690
|
try {
|
|
589
691
|
const bytes = readEntry(sjEntry);
|
|
590
|
-
|
|
591
|
-
|
|
692
|
+
// ①(P-177 同口径) 包内 scene.json 走宽容解析(官方允许尾逗号);原来解析失败就 null(静默丢 refs)
|
|
693
|
+
if (bytes && bytes.length) { sceneJson = mpwParseWeJson(textDecoder.decode(bytes)); sceneJsonRead = true; }
|
|
694
|
+
} catch (e) { sceneJson = mpwWeJsonSwallowed('enumerateAudioTracks:scene.json(容器内)', e); }
|
|
592
695
|
}
|
|
593
696
|
}
|
|
594
697
|
for (const { layer, path } of collectSoundLayerRefs(sceneJson)) note(path, layer, 0);
|
|
@@ -633,8 +736,9 @@ function collectDirAudioTracks(dir, opts = {}) {
|
|
|
633
736
|
let sceneJson = null;
|
|
634
737
|
try {
|
|
635
738
|
const sj = join(dir, 'scene.json');
|
|
636
|
-
|
|
637
|
-
|
|
739
|
+
// ①(P-177 同口径) 松散场景目录的 scene.json 也走宽容解析(原来解析失败静默 null)
|
|
740
|
+
if (existsSync(sj)) sceneJson = mpwParseWeJson(readFileSync(sj, 'utf8'));
|
|
741
|
+
} catch (e) { sceneJson = mpwWeJsonSwallowed('collectDirAudioTracks:' + join(dir, 'scene.json'), e); }
|
|
638
742
|
const readHead = (entry, n) => {
|
|
639
743
|
try {
|
|
640
744
|
const len = Math.max(0, Math.min(n, entry.size));
|
|
@@ -1351,9 +1455,10 @@ function pkgSceneAccess(pkgData) {
|
|
|
1351
1455
|
const file = readFile(path);
|
|
1352
1456
|
if (!file) return null;
|
|
1353
1457
|
try {
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1458
|
+
// ①(P-177 同口径) 容器内 scene.json / model.json / material.json 走宽容解析(官方允许尾逗号)
|
|
1459
|
+
return mpwParseWeJson(textDecoder.decode(file.bytes));
|
|
1460
|
+
} catch (e) {
|
|
1461
|
+
return mpwWeJsonSwallowed('pkgSceneAccess.readJson:' + path, e); // 原来就吞;现在记账
|
|
1357
1462
|
}
|
|
1358
1463
|
},
|
|
1359
1464
|
readFile,
|
|
@@ -1459,9 +1564,10 @@ function dirSceneAccess(dir) {
|
|
|
1459
1564
|
const file = readFile(path);
|
|
1460
1565
|
if (!file) return null;
|
|
1461
1566
|
try {
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1567
|
+
// ①(P-177 同口径) 松散场景目录里的 scene.json / model.json / material.json 走宽容解析
|
|
1568
|
+
return mpwParseWeJson(textDecoder.decode(file.bytes));
|
|
1569
|
+
} catch (e) {
|
|
1570
|
+
return mpwWeJsonSwallowed('dirSceneAccess.readJson:' + path, e); // 原来就吞;现在记账
|
|
1465
1571
|
}
|
|
1466
1572
|
},
|
|
1467
1573
|
readFile,
|
package/package.json
CHANGED