modelstage 0.0.0-stage → 0.5.28

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.
Files changed (281) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +274 -2
  3. package/THIRD_PARTY_NOTICES.md +90 -0
  4. package/bin/modelstage.mjs +137 -0
  5. package/config/diag-upload.example.json +7 -0
  6. package/config/models/live2d.assetbundls.json +1 -0
  7. package/config/models/live2d.azurlane.json +1 -0
  8. package/config/models/live2d.azurlane_bundle.json +1 -0
  9. package/config/models/live2d.azurlane_spine.json +1 -0
  10. package/config/models/live2d.bd2.json +1 -0
  11. package/config/models/live2d.bluearchive.json +1 -0
  12. package/config/models/live2d.live2d.json +1 -0
  13. package/config/models/live2d.nikke.json +1 -0
  14. package/config/models/live2d.nikke_2.json +1 -0
  15. package/config/models/live2d.npc.json +1 -0
  16. package/config/models/spine.assetbundls.json +1 -0
  17. package/config/models/spine.azurlane.json +1 -0
  18. package/config/models/spine.azurlane_bundle.json +1 -0
  19. package/config/models/spine.azurlane_spine.json +1 -0
  20. package/config/models/spine.bd2.json +1 -0
  21. package/config/models/spine.bluearchive.json +1 -0
  22. package/config/models/spine.live2d.json +1 -0
  23. package/config/models/spine.nikke.json +1 -0
  24. package/config/models/spine.nikke_2.json +1 -0
  25. package/config/models/spine.npc.json +1 -0
  26. package/config/names/NIKKE.json +1058 -0
  27. package/config/names/README.md +47 -0
  28. package/config/names//345/244/251/345/221/275/344/271/213/345/255/220.json +42897 -0
  29. package/config/names//346/243/225/350/211/262/345/260/230/345/237/2032.json +4016 -0
  30. package/config/names//347/242/247/350/223/235/350/210/252/347/272/277.json +38342 -0
  31. package/config/plugins.json +13 -0
  32. package/config/resources.example.json +40 -0
  33. package/package.json +63 -4
  34. package/plugins/unity-live2d-bundle/README.md +98 -0
  35. package/plugins/unity-live2d-bundle/index.js +891 -0
  36. package/plugins/unity-live2d-bundle/lib/anim-to-motion.js +402 -0
  37. package/plugins/unity-live2d-bundle/lib/bundle-probe.js +244 -0
  38. package/plugins/unity-live2d-bundle/lib/bytes.js +189 -0
  39. package/plugins/unity-live2d-bundle/lib/common-strings.js +46 -0
  40. package/plugins/unity-live2d-bundle/lib/dxt.js +197 -0
  41. package/plugins/unity-live2d-bundle/lib/errors.js +71 -0
  42. package/plugins/unity-live2d-bundle/lib/etc2.js +371 -0
  43. package/plugins/unity-live2d-bundle/lib/extract.js +242 -0
  44. package/plugins/unity-live2d-bundle/lib/lzma-d-min.LICENSE.txt +21 -0
  45. package/plugins/unity-live2d-bundle/lib/lzma-d-min.js +34 -0
  46. package/plugins/unity-live2d-bundle/lib/model3.js +137 -0
  47. package/plugins/unity-live2d-bundle/lib/png.js +424 -0
  48. package/plugins/unity-live2d-bundle/lib/serialized.js +233 -0
  49. package/plugins/unity-live2d-bundle/lib/texdecode.js +190 -0
  50. package/plugins/unity-live2d-bundle/lib/typetree.js +215 -0
  51. package/plugins/unity-live2d-bundle/lib/unityfs.js +396 -0
  52. package/plugins/unity-live2d-bundle/plugin.json +81 -0
  53. package/plugins/unity-live2d-bundle/profiles/unityfs-live2d.json +26 -0
  54. package/plugins/unity-spine-bundle/index.js +535 -0
  55. package/plugins/unity-spine-bundle/lib/bytes.js +189 -0
  56. package/plugins/unity-spine-bundle/lib/common-strings.js +46 -0
  57. package/plugins/unity-spine-bundle/lib/dxt.js +197 -0
  58. package/plugins/unity-spine-bundle/lib/errors.js +79 -0
  59. package/plugins/unity-spine-bundle/lib/etc2.js +371 -0
  60. package/plugins/unity-spine-bundle/lib/extract.js +242 -0
  61. package/plugins/unity-spine-bundle/lib/lzma-d-min.LICENSE.txt +21 -0
  62. package/plugins/unity-spine-bundle/lib/lzma-d-min.js +34 -0
  63. package/plugins/unity-spine-bundle/lib/png.js +424 -0
  64. package/plugins/unity-spine-bundle/lib/serialized.js +233 -0
  65. package/plugins/unity-spine-bundle/lib/texdecode.js +190 -0
  66. package/plugins/unity-spine-bundle/lib/typetree.js +215 -0
  67. package/plugins/unity-spine-bundle/lib/unityfs.js +396 -0
  68. package/plugins/unity-spine-bundle/plugin.json +75 -0
  69. package/src/server/README.md +69 -0
  70. package/src/server/assets/browse.mjs +56 -0
  71. package/src/server/assets/bundle-probe.mjs +94 -0
  72. package/src/server/assets/probe.mjs +184 -0
  73. package/src/server/core/config.mjs +173 -0
  74. package/src/server/core/env-num.mjs +60 -0
  75. package/src/server/core/env.mjs +49 -0
  76. package/src/server/core/fs-portable.mjs +37 -0
  77. package/src/server/core/log.mjs +107 -0
  78. package/src/server/core/paths.mjs +44 -0
  79. package/src/server/data/catalog.mjs +1091 -0
  80. package/src/server/data/entry-states.mjs +499 -0
  81. package/src/server/data/live2d.mjs +272 -0
  82. package/src/server/data/manifest-cache.mjs +58 -0
  83. package/src/server/data/manifest-stamp.mjs +135 -0
  84. package/src/server/data/model-meta.mjs +114 -0
  85. package/src/server/data/names.mjs +106 -0
  86. package/src/server/data/shape-probe-worker.mjs +33 -0
  87. package/src/server/data/shape-probe.mjs +626 -0
  88. package/src/server/data/spine.mjs +336 -0
  89. package/src/server/data/tree.mjs +126 -0
  90. package/src/server/http/atomic-json.mjs +145 -0
  91. package/src/server/http/background-solids.mjs +270 -0
  92. package/src/server/http/backgrounds.mjs +388 -0
  93. package/src/server/http/diag-upload.mjs +264 -0
  94. package/src/server/http/diag.mjs +173 -0
  95. package/src/server/http/ready.mjs +167 -0
  96. package/src/server/http/static.mjs +312 -0
  97. package/src/server/http/write.mjs +654 -0
  98. package/src/server/index.mjs +114 -0
  99. package/src/server/plugins/builtin-providers.json +30 -0
  100. package/src/server/plugins/registry.mjs +650 -0
  101. package/src/server/plugins/slot-catalog.mjs +286 -0
  102. package/src/server/routes/background-solids.mjs +56 -0
  103. package/src/server/routes/backgrounds.mjs +77 -0
  104. package/src/server/routes/config.mjs +38 -0
  105. package/src/server/routes/data.mjs +313 -0
  106. package/src/server/routes/diag.mjs +37 -0
  107. package/src/server/routes/entries.mjs +170 -0
  108. package/src/server/routes/ready.mjs +122 -0
  109. package/src/server/serve.mjs +396 -0
  110. package/src/server/unpack/capacity.mjs +217 -0
  111. package/src/server/unpack/mem-probe.mjs +27 -0
  112. package/src/server/unpack/plugin-runner.mjs +529 -0
  113. package/src/server/unpack/plugin-store.mjs +736 -0
  114. package/src/server/unpack/probe-store.mjs +73 -0
  115. package/src/server/unpack/routes.mjs +359 -0
  116. package/src/server/unpack/runtime/exec.mjs +558 -0
  117. package/src/server/unpack/runtime/index.mjs +141 -0
  118. package/src/server/unpack/runtime/node-vm.mjs +111 -0
  119. package/src/server/unpack/unpack-worker.mjs +105 -0
  120. package/src/server/unpack/worker-client.mjs +184 -0
  121. package/src/server/unpack/write-result.mjs +84 -0
  122. package/src/shared/bundle-shape.mjs +83 -0
  123. package/src/shared/entry-reasons.mjs +119 -0
  124. package/src/shared/names.mjs +45 -0
  125. package/src/shared/type-labels.mjs +31 -0
  126. package/src/web/README.md +193 -0
  127. package/src/web/app/anim-list.js +222 -0
  128. package/src/web/app/background-solids.js +150 -0
  129. package/src/web/app/backgrounds.js +152 -0
  130. package/src/web/app/camera-log.js +28 -0
  131. package/src/web/app/context.js +72 -0
  132. package/src/web/app/dir-picker.js +242 -0
  133. package/src/web/app/entry-report.js +71 -0
  134. package/src/web/app/memory-bars.js +91 -0
  135. package/src/web/app/memory-budget.js +228 -0
  136. package/src/web/app/memory-guard.js +204 -0
  137. package/src/web/app/model-actions.js +682 -0
  138. package/src/web/app/renderer-setting.js +70 -0
  139. package/src/web/app/root-manager.js +254 -0
  140. package/src/web/app/status.js +138 -0
  141. package/src/web/app/topbar-actions.js +357 -0
  142. package/src/web/app/transport-actions.js +297 -0
  143. package/src/web/app/ui-state.js +559 -0
  144. package/src/web/app/url-params.js +171 -0
  145. package/src/web/app/view-settings.js +186 -0
  146. package/src/web/components.css +524 -0
  147. package/src/web/debug/diag-hud.js +95 -0
  148. package/src/web/debug/drag.js +369 -0
  149. package/src/web/debug/log.js +320 -0
  150. package/src/web/debug/model-cmp.js +292 -0
  151. package/src/web/debug/panel.css +474 -0
  152. package/src/web/debug/panel.js +712 -0
  153. package/src/web/debug/race-probe.js +82 -0
  154. package/src/web/debug/upload.js +130 -0
  155. package/src/web/engine/adapters/atlas.js +47 -0
  156. package/src/web/engine/adapters/index.js +22 -0
  157. package/src/web/engine/adapters/renderer.js +223 -0
  158. package/src/web/engine/adapters/skeleton.js +105 -0
  159. package/src/web/engine/adapters/texture.js +64 -0
  160. package/src/web/engine/animation.js +181 -0
  161. package/src/web/engine/appearance.js +136 -0
  162. package/src/web/engine/camera-adapter.js +212 -0
  163. package/src/web/engine/effect-chain.js +138 -0
  164. package/src/web/engine/engine-state.js +120 -0
  165. package/src/web/engine/filters.js +224 -0
  166. package/src/web/engine/fit.js +197 -0
  167. package/src/web/engine/frame-gate.js +33 -0
  168. package/src/web/engine/gl-driver.js +56 -0
  169. package/src/web/engine/gl.js +77 -0
  170. package/src/web/engine/index.js +20 -0
  171. package/src/web/engine/loader.js +55 -0
  172. package/src/web/engine/model-info.js +26 -0
  173. package/src/web/engine/pipeline.js +200 -0
  174. package/src/web/engine/player.js +427 -0
  175. package/src/web/engine/pma/blend.js +76 -0
  176. package/src/web/engine/pma/detect.js +118 -0
  177. package/src/web/engine/pma/index.js +8 -0
  178. package/src/web/engine/pma/resolve.js +112 -0
  179. package/src/web/engine/pma/upload.js +127 -0
  180. package/src/web/engine/pma.js +5 -0
  181. package/src/web/engine/render-frame.js +93 -0
  182. package/src/web/engine/runtime-capabilities.js +209 -0
  183. package/src/web/engine/runtime.js +37 -0
  184. package/src/web/engine/skins.js +165 -0
  185. package/src/web/engine/textures.js +174 -0
  186. package/src/web/index.html +117 -0
  187. package/src/web/infra/api.js +531 -0
  188. package/src/web/infra/diag.js +509 -0
  189. package/src/web/infra/document.js +269 -0
  190. package/src/web/infra/dom.js +31 -0
  191. package/src/web/infra/events.mjs +27 -0
  192. package/src/web/infra/log.js +153 -0
  193. package/src/web/infra/mem-stats.js +112 -0
  194. package/src/web/infra/plugin-host-registration.mjs +285 -0
  195. package/src/web/infra/plugin-host-report.mjs +32 -0
  196. package/src/web/infra/plugin-host-unload.mjs +79 -0
  197. package/src/web/infra/plugin-host-unpack.mjs +899 -0
  198. package/src/web/infra/plugin-host-warmup.mjs +180 -0
  199. package/src/web/infra/plugin-host.js +628 -0
  200. package/src/web/infra/ready.js +461 -0
  201. package/src/web/infra/schedule.js +51 -0
  202. package/src/web/infra/shortcuts.js +129 -0
  203. package/src/web/infra/slot-registry.js +402 -0
  204. package/src/web/infra/vfs.js +247 -0
  205. package/src/web/live2d/bridge.js +400 -0
  206. package/src/web/live2d/model.js +321 -0
  207. package/src/web/live2d/overlay.js +142 -0
  208. package/src/web/live2d/pixi-app.js +111 -0
  209. package/src/web/live2d/playback-probe.js +92 -0
  210. package/src/web/live2d/probe-geometry.js +329 -0
  211. package/src/web/live2d/probe.js +119 -0
  212. package/src/web/live2d/release.js +66 -0
  213. package/src/web/live2d/runtime-adapter.js +289 -0
  214. package/src/web/live2d/stage-ctx.js +152 -0
  215. package/src/web/live2d/stage.js +204 -0
  216. package/src/web/live2d/vfs-settings.js +174 -0
  217. package/src/web/live2d/view.js +89 -0
  218. package/src/web/main.js +629 -0
  219. package/src/web/panel/background-list.js +373 -0
  220. package/src/web/panel/bg-source.js +291 -0
  221. package/src/web/panel/color-picker.css +199 -0
  222. package/src/web/panel/color-picker.js +278 -0
  223. package/src/web/panel/dialog.css +116 -0
  224. package/src/web/panel/dialog.js +173 -0
  225. package/src/web/panel/dom.js +35 -0
  226. package/src/web/panel/icons.js +58 -0
  227. package/src/web/panel/memory-bar.js +195 -0
  228. package/src/web/panel/panel-settings.js +1263 -0
  229. package/src/web/panel/panel.css +1304 -0
  230. package/src/web/panel/render-choice.js +104 -0
  231. package/src/web/panel/schema.js +784 -0
  232. package/src/web/panel/settings-io.js +291 -0
  233. package/src/web/panel/shell-controls.js +274 -0
  234. package/src/web/panel/slider.js +210 -0
  235. package/src/web/panel/toolbar-schema.js +111 -0
  236. package/src/web/panel/toolbar.js +195 -0
  237. package/src/web/panel/transport-picker.js +375 -0
  238. package/src/web/panel/transport.js +321 -0
  239. package/src/web/panel/view.js +553 -0
  240. package/src/web/plugin/unpack-worker.js +185 -0
  241. package/src/web/render/camera/constants.js +60 -0
  242. package/src/web/render/camera/index.js +123 -0
  243. package/src/web/render/camera/spine.js +124 -0
  244. package/src/web/render/capture.js +30 -0
  245. package/src/web/render/interaction.js +527 -0
  246. package/src/web/render/live2d.js +627 -0
  247. package/src/web/render/playback.js +47 -0
  248. package/src/web/render/reset-view.js +29 -0
  249. package/src/web/render/spine.js +386 -0
  250. package/src/web/render/surface-switch.js +176 -0
  251. package/src/web/render/surface.js +571 -0
  252. package/src/web/render/surfaces.js +70 -0
  253. package/src/web/roster/filter.js +31 -0
  254. package/src/web/roster/list.js +276 -0
  255. package/src/web/roster/persist.js +92 -0
  256. package/src/web/roster/reveal.js +77 -0
  257. package/src/web/roster/rows.js +248 -0
  258. package/src/web/roster/snapshot.js +39 -0
  259. package/src/web/roster/type-filter.js +97 -0
  260. package/src/web/roster/view.js +761 -0
  261. package/src/web/spine/canvas-api-groups.js +267 -0
  262. package/src/web/spine/canvas-api.js +97 -0
  263. package/src/web/spine/canvas-document.js +80 -0
  264. package/src/web/spine/canvas-engine.js +193 -0
  265. package/src/web/spine/canvas-entry.js +28 -0
  266. package/src/web/spine/canvas-pixels.js +36 -0
  267. package/src/web/spine/canvas-runtime.js +127 -0
  268. package/src/web/spine/canvas-view.js +108 -0
  269. package/src/web/spine/canvas.js +7 -0
  270. package/src/web/spine/runtime-hint.js +73 -0
  271. package/src/web/spine/runtimes.js +212 -0
  272. package/src/web/style.css +493 -0
  273. package/tool/lib/bundle-probe-node.mjs +121 -0
  274. package/tool/lib/paths.mjs +20 -0
  275. package/tool/lib/runtime-guides.mjs +65 -0
  276. package/tool/lib/runtime-package.mjs +403 -0
  277. package/tool/lib/spine-meta.mjs +227 -0
  278. package/tool/lib/spine-motions.mjs +104 -0
  279. package/tool/parse-jsonc.mjs +86 -0
  280. package/tool/resolve-live2d-runtimes.mjs +276 -0
  281. package/tool/spine-motion-rules.json +101 -0
