sci2-ts 0.0.0-stage → 0.2.1

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 (357) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/LICENSE +21 -0
  3. package/README.md +113 -2
  4. package/apps/viewer/editor.html +43 -0
  5. package/apps/viewer/explore.html +27 -0
  6. package/apps/viewer/index.html +26 -0
  7. package/apps/viewer/package.json +27 -0
  8. package/apps/viewer/play.html +26 -0
  9. package/apps/viewer/plugin.d.ts +18 -0
  10. package/apps/viewer/plugin.js +75 -0
  11. package/apps/viewer/plugin.ts +84 -0
  12. package/apps/viewer/src/adlib.d.ts +20 -0
  13. package/apps/viewer/src/adlib.js +55 -0
  14. package/apps/viewer/src/adlib.ts +62 -0
  15. package/apps/viewer/src/app.d.ts +5 -0
  16. package/apps/viewer/src/app.js +5 -0
  17. package/apps/viewer/src/app.ts +5 -0
  18. package/apps/viewer/src/asset-index.d.ts +22 -0
  19. package/apps/viewer/src/asset-index.js +74 -0
  20. package/apps/viewer/src/asset-index.ts +90 -0
  21. package/apps/viewer/src/audio.d.ts +28 -0
  22. package/apps/viewer/src/audio.js +102 -0
  23. package/apps/viewer/src/audio.ts +110 -0
  24. package/apps/viewer/src/editor-handles.d.ts +63 -0
  25. package/apps/viewer/src/editor-handles.js +332 -0
  26. package/apps/viewer/src/editor-handles.ts +368 -0
  27. package/apps/viewer/src/editor-languages.d.ts +23 -0
  28. package/apps/viewer/src/editor-languages.js +157 -0
  29. package/apps/viewer/src/editor-languages.ts +135 -0
  30. package/apps/viewer/src/editor.css +213 -0
  31. package/apps/viewer/src/editor.d.ts +1 -0
  32. package/apps/viewer/src/editor.js +393 -0
  33. package/apps/viewer/src/editor.ts +412 -0
  34. package/apps/viewer/src/explore/classes.d.ts +7 -0
  35. package/apps/viewer/src/explore/classes.js +63 -0
  36. package/apps/viewer/src/explore/classes.ts +61 -0
  37. package/apps/viewer/src/explore/context.d.ts +33 -0
  38. package/apps/viewer/src/explore/context.js +96 -0
  39. package/apps/viewer/src/explore/context.ts +124 -0
  40. package/apps/viewer/src/explore/explore.css +93 -0
  41. package/apps/viewer/src/explore/explore.d.ts +1 -0
  42. package/apps/viewer/src/explore/explore.js +102 -0
  43. package/apps/viewer/src/explore/explore.ts +104 -0
  44. package/apps/viewer/src/explore/inspect.d.ts +5 -0
  45. package/apps/viewer/src/explore/inspect.js +208 -0
  46. package/apps/viewer/src/explore/inspect.ts +279 -0
  47. package/apps/viewer/src/explore/live.d.ts +22 -0
  48. package/apps/viewer/src/explore/live.js +318 -0
  49. package/apps/viewer/src/explore/live.ts +365 -0
  50. package/apps/viewer/src/explore/map.d.ts +11 -0
  51. package/apps/viewer/src/explore/map.js +182 -0
  52. package/apps/viewer/src/explore/map.ts +193 -0
  53. package/apps/viewer/src/files.d.ts +21 -0
  54. package/apps/viewer/src/files.js +69 -0
  55. package/apps/viewer/src/files.ts +72 -0
  56. package/apps/viewer/src/game-profile.d.ts +19 -0
  57. package/apps/viewer/src/game-profile.js +4 -0
  58. package/apps/viewer/src/game-profile.ts +26 -0
  59. package/apps/viewer/src/main.d.ts +1 -0
  60. package/apps/viewer/src/main.js +473 -0
  61. package/apps/viewer/src/main.ts +527 -0
  62. package/apps/viewer/src/music.d.ts +14 -0
  63. package/apps/viewer/src/music.js +32 -0
  64. package/apps/viewer/src/music.ts +34 -0
  65. package/apps/viewer/src/opl-worklet.d.ts +1 -0
  66. package/apps/viewer/src/opl-worklet.js +47 -0
  67. package/apps/viewer/src/opl-worklet.ts +55 -0
  68. package/apps/viewer/src/overlay.d.ts +31 -0
  69. package/apps/viewer/src/overlay.js +103 -0
  70. package/apps/viewer/src/overlay.ts +123 -0
  71. package/apps/viewer/src/play.css +14 -0
  72. package/apps/viewer/src/play.d.ts +1 -0
  73. package/apps/viewer/src/play.js +179 -0
  74. package/apps/viewer/src/play.ts +177 -0
  75. package/apps/viewer/src/saves.d.ts +10 -0
  76. package/apps/viewer/src/saves.js +50 -0
  77. package/apps/viewer/src/saves.ts +51 -0
  78. package/apps/viewer/src/session.d.ts +91 -0
  79. package/apps/viewer/src/session.js +301 -0
  80. package/apps/viewer/src/session.ts +321 -0
  81. package/apps/viewer/src/sound-preview.d.ts +7 -0
  82. package/apps/viewer/src/sound-preview.js +214 -0
  83. package/apps/viewer/src/sound-preview.ts +228 -0
  84. package/apps/viewer/src/style.css +64 -0
  85. package/apps/viewer/vite.config.ts +25 -0
  86. package/bin/sci-ts.mjs +9 -0
  87. package/lib/994.sc +49 -0
  88. package/lib/995.sc +53 -0
  89. package/lib/996.sc +299 -0
  90. package/lib/997.sc +198 -0
  91. package/lib/998.sc +338 -0
  92. package/lib/999.sc +189 -0
  93. package/lib/resources.d.ts +2 -0
  94. package/lib/resources.js +84 -0
  95. package/lib/resources.ts +88 -0
  96. package/lib/system.sh +62 -0
  97. package/package.json +75 -4
  98. package/packages/content/room.schema.json +267 -0
  99. package/packages/content/src/index.d.ts +3 -0
  100. package/packages/content/src/index.js +3 -0
  101. package/packages/content/src/index.ts +3 -0
  102. package/packages/content/src/room.d.ts +111 -0
  103. package/packages/content/src/room.js +886 -0
  104. package/packages/content/src/room.ts +1011 -0
  105. package/packages/content/src/target.d.ts +80 -0
  106. package/packages/content/src/target.js +1 -0
  107. package/packages/content/src/target.ts +60 -0
  108. package/packages/content/src/yarn.d.ts +90 -0
  109. package/packages/content/src/yarn.js +214 -0
  110. package/packages/content/src/yarn.ts +261 -0
  111. package/packages/sci/src/analysis/game.d.ts +90 -0
  112. package/packages/sci/src/analysis/game.js +221 -0
  113. package/packages/sci/src/analysis/game.ts +305 -0
  114. package/packages/sci/src/analysis/runtime.d.ts +58 -0
  115. package/packages/sci/src/analysis/runtime.js +133 -0
  116. package/packages/sci/src/analysis/runtime.ts +163 -0
  117. package/packages/sci/src/audio/adlib.d.ts +73 -0
  118. package/packages/sci/src/audio/adlib.js +280 -0
  119. package/packages/sci/src/audio/adlib.ts +329 -0
  120. package/packages/sci/src/audio/index.d.ts +43 -0
  121. package/packages/sci/src/audio/index.js +99 -0
  122. package/packages/sci/src/audio/index.ts +122 -0
  123. package/packages/sci/src/audio/midi.d.ts +92 -0
  124. package/packages/sci/src/audio/midi.js +242 -0
  125. package/packages/sci/src/audio/midi.ts +262 -0
  126. package/packages/sci/src/audio/opl2.d.ts +74 -0
  127. package/packages/sci/src/audio/opl2.js +283 -0
  128. package/packages/sci/src/audio/opl2.ts +283 -0
  129. package/packages/sci/src/audio/sol.d.ts +32 -0
  130. package/packages/sci/src/audio/sol.js +81 -0
  131. package/packages/sci/src/audio/sol.ts +100 -0
  132. package/packages/sci/src/audio/write.d.ts +35 -0
  133. package/packages/sci/src/audio/write.js +91 -0
  134. package/packages/sci/src/audio/write.ts +108 -0
  135. package/packages/sci/src/gfx/cel.d.ts +36 -0
  136. package/packages/sci/src/gfx/cel.js +82 -0
  137. package/packages/sci/src/gfx/cel.ts +110 -0
  138. package/packages/sci/src/gfx/palette.d.ts +24 -0
  139. package/packages/sci/src/gfx/palette.js +49 -0
  140. package/packages/sci/src/gfx/palette.ts +56 -0
  141. package/packages/sci/src/gfx/pic-file.d.ts +46 -0
  142. package/packages/sci/src/gfx/pic-file.js +114 -0
  143. package/packages/sci/src/gfx/pic-file.ts +155 -0
  144. package/packages/sci/src/gfx/pic.d.ts +27 -0
  145. package/packages/sci/src/gfx/pic.js +34 -0
  146. package/packages/sci/src/gfx/pic.ts +53 -0
  147. package/packages/sci/src/gfx/quantize.d.ts +42 -0
  148. package/packages/sci/src/gfx/quantize.js +199 -0
  149. package/packages/sci/src/gfx/quantize.ts +218 -0
  150. package/packages/sci/src/gfx/view-file.d.ts +62 -0
  151. package/packages/sci/src/gfx/view-file.js +182 -0
  152. package/packages/sci/src/gfx/view-file.ts +233 -0
  153. package/packages/sci/src/gfx/view.d.ts +28 -0
  154. package/packages/sci/src/gfx/view.js +45 -0
  155. package/packages/sci/src/gfx/view.ts +62 -0
  156. package/packages/sci/src/index.d.ts +38 -0
  157. package/packages/sci/src/index.js +38 -0
  158. package/packages/sci/src/index.ts +38 -0
  159. package/packages/sci/src/motion/pathfinding.d.ts +40 -0
  160. package/packages/sci/src/motion/pathfinding.js +140 -0
  161. package/packages/sci/src/motion/pathfinding.ts +147 -0
  162. package/packages/sci/src/resource/archive.d.ts +16 -0
  163. package/packages/sci/src/resource/archive.js +51 -0
  164. package/packages/sci/src/resource/archive.ts +56 -0
  165. package/packages/sci/src/resource/lzs.d.ts +12 -0
  166. package/packages/sci/src/resource/lzs.js +64 -0
  167. package/packages/sci/src/resource/lzs.ts +66 -0
  168. package/packages/sci/src/resource/manager.d.ts +45 -0
  169. package/packages/sci/src/resource/manager.js +158 -0
  170. package/packages/sci/src/resource/manager.ts +184 -0
  171. package/packages/sci/src/resource/map.d.ts +18 -0
  172. package/packages/sci/src/resource/map.js +34 -0
  173. package/packages/sci/src/resource/map.ts +44 -0
  174. package/packages/sci/src/resource/types.d.ts +37 -0
  175. package/packages/sci/src/resource/types.js +45 -0
  176. package/packages/sci/src/resource/types.ts +57 -0
  177. package/packages/sci/src/script/assembly.d.ts +67 -0
  178. package/packages/sci/src/script/assembly.js +748 -0
  179. package/packages/sci/src/script/assembly.ts +714 -0
  180. package/packages/sci/src/script/compiler.d.ts +75 -0
  181. package/packages/sci/src/script/compiler.js +993 -0
  182. package/packages/sci/src/script/compiler.ts +1040 -0
  183. package/packages/sci/src/script/disasm.d.ts +81 -0
  184. package/packages/sci/src/script/disasm.js +318 -0
  185. package/packages/sci/src/script/disasm.ts +323 -0
  186. package/packages/sci/src/script/kernel-names.d.ts +6 -0
  187. package/packages/sci/src/script/kernel-names.js +165 -0
  188. package/packages/sci/src/script/kernel-names.ts +165 -0
  189. package/packages/sci/src/script/opcodes.d.ts +22 -0
  190. package/packages/sci/src/script/opcodes.js +45 -0
  191. package/packages/sci/src/script/opcodes.ts +72 -0
  192. package/packages/sci/src/script/script.d.ts +56 -0
  193. package/packages/sci/src/script/script.js +63 -0
  194. package/packages/sci/src/script/script.ts +111 -0
  195. package/packages/sci/src/script/sexpr.d.ts +45 -0
  196. package/packages/sci/src/script/sexpr.js +151 -0
  197. package/packages/sci/src/script/sexpr.ts +139 -0
  198. package/packages/sci/src/script/vocab.d.ts +15 -0
  199. package/packages/sci/src/script/vocab.js +44 -0
  200. package/packages/sci/src/script/vocab.ts +48 -0
  201. package/packages/sci/src/script/world.d.ts +25 -0
  202. package/packages/sci/src/script/world.js +65 -0
  203. package/packages/sci/src/script/world.ts +70 -0
  204. package/packages/sci/src/text/font.d.ts +37 -0
  205. package/packages/sci/src/text/font.js +103 -0
  206. package/packages/sci/src/text/font.ts +125 -0
  207. package/packages/sci/src/text/message.d.ts +69 -0
  208. package/packages/sci/src/text/message.js +171 -0
  209. package/packages/sci/src/text/message.ts +205 -0
  210. package/packages/sci/src/vm/kernels/arrays.d.ts +19 -0
  211. package/packages/sci/src/vm/kernels/arrays.js +182 -0
  212. package/packages/sci/src/vm/kernels/arrays.ts +164 -0
  213. package/packages/sci/src/vm/kernels/audio.d.ts +70 -0
  214. package/packages/sci/src/vm/kernels/audio.js +347 -0
  215. package/packages/sci/src/vm/kernels/audio.ts +349 -0
  216. package/packages/sci/src/vm/kernels/core.d.ts +5 -0
  217. package/packages/sci/src/vm/kernels/core.js +87 -0
  218. package/packages/sci/src/vm/kernels/core.ts +91 -0
  219. package/packages/sci/src/vm/kernels/graphics.d.ts +141 -0
  220. package/packages/sci/src/vm/kernels/graphics.js +467 -0
  221. package/packages/sci/src/vm/kernels/graphics.ts +502 -0
  222. package/packages/sci/src/vm/kernels/index.d.ts +8 -0
  223. package/packages/sci/src/vm/kernels/index.js +29 -0
  224. package/packages/sci/src/vm/kernels/index.ts +32 -0
  225. package/packages/sci/src/vm/kernels/lists.d.ts +6 -0
  226. package/packages/sci/src/vm/kernels/lists.js +127 -0
  227. package/packages/sci/src/vm/kernels/lists.ts +111 -0
  228. package/packages/sci/src/vm/kernels/motion.d.ts +7 -0
  229. package/packages/sci/src/vm/kernels/motion.js +204 -0
  230. package/packages/sci/src/vm/kernels/motion.ts +194 -0
  231. package/packages/sci/src/vm/kernels/palette.d.ts +82 -0
  232. package/packages/sci/src/vm/kernels/palette.js +292 -0
  233. package/packages/sci/src/vm/kernels/palette.ts +303 -0
  234. package/packages/sci/src/vm/kernels/saves.d.ts +45 -0
  235. package/packages/sci/src/vm/kernels/saves.js +123 -0
  236. package/packages/sci/src/vm/kernels/saves.ts +149 -0
  237. package/packages/sci/src/vm/kernels/system.d.ts +62 -0
  238. package/packages/sci/src/vm/kernels/system.js +110 -0
  239. package/packages/sci/src/vm/kernels/system.ts +128 -0
  240. package/packages/sci/src/vm/kernels/text.d.ts +13 -0
  241. package/packages/sci/src/vm/kernels/text.js +338 -0
  242. package/packages/sci/src/vm/kernels/text.ts +328 -0
  243. package/packages/sci/src/vm/memory.d.ts +138 -0
  244. package/packages/sci/src/vm/memory.js +168 -0
  245. package/packages/sci/src/vm/memory.ts +266 -0
  246. package/packages/sci/src/vm/replace.d.ts +11 -0
  247. package/packages/sci/src/vm/replace.js +14 -0
  248. package/packages/sci/src/vm/replace.ts +16 -0
  249. package/packages/sci/src/vm/savegame.d.ts +89 -0
  250. package/packages/sci/src/vm/savegame.js +161 -0
  251. package/packages/sci/src/vm/savegame.ts +188 -0
  252. package/packages/sci/src/vm/value.d.ts +17 -0
  253. package/packages/sci/src/vm/value.js +12 -0
  254. package/packages/sci/src/vm/value.ts +25 -0
  255. package/packages/sci/src/vm/vm.d.ts +155 -0
  256. package/packages/sci/src/vm/vm.js +1053 -0
  257. package/packages/sci/src/vm/vm.ts +914 -0
  258. package/tools/adlib-render.d.ts +1 -0
  259. package/tools/adlib-render.js +49 -0
  260. package/tools/adlib-render.ts +42 -0
  261. package/tools/analyze.d.ts +1 -0
  262. package/tools/analyze.js +25 -0
  263. package/tools/analyze.ts +25 -0
  264. package/tools/art/build.d.ts +34 -0
  265. package/tools/art/build.js +175 -0
  266. package/tools/art/build.ts +153 -0
  267. package/tools/art.d.ts +1 -0
  268. package/tools/art.js +31 -0
  269. package/tools/art.ts +29 -0
  270. package/tools/asm.d.ts +1 -0
  271. package/tools/asm.js +61 -0
  272. package/tools/asm.ts +55 -0
  273. package/tools/cli.d.ts +1 -0
  274. package/tools/cli.js +99 -0
  275. package/tools/cli.ts +100 -0
  276. package/tools/editor-server.d.ts +29 -0
  277. package/tools/editor-server.js +162 -0
  278. package/tools/editor-server.ts +176 -0
  279. package/tools/explore.d.ts +1 -0
  280. package/tools/explore.js +305 -0
  281. package/tools/explore.ts +250 -0
  282. package/tools/game/audio.d.ts +19 -0
  283. package/tools/game/audio.js +143 -0
  284. package/tools/game/audio.ts +130 -0
  285. package/tools/game/build.d.ts +46 -0
  286. package/tools/game/build.js +259 -0
  287. package/tools/game/build.ts +283 -0
  288. package/tools/game/defaults.d.ts +26 -0
  289. package/tools/game/defaults.js +82 -0
  290. package/tools/game/defaults.ts +83 -0
  291. package/tools/game/font.d.ts +3 -0
  292. package/tools/game/font.js +124 -0
  293. package/tools/game/font.ts +126 -0
  294. package/tools/game/kit.d.ts +7 -0
  295. package/tools/game/kit.js +7 -0
  296. package/tools/game/kit.ts +10 -0
  297. package/tools/game/new.d.ts +8 -0
  298. package/tools/game/new.js +39 -0
  299. package/tools/game/new.ts +38 -0
  300. package/tools/game/target.d.ts +7 -0
  301. package/tools/game/target.js +41 -0
  302. package/tools/game/target.ts +42 -0
  303. package/tools/game/template/resources.ts.tmpl +43 -0
  304. package/tools/game/template/rooms/1.room.yaml +9 -0
  305. package/tools/game/template/rooms/1.yarn +17 -0
  306. package/tools/game/template/scripts/0.sc +24 -0
  307. package/tools/game.d.ts +1 -0
  308. package/tools/game.js +77 -0
  309. package/tools/game.ts +70 -0
  310. package/tools/mod-build.d.ts +28 -0
  311. package/tools/mod-build.js +173 -0
  312. package/tools/mod-build.ts +179 -0
  313. package/tools/mod-target.d.ts +16 -0
  314. package/tools/mod-target.js +26 -0
  315. package/tools/mod-target.ts +32 -0
  316. package/tools/mod.d.ts +1 -0
  317. package/tools/mod.js +47 -0
  318. package/tools/mod.ts +45 -0
  319. package/tools/msg.d.ts +1 -0
  320. package/tools/msg.js +40 -0
  321. package/tools/msg.ts +36 -0
  322. package/tools/node-files.d.ts +3 -0
  323. package/tools/node-files.js +34 -0
  324. package/tools/node-files.ts +33 -0
  325. package/tools/patches.d.ts +3 -0
  326. package/tools/patches.js +13 -0
  327. package/tools/patches.ts +13 -0
  328. package/tools/pic-build.d.ts +9 -0
  329. package/tools/pic-build.js +26 -0
  330. package/tools/pic-build.ts +27 -0
  331. package/tools/pic.d.ts +1 -0
  332. package/tools/pic.js +64 -0
  333. package/tools/pic.ts +61 -0
  334. package/tools/play.d.ts +1 -0
  335. package/tools/play.js +54 -0
  336. package/tools/play.ts +54 -0
  337. package/tools/png.d.ts +16 -0
  338. package/tools/png.js +124 -0
  339. package/tools/png.ts +123 -0
  340. package/tools/res.d.ts +1 -0
  341. package/tools/res.js +48 -0
  342. package/tools/res.ts +50 -0
  343. package/tools/scenario.d.ts +1 -0
  344. package/tools/scenario.js +74 -0
  345. package/tools/scenario.ts +80 -0
  346. package/tools/site/build.d.ts +8 -0
  347. package/tools/site/build.js +110 -0
  348. package/tools/site/build.ts +116 -0
  349. package/tools/site/markdown.d.ts +11 -0
  350. package/tools/site/markdown.js +88 -0
  351. package/tools/site/markdown.ts +84 -0
  352. package/tools/view-build.d.ts +34 -0
  353. package/tools/view-build.js +47 -0
  354. package/tools/view-build.ts +71 -0
  355. package/tools/view.d.ts +1 -0
  356. package/tools/view.js +54 -0
  357. package/tools/view.ts +52 -0
