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,371 @@
1
+ // ============================================================================
2
+ // plugins/unity-live2d-bundle/lib/etc2.js —— ETC1 / ETC2 / EAC Alpha 解码
3
+ //
4
+ // 本文件是 GL 系压缩纹理解码的**逐位实现**,覆盖:
5
+ // · ETC1(个体 individual / 差分 differential)
6
+ // · ETC2(在 ETC1 差分基础上,按"差分溢出"选 T / H / 平面 planar 三模式)
7
+ // · ETC2A8(ETC2 RGB + 8 位 EAC Alpha)—— Unity 的 TextureFormat 47 = ETC2_RGBA8 走这条
8
+ //
9
+ // 两块数据的字节顺序(关键,弄反就整张图错位):
10
+ // ETC2A8 每个 4x4 块 16 字节 = **前 8 字节是 Alpha,后 8 字节才是 RGB**。
11
+ // 判据:本插件与参考实现(UnityPy 导出 oracle 时用的 texture2ddecoder)逐像素比对一致;
12
+ // 若把顺序反过来,同一份数据会解出完全不同的图。
13
+ //
14
+ // 位序约定:块内索引按"最低位对应第一个像素"逐 3/2 位右移取用,
15
+ // 像素落位用 WriteOrderTable(RGB)与 WriteOrderTableRev(Alpha)两张表,
16
+ // 表值即 4x4 块内的行优先下标(y*4+x)。这两张表来自参考实现,避免自行推导出错。
17
+ //
18
+ // 挂载:globalThis.__ULB__.etc2
19
+ // ============================================================================
20
+
21
+ 'use strict';
22
+
23
+ {
24
+ const root = globalThis; // 原 IIFE 的 root 引用(脚本级裸块内保持同名)
25
+ const NS = (root.__ULB__ = root.__ULB__ || {});
26
+
27
+ const WriteOrderTable = [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15];
28
+ const WriteOrderTableRev = [15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0];
29
+ const Etc1ModifierTable = [[2, 8], [5, 17], [9, 29], [13, 42], [18, 60], [24, 80], [33, 106], [47, 183]];
30
+ const Etc1SubblockTable = [
31
+ [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
32
+ [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1],
33
+ ];
34
+ const Etc2DistanceTable = [3, 6, 11, 16, 23, 32, 41, 64];
35
+ const Etc2AlphaModTable = [
36
+ [-3, -6, -9, -15, 2, 5, 8, 14], [-3, -7, -10, -13, 2, 6, 9, 12], [-2, -5, -8, -13, 1, 4, 7, 12],
37
+ [-2, -4, -6, -13, 1, 3, 5, 12], [-3, -6, -8, -12, 2, 5, 7, 11], [-3, -7, -9, -11, 2, 6, 8, 10],
38
+ [-4, -7, -8, -11, 3, 6, 7, 10], [-3, -5, -8, -11, 2, 4, 7, 10], [-2, -6, -8, -10, 1, 5, 7, 9],
39
+ [-2, -5, -8, -10, 1, 4, 7, 9], [-2, -4, -8, -10, 1, 3, 7, 9], [-2, -5, -7, -10, 1, 4, 6, 9],
40
+ [-3, -4, -7, -10, 2, 3, 6, 9], [-1, -2, -3, -10, 0, 1, 2, 9], [-4, -6, -8, -9, 3, 5, 7, 8],
41
+ [-3, -5, -7, -9, 2, 4, 6, 8],
42
+ ];
43
+
44
+ const clamp = (n) => (n < 0 ? 0 : n > 255 ? 255 : n);
45
+
46
+ /** 把 4x4 块的 RGBA 写进整图(block 内下标记为行优先 y*4+x) */
47
+ function writeBlock(rgba, blockRGBA, out, outX, outY, width, height) {
48
+ for (let y = 0; y < 4; y++) {
49
+ const py = outY + y;
50
+ if (py >= height) continue;
51
+ for (let x = 0; x < 4; x++) {
52
+ const px = outX + x;
53
+ if (px >= width) continue;
54
+ const src = (y * 4 + x) * 4;
55
+ const dst = (py * width + px) * 4;
56
+ rgba[dst] = blockRGBA[src];
57
+ rgba[dst + 1] = blockRGBA[src + 1];
58
+ rgba[dst + 2] = blockRGBA[src + 2];
59
+ rgba[dst + 3] = blockRGBA[src + 3];
60
+ }
61
+ }
62
+ void out;
63
+ }
64
+
65
+ /** 写块内第 idx 个像素(按 WriteOrderTable 的扫描顺序) */
66
+ function putPixel(out, idx, r, g, bl) {
67
+ const p = WriteOrderTable[idx] * 4;
68
+ out[p] = clamp(r);
69
+ out[p + 1] = clamp(g);
70
+ out[p + 2] = clamp(bl);
71
+ out[p + 3] = 255;
72
+ }
73
+
74
+ /** 差分位(低 3 位)的符号扩展,范围 -8..+7。三个通道共用同一个表达式 */
75
+ const deltaOf = (x) => ((x << 3) & 0x18) - ((x << 3) & 0x20);
76
+
77
+ /**
78
+ * 选择 RGB 块的子模式。
79
+ *
80
+ * **判定顺序即规范,不可调整**:
81
+ * individual → T(R 差分溢出) → H(G 差分溢出) → Planar(B 差分溢出) → differential。
82
+ * 顺序一变,同一个 block 会落进不同模式、解码结果就变了 ——
83
+ * 不要为了"看起来整齐"把这些 if 重排或合并。
84
+ * ETC1(etc2 === false)没有 T/H/Planar 三种模式:individual 之后直接进 differential。
85
+ * @returns {'individual'|'t'|'h'|'planar'|'differential'}
86
+ */
87
+ function selectRgbMode(b, o, etc2) {
88
+ if ((b[o + 3] & 2) === 0) return 'individual';
89
+ if (!etc2) return 'differential';
90
+ const r0 = b[o] & 0xf8;
91
+ const g0 = b[o + 1] & 0xf8;
92
+ const bl0 = b[o + 2] & 0xf8;
93
+ if (r0 + deltaOf(b[o]) < 0 || r0 + deltaOf(b[o]) > 255) return 't';
94
+ if (g0 + deltaOf(b[o + 1]) < 0 || g0 + deltaOf(b[o + 1]) > 255) return 'h';
95
+ if (bl0 + deltaOf(b[o + 2]) < 0 || bl0 + deltaOf(b[o + 2]) > 255) return 'planar';
96
+ return 'differential';
97
+ }
98
+
99
+ /** ① 个体模式:两半各自 4 位颜色(不做通道差分) */
100
+ function decodeIndividualBlock(b, o, j, k, out) {
101
+ const flip = b[o + 3] & 1;
102
+ const code = [b[o + 3] >> 5, (b[o + 3] >> 2) & 7];
103
+ const subblock = Etc1SubblockTable[flip];
104
+ const c = [[0, 0, 0], [0, 0, 0]];
105
+ c[0][0] = ((b[o] & 0xf0) | (b[o] >> 4)) & 0xff;
106
+ c[1][0] = ((b[o] & 0x0f) | (b[o] << 4)) & 0xff;
107
+ c[0][1] = ((b[o + 1] & 0xf0) | (b[o + 1] >> 4)) & 0xff;
108
+ c[1][1] = ((b[o + 1] & 0x0f) | (b[o + 1] << 4)) & 0xff;
109
+ c[0][2] = ((b[o + 2] & 0xf0) | (b[o + 2] >> 4)) & 0xff;
110
+ c[1][2] = ((b[o + 2] & 0x0f) | (b[o + 2] << 4)) & 0xff;
111
+ for (let i = 0; i < 16; i++, j >>= 1, k >>= 1) {
112
+ const s = subblock[i];
113
+ const m = Etc1ModifierTable[code[s]][j & 1];
114
+ putPixel(out, i, c[s][0] + (k & 1 ? -m : m), c[s][1] + (k & 1 ? -m : m), c[s][2] + (k & 1 ? -m : m));
115
+ }
116
+ return out;
117
+ }
118
+
119
+ /** ② T 模式(ETC2):两个端点色 + 一组距离偏移,索引为 3 bit */
120
+ function decodeTBlock(b, o, j, k, out) {
121
+ const c0 = [
122
+ ((b[o] << 3) & 0xc0) | ((b[o] << 4) & 0x30) | ((b[o] >> 1) & 0xc) | (b[o] & 3),
123
+ ((b[o + 1] & 0xf0) | (b[o + 1] >> 4)) & 0xff,
124
+ ((b[o + 1] & 0x0f) | (b[o + 1] << 4)) & 0xff,
125
+ ];
126
+ const c1 = [
127
+ ((b[o + 2] & 0xf0) | (b[o + 2] >> 4)) & 0xff,
128
+ ((b[o + 2] & 0x0f) | (b[o + 2] << 4)) & 0xff,
129
+ ((b[o + 3] & 0xf0) | (b[o + 3] >> 4)) & 0xff,
130
+ ];
131
+ const d = Etc2DistanceTable[((b[o + 3] >> 1) & 6) | (b[o + 3] & 1)];
132
+ const set = [
133
+ [c0[0], c0[1], c0[2]],
134
+ [c1[0] + d, c1[1] + d, c1[2] + d],
135
+ [c1[0], c1[1], c1[2]],
136
+ [c1[0] - d, c1[1] - d, c1[2] - d],
137
+ ];
138
+ k = (k << 1) >>> 0;
139
+ for (let i = 0; i < 16; i++, j >>= 1, k >>= 1) {
140
+ const sel = ((k & 2) | (j & 1));
141
+ putPixel(out, i, set[sel][0], set[sel][1], set[sel][2]);
142
+ }
143
+ return out;
144
+ }
145
+
146
+ /** ③ H 模式(ETC2):两色 + 距离偏移;d 的档位由两色大小关系决定 */
147
+ function decodeHBlock(b, o, j, k, out) {
148
+ const c0 = [0, 0, 0];
149
+ const c1 = [0, 0, 0];
150
+ c0[0] = ((b[o] << 1) & 0xf0) | ((b[o] >> 3) & 0x0f);
151
+ c0[1] = ((b[o] << 5) & 0xe0) | (b[o + 1] & 0x10);
152
+ c0[1] |= c0[1] >> 4;
153
+ c0[2] = ((b[o + 1] & 8) | ((b[o + 1] << 1) & 6) | (b[o + 2] >> 7)) & 0xff;
154
+ c0[2] |= (c0[2] << 4) & 0xff;
155
+ c1[0] = ((b[o + 2] << 1) & 0xf0) | ((b[o + 2] >> 3) & 0x0f);
156
+ c1[1] = ((b[o + 2] << 5) & 0xe0) | ((b[o + 3] >> 3) & 0x10);
157
+ c1[1] |= c1[1] >> 4;
158
+ c1[2] = ((b[o + 3] << 1) & 0xf0) | ((b[o + 3] >> 3) & 0x0f);
159
+ let d = (b[o + 3] & 4) | ((b[o + 3] << 1) & 2);
160
+ if (c0[0] > c1[0]
161
+ || (c0[0] === c1[0] && (c0[1] > c1[1] || (c0[1] === c1[1] && c0[2] >= c1[2])))) d++;
162
+ d = Etc2DistanceTable[d];
163
+ const set = [
164
+ [c0[0] + d, c0[1] + d, c0[2] + d],
165
+ [c0[0] - d, c0[1] - d, c0[2] - d],
166
+ [c1[0] + d, c1[1] + d, c1[2] + d],
167
+ [c1[0] - d, c1[1] - d, c1[2] - d],
168
+ ];
169
+ k = (k << 1) >>> 0;
170
+ for (let i = 0; i < 16; i++, j >>= 1, k >>= 1) {
171
+ const sel = ((k & 2) | (j & 1));
172
+ putPixel(out, i, set[sel][0], set[sel][1], set[sel][2]);
173
+ }
174
+ return out;
175
+ }
176
+
177
+ /** ④ 平面模式(ETC2):三顶点色做平面插值;**不用索引位**,按 i*4 顺序直接写 */
178
+ function decodePlanarBlock(b, o, j, k, out) {
179
+ void j; void k; // 平面模式不使用索引位(参数保留只为统一签名)
180
+ const c = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
181
+ c[0][0] = ((b[o] << 1) & 0xfc) | ((b[o] >> 5) & 3);
182
+ c[0][1] = ((b[o] << 7) & 0x80) | (b[o + 1] & 0x7e) | (b[o] & 1);
183
+ c[0][2] = ((b[o + 1] << 7) & 0x80) | ((b[o + 2] << 2) & 0x60) | ((b[o + 2] << 3) & 0x18) | ((b[o + 3] >> 5) & 4);
184
+ c[0][2] |= c[0][2] >> 6;
185
+ c[1][0] = ((b[o + 3] << 1) & 0xf8) | ((b[o + 3] << 2) & 4) | ((b[o + 3] >> 5) & 3);
186
+ c[1][1] = (b[o + 4] & 0xfe) | (b[o + 4] >> 7);
187
+ c[1][2] = ((b[o + 4] << 7) & 0x80) | ((b[o + 5] >> 1) & 0x7c);
188
+ c[1][2] |= c[1][2] >> 6;
189
+ c[2][0] = ((b[o + 5] << 5) & 0xe0) | ((b[o + 6] >> 3) & 0x1c) | ((b[o + 5] >> 1) & 3);
190
+ c[2][1] = ((b[o + 6] << 3) & 0xf8) | ((b[o + 7] >> 5) & 0x6) | ((b[o + 6] >> 4) & 1);
191
+ c[2][2] = ((b[o + 7] << 2) | ((b[o + 7] >> 4) & 3)) & 0xff;
192
+ for (let y = 0, i = 0; y < 4; y++) {
193
+ for (let x = 0; x < 4; x++, i++) {
194
+ const rr = clamp((x * (c[1][0] - c[0][0]) + y * (c[2][0] - c[0][0]) + 4 * c[0][0] + 2) >> 2);
195
+ const gg = clamp((x * (c[1][1] - c[0][1]) + y * (c[2][1] - c[0][1]) + 4 * c[0][1] + 2) >> 2);
196
+ const bb = clamp((x * (c[1][2] - c[0][2]) + y * (c[2][2] - c[0][2]) + 4 * c[0][2] + 2) >> 2);
197
+ const p = i * 4;
198
+ out[p] = rr; out[p + 1] = gg; out[p + 2] = bb; out[p + 3] = 255;
199
+ }
200
+ }
201
+ return out;
202
+ }
203
+
204
+ /** ⑤ 差分模式(ETC1 本体;ETC2 下三通道都不溢出时也走这里) */
205
+ function decodeDifferentialBlock(b, o, j, k, out) {
206
+ const r0 = b[o] & 0xf8;
207
+ const g0 = b[o + 1] & 0xf8;
208
+ const bl0 = b[o + 2] & 0xf8;
209
+ const dr = deltaOf(b[o]);
210
+ const dg = deltaOf(b[o + 1]);
211
+ const db = deltaOf(b[o + 2]);
212
+ const flip = b[o + 3] & 1;
213
+ const code = [b[o + 3] >> 5, (b[o + 3] >> 2) & 7];
214
+ const subblock = Etc1SubblockTable[flip];
215
+ const c = [[0, 0, 0], [0, 0, 0]];
216
+ c[0][0] = r0 | (r0 >> 5);
217
+ c[0][1] = g0 | (g0 >> 5);
218
+ c[0][2] = bl0 | (bl0 >> 5);
219
+ c[1][0] = (r0 + dr) | ((r0 + dr) >> 5);
220
+ c[1][1] = (g0 + dg) | ((g0 + dg) >> 5);
221
+ c[1][2] = (bl0 + db) | ((bl0 + db) >> 5);
222
+ for (let i = 0; i < 16; i++, j >>= 1, k >>= 1) {
223
+ const s = subblock[i];
224
+ const m = Etc1ModifierTable[code[s]][j & 1];
225
+ putPixel(out, i, c[s][0] + (k & 1 ? -m : m), c[s][1] + (k & 1 ? -m : m), c[s][2] + (k & 1 ? -m : m));
226
+ }
227
+ return out;
228
+ }
229
+
230
+ /** 模式名 → 解码函数(签名统一 `(b, o, j, k, out) → out`,out 是 16 像素 × RGBA 的块缓冲) */
231
+ const RGB_BLOCK_MODES = {
232
+ individual: decodeIndividualBlock,
233
+ t: decodeTBlock,
234
+ h: decodeHBlock,
235
+ planar: decodePlanarBlock,
236
+ differential: decodeDifferentialBlock,
237
+ };
238
+
239
+ /** ETC1/ETC2 的 RGB 块(8 字节)→ 4x4 RGBA(alpha 先填 255) */
240
+ function decodeRgbBlock(data, off, etc2) {
241
+ const out = new Uint8Array(16 * 4);
242
+ // 公共头:索引低半(j)与高半(k)各 16 bit
243
+ const j = (data[off + 6] << 8) | data[off + 7];
244
+ const k = (data[off + 4] << 8) | data[off + 5];
245
+ // 先选子模式(顺序即规范,见 selectRgbMode 的注释),再交给对应的模式函数
246
+ const mode = selectRgbMode(data, off, etc2);
247
+ return RGB_BLOCK_MODES[mode](data, off, j, k, out);
248
+ }
249
+
250
+ /** ETC2A8 的 Alpha 块(前 8 字节)→ 写进块缓冲的第 4 通道 */
251
+ function decodeAlphaBlock(data, off, blockRGBA) {
252
+ const base = data[off];
253
+ const mullow = data[off + 1];
254
+ if ((mullow & 0xf0) === 0) {
255
+ for (let i = 0; i < 16; i++) blockRGBA[i * 4 + 3] = base;
256
+ return;
257
+ }
258
+ const multiplier = mullow >> 4;
259
+ const table = Etc2AlphaModTable[mullow & 0x0f];
260
+ // 前 8 字节按大端读取成 64 位,从最低 3 位起每 3 位对应一个像素(落位见 WriteOrderTableRev)
261
+ let l = 0n;
262
+ for (let i = 0; i < 8; i++) l = (l << 8n) | BigInt(data[off + i]);
263
+ for (let i = 0; i < 16; i++, l >>= 3n) {
264
+ const v = base + multiplier * table[Number(l & 7n)];
265
+ blockRGBA[WriteOrderTableRev[i] * 4 + 3] = clamp(v);
266
+ }
267
+ }
268
+
269
+ /**
270
+ * 解 ETC2_RGBA8(Unity TextureFormat 47)。
271
+ * @param {Uint8Array} data 压缩数据
272
+ * @param {number} width 宽
273
+ * @param {number} height 高
274
+ * @returns {Uint8Array} RGBA
275
+ */
276
+ function decodeEtc2Rgba8(data, width, height) {
277
+ const bw = Math.ceil(width / 4);
278
+ const bh = Math.ceil(height / 4);
279
+ const need = bw * bh * 16;
280
+ if (data.length < need) throw NS.errors.fail('ULB_ERR_TEX_DATA_SHORT', `ETC2_RGBA8 数据不足:需要 ${need} 字节,实际 ${data.length}`);
281
+ const rgba = new Uint8Array(width * height * 4);
282
+ for (let by = 0; by < bh; by++) {
283
+ for (let bx = 0; bx < bw; bx++) {
284
+ const off = (by * bw + bx) * 16;
285
+ // 前 8 字节 Alpha,后 8 字节 RGB(顺序来自参考实现的调用:decode_etc2_block(data+8) + decode_etc2a8_block(data))
286
+ const block = decodeRgbBlock(data, off + 8, true);
287
+ decodeAlphaBlock(data, off, block);
288
+ writeBlock(rgba, block, null, bx * 4, by * 4, width, height);
289
+ }
290
+ }
291
+ return rgba;
292
+ }
293
+
294
+ /**
295
+ * 解 ETC2_RGBA1(Unity TextureFormat 48)—— **ETC2 RGB 块 + 1bit alpha 平面**。
296
+ *
297
+ * 块布局(每 4×4 像素固定 16 字节):
298
+ * [0..7] 8 字节 ETC2 RGB 块(与 TextureFormat 45 ETC2_RGB 的块同构,可直接复用 decodeRgbBlock)
299
+ * [8..15] 8 字节 1bit alpha 平面 = 64 bit = 4×4 每像素 1 bit
300
+ *
301
+ * alpha 位序(ETC2 规范):**位 0 对应块内第一个像素 (x=0,y=0),按行推进、LSB first** ——
302
+ * 即 bitIndex = py*4 + px,字节 = 8 + (bitIndex >> 3),位 = bitIndex & 7;
303
+ * 置位 = 完全不透明(255),清零 = 全透明(0)。(与 RGBA8 的 8 字节 EAC 4bit alpha 完全不同,
304
+ * 不能复用 decodeAlphaBlock。)
305
+ *
306
+ * @param {Uint8Array} data 压缩数据
307
+ * @param {number} width 宽
308
+ * @param {number} height 高
309
+ * @returns {Uint8Array} RGBA
310
+ */
311
+ function decodeEtc2Rgba1(data, width, height) {
312
+ const bw = Math.ceil(width / 4);
313
+ const bh = Math.ceil(height / 4);
314
+ const need = bw * bh * 16;
315
+ if (data.length < need) throw NS.errors.fail('ULB_ERR_TEX_DATA_SHORT', `ETC2_RGBA1 数据不足:需要 ${need} 字节,实际 ${data.length}`);
316
+ const rgba = new Uint8Array(width * height * 4);
317
+ for (let by = 0; by < bh; by++) {
318
+ for (let bx = 0; bx < bw; bx++) {
319
+ const off = (by * bw + bx) * 16;
320
+ // 前 8 字节 RGB:复用 ETC2 RGB 解出的 4×4 块(其 alpha 由 punch-through 决定,下面会被覆盖)
321
+ const block = decodeRgbBlock(data, off, true);
322
+ // 后 8 字节 1bit alpha:逐行、LSB first
323
+ for (let py = 0; py < 4; py++) {
324
+ for (let px = 0; px < 4; px++) {
325
+ const bit = py * 4 + px;
326
+ const on = (data[off + 8 + (bit >> 3)] >> (bit & 7)) & 1;
327
+ block[(py * 4 + px) * 4 + 3] = on ? 255 : 0;
328
+ }
329
+ }
330
+ writeBlock(rgba, block, null, bx * 4, by * 4, width, height);
331
+ }
332
+ }
333
+ return rgba;
334
+ }
335
+
336
+ /** 解 ETC1(无 alpha,统一 255)—— 供其他 TextureFormat 复用 */
337
+ function decodeEtc1Rgb(data, width, height) {
338
+ const bw = Math.ceil(width / 4);
339
+ const bh = Math.ceil(height / 4);
340
+ if (data.length < bw * bh * 8) throw NS.errors.fail('ULB_ERR_TEX_DATA_SHORT', `ETC1 数据不足:需要 ${bw * bh * 8} 字节,实际 ${data.length}`);
341
+ const rgba = new Uint8Array(width * height * 4);
342
+ for (let by = 0; by < bh; by++) {
343
+ for (let bx = 0; bx < bw; bx++) {
344
+ const block = decodeRgbBlock(data, (by * bw + bx) * 8, false);
345
+ writeBlock(rgba, block, null, bx * 4, by * 4, width, height);
346
+ }
347
+ }
348
+ return rgba;
349
+ }
350
+
351
+ /** 解 ETC2 RGB(无 alpha,统一 255) */
352
+ function decodeEtc2Rgb(data, width, height) {
353
+ const bw = Math.ceil(width / 4);
354
+ const bh = Math.ceil(height / 4);
355
+ if (data.length < bw * bh * 8) throw NS.errors.fail('ULB_ERR_TEX_DATA_SHORT', `ETC2 数据不足:需要 ${bw * bh * 8} 字节,实际 ${data.length}`);
356
+ const rgba = new Uint8Array(width * height * 4);
357
+ for (let by = 0; by < bh; by++) {
358
+ for (let bx = 0; bx < bw; bx++) {
359
+ const block = decodeRgbBlock(data, (by * bw + bx) * 8, true);
360
+ writeBlock(rgba, block, null, bx * 4, by * 4, width, height);
361
+ }
362
+ }
363
+ return rgba;
364
+ }
365
+
366
+ NS.etc2 = {
367
+ decodeEtc2Rgba8, decodeEtc2Rgba1, decodeEtc1Rgb, decodeEtc2Rgb, decodeRgbBlock, decodeAlphaBlock,
368
+ WriteOrderTable, WriteOrderTableRev, Etc1ModifierTable, Etc1SubblockTable,
369
+ Etc2DistanceTable, Etc2AlphaModTable, clamp,
370
+ };
371
+ }
@@ -0,0 +1,242 @@
1
+ // ============================================================================
2
+ // plugins/unity-live2d-bundle/lib/extract.js —— 从对象表里抽取资源
3
+ //
4
+ // 这里回答"包里的东西分别在哪、怎么取出来",与游戏无关(碧蓝只是首个样本):
5
+ // · moc3 :MonoBehaviour 对象里的字节数组(TypelessData)。本实现用
6
+ // 「MOC3 魔数 + 前置 u32 长度」定位(实测该方法在本样本上精确成立:
7
+ // 魔数前 4 字节 = 3,225,600,恰好等于该对象内剩余长度);
8
+ // 同时保留类型树读数作为对照口径。
9
+ // · physics3.json:TextAsset 的 m_Script(UTF-8 字符串 → 字节)。
10
+ // · model3/motion3:**原生 Cubism 包**里直接带的 TextAsset/文件(本样本没有,属另一种包型)。
11
+ // · 纹理元信息 :Texture2D 的宽高/格式/完整尺寸 + m_StreamData(数据在 .resS 里的偏移与长度),
12
+ // 没有流数据时退回对象内的内联 image data。
13
+ //
14
+ // 两种包型都必须支持(同一插件要能读不止一个游戏的包):
15
+ // ① 原生 Cubism 文件集:包内直接有 .moc3 / .model3.json / .motion3.json / .physics3.json
16
+ // ② Unity 集成版:只有 moc3(MonoBehaviour 字节数组)+ physics3.json(TextAsset)+ 纹理 + 动画曲线
17
+ // 本文件负责把两种形态都"找出来",是否需要重建 model3/motion3 由上层按"有没有"决定。
18
+ //
19
+ // 挂载:globalThis.__ULB__.extract
20
+ // ============================================================================
21
+
22
+ 'use strict';
23
+
24
+ {
25
+ const root = globalThis; // 原 IIFE 的 root 引用(脚本级裸块内保持同名)
26
+ const NS = (root.__ULB__ = root.__ULB__ || {});
27
+ const B = NS.bytes;
28
+
29
+ const MOC3_MAGIC = [0x4d, 0x4f, 0x43, 0x33]; // "MOC3"
30
+
31
+ const utf8 = (str) => new TextEncoder().encode(str);
32
+ const decodeUtf8 = (u8) => new TextDecoder('utf-8').decode(u8);
33
+
34
+ /** 在字节里找第一个匹配 magic 的位置(-1 表示没有) */
35
+ function indexOfMagic(u8, magic, from) {
36
+ const start = from || 0;
37
+ outer:
38
+ for (let i = start; i <= u8.length - magic.length; i++) {
39
+ for (let k = 0; k < magic.length; k++) if (u8[i + k] !== magic[k]) continue outer;
40
+ return i;
41
+ }
42
+ return -1;
43
+ }
44
+
45
+ /**
46
+ * 在 MonoBehaviour 对象里找 moc3。
47
+ * 判据(两条都满足才算命中,宁缺勿错):
48
+ * ① 对象里出现 "MOC3" 魔数,且前面 4 字节(小端 u32)是长度 L;
49
+ * ② L 恰好等于「魔数起点 + L = 对象末尾」(对象里该字段就是最后一段)。
50
+ * @returns {{object:object, magicOffset:number, bytes:Uint8Array}|null}
51
+ */
52
+ function findMoc3(sf) {
53
+ const monos = NS.serialized.byClass(sf, 114);
54
+ const candidates = [];
55
+ for (const obj of monos) {
56
+ const bytes = sf.bytes.subarray(obj.dataOffset, obj.dataOffset + obj.byteSize);
57
+ let from = 0;
58
+ for (;;) {
59
+ const magicOffset = indexOfMagic(bytes, MOC3_MAGIC, from);
60
+ if (magicOffset < 0) break;
61
+ if (magicOffset >= 4) {
62
+ const r = new B.Reader(bytes, { pos: magicOffset - 4 });
63
+ const len = r.u32();
64
+ if (len > 64 && magicOffset + len === bytes.length) {
65
+ candidates.push({
66
+ object: obj,
67
+ magicOffset,
68
+ length: len,
69
+ bytes: bytes.subarray(magicOffset, magicOffset + len),
70
+ });
71
+ break;
72
+ }
73
+ }
74
+ from = magicOffset + 1;
75
+ }
76
+ }
77
+ if (!candidates.length) return null;
78
+ // 多个候选时取最大的那个(moc3 是本插件里最大的嵌入式模型数据)
79
+ candidates.sort((a, b) => b.length - a.length);
80
+ return candidates[0];
81
+ }
82
+
83
+ /** 取全部 TextAsset(名 + 字节) */
84
+ function findTextAssets(sf) {
85
+ if (!NS.typetree) return [];
86
+ const out = [];
87
+ for (const obj of NS.serialized.byClass(sf, 49)) {
88
+ let fields;
89
+ try { fields = NS.typetree.readObject(sf, obj); }
90
+ catch (e) { out.push({ object: obj, name: null, bytes: null, error: e.message }); continue; }
91
+ const name = fields.m_Name || '';
92
+ const script = fields.m_Script;
93
+ out.push({
94
+ object: obj,
95
+ name,
96
+ pathName: fields.m_PathName || '',
97
+ bytes: typeof script === 'string' ? utf8(script) : null,
98
+ });
99
+ }
100
+ return out;
101
+ }
102
+
103
+ /**
104
+ * 取 Texture2D 元信息(不解码,只给出"数据在哪、什么格式、多大")。
105
+ * @returns {Array<{object:object,name:string,width:number,height:number,format:number,
106
+ * completeImageSize:number,mipCount:number,stream:{offset:number,size:number,path:string}|null,
107
+ * inline:Uint8Array|null, raw:object}>}
108
+ */
109
+ function findTextures(sf) {
110
+ if (!NS.typetree) throw NS.errors.fail('ULB_ERR_EXTRACT', '缺少 lib/typetree.js,无法读取 Texture2D 字段');
111
+ // 字段名在类型树里可能是带空格的可读名(如 "image data"),也可能是 m_ 前缀名;
112
+ // 两种都认,避免因类型树命名差异把"内联图像数据"当成不存在(本样本 texture_00 就是 "image data")。
113
+ const pick = (obj, names) => {
114
+ for (const n of names) if (obj[n] !== undefined && obj[n] !== null) return obj[n];
115
+ return undefined;
116
+ };
117
+ const out = [];
118
+ for (const obj of NS.serialized.byClass(sf, 28)) {
119
+ const f = NS.typetree.readObject(sf, obj);
120
+ const sd = pick(f, ['m_StreamData', 'StreamData']) || {};
121
+ const rawInline = pick(f, ['m_ImageData', 'image data', 'ImageData']);
122
+ const inline = rawInline && rawInline.byteLength !== undefined && rawInline.byteLength > 0 ? rawInline : null;
123
+ const stream = sd.path
124
+ ? { offset: Number(sd.offset || 0), size: Number(sd.size || 0), path: String(sd.path) }
125
+ : null;
126
+ out.push({
127
+ object: obj,
128
+ name: pick(f, ['m_Name', 'name']) || '',
129
+ width: pick(f, ['m_Width', 'width']) || 0,
130
+ height: pick(f, ['m_Height', 'height']) || 0,
131
+ format: pick(f, ['m_TextureFormat', 'm_Format']),
132
+ formatName: NS.texdecode ? NS.texdecode.formatName(pick(f, ['m_TextureFormat', 'm_Format'])) : '',
133
+ completeImageSize: Number(pick(f, ['m_CompleteImageSize']) || 0),
134
+ mipCount: pick(f, ['m_MipCount', 'm_MipmapCount']) || 0,
135
+ stream,
136
+ inline,
137
+ raw: f,
138
+ });
139
+ }
140
+ // 按名字里的数字排序(texture_00 < texture_01 < …)。
141
+ // 为什么重要:Cubism 的 model3.json 用 **Textures 数组下标** 给绘制对象分配贴图,
142
+ // Unity 资源包里没有 model3,贴图顺序只能从名字恢复;导出件的命名就是 texture_NN,
143
+ // 顺序即下标。若不排序(按对象表顺序取,实测是 texture_01 在前),
144
+ // 整个模型会用错贴图 —— 表现就是"碎板/花屏"。
145
+ out.sort((a, b) => String(a.name).localeCompare(String(b.name), undefined, { numeric: true }));
146
+ return out;
147
+ }
148
+
149
+ /**
150
+ * 原生 Cubism 文件集:把包内以 .moc3 / .model3.json / .motion3.json / .physics3.json /
151
+ * .cdi3.json 结尾的 TextAsset 按"文件"收集起来(另一种包型的直接通路,不需要重建)。
152
+ */
153
+ function findNativeCubismFiles(sf) {
154
+ const wanted = ['.moc3', '.model3.json', '.motion3.json', '.physics3.json', '.cdi3.json', '.exp3.json'];
155
+ const files = [];
156
+ for (const ta of findTextAssets(sf)) {
157
+ const lower = String(ta.name || '').toLowerCase();
158
+ if (!wanted.some((w) => lower.endsWith(w))) continue;
159
+ files.push({ name: ta.name, kind: lower.slice(lower.lastIndexOf('.') + 1), bytes: ta.bytes });
160
+ }
161
+ return files;
162
+ }
163
+
164
+ /**
165
+ * 从 moc3 里扫出**可作为参数名/部件名候选的字符串**。
166
+ *
167
+ * 为什么要它:Unity 的动画绑定只存 32 位哈希(CRC32),要还原成参数名就得有一份候选名单。
168
+ * moc3 里带有模型的参数 ID 表与部件名,是最完整的候选来源;这里用「可打印字符串扫描」
169
+ * 而不去解析 moc3 二进制布局——布局随 moc3 版本变化,扫描对版本不敏感,
170
+ * 多余的候选无害(最终按 CRC 精确匹配,撞不上的名字不会被用)。
171
+ *
172
+ * @param {Uint8Array} moc3Bytes
173
+ * @param {number} [minLen] 最短长度(默认 3)
174
+ * @returns {string[]} 去重后的候选字符串
175
+ */
176
+ function moc3NameCandidates(moc3Bytes, minLen) {
177
+ const min = minLen || 3;
178
+ const out = new Set();
179
+ if (!moc3Bytes) return [];
180
+ let start = -1;
181
+ for (let i = 0; i <= moc3Bytes.length; i++) {
182
+ const b = i < moc3Bytes.length ? moc3Bytes[i] : 0;
183
+ const printable = b >= 0x20 && b <= 0x7e;
184
+ if (printable) {
185
+ if (start < 0) start = i;
186
+ } else {
187
+ if (start >= 0) {
188
+ const len = i - start;
189
+ if (len >= min && len <= 64) {
190
+ const s = B.latin1(moc3Bytes, start, i);
191
+ // 只要「像标识符」的字符串:字母或下划线开头,其余为字母数字/下划线
192
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(s)) out.add(s);
193
+ }
194
+ start = -1;
195
+ }
196
+ }
197
+ }
198
+ return [...out];
199
+ }
200
+
201
+ /**
202
+ * 一次抽齐(M5/M6 用)。
203
+ * @param {{cabBytes:Uint8Array, resSBytes:Uint8Array, sf?:object}} input
204
+ */
205
+ function extractAll(input) {
206
+ const sf = input.sf || NS.serialized.parse(input.cabBytes);
207
+ const moc3Hit = findMoc3(sf);
208
+ const textAssets = findTextAssets(sf);
209
+ const physTa = textAssets.find((t) => /\.physics3$/i.test(t.name || ''));
210
+ const textures = findTextures(sf);
211
+ return {
212
+ sf,
213
+ moc3: moc3Hit ? moc3Hit.bytes : null,
214
+ moc3Object: moc3Hit ? moc3Hit.object : null,
215
+ moc3MagicOffset: moc3Hit ? moc3Hit.magicOffset : -1,
216
+ physics3: physTa ? physTa.bytes : null,
217
+ textAssets,
218
+ textures,
219
+ nativeFiles: findNativeCubismFiles(sf),
220
+ containerPaths: sf.externals.map((e) => e.path),
221
+ };
222
+ }
223
+
224
+ /** 取某张纹理的压缩数据:优先 m_StreamData 指向的 .resS 切片,其次对象内联数据 */
225
+ function textureCompressedBytes(texture, resSBytes) {
226
+ if (texture.stream) {
227
+ if (!resSBytes) throw NS.errors.fail('ULB_ERR_EXTRACT', `纹理 ${texture.name} 的数据在 .resS 里,但没有提供 .resS 字节`);
228
+ const { offset, size } = texture.stream;
229
+ if (offset + size > resSBytes.length) {
230
+ throw NS.errors.fail('ULB_ERR_EXTRACT', `纹理 ${texture.name} 的流数据越界:offset=${offset} size=${size} .resS=${resSBytes.length}`);
231
+ }
232
+ return resSBytes.subarray(offset, offset + size);
233
+ }
234
+ if (texture.inline) return texture.inline;
235
+ throw NS.errors.fail('ULB_ERR_EXTRACT', `纹理 ${texture.name} 既没有流数据也没有内联数据`);
236
+ }
237
+
238
+ NS.extract = {
239
+ findMoc3, findTextAssets, findTextures, findNativeCubismFiles,
240
+ extractAll, textureCompressedBytes, indexOfMagic, decodeUtf8, moc3NameCandidates,
241
+ };
242
+ }
@@ -0,0 +1,21 @@
1
+ // dsh-skip-sensitive: MIT 许可全文中的作者邮箱
2
+ © 2016 Nathan Rugg <nmrugg@gmail.com>
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.