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.
- package/CHANGELOG.md +72 -0
- package/LICENSE +21 -0
- package/README.md +113 -2
- package/apps/viewer/editor.html +43 -0
- package/apps/viewer/explore.html +27 -0
- package/apps/viewer/index.html +26 -0
- package/apps/viewer/package.json +27 -0
- package/apps/viewer/play.html +26 -0
- package/apps/viewer/plugin.d.ts +18 -0
- package/apps/viewer/plugin.js +75 -0
- package/apps/viewer/plugin.ts +84 -0
- package/apps/viewer/src/adlib.d.ts +20 -0
- package/apps/viewer/src/adlib.js +55 -0
- package/apps/viewer/src/adlib.ts +62 -0
- package/apps/viewer/src/app.d.ts +5 -0
- package/apps/viewer/src/app.js +5 -0
- package/apps/viewer/src/app.ts +5 -0
- package/apps/viewer/src/asset-index.d.ts +22 -0
- package/apps/viewer/src/asset-index.js +74 -0
- package/apps/viewer/src/asset-index.ts +90 -0
- package/apps/viewer/src/audio.d.ts +28 -0
- package/apps/viewer/src/audio.js +102 -0
- package/apps/viewer/src/audio.ts +110 -0
- package/apps/viewer/src/editor-handles.d.ts +63 -0
- package/apps/viewer/src/editor-handles.js +332 -0
- package/apps/viewer/src/editor-handles.ts +368 -0
- package/apps/viewer/src/editor-languages.d.ts +23 -0
- package/apps/viewer/src/editor-languages.js +157 -0
- package/apps/viewer/src/editor-languages.ts +135 -0
- package/apps/viewer/src/editor.css +213 -0
- package/apps/viewer/src/editor.d.ts +1 -0
- package/apps/viewer/src/editor.js +393 -0
- package/apps/viewer/src/editor.ts +412 -0
- package/apps/viewer/src/explore/classes.d.ts +7 -0
- package/apps/viewer/src/explore/classes.js +63 -0
- package/apps/viewer/src/explore/classes.ts +61 -0
- package/apps/viewer/src/explore/context.d.ts +33 -0
- package/apps/viewer/src/explore/context.js +96 -0
- package/apps/viewer/src/explore/context.ts +124 -0
- package/apps/viewer/src/explore/explore.css +93 -0
- package/apps/viewer/src/explore/explore.d.ts +1 -0
- package/apps/viewer/src/explore/explore.js +102 -0
- package/apps/viewer/src/explore/explore.ts +104 -0
- package/apps/viewer/src/explore/inspect.d.ts +5 -0
- package/apps/viewer/src/explore/inspect.js +208 -0
- package/apps/viewer/src/explore/inspect.ts +279 -0
- package/apps/viewer/src/explore/live.d.ts +22 -0
- package/apps/viewer/src/explore/live.js +318 -0
- package/apps/viewer/src/explore/live.ts +365 -0
- package/apps/viewer/src/explore/map.d.ts +11 -0
- package/apps/viewer/src/explore/map.js +182 -0
- package/apps/viewer/src/explore/map.ts +193 -0
- package/apps/viewer/src/files.d.ts +21 -0
- package/apps/viewer/src/files.js +69 -0
- package/apps/viewer/src/files.ts +72 -0
- package/apps/viewer/src/game-profile.d.ts +19 -0
- package/apps/viewer/src/game-profile.js +4 -0
- package/apps/viewer/src/game-profile.ts +26 -0
- package/apps/viewer/src/main.d.ts +1 -0
- package/apps/viewer/src/main.js +473 -0
- package/apps/viewer/src/main.ts +527 -0
- package/apps/viewer/src/music.d.ts +14 -0
- package/apps/viewer/src/music.js +32 -0
- package/apps/viewer/src/music.ts +34 -0
- package/apps/viewer/src/opl-worklet.d.ts +1 -0
- package/apps/viewer/src/opl-worklet.js +47 -0
- package/apps/viewer/src/opl-worklet.ts +55 -0
- package/apps/viewer/src/overlay.d.ts +31 -0
- package/apps/viewer/src/overlay.js +103 -0
- package/apps/viewer/src/overlay.ts +123 -0
- package/apps/viewer/src/play.css +14 -0
- package/apps/viewer/src/play.d.ts +1 -0
- package/apps/viewer/src/play.js +179 -0
- package/apps/viewer/src/play.ts +177 -0
- package/apps/viewer/src/saves.d.ts +10 -0
- package/apps/viewer/src/saves.js +50 -0
- package/apps/viewer/src/saves.ts +51 -0
- package/apps/viewer/src/session.d.ts +91 -0
- package/apps/viewer/src/session.js +301 -0
- package/apps/viewer/src/session.ts +321 -0
- package/apps/viewer/src/sound-preview.d.ts +7 -0
- package/apps/viewer/src/sound-preview.js +214 -0
- package/apps/viewer/src/sound-preview.ts +228 -0
- package/apps/viewer/src/style.css +64 -0
- package/apps/viewer/vite.config.ts +25 -0
- package/bin/sci-ts.mjs +9 -0
- package/lib/994.sc +49 -0
- package/lib/995.sc +53 -0
- package/lib/996.sc +299 -0
- package/lib/997.sc +198 -0
- package/lib/998.sc +338 -0
- package/lib/999.sc +189 -0
- package/lib/resources.d.ts +2 -0
- package/lib/resources.js +84 -0
- package/lib/resources.ts +88 -0
- package/lib/system.sh +62 -0
- package/package.json +75 -4
- package/packages/content/room.schema.json +267 -0
- package/packages/content/src/index.d.ts +3 -0
- package/packages/content/src/index.js +3 -0
- package/packages/content/src/index.ts +3 -0
- package/packages/content/src/room.d.ts +111 -0
- package/packages/content/src/room.js +886 -0
- package/packages/content/src/room.ts +1011 -0
- package/packages/content/src/target.d.ts +80 -0
- package/packages/content/src/target.js +1 -0
- package/packages/content/src/target.ts +60 -0
- package/packages/content/src/yarn.d.ts +90 -0
- package/packages/content/src/yarn.js +214 -0
- package/packages/content/src/yarn.ts +261 -0
- package/packages/sci/src/analysis/game.d.ts +90 -0
- package/packages/sci/src/analysis/game.js +221 -0
- package/packages/sci/src/analysis/game.ts +305 -0
- package/packages/sci/src/analysis/runtime.d.ts +58 -0
- package/packages/sci/src/analysis/runtime.js +133 -0
- package/packages/sci/src/analysis/runtime.ts +163 -0
- package/packages/sci/src/audio/adlib.d.ts +73 -0
- package/packages/sci/src/audio/adlib.js +280 -0
- package/packages/sci/src/audio/adlib.ts +329 -0
- package/packages/sci/src/audio/index.d.ts +43 -0
- package/packages/sci/src/audio/index.js +99 -0
- package/packages/sci/src/audio/index.ts +122 -0
- package/packages/sci/src/audio/midi.d.ts +92 -0
- package/packages/sci/src/audio/midi.js +242 -0
- package/packages/sci/src/audio/midi.ts +262 -0
- package/packages/sci/src/audio/opl2.d.ts +74 -0
- package/packages/sci/src/audio/opl2.js +283 -0
- package/packages/sci/src/audio/opl2.ts +283 -0
- package/packages/sci/src/audio/sol.d.ts +32 -0
- package/packages/sci/src/audio/sol.js +81 -0
- package/packages/sci/src/audio/sol.ts +100 -0
- package/packages/sci/src/audio/write.d.ts +35 -0
- package/packages/sci/src/audio/write.js +91 -0
- package/packages/sci/src/audio/write.ts +108 -0
- package/packages/sci/src/gfx/cel.d.ts +36 -0
- package/packages/sci/src/gfx/cel.js +82 -0
- package/packages/sci/src/gfx/cel.ts +110 -0
- package/packages/sci/src/gfx/palette.d.ts +24 -0
- package/packages/sci/src/gfx/palette.js +49 -0
- package/packages/sci/src/gfx/palette.ts +56 -0
- package/packages/sci/src/gfx/pic-file.d.ts +46 -0
- package/packages/sci/src/gfx/pic-file.js +114 -0
- package/packages/sci/src/gfx/pic-file.ts +155 -0
- package/packages/sci/src/gfx/pic.d.ts +27 -0
- package/packages/sci/src/gfx/pic.js +34 -0
- package/packages/sci/src/gfx/pic.ts +53 -0
- package/packages/sci/src/gfx/quantize.d.ts +42 -0
- package/packages/sci/src/gfx/quantize.js +199 -0
- package/packages/sci/src/gfx/quantize.ts +218 -0
- package/packages/sci/src/gfx/view-file.d.ts +62 -0
- package/packages/sci/src/gfx/view-file.js +182 -0
- package/packages/sci/src/gfx/view-file.ts +233 -0
- package/packages/sci/src/gfx/view.d.ts +28 -0
- package/packages/sci/src/gfx/view.js +45 -0
- package/packages/sci/src/gfx/view.ts +62 -0
- package/packages/sci/src/index.d.ts +38 -0
- package/packages/sci/src/index.js +38 -0
- package/packages/sci/src/index.ts +38 -0
- package/packages/sci/src/motion/pathfinding.d.ts +40 -0
- package/packages/sci/src/motion/pathfinding.js +140 -0
- package/packages/sci/src/motion/pathfinding.ts +147 -0
- package/packages/sci/src/resource/archive.d.ts +16 -0
- package/packages/sci/src/resource/archive.js +51 -0
- package/packages/sci/src/resource/archive.ts +56 -0
- package/packages/sci/src/resource/lzs.d.ts +12 -0
- package/packages/sci/src/resource/lzs.js +64 -0
- package/packages/sci/src/resource/lzs.ts +66 -0
- package/packages/sci/src/resource/manager.d.ts +45 -0
- package/packages/sci/src/resource/manager.js +158 -0
- package/packages/sci/src/resource/manager.ts +184 -0
- package/packages/sci/src/resource/map.d.ts +18 -0
- package/packages/sci/src/resource/map.js +34 -0
- package/packages/sci/src/resource/map.ts +44 -0
- package/packages/sci/src/resource/types.d.ts +37 -0
- package/packages/sci/src/resource/types.js +45 -0
- package/packages/sci/src/resource/types.ts +57 -0
- package/packages/sci/src/script/assembly.d.ts +67 -0
- package/packages/sci/src/script/assembly.js +748 -0
- package/packages/sci/src/script/assembly.ts +714 -0
- package/packages/sci/src/script/compiler.d.ts +75 -0
- package/packages/sci/src/script/compiler.js +993 -0
- package/packages/sci/src/script/compiler.ts +1040 -0
- package/packages/sci/src/script/disasm.d.ts +81 -0
- package/packages/sci/src/script/disasm.js +318 -0
- package/packages/sci/src/script/disasm.ts +323 -0
- package/packages/sci/src/script/kernel-names.d.ts +6 -0
- package/packages/sci/src/script/kernel-names.js +165 -0
- package/packages/sci/src/script/kernel-names.ts +165 -0
- package/packages/sci/src/script/opcodes.d.ts +22 -0
- package/packages/sci/src/script/opcodes.js +45 -0
- package/packages/sci/src/script/opcodes.ts +72 -0
- package/packages/sci/src/script/script.d.ts +56 -0
- package/packages/sci/src/script/script.js +63 -0
- package/packages/sci/src/script/script.ts +111 -0
- package/packages/sci/src/script/sexpr.d.ts +45 -0
- package/packages/sci/src/script/sexpr.js +151 -0
- package/packages/sci/src/script/sexpr.ts +139 -0
- package/packages/sci/src/script/vocab.d.ts +15 -0
- package/packages/sci/src/script/vocab.js +44 -0
- package/packages/sci/src/script/vocab.ts +48 -0
- package/packages/sci/src/script/world.d.ts +25 -0
- package/packages/sci/src/script/world.js +65 -0
- package/packages/sci/src/script/world.ts +70 -0
- package/packages/sci/src/text/font.d.ts +37 -0
- package/packages/sci/src/text/font.js +103 -0
- package/packages/sci/src/text/font.ts +125 -0
- package/packages/sci/src/text/message.d.ts +69 -0
- package/packages/sci/src/text/message.js +171 -0
- package/packages/sci/src/text/message.ts +205 -0
- package/packages/sci/src/vm/kernels/arrays.d.ts +19 -0
- package/packages/sci/src/vm/kernels/arrays.js +182 -0
- package/packages/sci/src/vm/kernels/arrays.ts +164 -0
- package/packages/sci/src/vm/kernels/audio.d.ts +70 -0
- package/packages/sci/src/vm/kernels/audio.js +347 -0
- package/packages/sci/src/vm/kernels/audio.ts +349 -0
- package/packages/sci/src/vm/kernels/core.d.ts +5 -0
- package/packages/sci/src/vm/kernels/core.js +87 -0
- package/packages/sci/src/vm/kernels/core.ts +91 -0
- package/packages/sci/src/vm/kernels/graphics.d.ts +141 -0
- package/packages/sci/src/vm/kernels/graphics.js +467 -0
- package/packages/sci/src/vm/kernels/graphics.ts +502 -0
- package/packages/sci/src/vm/kernels/index.d.ts +8 -0
- package/packages/sci/src/vm/kernels/index.js +29 -0
- package/packages/sci/src/vm/kernels/index.ts +32 -0
- package/packages/sci/src/vm/kernels/lists.d.ts +6 -0
- package/packages/sci/src/vm/kernels/lists.js +127 -0
- package/packages/sci/src/vm/kernels/lists.ts +111 -0
- package/packages/sci/src/vm/kernels/motion.d.ts +7 -0
- package/packages/sci/src/vm/kernels/motion.js +204 -0
- package/packages/sci/src/vm/kernels/motion.ts +194 -0
- package/packages/sci/src/vm/kernels/palette.d.ts +82 -0
- package/packages/sci/src/vm/kernels/palette.js +292 -0
- package/packages/sci/src/vm/kernels/palette.ts +303 -0
- package/packages/sci/src/vm/kernels/saves.d.ts +45 -0
- package/packages/sci/src/vm/kernels/saves.js +123 -0
- package/packages/sci/src/vm/kernels/saves.ts +149 -0
- package/packages/sci/src/vm/kernels/system.d.ts +62 -0
- package/packages/sci/src/vm/kernels/system.js +110 -0
- package/packages/sci/src/vm/kernels/system.ts +128 -0
- package/packages/sci/src/vm/kernels/text.d.ts +13 -0
- package/packages/sci/src/vm/kernels/text.js +338 -0
- package/packages/sci/src/vm/kernels/text.ts +328 -0
- package/packages/sci/src/vm/memory.d.ts +138 -0
- package/packages/sci/src/vm/memory.js +168 -0
- package/packages/sci/src/vm/memory.ts +266 -0
- package/packages/sci/src/vm/replace.d.ts +11 -0
- package/packages/sci/src/vm/replace.js +14 -0
- package/packages/sci/src/vm/replace.ts +16 -0
- package/packages/sci/src/vm/savegame.d.ts +89 -0
- package/packages/sci/src/vm/savegame.js +161 -0
- package/packages/sci/src/vm/savegame.ts +188 -0
- package/packages/sci/src/vm/value.d.ts +17 -0
- package/packages/sci/src/vm/value.js +12 -0
- package/packages/sci/src/vm/value.ts +25 -0
- package/packages/sci/src/vm/vm.d.ts +155 -0
- package/packages/sci/src/vm/vm.js +1053 -0
- package/packages/sci/src/vm/vm.ts +914 -0
- package/tools/adlib-render.d.ts +1 -0
- package/tools/adlib-render.js +49 -0
- package/tools/adlib-render.ts +42 -0
- package/tools/analyze.d.ts +1 -0
- package/tools/analyze.js +25 -0
- package/tools/analyze.ts +25 -0
- package/tools/art/build.d.ts +34 -0
- package/tools/art/build.js +175 -0
- package/tools/art/build.ts +153 -0
- package/tools/art.d.ts +1 -0
- package/tools/art.js +31 -0
- package/tools/art.ts +29 -0
- package/tools/asm.d.ts +1 -0
- package/tools/asm.js +61 -0
- package/tools/asm.ts +55 -0
- package/tools/cli.d.ts +1 -0
- package/tools/cli.js +99 -0
- package/tools/cli.ts +100 -0
- package/tools/editor-server.d.ts +29 -0
- package/tools/editor-server.js +162 -0
- package/tools/editor-server.ts +176 -0
- package/tools/explore.d.ts +1 -0
- package/tools/explore.js +305 -0
- package/tools/explore.ts +250 -0
- package/tools/game/audio.d.ts +19 -0
- package/tools/game/audio.js +143 -0
- package/tools/game/audio.ts +130 -0
- package/tools/game/build.d.ts +46 -0
- package/tools/game/build.js +259 -0
- package/tools/game/build.ts +283 -0
- package/tools/game/defaults.d.ts +26 -0
- package/tools/game/defaults.js +82 -0
- package/tools/game/defaults.ts +83 -0
- package/tools/game/font.d.ts +3 -0
- package/tools/game/font.js +124 -0
- package/tools/game/font.ts +126 -0
- package/tools/game/kit.d.ts +7 -0
- package/tools/game/kit.js +7 -0
- package/tools/game/kit.ts +10 -0
- package/tools/game/new.d.ts +8 -0
- package/tools/game/new.js +39 -0
- package/tools/game/new.ts +38 -0
- package/tools/game/target.d.ts +7 -0
- package/tools/game/target.js +41 -0
- package/tools/game/target.ts +42 -0
- package/tools/game/template/resources.ts.tmpl +43 -0
- package/tools/game/template/rooms/1.room.yaml +9 -0
- package/tools/game/template/rooms/1.yarn +17 -0
- package/tools/game/template/scripts/0.sc +24 -0
- package/tools/game.d.ts +1 -0
- package/tools/game.js +77 -0
- package/tools/game.ts +70 -0
- package/tools/mod-build.d.ts +28 -0
- package/tools/mod-build.js +173 -0
- package/tools/mod-build.ts +179 -0
- package/tools/mod-target.d.ts +16 -0
- package/tools/mod-target.js +26 -0
- package/tools/mod-target.ts +32 -0
- package/tools/mod.d.ts +1 -0
- package/tools/mod.js +47 -0
- package/tools/mod.ts +45 -0
- package/tools/msg.d.ts +1 -0
- package/tools/msg.js +40 -0
- package/tools/msg.ts +36 -0
- package/tools/node-files.d.ts +3 -0
- package/tools/node-files.js +34 -0
- package/tools/node-files.ts +33 -0
- package/tools/patches.d.ts +3 -0
- package/tools/patches.js +13 -0
- package/tools/patches.ts +13 -0
- package/tools/pic-build.d.ts +9 -0
- package/tools/pic-build.js +26 -0
- package/tools/pic-build.ts +27 -0
- package/tools/pic.d.ts +1 -0
- package/tools/pic.js +64 -0
- package/tools/pic.ts +61 -0
- package/tools/play.d.ts +1 -0
- package/tools/play.js +54 -0
- package/tools/play.ts +54 -0
- package/tools/png.d.ts +16 -0
- package/tools/png.js +124 -0
- package/tools/png.ts +123 -0
- package/tools/res.d.ts +1 -0
- package/tools/res.js +48 -0
- package/tools/res.ts +50 -0
- package/tools/scenario.d.ts +1 -0
- package/tools/scenario.js +74 -0
- package/tools/scenario.ts +80 -0
- package/tools/site/build.d.ts +8 -0
- package/tools/site/build.js +110 -0
- package/tools/site/build.ts +116 -0
- package/tools/site/markdown.d.ts +11 -0
- package/tools/site/markdown.js +88 -0
- package/tools/site/markdown.ts +84 -0
- package/tools/view-build.d.ts +34 -0
- package/tools/view-build.js +47 -0
- package/tools/view-build.ts +71 -0
- package/tools/view.d.ts +1 -0
- package/tools/view.js +54 -0
- 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;
|