@@ -0,0 +1,184 @@
1
+ import { decompressLzs } from "./lzs.ts";
2
+ import { parseResourceMap } from "./map.ts";
3
+ import { patchExtensions, resourceKey, type ResourceId, type ResourceType } from "./types.ts";
4
+
5
+ /** Where game files come from: Node fs in tools, fetch/File API in the browser. */
6
+ export interface FileSource {
7
+ read(path: string): Promise<Uint8Array | undefined>;
8
+ /** Byte range read, so large volumes needn't be loaded whole. */
9
+ readRange(path: string, offset: number, length: number): Promise<Uint8Array>;
10
+ list(dir: string): Promise<string[]>;
11
+ }
12
+
13
+ export interface ResourceInfo extends ResourceId {
14
+ source: "volume" | "patch" | "memory";
15
+ /** Volume: header offset. Patch: file path. */
16
+ location: number | string;
17
+ }
18
+
19
+ export interface Resource extends ResourceId {
20
+ data: Uint8Array;
21
+ }
22
+
23
+ const VOLUME = "RESOURCE.000";
24
+ const VOLUME_HEADER_SIZE = 13; // type u8, number u16, packed u32, unpacked u32, method u16
25
+
26
+ const Compression = { None: 0, Lzs: 32 } as const;
27
+
28
+ /** Extra-header sizes for patch size bytes with bit 7 set (per ScummVM). */
29
+ const patchHeaderCodes: Record<number, number> = { 0: 24, 1: 2, 4: 8 };
30
+
31
+ export class ResourceManager {
32
+ private readonly index = new Map<string, ResourceInfo>();
33
+ private volume: Uint8Array | undefined;
34
+ private readonly patches = new Map<string, Uint8Array>();
35
+ private readonly extras = new Map<string, Uint8Array>();
36
+ /** Resources replaced at run time (an editor's latest build), by key. */
37
+ private readonly memory = new Map<string, Uint8Array>();
38
+
39
+ private constructor(private readonly files: FileSource) {}
40
+
41
+ /** Indexes RESOURCE.MAP, then lets loose patch files (game dir, then PATCHES/) override it. */
42
+ static async open(files: FileSource, patchDirs = ["", "PATCHES"]): Promise<ResourceManager> {
43
+ const rm = new ResourceManager(files);
44
+ const map = await files.read("RESOURCE.MAP");
45
+ if (!map) throw new Error("RESOURCE.MAP not found");
46
+
47
+ for (const e of parseResourceMap(map)) {
48
+ rm.index.set(resourceKey(e), { type: e.type, number: e.number, source: "volume", location: e.offset });
49
+ }
50
+
51
+ for (const dir of patchDirs) {
52
+ for (const name of await files.list(dir)) {
53
+ const m = /^(\d+)\.([a-z0-9]+)$/i.exec(name);
54
+ const type = m && patchExtensions[m[2]!.toLowerCase()];
55
+ if (type === undefined || type === null) continue;
56
+ const id = { type, number: Number(m![1]) };
57
+ rm.index.set(resourceKey(id), { ...id, source: "patch", location: dir ? `${dir}/${name}` : name });
58
+ }
59
+ }
60
+ return rm;
61
+ }
62
+
63
+ list(type?: ResourceType): ResourceInfo[] {
64
+ const all = [...this.index.values()];
65
+ return (type === undefined ? all : all.filter((r) => r.type === type)).sort(
66
+ (a, b) => a.type - b.type || a.number - b.number,
67
+ );
68
+ }
69
+
70
+ has(id: ResourceId): boolean {
71
+ return this.index.has(resourceKey(id));
72
+ }
73
+
74
+ /**
75
+ * Replaces (or adds) a resource with already decoded data, until the page goes away: a
76
+ * live editor's rebuilt room. Whatever parsed the old one must forget it (replaceResources).
77
+ */
78
+ override(id: ResourceId, data: Uint8Array): void {
79
+ const key = resourceKey(id);
80
+ this.memory.set(key, data);
81
+ this.index.set(key, { type: id.type, number: id.number, source: "memory", location: key });
82
+ }
83
+
84
+ async load(id: ResourceId): Promise<Resource> {
85
+ const info = this.info(id);
86
+ let data: Uint8Array;
87
+ if (info.source === "memory") {
88
+ data = this.memory.get(info.location as string)!;
89
+ } else if (info.source === "patch") {
90
+ const file = await this.files.read(info.location as string);
91
+ if (!file) throw new Error(`Patch file ${info.location} disappeared`);
92
+ data = decodePatch(info, file);
93
+ } else if (this.volume) {
94
+ data = decodeVolumeEntry(info, this.volume, info.location as number);
95
+ } else {
96
+ const offset = info.location as number;
97
+ const header = await this.files.readRange(VOLUME, offset, VOLUME_HEADER_SIZE);
98
+ const packed = new DataView(header.buffer, header.byteOffset).getUint32(3, true);
99
+ const entry = new Uint8Array(VOLUME_HEADER_SIZE + packed);
100
+ entry.set(header);
101
+ entry.set(await this.files.readRange(VOLUME, offset + VOLUME_HEADER_SIZE, packed), VOLUME_HEADER_SIZE);
102
+ data = decodeVolumeEntry(info, entry, 0);
103
+ }
104
+ return { type: info.type, number: info.number, data };
105
+ }
106
+
107
+ /**
108
+ * Reads the whole volume and every patch into memory so `loadSync` works. The VM needs
109
+ * this: bytecode can load a script or look up a view in the middle of an instruction.
110
+ */
111
+ async preload(extraFiles: string[] = ["RESOURCE.SFX"]): Promise<void> {
112
+ this.volume ??= await this.files.read(VOLUME);
113
+ for (const name of extraFiles) {
114
+ const data = this.extras.has(name) ? undefined : await this.files.read(name);
115
+ if (data) this.extras.set(name, data);
116
+ }
117
+ for (const info of this.index.values()) {
118
+ if (info.source === "patch" && !this.patches.has(info.location as string)) {
119
+ const file = await this.files.read(info.location as string);
120
+ if (file) this.patches.set(info.location as string, file);
121
+ }
122
+ }
123
+ }
124
+
125
+ loadSync(id: ResourceId): Resource {
126
+ const info = this.info(id);
127
+ if (!this.volume) throw new Error("loadSync before preload()");
128
+ const data =
129
+ info.source === "memory"
130
+ ? this.memory.get(info.location as string)!
131
+ : info.source === "patch"
132
+ ? decodePatch(info, this.patches.get(info.location as string)!)
133
+ : decodeVolumeEntry(info, this.volume, info.location as number);
134
+ return { type: info.type, number: info.number, data };
135
+ }
136
+
137
+ /** A whole game file read by `preload` (e.g. RESOURCE.SFX), if present. */
138
+ file(name: string): Uint8Array | undefined {
139
+ return this.extras.get(name);
140
+ }
141
+
142
+ private info(id: ResourceId): ResourceInfo {
143
+ const info = this.index.get(resourceKey(id));
144
+ if (!info) throw new Error(`Resource ${resourceKey(id)} not found`);
145
+ return info;
146
+ }
147
+ }
148
+
149
+ /** Decodes the volume entry at `offset` in `bytes`. */
150
+ function decodeVolumeEntry(info: ResourceInfo, bytes: Uint8Array, offset: number): Uint8Array {
151
+ const h = new DataView(bytes.buffer, bytes.byteOffset + offset, VOLUME_HEADER_SIZE);
152
+ const type = h.getUint8(0) & 0x7f;
153
+ const number = h.getUint16(1, true);
154
+ const packed = h.getUint32(3, true);
155
+ const unpacked = h.getUint32(7, true);
156
+ const method = h.getUint16(11, true);
157
+
158
+ if (type !== info.type || number !== info.number) {
159
+ throw new Error(`Volume header mismatch at ${info.location}: got ${type}:${number}, expected ${resourceKey(info)}`);
160
+ }
161
+
162
+ const body = bytes.subarray(offset + VOLUME_HEADER_SIZE, offset + VOLUME_HEADER_SIZE + packed);
163
+ switch (method) {
164
+ case Compression.None: return body;
165
+ case Compression.Lzs: return decompressLzs(body, unpacked);
166
+ default: throw new Error(`Unsupported compression method ${method} for ${resourceKey(info)}`);
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Patch files: u8 type, u8 extra-header size, [extra header], data.
172
+ * SCI32 view/pic patches set bit 7 of the size byte and store a code instead.
173
+ */
174
+ function decodePatch(info: ResourceInfo, file: Uint8Array): Uint8Array {
175
+ const type = file[0]! & 0x7f;
176
+ if (type !== info.type) throw new Error(`Patch ${info.location} has type ${type}, expected ${info.type}`);
177
+ let extra = file[1]!;
178
+ if (extra & 0x80) {
179
+ const size = patchHeaderCodes[extra & 0x7f];
180
+ if (size === undefined) throw new Error(`Patch ${info.location}: unknown header code ${extra & 0x7f}`);
181
+ extra = size;
182
+ }
183
+ return file.subarray(2 + extra);
184
+ }
@@ -0,0 +1,18 @@
1
+ import type { ResourceType } from "./types.ts";
2
+ export interface MapEntry {
3
+ type: ResourceType;
4
+ number: number;
5
+ /** Byte offset of the resource header inside the volume (RESOURCE.000). */
6
+ offset: number;
7
+ }
8
+ /**
9
+ * Parses an SCI2 RESOURCE.MAP.
10
+ *
11
+ * Layout:
12
+ * header: repeated { type: u8, tableOffset: u16 } terminated by type 0xFF
13
+ * (the terminator's offset is the end of the last table)
14
+ * tables: per type, repeated { number: u16, volumeOffset: u32 } (6 bytes)
15
+ *
16
+ * SCI1.1 used 5-byte entries (u24 offset, shifted).
17
+ */
18
+ export declare function parseResourceMap(data: Uint8Array): MapEntry[];
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Parses an SCI2 RESOURCE.MAP.
3
+ *
4
+ * Layout:
5
+ * header: repeated { type: u8, tableOffset: u16 } terminated by type 0xFF
6
+ * (the terminator's offset is the end of the last table)
7
+ * tables: per type, repeated { number: u16, volumeOffset: u32 } (6 bytes)
8
+ *
9
+ * SCI1.1 used 5-byte entries (u24 offset, shifted).
10
+ */
11
+ export function parseResourceMap(data) {
12
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
13
+ const tables = [];
14
+ for (let pos = 0;; pos += 3) {
15
+ const type = view.getUint8(pos);
16
+ const offset = view.getUint16(pos + 1, true);
17
+ tables.push({ type, offset });
18
+ if (type === 0xff)
19
+ break;
20
+ }
21
+ const entries = [];
22
+ for (let i = 0; i < tables.length - 1; i++) {
23
+ const { type, offset: start } = tables[i];
24
+ const end = tables[i + 1].offset;
25
+ for (let pos = start; pos < end; pos += 6) {
26
+ entries.push({
27
+ type: type,
28
+ number: view.getUint16(pos, true),
29
+ offset: view.getUint32(pos + 2, true),
30
+ });
31
+ }
32
+ }
33
+ return entries;
34
+ }
@@ -0,0 +1,44 @@
1
+ import type { ResourceType } from "./types.ts";
2
+
3
+ export interface MapEntry {
4
+ type: ResourceType;
5
+ number: number;
6
+ /** Byte offset of the resource header inside the volume (RESOURCE.000). */
7
+ offset: number;
8
+ }
9
+
10
+ /**
11
+ * Parses an SCI2 RESOURCE.MAP.
12
+ *
13
+ * Layout:
14
+ * header: repeated { type: u8, tableOffset: u16 } terminated by type 0xFF
15
+ * (the terminator's offset is the end of the last table)
16
+ * tables: per type, repeated { number: u16, volumeOffset: u32 } (6 bytes)
17
+ *
18
+ * SCI1.1 used 5-byte entries (u24 offset, shifted).
19
+ */
20
+ export function parseResourceMap(data: Uint8Array): MapEntry[] {
21
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
22
+ const tables: { type: number; offset: number }[] = [];
23
+
24
+ for (let pos = 0; ; pos += 3) {
25
+ const type = view.getUint8(pos);
26
+ const offset = view.getUint16(pos + 1, true);
27
+ tables.push({ type, offset });
28
+ if (type === 0xff) break;
29
+ }
30
+
31
+ const entries: MapEntry[] = [];
32
+ for (let i = 0; i < tables.length - 1; i++) {
33
+ const { type, offset: start } = tables[i]!;
34
+ const end = tables[i + 1]!.offset;
35
+ for (let pos = start; pos < end; pos += 6) {
36
+ entries.push({
37
+ type: type as ResourceType,
38
+ number: view.getUint16(pos, true),
39
+ offset: view.getUint32(pos + 2, true),
40
+ });
41
+ }
42
+ }
43
+ return entries;
44
+ }
@@ -0,0 +1,37 @@
1
+ /** Resource type ids as stored in RESOURCE.MAP and volume headers (SCI1.1 / SCI2 numbering). */
2
+ export declare const ResourceType: {
3
+ readonly View: 0;
4
+ readonly Pic: 1;
5
+ readonly Script: 2;
6
+ readonly Text: 3;
7
+ readonly Sound: 4;
8
+ readonly Memory: 5;
9
+ readonly Vocab: 6;
10
+ readonly Font: 7;
11
+ readonly Cursor: 8;
12
+ readonly Patch: 9;
13
+ readonly Bitmap: 10;
14
+ readonly Palette: 11;
15
+ readonly CdAudio: 12;
16
+ readonly Audio: 13;
17
+ readonly Sync: 14;
18
+ readonly Message: 15;
19
+ readonly Map: 16;
20
+ readonly Heap: 17;
21
+ readonly Audio36: 18;
22
+ readonly Sync36: 19;
23
+ readonly Translation: 20;
24
+ readonly Robot: 21;
25
+ readonly Vmd: 22;
26
+ readonly Chunk: 23;
27
+ readonly Animation: 24;
28
+ };
29
+ export type ResourceType = (typeof ResourceType)[keyof typeof ResourceType];
30
+ export declare const resourceTypeName: Record<number, string>;
31
+ /** Loose files in the game dir or PATCHES/ override volume resources: `<number>.<ext>`. */
32
+ export declare const patchExtensions: Record<string, ResourceType>;
33
+ export interface ResourceId {
34
+ type: ResourceType;
35
+ number: number;
36
+ }
37
+ export declare const resourceKey: ({ type, number }: ResourceId) => string;
@@ -0,0 +1,45 @@
1
+ /** Resource type ids as stored in RESOURCE.MAP and volume headers (SCI1.1 / SCI2 numbering). */
2
+ export const ResourceType = {
3
+ View: 0x00,
4
+ Pic: 0x01,
5
+ Script: 0x02,
6
+ Text: 0x03,
7
+ Sound: 0x04,
8
+ Memory: 0x05,
9
+ Vocab: 0x06,
10
+ Font: 0x07,
11
+ Cursor: 0x08,
12
+ Patch: 0x09,
13
+ Bitmap: 0x0a,
14
+ Palette: 0x0b,
15
+ CdAudio: 0x0c,
16
+ Audio: 0x0d,
17
+ Sync: 0x0e,
18
+ Message: 0x0f,
19
+ Map: 0x10,
20
+ Heap: 0x11,
21
+ Audio36: 0x12,
22
+ Sync36: 0x13,
23
+ Translation: 0x14,
24
+ Robot: 0x15,
25
+ Vmd: 0x16,
26
+ Chunk: 0x17,
27
+ Animation: 0x18,
28
+ };
29
+ export const resourceTypeName = Object.fromEntries(Object.entries(ResourceType).map(([name, id]) => [id, name.toLowerCase()]));
30
+ /** Loose files in the game dir or PATCHES/ override volume resources: `<number>.<ext>`. */
31
+ export const patchExtensions = {
32
+ v56: ResourceType.View,
33
+ p56: ResourceType.Pic,
34
+ scr: ResourceType.Script,
35
+ hep: ResourceType.Heap,
36
+ snd: ResourceType.Sound,
37
+ voc: ResourceType.Vocab,
38
+ fon: ResourceType.Font,
39
+ cur: ResourceType.Cursor,
40
+ pat: ResourceType.Patch,
41
+ pal: ResourceType.Palette,
42
+ msg: ResourceType.Message,
43
+ map: ResourceType.Map,
44
+ };
45
+ export const resourceKey = ({ type, number }) => `${type}:${number}`;
@@ -0,0 +1,57 @@
1
+ /** Resource type ids as stored in RESOURCE.MAP and volume headers (SCI1.1 / SCI2 numbering). */
2
+ export const ResourceType = {
3
+ View: 0x00,
4
+ Pic: 0x01,
5
+ Script: 0x02,
6
+ Text: 0x03,
7
+ Sound: 0x04,
8
+ Memory: 0x05,
9
+ Vocab: 0x06,
10
+ Font: 0x07,
11
+ Cursor: 0x08,
12
+ Patch: 0x09,
13
+ Bitmap: 0x0a,
14
+ Palette: 0x0b,
15
+ CdAudio: 0x0c,
16
+ Audio: 0x0d,
17
+ Sync: 0x0e,
18
+ Message: 0x0f,
19
+ Map: 0x10,
20
+ Heap: 0x11,
21
+ Audio36: 0x12,
22
+ Sync36: 0x13,
23
+ Translation: 0x14,
24
+ Robot: 0x15,
25
+ Vmd: 0x16,
26
+ Chunk: 0x17,
27
+ Animation: 0x18,
28
+ } as const;
29
+
30
+ export type ResourceType = (typeof ResourceType)[keyof typeof ResourceType];
31
+
32
+ export const resourceTypeName: Record<number, string> = Object.fromEntries(
33
+ Object.entries(ResourceType).map(([name, id]) => [id, name.toLowerCase()]),
34
+ );
35
+
36
+ /** Loose files in the game dir or PATCHES/ override volume resources: `<number>.<ext>`. */
37
+ export const patchExtensions: Record<string, ResourceType> = {
38
+ v56: ResourceType.View,
39
+ p56: ResourceType.Pic,
40
+ scr: ResourceType.Script,
41
+ hep: ResourceType.Heap,
42
+ snd: ResourceType.Sound,
43
+ voc: ResourceType.Vocab,
44
+ fon: ResourceType.Font,
45
+ cur: ResourceType.Cursor,
46
+ pat: ResourceType.Patch,
47
+ pal: ResourceType.Palette,
48
+ msg: ResourceType.Message,
49
+ map: ResourceType.Map,
50
+ };
51
+
52
+ export interface ResourceId {
53
+ type: ResourceType;
54
+ number: number;
55
+ }
56
+
57
+ export const resourceKey = ({ type, number }: ResourceId): string => `${type}:${number}`;
@@ -0,0 +1,67 @@
1
+ import { type DisasmContext } from "./disasm.ts";
2
+ import { type Script } from "./script.ts";
3
+ /**
4
+ * SCI2 assembly: a text form of a script + heap pair that assembles back to the same
5
+ * bytes, and that you can write by hand.
6
+ *
7
+ * script 710
8
+ * locals 0 0 5 ; initial values (@label for heap addresses)
9
+ * exports
10
+ * rm710 ; an object, a code label, or 0 (unused)
11
+ * instance rm710 of Room ; or: class Name of Super species 166
12
+ * classScript 0 ; header slots that aren't computed
13
+ * info 0
14
+ * name @s_0b2c
15
+ * picture 710 ; properties by name (or #slot)
16
+ * method init rm710::init
17
+ * layout -objID- ... name x y ; optional: the full property layout, so the class
18
+ * ; needn't be known when assembling (compiled code)
19
+ * strings
20
+ * s_0b2c "rm710"
21
+ * code
22
+ * rm710::init: ; code owned by rm710 (self)
23
+ * lsg 201
24
+ * bnt .L015c ; minimal form unless .b / .w says otherwise
25
+ * pushi #init ; #selector, @heap label, class Name, callk Name
26
+ * pToa register ; properties of self by name
27
+ * proc_0abc:
28
+ * owner sExit ; procedures say whose properties they use
29
+ *
30
+ * Layout rules, measured on all 285 scripts of an SCI2 game (and required to hold for byte-exact
31
+ * round trips):
32
+ * script header (reloc offset, 0, 0, export count) | exports | per object in heap order:
33
+ * class propDict, then methDict | code (no gaps) | code relocation table
34
+ * heap header (reloc offset, local count) | locals | objects | 0 | strings | heap relocs
35
+ * Code relocations are object exports and every lofsa/lofss operand (always 16-bit);
36
+ * heap relocations are object slots holding heap addresses (names, string properties).
37
+ *
38
+ * Operand forms: the compiler didn't always pick the smallest encoding (forward jumps and
39
+ * selector pushes are split), so round trips mark the exceptions with `.w` / `.b`. By
40
+ * default the assembler picks the smallest form, except send/self (almost always 16-bit
41
+ * in the original scripts) and lofsa/lofss (relocated, so always 16-bit).
42
+ */
43
+ export interface AsmContext extends DisasmContext {
44
+ /** Class name → class number, for `of Name`, `class Name` and `super Name`. */
45
+ classSpecies(name: string): number | undefined;
46
+ /** Property selectors of a class by number (for instances of classes in other scripts). */
47
+ classPropSelectors(species: number): readonly number[] | undefined;
48
+ /** A class's default property values, all slots (instances start from these). */
49
+ classDefaults(species: number): readonly number[] | undefined;
50
+ /**
51
+ * A selector the context doesn't know yet: a game built from nothing numbers its selectors
52
+ * as its scripts use them. Without it, unknown selectors are errors.
53
+ */
54
+ newSelector?(name: string): number;
55
+ }
56
+ export declare class AsmError extends Error {
57
+ readonly line?: number | undefined;
58
+ constructor(message: string, line?: number | undefined);
59
+ }
60
+ export declare function toAssembly(script: Script, ctx: AsmContext): string;
61
+ export declare function assemble(text: string, ctx: AsmContext): {
62
+ number: number;
63
+ code: Uint8Array;
64
+ heap: Uint8Array;
65
+ };
66
+ /** Reads the heap string at an address (for tools that show assembled output). */
67
+ export declare const heapString: (heap: Uint8Array, offset: number) => string;