@@ -0,0 +1,34 @@
1
+ // dsh-skip-sensitive: MIT 许可要求保留作者邮箱署名,非敏感信息泄露
2
+ /// lzma-d-min.js —— LZMA 解码器 vendor 件(**已就地打补丁**,见文末「本仓补丁」段)。
3
+ /// 上游版本:lzma@2.3.2(精确版本,见 package 名与上方来源行)。
4
+ /// 补丁目的:上游把「滑动窗口」与「输出累加器」都建成普通 Array(输出还是 o(32) 起步、
5
+ /// 靠索引赋值无限增长),33MB 的解压产物会让 V8 堆多占数百 MB,且峰值随 GC 时机剧烈波动。
6
+ /// 补丁内容(三处,逐条见文末):① 窗口缓冲改 new Uint8Array ② 输出累加器按已知长度预分配
7
+ /// ③ 取结果时用 subarray(typed array 的 length 只读)。
8
+ /// 来源:npm 包 lzma@2.3.2 的 src/lzma-d-min.js(上游产物本身是 IIFE,末尾挂 globalThis.LZMA)。
9
+ /// © 2015 Nathan Rugg <nmrugg@gmail.com> | MIT License(许可全文见同目录 lzma-d-min.LICENSE.txt)。
10
+ /// 为什么加这段:min 版产物里被 minify 掉了版权声明,而 MIT 要求副本保留声明。
11
+ /// 用途:UnityFS 容器里 LZMA 压缩的 blocksInfo / block 数据解码,调用点见 lib/unityfs.js 的 decompress()。
12
+ var e=function(){"use strict";function r(e,r){postMessage({action:nr,cbn:r,result:e})}function o(e){var r=[];return r[e-1]=void 0,r}function n(e,r){return i(e[0]+r[0],e[1]+r[1])}function t(e,r){var o,n;return e[0]==r[0]&&e[1]==r[1]?0:(o=0>e[1],n=0>r[1],o&&!n?-1:!o&&n?1:d(e,r)[1]<0?-1:1)}function i(e,r){var o,n;for(r%=0x10000000000000000,e%=0x10000000000000000,o=r%ir,n=Math.floor(e/ir)*ir,r=r-o+n,e=e-n+o;0>e;)e+=ir,r-=ir;for(;e>4294967295;)e-=ir,r+=ir;for(r%=0x10000000000000000;r>0x7fffffff00000000;)r-=0x10000000000000000;for(;-0x8000000000000000>r;)r+=0x10000000000000000;return[e,r]}function u(e){return e>=0?[e,0]:[e+ir,-ir]}function s(e){return e[0]>=2147483648?~~Math.max(Math.min(e[0]-ir,2147483647),-2147483648):~~Math.max(Math.min(e[0],2147483647),-2147483648)}function d(e,r){return i(e[0]-r[0],e[1]-r[1])}function c(e,r){return e.ab=r,e.cb=0,e.O=r.length,e}function m(e){return e.cb>=e.O?-1:255&e.ab[e.cb++]}function a(e){var n=e.bb>0?e.bb:0;return e.ab=n?new Uint8Array(n):o(32),e.O=0,e}function _(e){var r=e.ab;return r instanceof Uint8Array?r.subarray(0,e.O):(r.length=e.O,r)}function f(e,r,o,n){p(r,o,e.ab,e.O,n),e.O+=n}function p(e,r,o,n,t){for(var i=0;t>i;++i)o[n+i]=e[r+i]}function D(e,r,o){var n,t,i,s,d="",c=[];for(t=0;5>t;++t){if(i=m(r),-1==i)throw Error("truncated input");c[t]=i<<24>>24}if(n=N({}),!z(n,c))throw Error("corrupted input");for(t=0;64>t;t+=8){if(i=m(r),-1==i)throw Error("truncated input");i=i.toString(16),1==i.length&&(i="0"+i),d=i+""+d}/^0+$|^f+$/i.test(d)?e.N=ur:(s=parseInt(d,16),e.N=s>4294967295?ur:u(s)),e.Q=B(n,r,o,e.N)}function l(e,r){return e.S=a({}),D(e,c({},r),e.S),e}function g(e,r,o){var n=e.D-r-1;for(0>n&&(n+=e.c);0!=o;--o)n>=e.c&&(n=0),e.x[e.D++]=e.x[n++],e.D>=e.c&&w(e)}function v(e,r){(null==e.x||e.c!=r)&&(e.x=new Uint8Array(r)),e.c=r,e.D=0,e.w=0}function w(e){var r=e.D-e.w;r&&(f(e.V,e.x,e.w,r),e.D>=e.c&&(e.D=0),e.w=e.D)}function R(e,r){var o=e.D-r-1;return 0>o&&(o+=e.c),e.x[o]}function h(e,r){e.x[e.D++]=r,e.D>=e.c&&w(e)}function P(e){w(e),e.V=null}function C(e){return e-=2,4>e?e:3}function S(e){return 4>e?0:10>e?e-3:e-6}function M(e,r){return e.h=r,e.bb=null,e.X=1,e}function L(e){if(!e.X)throw Error("bad state");if(e.bb)throw Error("No encoding");return y(e),e.X}function y(e){var r=I(e.h);if(-1==r)throw Error("corrupted input");e.$=ur,e.Z=e.h.d,(r||t(e.h.U,sr)>=0&&t(e.h.d,e.h.U)>=0)&&(w(e.h.b),P(e.h.b),e.h.a.K=null,e.X=0)}function B(e,r,o,n){return e.a.K=r,P(e.b),e.b.V=o,b(e),e.f=0,e.l=0,e.T=0,e.R=0,e._=0,e.U=n,e.d=sr,e.I=0,M({},e)}function I(e){var r,o,i,d,c,m;if(m=s(e.d)&e.P,Q(e.a,e.q,(e.f<<4)+m)){if(Q(e.a,e.E,e.f))i=0,Q(e.a,e.s,e.f)?(Q(e.a,e.u,e.f)?(Q(e.a,e.r,e.f)?(o=e._,e._=e.R):o=e.R,e.R=e.T):o=e.T,e.T=e.l,e.l=o):Q(e.a,e.n,(e.f<<4)+m)||(e.f=7>e.f?9:11,i=1),i||(i=x(e.o,e.a,m)+2,e.f=7>e.f?8:11);else if(e._=e.R,e.R=e.T,e.T=e.l,i=2+x(e.C,e.a,m),e.f=7>e.f?7:10,c=q(e.j[C(i)],e.a),c>=4){if(d=(c>>1)-1,e.l=(2|1&c)<<d,14>c)e.l+=J(e.J,e.l-c-1,e.a,d);else if(e.l+=U(e.a,d-4)<<4,e.l+=F(e.t,e.a),0>e.l)return-1==e.l?1:-1}else e.l=c;if(t(u(e.l),e.d)>=0||e.l>=e.m)return-1;g(e.b,e.l,i),e.d=n(e.d,u(i)),e.I=R(e.b,0)}else r=Z(e.k,s(e.d),e.I),e.I=7>e.f?T(r,e.a):$(r,e.a,R(e.b,e.l)),h(e.b,e.I),e.f=S(e.f),e.d=n(e.d,dr);return 0}function N(e){e.b={},e.a={},e.q=o(192),e.E=o(12),e.s=o(12),e.u=o(12),e.r=o(12),e.n=o(192),e.j=o(4),e.J=o(114),e.t=K({},4),e.C=G({}),e.o=G({}),e.k={};for(var r=0;4>r;++r)e.j[r]=K({},6);return e}function b(e){e.b.w=0,e.b.D=0,X(e.q),X(e.n),X(e.E),X(e.s),X(e.u),X(e.r),X(e.J),H(e.k);for(var r=0;4>r;++r)X(e.j[r].B);A(e.C),A(e.o),X(e.t.B),V(e.a)}function z(e,r){var o,n,t,i,u,s,d;if(5>r.length)return 0;for(d=255&r[0],t=d%9,s=~~(d/9),i=s%5,u=~~(s/5),o=0,n=0;4>n;++n)o+=(255&r[1+n])<<8*n;return o>99999999||!W(e,t,i,u)?0:O(e,o)}function O(e,r){return 0>r?0:(e.z!=r&&(e.z=r,e.m=Math.max(e.z,1),v(e.b,Math.max(e.m,4096))),1)}function W(e,r,o,n){if(r>8||o>4||n>4)return 0;E(e.k,o,r);var t=1<<n;return k(e.C,t),k(e.o,t),e.P=t-1,1}function k(e,r){for(;r>e.e;++e.e)e.G[e.e]=K({},3),e.H[e.e]=K({},3)}function x(e,r,o){if(!Q(r,e.M,0))return q(e.G[o],r);var n=8;return n+=Q(r,e.M,1)?8+q(e.L,r):q(e.H[o],r)}function G(e){return e.M=o(2),e.G=o(16),e.H=o(16),e.L=K({},8),e.e=0,e}function A(e){X(e.M);for(var r=0;e.e>r;++r)X(e.G[r].B),X(e.H[r].B);X(e.L.B)}function E(e,r,n){var t,i;if(null==e.F||e.g!=n||e.y!=r)for(e.y=r,e.Y=(1<<r)-1,e.g=n,i=1<<e.g+e.y,e.F=o(i),t=0;i>t;++t)e.F[t]=j({})}function Z(e,r,o){return e.F[((r&e.Y)<<e.g)+((255&o)>>>8-e.g)]}function H(e){var r,o;for(o=1<<e.g+e.y,r=0;o>r;++r)X(e.F[r].v)}function T(e,r){var o=1;do o=o<<1|Q(r,e.v,o);while(256>o);return o<<24>>24}function $(e,r,o){var n,t,i=1;do if(t=o>>7&1,o<<=1,n=Q(r,e.v,(1+t<<8)+i),i=i<<1|n,t!=n){for(;256>i;)i=i<<1|Q(r,e.v,i);break}while(256>i);return i<<24>>24}function j(e){return e.v=o(768),e}function K(e,r){return e.A=r,e.B=o(1<<r),e}function q(e,r){var o,n=1;for(o=e.A;0!=o;--o)n=(n<<1)+Q(r,e.B,n);return n-(1<<e.A)}function F(e,r){var o,n,t=1,i=0;for(n=0;e.A>n;++n)o=Q(r,e.B,t),t<<=1,t+=o,i|=o<<n;return i}function J(e,r,o,n){var t,i,u=1,s=0;for(i=0;n>i;++i)t=Q(o,e,r+u),u<<=1,u+=t,s|=t<<i;return s}function Q(e,r,o){var n,t=r[o];return n=(e.i>>>11)*t,(-2147483648^n)>(-2147483648^e.p)?(e.i=n,r[o]=t+(2048-t>>>5)<<16>>16,-16777216&e.i||(e.p=e.p<<8|m(e.K),e.i<<=8),0):(e.i-=n,e.p-=n,r[o]=t-(t>>>5)<<16>>16,-16777216&e.i||(e.p=e.p<<8|m(e.K),e.i<<=8),1)}function U(e,r){var o,n,t=0;for(o=r;0!=o;--o)e.i>>>=1,n=e.p-e.i>>>31,e.p-=e.i&n-1,t=t<<1|1-n,-16777216&e.i||(e.p=e.p<<8|m(e.K),e.i<<=8);return t}function V(e){e.p=0,e.i=-1;for(var r=0;5>r;++r)e.p=e.p<<8|m(e.K)}function X(e){for(var r=e.length-1;r>=0;--r)e[r]=1024}function Y(e){for(var r,o,n,t=0,i=0,u=e.length,s=[],d=[];u>t;++t,++i){if(r=255&e[t],128&r)if(192==(224&r)){if(t+1>=u)return e;if(o=255&e[++t],128!=(192&o))return e;d[i]=(31&r)<<6|63&o}else{if(224!=(240&r))return e;if(t+2>=u)return e;if(o=255&e[++t],128!=(192&o))return e;if(n=255&e[++t],128!=(192&n))return e;d[i]=(15&r)<<12|(63&o)<<6|63&n}else{if(!r)return e;d[i]=r}16383==i&&(s.push(String.fromCharCode.apply(String,d)),i=-1)}return i>0&&(d.length=i,s.push(String.fromCharCode.apply(String,d))),s.join("")}function er(e){return e[1]+e[0]}function rr(e,o,n){function t(){try{for(var e,r=0,u=(new Date).getTime();L(c.d.Q);)if(++r%1e3==0&&(new Date).getTime()-u>200)return s&&(i=er(c.d.Q.h.d)/d,n(i)),tr(t,0),0;n(1),e=Y(_(c.d.S)),tr(o.bind(null,e),0)}catch(m){o(null,m)}}var i,u,s,d,c={},m=void 0===o&&void 0===n;if("function"!=typeof o&&(u=o,o=n=0),n=n||function(e){return void 0!==u?r(s?e:-1,u):void 0},o=o||function(e,r){return void 0!==u?postMessage({action:or,cbn:u,result:e,error:r}):void 0},m){c.d=l({},e);var dd=er(c.d.N);dd>0&&(c.d.S.bb=dd,c.d.S.ab=new Uint8Array(dd));for(;L(c.d.Q););return Y(_(c.d.S))}try{c.d=l({},e),d=er(c.d.N),s=d>-1,s&&d>0&&(c.d.S.bb=d,c.d.S.ab=new Uint8Array(d)),n(0)}catch(a){return o(null,a)}tr(t,0)}var or=2,nr=3,tr="function"==typeof setImmediate?setImmediate:setTimeout,ir=4294967296,ur=[4294967295,-ir],sr=[0,0],dr=[1,0];return"undefined"==typeof onmessage||"undefined"!=typeof window&&void 0!==window.document||!function(){onmessage=function(r){r&&r.W&&r.W.action==or&&e.decompress(r.W.W,r.W.cbn)}}(),{decompress:rr}}();this.LZMA=this.LZMA_WORKER=e;
13
+
14
+ /* ============================================================================
15
+ * 本仓补丁(相对 lzma@2.3.2 的 src/lzma-d-min.js),共 3 处:
16
+ *
17
+ * ① function v(e,r){…e.x=o(r)…} → e.x=new Uint8Array(r)
18
+ * 滑动窗口只做 e.x[i]=… 与 e.x[i] 读取,换成 typed array 语义不变。
19
+ *
20
+ * ② 输出累加器 ab 改为"按 alone 头声明的未压缩长度预分配":
21
+ * · rr() 的**两条分支都要打**(踩过:只打异步分支 ⇒ 单参数调用走同步分支、补丁完全没生效):
22
+ * · 同步分支(形如 `,m){for(c.d=l({},e);L(c.d.Q););return Y(_(c.d.S))}`):
23
+ * `,m){c.d=l({},e);var dd=er(c.d.N);dd>0&&(c.d.S.bb=dd,c.d.S.ab=new Uint8Array(dd));for(;L(c.d.Q););return Y(_(c.d.S))}`
24
+ * · 异步分支:`d=er(c.d.N),s=d>-1` 之后 `c.d.S.bb=d,c.d.S.ab=new Uint8Array(d)`
25
+ * · a() 改成 `e.ab = e.bb>0 ? new Uint8Array(e.bb) : o(32)`(bb 是长度提示,不清除)
26
+ * 为什么要两处:只赋值 ab 会被后续 a() 重置回 o(32)(实测:补丁后 decompress 仍返回普通 Array,
27
+ * heap 341MB);把长度提示存到对象上,之后无论 a() 被调几次都还是 typed array。
28
+ * 写满即止(越界写被 typed array 静默丢弃,解码长度另有 unityfs.js 二次校验,不会静默截断)。
29
+ *
30
+ * ③ function _(e){var r=e.ab;return r.length=e.O,r} → 对 Uint8Array 用 subarray(0,e.O)
31
+ * typed array 的 length 是只读属性,直接赋值会被忽略。
32
+ *
33
+ * 逐字节一致性:换实现前后对 61 个真实 LZMA 包做 sha256 全量比对,全部相同。
34
+ * ========================================================================== */
@@ -0,0 +1,424 @@
1
+ // ============================================================================
2
+ // plugins/unity-live2d-bundle/lib/png.js —— RGBA ⇄ PNG(编码 + 解码/比对)
3
+ //
4
+ // 两个用途:
5
+ // ① 编码:把解出的 RGBA 写成 PNG(给页面 <img> 或下载用)。**零依赖**实现:
6
+ // zlib 流用「stored(未压缩)deflate 块」拼出来,只算 adler32/crc32,不需要压缩库。
7
+ // ② 解码 + 比对:把参考 PNG 读成 RGBA,与本地解码结果逐像素比对(自检用)。
8
+ // inflate 不可自带,按环境注入:Node 用 node:zlib(setInflate),
9
+ // 浏览器用 DecompressionStream('deflate')(现代 Chrome 自带),两处都不可用则抛可读错误。
10
+ //
11
+ // 支持范围(够用即可,不做通用图片库):8 位深、颜色类型 0/2/4/6、过滤方式 0..4,隔行扫描不支持(抛错)。
12
+ //
13
+ // 挂载:globalThis.__ULB__.png
14
+ // ============================================================================
15
+
16
+ 'use strict';
17
+
18
+ {
19
+ const root = globalThis; // 原 IIFE 的 root 引用(脚本级裸块内保持同名)
20
+ const NS = (root.__ULB__ = root.__ULB__ || {});
21
+ const B = NS.bytes;
22
+
23
+ // ---------------------------------------------------------------- CRC32 / Adler32
24
+ const CRC_TABLE = (() => {
25
+ const t = new Uint32Array(256);
26
+ for (let n = 0; n < 256; n++) {
27
+ let c = n;
28
+ for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
29
+ t[n] = c >>> 0;
30
+ }
31
+ return t;
32
+ })();
33
+
34
+ function crc32(u8) {
35
+ let c = 0xffffffff;
36
+ for (let i = 0; i < u8.length; i++) c = CRC_TABLE[(c ^ u8[i]) & 0xff] ^ (c >>> 8);
37
+ return (c ^ 0xffffffff) >>> 0;
38
+ }
39
+
40
+ /**
41
+ * 增量 CRC32:对多个片段**连续**计算(PNG 的 CRC 覆盖 "类型 + 数据")。
42
+ * 为什么不先拼再算:IDAT 数据动辄 18MB,拼一次就是一次全量拷贝 —— 而 CRC 本身就是流式的。
43
+ * @param {Array<Uint8Array>} parts
44
+ * @returns {number}
45
+ */
46
+ function crc32OfParts(parts) {
47
+ let c = 0xffffffff;
48
+ for (const u8 of parts) {
49
+ for (let i = 0; i < u8.length; i++) c = CRC_TABLE[(c ^ u8[i]) & 0xff] ^ (c >>> 8);
50
+ }
51
+ return (c ^ 0xffffffff) >>> 0;
52
+ }
53
+
54
+ function adler32(u8) {
55
+ let a = 1;
56
+ let b = 0;
57
+ for (let i = 0; i < u8.length; i++) {
58
+ a = (a + u8[i]) % 65521;
59
+ b = (b + a) % 65521;
60
+ }
61
+ return ((b << 16) | a) >>> 0;
62
+ }
63
+
64
+ // ---------------------------------------------------------------- 编码
65
+ function chunk(type, data) {
66
+ const out = new Uint8Array(12 + data.length);
67
+ const dv = new DataView(out.buffer);
68
+ dv.setUint32(0, data.length);
69
+ for (let i = 0; i < 4; i++) out[4 + i] = type.charCodeAt(i);
70
+ out.set(data, 8);
71
+ const crcBuf = new Uint8Array(4 + data.length);
72
+ crcBuf.set(out.subarray(4, 8), 0);
73
+ crcBuf.set(data, 4);
74
+ dv.setUint32(8 + data.length, crc32(crcBuf));
75
+ return out;
76
+ }
77
+
78
+ /** deflate 的 stored(无压缩)块序列 + zlib 头/adler32 */
79
+ function zlibStored(u8) {
80
+ const MAX = 65535;
81
+ const blocks = Math.max(1, Math.ceil(u8.length / MAX));
82
+ const parts = [new Uint8Array([0x78, 0x01])];
83
+ for (let i = 0; i < blocks; i++) {
84
+ const slice = u8.subarray(i * MAX, Math.min(u8.length, (i + 1) * MAX));
85
+ const last = i === blocks - 1 ? 1 : 0;
86
+ const head = new Uint8Array(5);
87
+ head[0] = last;
88
+ const len = slice.length;
89
+ head[1] = len & 0xff;
90
+ head[2] = (len >> 8) & 0xff;
91
+ head[3] = (~len) & 0xff;
92
+ head[4] = ((~len) >> 8) & 0xff;
93
+ parts.push(head, slice);
94
+ }
95
+ const ad = new Uint8Array(4);
96
+ new DataView(ad.buffer).setUint32(0, adler32(u8));
97
+ parts.push(ad);
98
+ return B.concatBytes(parts);
99
+ }
100
+
101
+ /**
102
+ * RGBA → PNG 字节。
103
+ * @param {Uint8Array} rgba
104
+ * @param {number} width
105
+ * @param {number} height
106
+ */
107
+ function encodePng(rgba, width, height) {
108
+ const stride = width * 4;
109
+ const raw = new Uint8Array((stride + 1) * height);
110
+ for (let y = 0; y < height; y++) {
111
+ raw[y * (stride + 1)] = 0; // 过滤方式 0(None)
112
+ raw.set(rgba.subarray(y * stride, (y + 1) * stride), y * (stride + 1) + 1);
113
+ }
114
+ const ihdr = new Uint8Array(13);
115
+ const dv = new DataView(ihdr.buffer);
116
+ dv.setUint32(0, width);
117
+ dv.setUint32(4, height);
118
+ ihdr[8] = 8; // 位深
119
+ ihdr[9] = 6; // 颜色类型 RGBA
120
+ ihdr[10] = 0; // 压缩方式
121
+ ihdr[11] = 0; // 过滤方式
122
+ ihdr[12] = 0; // 非隔行
123
+ const sig = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
124
+ return B.concatBytes([sig, chunk('IHDR', ihdr), chunk('IDAT', zlibStored(raw)), chunk('IEND', new Uint8Array(0))]);
125
+ }
126
+
127
+ /**
128
+ * RGBA → PNG 字节(**压缩**版,异步)。
129
+ *
130
+ * 为什么需要压缩版:stored(不压缩)的 PNG 会膨胀到 67 MB(4096² 的 RGBA),
131
+ * 页面在「解码 ETC2 → 64MB RGBA → 67MB PNG」之间会同时占用上百兆,
132
+ * 实测会触发浏览器图像解码失败(表现为加载器的 "Texture loading error")。
133
+ * 压缩后约 6 MB(与参考导出件同量级),内存与解码耗时都降一个数量级。
134
+ *
135
+ * deflate 来源:浏览器用 CompressionStream('deflate')(产出 zlib 流,正好是 IDAT 需要的格式);
136
+ * Node 侧由调用方注入(调用方在需要时 setDeflate)。都没有时退回 stored 版本(仍可用,只是更大)。
137
+ */
138
+ let deflateImpl = null;
139
+ function setDeflate(fn) { deflateImpl = fn; }
140
+
141
+ /**
142
+ * 流式 deflate 注入(Node 侧注入 zlib.createDeflate 版本)。
143
+ * 为什么不直接用 deflateImpl:那是一次性接口(吃整块、吐整块),
144
+ * 而整块 = 4096² 的"过滤后扫描线"67MB + 一次全量拷贝 67MB(`Buffer.from`)。
145
+ * 给一个**吃块迭代器**的接口,编码器就能按行块喂,全程不造整张。
146
+ * @param {(chunks: Iterable<Uint8Array>) => Promise<Uint8Array>} fn
147
+ */
148
+ let deflateStreamImpl = null;
149
+ function setDeflateStream(fn) { deflateStreamImpl = fn; }
150
+
151
+ /** 每块的扫描行数:64 行 ≈ 4096² 时 1MB/块(够大能摊掉调用开销,够小不会造出第二张整图的量级) */
152
+ const STREAM_ROWS = 64;
153
+
154
+ /**
155
+ * 把「过滤后的扫描线」切成小块逐个喂给 deflate,返回完整 zlib 流。
156
+ *
157
+ * 关键前提(已实测,见工具输出):分块 write 与一次性 deflateSync 产出的 zlib 流**逐字节相同**
158
+ * —— 只要不调 flush(),deflate 的切分对输出无影响(块大小 2B / 64KB / 1MB 都验过)。
159
+ * 所以这条路径不会改变 PNG 产物。
160
+ * @param {Uint8Array} rgba 源像素(整张,LZ4 解码产物,这部分省不掉)
161
+ * @param {number} width
162
+ * @param {number} height
163
+ * @returns {Promise<{chunks: Uint8Array[], length: number}>} zlib 流的**片段**(不拼接:拼接是调用方在一次预分配里做的)
164
+ */
165
+ async function deflateScanlines(rgba, width, height) {
166
+ const stride = width * 4;
167
+ /** 生成"每 64 行一块"的 [0, row..., 0, row...] 缓冲区(过滤方式 None) */
168
+ function* blocks() {
169
+ for (let y0 = 0; y0 < height; y0 += STREAM_ROWS) {
170
+ const rows = Math.min(STREAM_ROWS, height - y0);
171
+ const chunk = new Uint8Array((stride + 1) * rows);
172
+ for (let r = 0; r < rows; r++) {
173
+ const y = y0 + r;
174
+ chunk[r * (stride + 1)] = 0; // 过滤方式 0(None)
175
+ chunk.set(rgba.subarray(y * stride, (y + 1) * stride), r * (stride + 1) + 1);
176
+ }
177
+ yield chunk;
178
+ }
179
+ }
180
+
181
+ if (deflateStreamImpl) return normalizeChunks(await deflateStreamImpl(blocks()));
182
+
183
+ // 浏览器:CompressionStream 也能流式写(先挂上读取再写,避免背压死锁)
184
+ const CS = root.CompressionStream;
185
+ if (CS) {
186
+ const cs = new CS('deflate');
187
+ const writer = cs.writable.getWriter();
188
+ const collected = new Response(cs.readable).arrayBuffer(); // 先启动读取
189
+ for (const chunk of blocks()) await writer.write(chunk);
190
+ await writer.close();
191
+ // CompressionStream 只给整体缓冲,拿不到分段 ⇒ 单片段返回(内存上仍是"一份")
192
+ return normalizeChunks([new Uint8Array(await collected)]);
193
+ }
194
+
195
+ // 兜底:没有流式能力 ⇒ 退回一次性(拼起来只是为了拿 stored 版本,仍正确)
196
+ const parts = []; let total = 0;
197
+ for (const chunk of blocks()) { parts.push(chunk); total += chunk.length; }
198
+ const raw = new Uint8Array(total);
199
+ let off = 0;
200
+ for (const part of parts) { raw.set(part, off); off += part.length; }
201
+ return normalizeChunks([zlibStored(raw)]);
202
+ }
203
+
204
+ /** 统一成「片段数组 + 总长」(注入实现可能返回数组或单块) */
205
+ function normalizeChunks(v) {
206
+ const chunks = Array.isArray(v) ? v : [v];
207
+ let length = 0;
208
+ for (const c of chunks) length += c.length;
209
+ return { chunks, length };
210
+ }
211
+
212
+ async function deflateCompress(u8) {
213
+ if (deflateImpl) return deflateImpl(u8);
214
+ const CS = root.CompressionStream;
215
+ if (CS) {
216
+ const cs = new CS('deflate');
217
+ const stream = new Blob([u8]).stream().pipeThrough(cs);
218
+ return new Uint8Array(await new Response(stream).arrayBuffer());
219
+ }
220
+ return zlibStored(u8);
221
+ }
222
+
223
+ async function encodePngAsync(rgba, width, height) {
224
+ // 流式:按 64 行一块喂 deflate,**不构造整张"过滤后扫描线"**(4096² 时那是 67MB),
225
+ // 也不再把它转成 Buffer(`Buffer.from` 是又一次 67MB 全量拷贝)。
226
+ // 产物与旧实现**逐字节相同**(分块不影响 deflate 输出,已实测三种块大小)。
227
+ const ihdr = new Uint8Array(13);
228
+ const dv = new DataView(ihdr.buffer);
229
+ dv.setUint32(0, width);
230
+ dv.setUint32(4, height);
231
+ ihdr[8] = 8;
232
+ ihdr[9] = 6;
233
+ const sig = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
234
+ const { chunks, length } = await deflateScanlines(rgba, width, height);
235
+ return assemblePng(sig, ihdr, chunks, length);
236
+ }
237
+
238
+ const IHDR_TAG = new Uint8Array([0x49, 0x48, 0x44, 0x52]); // 'IHDR'
239
+ const IDAT_TAG = new Uint8Array([0x49, 0x44, 0x41, 0x54]); // 'IDAT'
240
+ const IEND_TAIL = new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82]); // 长度0 + 'IEND' + 固定 CRC
241
+
242
+ /**
243
+ * 一次分配最终 PNG 并逐段写入。
244
+ *
245
+ * 为什么不再走「chunk() 造 IDAT + concatBytes 汇总」那套:
246
+ * 那会为 IDAT 额外分配两次(chunk 的输出 + CRC 用的 crcBuf)+ 汇总再复制一次,
247
+ * 单张 4096² 的 IDAT 约 18MB ⇒ 白扔 3 份 18MB。
248
+ * 这里总长已知(deflate 片段收完就有 length),所以只分配一次,
249
+ * 单 IDAT 形态**不变** ⇒ 产物逐字节不变。
250
+ * @param {Uint8Array} sig 8 字节签名
251
+ * @param {Uint8Array} ihdr 13 字节图像头
252
+ * @param {Uint8Array[]} chunks deflate 输出片段
253
+ * @param {number} length 片段总长
254
+ */
255
+ function assemblePng(sig, ihdr, chunks, length) {
256
+ // sig(8) + IHDR(4+4+13+4) + IDAT(4+4+length+4) + IEND(12)
257
+ const png = new Uint8Array(8 + 25 + (12 + length) + 12);
258
+ const dv = new DataView(png.buffer);
259
+ let o = 0;
260
+ png.set(sig, o); o += 8;
261
+ // IHDR
262
+ dv.setUint32(o, 13); o += 4;
263
+ png.set(IHDR_TAG, o); o += 4;
264
+ png.set(ihdr, o); o += 13;
265
+ dv.setUint32(o, crc32OfParts([IHDR_TAG, ihdr])); o += 4;
266
+ // IDAT(单块:长度 + 类型 + 数据 + CRC;CRC 对"类型+数据"增量算)
267
+ dv.setUint32(o, length); o += 4;
268
+ png.set(IDAT_TAG, o); o += 4;
269
+ for (const c of chunks) { png.set(c, o); o += c.length; }
270
+ dv.setUint32(o, crc32OfParts([IDAT_TAG, ...chunks])); o += 4;
271
+ // IEND(长度 0 + 'IEND' + 固定 CRC)
272
+ png.set(IEND_TAIL, o);
273
+ return png;
274
+ }
275
+
276
+ // ---------------------------------------------------------------- 解码
277
+ let inflateImpl = null;
278
+ /** 注入 inflate(Node:node:zlib 的 inflateSync) */
279
+ function setInflate(fn) { inflateImpl = fn; }
280
+
281
+ async function inflate(zlibBytes) {
282
+ if (inflateImpl) return inflateImpl(zlibBytes);
283
+ const DS = root.DecompressionStream;
284
+ if (DS) {
285
+ const ds = new DS('deflate');
286
+ const stream = new Blob([zlibBytes]).stream().pipeThrough(ds);
287
+ const buf = await new Response(stream).arrayBuffer();
288
+ return new Uint8Array(buf);
289
+ }
290
+ throw NS.errors.fail('ULB_ERR_TEX_DECODE', '当前环境没有 inflate 实现(Node 请注入 node:zlib;浏览器需支持 DecompressionStream)');
291
+ }
292
+
293
+ /**
294
+ * 解 PNG → { rgba, width, height }。
295
+ * @param {Uint8Array} u8 PNG 字节
296
+ */
297
+ async function decodePng(u8) {
298
+ const sig = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
299
+ for (let i = 0; i < 8; i++) if (u8[i] !== sig[i]) throw NS.errors.fail('ULB_ERR_TEX_DECODE', '不是 PNG(签名不符)');
300
+ // PNG 的块长度与字段都是**大端**(与 Unity 的小端相反,这里必须换读法)
301
+ const r = new B.Reader(u8, { pos: 8, endian: '>' });
302
+ let width = 0;
303
+ let height = 0;
304
+ let bitDepth = 0;
305
+ let colorType = 0;
306
+ let interlace = 0;
307
+ const idat = [];
308
+ let palette = null;
309
+ for (;;) {
310
+ const len = r.u32();
311
+ const type = B.latin1(r.bytes(4), 0, 4);
312
+ const data = r.bytes(len);
313
+ r.skip(4); // CRC
314
+ if (type === 'IHDR') {
315
+ width = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
316
+ height = (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7];
317
+ bitDepth = data[8];
318
+ colorType = data[9];
319
+ interlace = data[12];
320
+ } else if (type === 'PLTE') {
321
+ palette = data;
322
+ } else if (type === 'IDAT') {
323
+ idat.push(data);
324
+ } else if (type === 'IEND') {
325
+ break;
326
+ }
327
+ }
328
+ if (bitDepth !== 8) throw NS.errors.fail('ULB_ERR_TEX_DECODE', `暂只支持 8 位深 PNG,实际 ${bitDepth}`);
329
+ if (interlace !== 0) throw NS.errors.fail('ULB_ERR_TEX_DECODE', '暂不支持隔行扫描 PNG');
330
+ const channels = colorType === 0 ? 1 : colorType === 2 ? 3 : colorType === 4 ? 2 : colorType === 6 ? 4 : 0;
331
+ if (!channels && colorType !== 3) throw NS.errors.fail('ULB_ERR_TEX_DECODE', `暂不支持的颜色类型 ${colorType}`);
332
+ const raw = await inflate(B.concatBytes(idat));
333
+ const bpp = colorType === 3 ? 1 : channels;
334
+ const stride = width * bpp;
335
+ const img = new Uint8Array(height * stride);
336
+ let pos = 0;
337
+ for (let y = 0; y < height; y++) {
338
+ const filter = raw[pos++];
339
+ const line = raw.subarray(pos, pos + stride);
340
+ pos += stride;
341
+ const out = img.subarray(y * stride, (y + 1) * stride);
342
+ const prev = y > 0 ? img.subarray((y - 1) * stride, y * stride) : null;
343
+ for (let i = 0; i < stride; i++) {
344
+ const a = i >= bpp ? out[i - bpp] : 0;
345
+ const b = prev ? prev[i] : 0;
346
+ const c = prev && i >= bpp ? prev[i - bpp] : 0;
347
+ let v = line[i];
348
+ if (filter === 1) v += a;
349
+ else if (filter === 2) v += b;
350
+ else if (filter === 3) v += (a + b) >> 1;
351
+ else if (filter === 4) {
352
+ const p = a + b - c;
353
+ const pa = Math.abs(p - a);
354
+ const pb = Math.abs(p - b);
355
+ const pc = Math.abs(p - c);
356
+ v += (pa <= pb && pa <= pc) ? a : (pb <= pc ? b : c);
357
+ }
358
+ out[i] = v & 0xff;
359
+ }
360
+ }
361
+ // 统一成 RGBA
362
+ const rgba = new Uint8Array(width * height * 4);
363
+ for (let y = 0; y < height; y++) {
364
+ for (let x = 0; x < width; x++) {
365
+ const di = (y * width + x) * 4;
366
+ if (colorType === 6) {
367
+ const si = y * stride + x * 4;
368
+ rgba[di] = img[si]; rgba[di + 1] = img[si + 1]; rgba[di + 2] = img[si + 2]; rgba[di + 3] = img[si + 3];
369
+ } else if (colorType === 2) {
370
+ const si = y * stride + x * 3;
371
+ rgba[di] = img[si]; rgba[di + 1] = img[si + 1]; rgba[di + 2] = img[si + 2]; rgba[di + 3] = 255;
372
+ } else if (colorType === 0) {
373
+ const v = img[y * stride + x];
374
+ rgba[di] = v; rgba[di + 1] = v; rgba[di + 2] = v; rgba[di + 3] = 255;
375
+ } else if (colorType === 4) {
376
+ const si = y * stride + x * 2;
377
+ const v = img[si];
378
+ rgba[di] = v; rgba[di + 1] = v; rgba[di + 2] = v; rgba[di + 3] = img[si + 1];
379
+ } else { // 调色板
380
+ const idx = img[y * stride + x];
381
+ rgba[di] = palette[idx * 3]; rgba[di + 1] = palette[idx * 3 + 1]; rgba[di + 2] = palette[idx * 3 + 2];
382
+ rgba[di + 3] = palette.length > idx * 3 + 3 ? palette[idx * 3 + 3] : 255;
383
+ }
384
+ }
385
+ }
386
+ return { rgba, width, height, colorType, bitDepth };
387
+ }
388
+
389
+ /**
390
+ * 与一张参考 PNG 逐像素比对。
391
+ * @returns {Promise<{diffPixels:number, maxChannelDiff:number, expectedWidth:number, expectedHeight:number, mismatchSample:Array}>}
392
+ */
393
+ async function compareWithPng(rgba, width, height, pngBytes) {
394
+ const ref = await decodePng(pngBytes instanceof Uint8Array ? pngBytes : new Uint8Array(pngBytes));
395
+ if (ref.width !== width || ref.height !== height) {
396
+ return { diffPixels: width * height, maxChannelDiff: 255, expectedWidth: ref.width, expectedHeight: ref.height, sizeMismatch: true, mismatchSample: [] };
397
+ }
398
+ let diff = 0;
399
+ let maxDiff = 0;
400
+ const sample = [];
401
+ for (let i = 0; i < width * height; i++) {
402
+ const o = i * 4;
403
+ const d0 = Math.abs(rgba[o] - ref.rgba[o]);
404
+ const d1 = Math.abs(rgba[o + 1] - ref.rgba[o + 1]);
405
+ const d2 = Math.abs(rgba[o + 2] - ref.rgba[o + 2]);
406
+ const d3 = Math.abs(rgba[o + 3] - ref.rgba[o + 3]);
407
+ const m = Math.max(d0, d1, d2, d3);
408
+ if (m > 0) {
409
+ diff++;
410
+ if (m > maxDiff) maxDiff = m;
411
+ if (sample.length < 8) {
412
+ sample.push({
413
+ pixel: i, xy: [i % width, Math.floor(i / width)],
414
+ ours: [rgba[o], rgba[o + 1], rgba[o + 2], rgba[o + 3]],
415
+ ref: [ref.rgba[o], ref.rgba[o + 1], ref.rgba[o + 2], ref.rgba[o + 3]],
416
+ });
417
+ }
418
+ }
419
+ }
420
+ return { diffPixels: diff, maxChannelDiff: maxDiff, expectedWidth: ref.width, expectedHeight: ref.height, mismatchSample: sample };
421
+ }
422
+
423
+ NS.png = { encodePng, encodePngAsync, decodePng, compareWithPng, setInflate, setDeflate, setDeflateStream, crc32, adler32, zlibStored };
424
+ }