romdevtools 0.71.1 → 0.84.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/AGENTS.md +19 -16
- package/CHANGELOG.md +184 -0
- package/README.md +5 -5
- package/examples/README.md +1 -0
- package/examples/gametank/templates/gt_draw.h +91 -0
- package/examples/gametank/templates/gt_hud.h +89 -0
- package/examples/gametank/templates/gt_palette.h +59 -0
- package/examples/gametank/templates/gt_sound.h +90 -0
- package/examples/gametank/templates/gt_sprites.h +53 -0
- package/examples/gametank/templates/platformer.c +318 -0
- package/examples/gametank/templates/puzzle.c +295 -0
- package/examples/gametank/templates/racing.c +139 -0
- package/examples/gametank/templates/shmup.c +277 -0
- package/examples/gametank/templates/sports.c +119 -0
- package/package.json +24 -22
- package/src/cores/capabilities.js +24 -0
- package/src/cores/registry.js +7 -1
- package/src/host/LibretroHost.js +90 -17
- package/src/host/callbacks.js +20 -2
- package/src/host/cpu-state.js +17 -0
- package/src/host/gametank-acp-state.js +53 -0
- package/src/http/skill-doc.js +10 -6
- package/src/mcp/tools/audio.js +1 -1
- package/src/mcp/tools/cart-parts.js +76 -0
- package/src/mcp/tools/lifecycle.js +1 -1
- package/src/mcp/tools/platform-tools.js +9 -1
- package/src/platforms/gametank/lib/gt/audio/acp_image.bin +0 -0
- package/src/platforms/gametank/lib/gt/audio/audio_coprocessor.c +55 -0
- package/src/platforms/gametank/lib/gt/audio/audio_coprocessor.h +34 -0
- package/src/platforms/gametank/lib/gt/audio/audio_fw.asm +223 -0
- package/src/platforms/gametank/lib/gt/audio/audio_fw_blob.s +8 -0
- package/src/platforms/gametank/lib/gt/audio/instruments.c +79 -0
- package/src/platforms/gametank/lib/gt/audio/instruments.h +25 -0
- package/src/platforms/gametank/lib/gt/audio/music.c +370 -0
- package/src/platforms/gametank/lib/gt/audio/music.h +35 -0
- package/src/platforms/gametank/lib/gt/audio/note_numbers.h +132 -0
- package/src/platforms/gametank/lib/gt/audio/scaled_sines.raw +0 -0
- package/src/platforms/gametank/lib/gt/audio/sine_256_-63_63.bin +0 -0
- package/src/platforms/gametank/lib/gt/banking.c +29 -0
- package/src/platforms/gametank/lib/gt/banking.h +8 -0
- package/src/platforms/gametank/lib/gt/banking2.s +41 -0
- package/src/platforms/gametank/lib/gt/crt0.s +102 -0
- package/src/platforms/gametank/lib/gt/draw_logo.s +55 -0
- package/src/platforms/gametank/lib/gt/feature/persist/persist.c +103 -0
- package/src/platforms/gametank/lib/gt/feature/persist/persist.h +13 -0
- package/src/platforms/gametank/lib/gt/feature/random/random.c +40 -0
- package/src/platforms/gametank/lib/gt/feature/random/random.h +18 -0
- package/src/platforms/gametank/lib/gt/feature/text/text.c +111 -0
- package/src/platforms/gametank/lib/gt/feature/text/text.h +25 -0
- package/src/platforms/gametank/lib/gt/gametank.c +12 -0
- package/src/platforms/gametank/lib/gt/gametank.h +80 -0
- package/src/platforms/gametank/lib/gt/gametank_logo.inc +393 -0
- package/src/platforms/gametank/lib/gt/gen/assets/assets_index.h +9 -0
- package/src/platforms/gametank/lib/gt/gen/assets/sdk_default.h +5 -0
- package/src/platforms/gametank/lib/gt/gen/bank_nums.h +11 -0
- package/src/platforms/gametank/lib/gt/gen/modules_enabled.h +4 -0
- package/src/platforms/gametank/lib/gt/gen/modules_enabled.inc +4 -0
- package/src/platforms/gametank/lib/gt/gfx/draw_direct.c +132 -0
- package/src/platforms/gametank/lib/gt/gfx/draw_direct.h +75 -0
- package/src/platforms/gametank/lib/gt/gfx/draw_queue.c +177 -0
- package/src/platforms/gametank/lib/gt/gfx/draw_queue.h +35 -0
- package/src/platforms/gametank/lib/gt/gfx/draw_util.s +157 -0
- package/src/platforms/gametank/lib/gt/gfx/gfx_sys.c +43 -0
- package/src/platforms/gametank/lib/gt/gfx/gfx_sys.h +46 -0
- package/src/platforms/gametank/lib/gt/gfx/sprites.c +216 -0
- package/src/platforms/gametank/lib/gt/gfx/sprites.h +41 -0
- package/src/platforms/gametank/lib/gt/init.c +9 -0
- package/src/platforms/gametank/lib/gt/input.c +26 -0
- package/src/platforms/gametank/lib/gt/input.h +20 -0
- package/src/platforms/gametank/lib/gt/interrupt.s +67 -0
- package/src/platforms/gametank/lib/gt/vectors.s +14 -0
- package/src/platforms/gametank/lib/gt/wait.s +53 -0
- package/src/playtest/playtest.js +174 -26
- package/src/toolchains/arm-none-eabi-gcc/gcc.js +39 -188
- package/src/toolchains/asar/asar.js +10 -15
- package/src/toolchains/cc65/cc65.js +82 -92
- package/src/toolchains/cc65/da65.js +12 -17
- package/src/toolchains/cc65/preset-resolver.js +13 -2
- package/src/toolchains/cc65/presets/gametank/gametank.h +80 -0
- package/src/toolchains/cc65/presets/gametank/sdk.cfg +32 -0
- package/src/toolchains/cc65/presets/gametank/sdk.crt0.s +61 -0
- package/src/toolchains/cc65/presets/gametank/sdk.vectors.s +11 -0
- package/src/toolchains/cc65/presets/gametank/single-bank.cfg +28 -0
- package/src/toolchains/cc65/presets/gametank/single-bank.crt0.s +71 -0
- package/src/toolchains/cc65/presets/gametank/single-bank.vectors.s +12 -0
- package/src/toolchains/common/c-build.js +109 -0
- package/src/toolchains/common/gcc-toolchain.js +164 -0
- package/src/toolchains/common/wasm-tool.js +101 -0
- package/src/toolchains/dasm/dasm.js +12 -18
- package/src/toolchains/gba-c/gba-c.js +253 -305
- package/src/toolchains/genesis-c/genesis-c.js +196 -225
- package/src/toolchains/index.js +58 -4
- package/src/toolchains/m68k-elf-gcc/gcc.js +39 -202
- package/src/toolchains/mips-c/mips-c.js +68 -78
- package/src/toolchains/mips-elf-gcc/gcc.js +55 -118
- package/src/toolchains/rgbds/rgbds.js +7 -19
- package/src/toolchains/sdcc/sdcc.js +35 -26
- package/src/toolchains/sh-c/sh-c.js +44 -52
- package/src/toolchains/sh-elf-gcc/gcc.js +55 -110
- package/src/toolchains/sjasm/sjasm.js +10 -14
- package/src/toolchains/snes-c/snes-c.js +125 -150
- package/src/toolchains/tcc816/tcc816.js +10 -15
- package/src/toolchains/vasm68k/vasm68k.js +11 -16
- package/src/toolchains/wladx/wladx.js +5 -17
- package/src/toolchains/z80/binutils.js +5 -11
|
@@ -8,32 +8,20 @@
|
|
|
8
8
|
// crash? } in addition to tool-specific fields.
|
|
9
9
|
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
|
-
import { existsSync } from "node:fs";
|
|
12
11
|
import path from "node:path";
|
|
13
12
|
|
|
14
13
|
import { runIsolated, textFile, binaryFile, getOutputBytes } from "../_worker/run.js";
|
|
14
|
+
import { makeGlueResolver } from "../common/wasm-tool.js";
|
|
15
15
|
|
|
16
16
|
const __filename = fileURLToPath(import.meta.url);
|
|
17
17
|
const __dirname = path.dirname(__filename);
|
|
18
18
|
|
|
19
|
-
// RGBDS's WASM ships in romdev-toolchain-rgbds. Resolve each tool's glue
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const p = path.join(path.dirname(fileURLToPath(u)), "wasm", file);
|
|
26
|
-
if (existsSync(p)) return p;
|
|
27
|
-
} catch { /* package not resolvable — fall through to local */ }
|
|
28
|
-
const local = path.join(__dirname, "wasm", file);
|
|
29
|
-
if (existsSync(local)) return local;
|
|
30
|
-
throw new Error(`RGBDS WASM (${file}) not found — install romdev-toolchain-rgbds`);
|
|
31
|
-
}
|
|
32
|
-
// Lazy + memoized per tool: resolve (and possibly throw "not installed") only
|
|
33
|
-
// on the first GB/GBC asm build that uses each tool, not at module load — so
|
|
34
|
-
// booting the server never touches this package unless RGBDS is actually used.
|
|
35
|
-
const _glue = {};
|
|
36
|
-
const rgbdsGlue = (file) => (_glue[file] ??= resolveRgbdsGlue(file));
|
|
19
|
+
// RGBDS's WASM ships in romdev-toolchain-rgbds. Resolve each tool's glue from
|
|
20
|
+
// that package; fall back to a local copy under src/ if present (transition /
|
|
21
|
+
// dev). Lazy + memoized per tool: resolve (and possibly throw "not installed")
|
|
22
|
+
// only on the first GB/GBC asm build that uses each tool, not at module load —
|
|
23
|
+
// so booting the server never touches this package unless RGBDS is actually used.
|
|
24
|
+
const rgbdsGlue = makeGlueResolver({ pkg: "romdev-toolchain-rgbds", localDir: __dirname, label: "RGBDS" });
|
|
37
25
|
|
|
38
26
|
/**
|
|
39
27
|
* Run rgbasm on a source program.
|
|
@@ -13,10 +13,17 @@
|
|
|
13
13
|
// share/sdcc/{include,lib/<port>} is mounted at /share/sdcc/ at call time.
|
|
14
14
|
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
|
-
import { existsSync } from "node:fs";
|
|
17
16
|
import path from "node:path";
|
|
18
17
|
import { readFile } from "node:fs/promises";
|
|
19
18
|
|
|
19
|
+
import { resolveToolBaseDir } from "../common/wasm-tool.js";
|
|
20
|
+
// sdcc uses CBuild ONLY as the log accumulator (cb.log) — every one of its stages
|
|
21
|
+
// has custom failure handling (the `[buildZ80C] FAILED on TU` context line +
|
|
22
|
+
// failedTU/compiledOK fields, and sdld's raw-exitCode return) that doesn't fit the
|
|
23
|
+
// generic cb.stage(throw-on-fail) shape, so they stay inline. Same partial-fit call
|
|
24
|
+
// the snes-c migration made for its SDK-runtime sites.
|
|
25
|
+
import { CBuild } from "../common/c-build.js";
|
|
26
|
+
|
|
20
27
|
const __filename = fileURLToPath(import.meta.url);
|
|
21
28
|
const __dirname = path.dirname(__filename);
|
|
22
29
|
|
|
@@ -25,21 +32,19 @@ const __dirname = path.dirname(__filename);
|
|
|
25
32
|
// back to a local copy under src/ if present (transition / dev). The package
|
|
26
33
|
// is a hard dep of romdev. The share/ files are mounted into MEMFS at call
|
|
27
34
|
// time. (Mirrors the cc65 resolver.)
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
const u = import.meta.resolve("romdev-toolchain-sdcc");
|
|
31
|
-
const dir = path.dirname(fileURLToPath(u));
|
|
32
|
-
if (existsSync(path.join(dir, "wasm", "sdcc.js"))) return dir;
|
|
33
|
-
} catch { /* package not resolvable — fall through to local */ }
|
|
34
|
-
if (existsSync(path.join(__dirname, "wasm", "sdcc.js"))) return __dirname;
|
|
35
|
-
throw new Error("SDCC WASM not found — install romdev-toolchain-sdcc");
|
|
36
|
-
}
|
|
35
|
+
//
|
|
37
36
|
// Lazy + memoized: resolve (and possibly throw "not installed") only on the
|
|
38
37
|
// first SDCC build (GB/GBC/SMS/GG C), not at module load — so booting the
|
|
39
38
|
// server never touches this package unless SDCC is actually used. Resolve the
|
|
40
39
|
// base dir once; derive each tool glue + the share dir from it on demand.
|
|
41
40
|
let _sdccBase;
|
|
42
|
-
const sdccBase = () =>
|
|
41
|
+
const sdccBase = () =>
|
|
42
|
+
(_sdccBase ??= resolveToolBaseDir({
|
|
43
|
+
pkg: "romdev-toolchain-sdcc",
|
|
44
|
+
sentinel: "wasm/sdcc.js",
|
|
45
|
+
localDir: __dirname,
|
|
46
|
+
label: "SDCC",
|
|
47
|
+
}));
|
|
43
48
|
const sdccGlue = (file) => path.join(sdccBase(), "wasm", file);
|
|
44
49
|
const shareDir = () => path.join(sdccBase(), "share", "sdcc");
|
|
45
50
|
|
|
@@ -363,7 +368,7 @@ export function ihxToBin(ihx, size, fill = 0xFF) {
|
|
|
363
368
|
*/
|
|
364
369
|
export async function buildZ80C(args) {
|
|
365
370
|
const sources = args.sources ?? { "main.c": args.source };
|
|
366
|
-
|
|
371
|
+
const cb = new CBuild();
|
|
367
372
|
/** @type {Record<string, string>} */
|
|
368
373
|
const objects = {};
|
|
369
374
|
// SM83 (Game Boy) uses sdasgb instead of sdasz80 — different instruction set.
|
|
@@ -379,10 +384,10 @@ export async function buildZ80C(args) {
|
|
|
379
384
|
const asmRun = isSm83 ? runSdasgb : runSdasz80;
|
|
380
385
|
const asmLabel = isSm83 ? "sdasgb" : "sdasz80";
|
|
381
386
|
const r = await asmRun({ source: src });
|
|
382
|
-
log += `--- ${asmLabel} (${name}) ---\n` + r.log + "\n";
|
|
387
|
+
cb.log += `--- ${asmLabel} (${name}) ---\n` + r.log + "\n";
|
|
383
388
|
if (r.exitCode !== 0 || !r.rel) {
|
|
384
|
-
log += `\n[buildZ80C] FAILED on TU '${name}' (${asmLabel}). Successfully compiled before this: ${compiledOK.length === 0 ? "(none)" : compiledOK.join(", ")}\n`;
|
|
385
|
-
return { binary: null, log, exitCode: r.exitCode || 1, stage: asmLabel, failedTU: name, compiledOK: [...compiledOK] };
|
|
389
|
+
cb.log += `\n[buildZ80C] FAILED on TU '${name}' (${asmLabel}). Successfully compiled before this: ${compiledOK.length === 0 ? "(none)" : compiledOK.join(", ")}\n`;
|
|
390
|
+
return { binary: null, log: cb.log, exitCode: r.exitCode || 1, stage: asmLabel, failedTU: name, compiledOK: [...compiledOK] };
|
|
386
391
|
}
|
|
387
392
|
compiledOK.push(name);
|
|
388
393
|
objects[name.replace(/\.(s|asm)$/i, ".rel")] = r.rel;
|
|
@@ -393,10 +398,10 @@ export async function buildZ80C(args) {
|
|
|
393
398
|
port: args.port,
|
|
394
399
|
headers: args.headers,
|
|
395
400
|
});
|
|
396
|
-
log += `--- sdcc (${name}) ---\n` + r.log + "\n";
|
|
401
|
+
cb.log += `--- sdcc (${name}) ---\n` + r.log + "\n";
|
|
397
402
|
if (r.exitCode !== 0 || !r.rel) {
|
|
398
|
-
log += `\n[buildZ80C] FAILED on TU '${name}' (sdcc). Successfully compiled before this: ${compiledOK.length === 0 ? "(none)" : compiledOK.join(", ")}\n`;
|
|
399
|
-
return { binary: null, log, exitCode: r.exitCode || 1, stage: "sdcc", failedTU: name, compiledOK: [...compiledOK] };
|
|
403
|
+
cb.log += `\n[buildZ80C] FAILED on TU '${name}' (sdcc). Successfully compiled before this: ${compiledOK.length === 0 ? "(none)" : compiledOK.join(", ")}\n`;
|
|
404
|
+
return { binary: null, log: cb.log, exitCode: r.exitCode || 1, stage: "sdcc", failedTU: name, compiledOK: [...compiledOK] };
|
|
400
405
|
}
|
|
401
406
|
compiledOK.push(name);
|
|
402
407
|
objects[name.replace(/\.(c|h)$/i, ".rel")] = r.rel;
|
|
@@ -422,16 +427,20 @@ export async function buildZ80C(args) {
|
|
|
422
427
|
let crt0Rel = args.crt0;
|
|
423
428
|
if (crt0Rel && !/^XL[2-4]\b/.test(crt0Rel.trim())) {
|
|
424
429
|
// Looks like .s source — assemble it via the same path we use for
|
|
425
|
-
// user .s sources.
|
|
430
|
+
// user .s sources. (Keeps its custom FAILED-context line, so inline.)
|
|
426
431
|
const crt0Asm = await (isSm83 ? runSdasgb : runSdasz80)({ source: crt0Rel });
|
|
427
|
-
log += `--- ${isSm83 ? "sdasgb" : "sdasz80"} (crt0) ---\n` + crt0Asm.log + "\n";
|
|
432
|
+
cb.log += `--- ${isSm83 ? "sdasgb" : "sdasz80"} (crt0) ---\n` + crt0Asm.log + "\n";
|
|
428
433
|
if (crt0Asm.exitCode !== 0 || !crt0Asm.rel) {
|
|
429
|
-
log += `\n[buildZ80C] FAILED to assemble crt0 source. Successfully compiled before this: ${compiledOK.length === 0 ? "(none)" : compiledOK.join(", ")}\n`;
|
|
430
|
-
return { binary: null, log, exitCode: crt0Asm.exitCode || 1, stage: "sdasgb-crt0" };
|
|
434
|
+
cb.log += `\n[buildZ80C] FAILED to assemble crt0 source. Successfully compiled before this: ${compiledOK.length === 0 ? "(none)" : compiledOK.join(", ")}\n`;
|
|
435
|
+
return { binary: null, log: cb.log, exitCode: crt0Asm.exitCode || 1, stage: "sdasgb-crt0" };
|
|
431
436
|
}
|
|
432
437
|
crt0Rel = crt0Asm.rel;
|
|
433
438
|
}
|
|
434
439
|
|
|
440
|
+
// sdld is a clean stage (no per-TU context) → cb.stage. NOTE: the failure
|
|
441
|
+
// shape here had NO `exitCode || 1` fallback (raw link.exitCode); CBuild uses
|
|
442
|
+
// `exitCode || 1`. link.exitCode is non-zero on a real link failure, so this
|
|
443
|
+
// is equivalent in practice — but to stay byte-identical, handle it inline.
|
|
435
444
|
const link = await runSdld({
|
|
436
445
|
objects,
|
|
437
446
|
port: args.port,
|
|
@@ -440,9 +449,9 @@ export async function buildZ80C(args) {
|
|
|
440
449
|
dataLoc: args.dataLoc,
|
|
441
450
|
crt0: crt0Rel,
|
|
442
451
|
});
|
|
443
|
-
log += "--- sdld ---\n" + link.log;
|
|
452
|
+
cb.log += "--- sdld ---\n" + link.log;
|
|
444
453
|
if (link.exitCode !== 0 || !link.ihx) {
|
|
445
|
-
return { binary: null, log, exitCode: link.exitCode, stage: "sdld" };
|
|
454
|
+
return { binary: null, log: cb.log, exitCode: link.exitCode, stage: "sdld" };
|
|
446
455
|
}
|
|
447
456
|
// `romBase` (e.g. MSX $4000) means the cartridge maps at that address: the
|
|
448
457
|
// ihx writes records at absolute $4000+, so size the buffer to cover them,
|
|
@@ -453,7 +462,7 @@ export async function buildZ80C(args) {
|
|
|
453
462
|
const span = args.romBase + (args.romSize || 0);
|
|
454
463
|
const full = ihxToBin(link.ihx, span);
|
|
455
464
|
const end = args.romSize ? args.romBase + args.romSize : full.length;
|
|
456
|
-
return { binary: full.slice(args.romBase, end), log, exitCode: 0, stage: "done", map: link.map ?? null };
|
|
465
|
+
return { binary: full.slice(args.romBase, end), log: cb.log, exitCode: 0, stage: "done", map: link.map ?? null };
|
|
457
466
|
}
|
|
458
467
|
// If romSize is null/0, size the buffer to the highest address the ihx
|
|
459
468
|
// actually wrote (no padding to a ROM size). Used for tape targets like
|
|
@@ -466,7 +475,7 @@ export async function buildZ80C(args) {
|
|
|
466
475
|
if (!args.romSize && args.codeLoc) {
|
|
467
476
|
trimmed = binary.slice(args.codeLoc);
|
|
468
477
|
}
|
|
469
|
-
return { binary: trimmed, log, exitCode: 0, stage: "done", map: link.map ?? null };
|
|
478
|
+
return { binary: trimmed, log: cb.log, exitCode: 0, stage: "done", map: link.map ?? null };
|
|
470
479
|
}
|
|
471
480
|
|
|
472
481
|
/**
|
|
@@ -16,6 +16,7 @@ import { fileURLToPath } from "node:url";
|
|
|
16
16
|
import path from "node:path";
|
|
17
17
|
|
|
18
18
|
import { runCc1sh, runShAs, runShLd } from "../sh-elf-gcc/gcc.js";
|
|
19
|
+
import { CBuild, BuildError } from "../common/c-build.js";
|
|
19
20
|
|
|
20
21
|
const __filename = fileURLToPath(import.meta.url);
|
|
21
22
|
const __dirname = path.dirname(__filename);
|
|
@@ -45,60 +46,51 @@ export async function buildShC(args) {
|
|
|
45
46
|
const hasOpt = userOpts.some((o) => /^-O/.test(o));
|
|
46
47
|
const cc1Options = [...(hasOpt ? [] : ["-O1"]), ...userOpts, "-ffreestanding", "-fno-builtin", "-Wall"];
|
|
47
48
|
const sources = args.sources ?? (args.source != null ? { "main.c": args.source } : {});
|
|
48
|
-
|
|
49
|
+
const cb = new CBuild();
|
|
50
|
+
const as = (source) => runShAs({ source });
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// raw .s sources too
|
|
64
|
-
for (const sName of Object.keys(sources).filter((n) => /\.(s|asm)$/i.test(n))) {
|
|
65
|
-
const as = await runShAs({ source: sources[sName] });
|
|
66
|
-
log += `--- as (${sName}) ---\n${as.log || "(ok)"}\n`;
|
|
67
|
-
if (as.exitCode !== 0 || !as.object)
|
|
68
|
-
return { ok: false, binary: null, log, exitCode: as.exitCode || 1, stage: `as (${sName})`, ...(as.crash ? { crash: as.crash } : {}) };
|
|
69
|
-
userObjs[sName.replace(/\.(s|asm)$/i, ".o")] = as.object;
|
|
70
|
-
}
|
|
52
|
+
try {
|
|
53
|
+
/** @type {Record<string, Uint8Array>} */
|
|
54
|
+
const userObjs = {};
|
|
55
|
+
for (const cName of Object.keys(sources).filter((n) => /\.c$/i.test(n))) {
|
|
56
|
+
const cc = await cb.stage(`cc1 (${cName})`, () => runCc1sh({ source: sources[cName], headers, options: cc1Options }), (r) => r.asmSource);
|
|
57
|
+
const ao = await cb.stage(`as (${cName})`, () => as(cc.asmSource), (r) => r.object);
|
|
58
|
+
userObjs[cName.replace(/\.c$/i, ".o")] = ao.object;
|
|
59
|
+
}
|
|
60
|
+
// raw .s sources too
|
|
61
|
+
for (const sName of Object.keys(sources).filter((n) => /\.(s|asm)$/i.test(n))) {
|
|
62
|
+
const ao = await cb.stage(`as (${sName})`, () => as(sources[sName]), (r) => r.object);
|
|
63
|
+
userObjs[sName.replace(/\.(s|asm)$/i, ".o")] = ao.object;
|
|
64
|
+
}
|
|
71
65
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
log += `--- as (dc-crt0.s) ---\n${crt0As.log || "(ok)"}\n`;
|
|
76
|
-
if (crt0As.exitCode !== 0 || !crt0As.object)
|
|
77
|
-
return { ok: false, binary: null, log, exitCode: crt0As.exitCode || 1, stage: "as (crt0)", ...(crt0As.crash ? { crash: crt0As.crash } : {}) };
|
|
66
|
+
// crt0 (stack + .bss clear + main()).
|
|
67
|
+
const crt0Src = await readFile(path.join(LIB, "dc-crt0.s"), "utf-8");
|
|
68
|
+
const crt0As = await cb.stage("as (dc-crt0.s)", () => as(crt0Src), (r) => r.object);
|
|
78
69
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
log += `--- ld ---\n${ld.log || "(ok)"}\n`;
|
|
99
|
-
if (ld.exitCode !== 0 || !ld.elf)
|
|
100
|
-
return { ok: false, binary: null, log, exitCode: ld.exitCode || 1, stage: "ld", ...(ld.crash ? { crash: ld.crash } : {}) };
|
|
70
|
+
// link: crt0 + user objects + newlib (libc/libm) + libgcc.
|
|
71
|
+
const linkScript = await readFile(path.join(LIB, "dc.ld"), "utf-8");
|
|
72
|
+
const [libc, libm, libgcc] = await Promise.all([
|
|
73
|
+
readFile(path.join(LIB, "libc.a")),
|
|
74
|
+
readFile(path.join(LIB, "libm.a")),
|
|
75
|
+
readFile(path.join(LIB, "libgcc.a")),
|
|
76
|
+
]);
|
|
77
|
+
const ld = await cb.stage("ld", () => runShLd({
|
|
78
|
+
objects: { "crt0.o": crt0As.object, ...userObjs },
|
|
79
|
+
linkScript,
|
|
80
|
+
archives: {
|
|
81
|
+
"libc.a": new Uint8Array(libc),
|
|
82
|
+
"libm.a": new Uint8Array(libm),
|
|
83
|
+
"libgcc.a": new Uint8Array(libgcc),
|
|
84
|
+
},
|
|
85
|
+
libraries: ["c", "m", "gcc"],
|
|
86
|
+
libraryPaths: ["/work"],
|
|
87
|
+
options: ["--no-warn-rwx-segments"],
|
|
88
|
+
}), (r) => r.elf);
|
|
101
89
|
|
|
102
|
-
|
|
103
|
-
|
|
90
|
+
// The ELF IS the deliverable — reios boots it directly.
|
|
91
|
+
return { ok: true, binary: ld.elf, log: cb.log, exitCode: 0, stage: "done", ...(ld.map ? { symbols: ld.map } : {}) };
|
|
92
|
+
} catch (e) {
|
|
93
|
+
if (e instanceof BuildError) return e.toResult();
|
|
94
|
+
throw e;
|
|
95
|
+
}
|
|
104
96
|
}
|
|
@@ -1,122 +1,67 @@
|
|
|
1
1
|
// sh-elf-gcc — WASM toolchain wrappers for Dreamcast (SH-4) C builds.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// runShObjcopy({elf}) → raw .bin
|
|
3
|
+
// The full pipeline:
|
|
4
|
+
// runCc1sh({source, headers, options}) → SH assembly text (.s)
|
|
5
|
+
// runShAs({source, includes}) → .o ELF object
|
|
6
|
+
// runShLd({objects, linkScript}) → linked .elf (+ map)
|
|
7
|
+
// runShObjcopy({elf}) → raw .bin
|
|
9
8
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
|
|
9
|
+
// 0.81.0: the 4 stages come from the shared makeGccToolchain() factory
|
|
10
|
+
// (common/gcc-toolchain.js); this file is just the SH-4 config + thin re-exports.
|
|
11
|
+
// SH-4 little-endian, single-precision FP only (-m4-single-only at the cc1 level;
|
|
12
|
+
// --isa=sh4 at as; -EL at ld). NOTE: callers should default cc1 to -O1, not -O2 —
|
|
13
|
+
// the sh-elf cc1.wasm has an -O2-only pass that aborts on common control flow; that
|
|
14
|
+
// default lives in the sh-c builder, not here (this wrapper passes options through).
|
|
15
|
+
//
|
|
16
|
+
// WASM glue ships in romdev-toolchain-sh-gcc; resolution is lazy + memoized.
|
|
14
17
|
import { fileURLToPath } from "node:url";
|
|
15
|
-
import { existsSync } from "node:fs";
|
|
16
18
|
import path from "node:path";
|
|
17
19
|
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
-
const __dirname = path.dirname(__filename);
|
|
20
|
+
import { makeGccToolchain } from "../common/gcc-toolchain.js";
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
function resolveShGlue(file) {
|
|
25
|
-
try {
|
|
26
|
-
const u = import.meta.resolve("romdev-toolchain-sh-gcc");
|
|
27
|
-
const p = path.join(path.dirname(fileURLToPath(u)), "wasm", file);
|
|
28
|
-
if (existsSync(p)) return p;
|
|
29
|
-
} catch { /* not resolvable — fall through */ }
|
|
30
|
-
const local = path.join(__dirname, "wasm", file);
|
|
31
|
-
if (existsSync(local)) return local;
|
|
32
|
-
throw new Error(`sh-elf-gcc WASM (${file}) not found — build it with scripts/build-sh-wasm-tools.sh`);
|
|
33
|
-
}
|
|
34
|
-
const _glue = {};
|
|
35
|
-
const shGlue = (file) => (_glue[file] ??= resolveShGlue(file));
|
|
22
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
36
23
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
24
|
+
const { runCc1, runAs, runLd, runObjcopy } = makeGccToolchain({
|
|
25
|
+
pkg: "romdev-toolchain-sh-gcc",
|
|
26
|
+
localDir: __dirname,
|
|
27
|
+
label: "sh-elf-gcc",
|
|
28
|
+
glue: {
|
|
29
|
+
cc1: "cc1.mjs",
|
|
30
|
+
as: "sh-elf-as.mjs",
|
|
31
|
+
ld: "sh-elf-ld.mjs",
|
|
32
|
+
objcopy: "sh-elf-objcopy.mjs",
|
|
33
|
+
},
|
|
34
|
+
cc1Flags: ["-ml", "-m4-single-only"],
|
|
35
|
+
asFlags: ["-little", "--isa=sh4"],
|
|
36
|
+
ldFlags: ["-EL"],
|
|
37
|
+
ldScriptName: "link.ld",
|
|
38
|
+
outputName: "main.bin",
|
|
39
|
+
});
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const argv = [
|
|
48
|
-
...CC1_ARCH,
|
|
49
|
-
"-iquote", "/work", "-I", "/work",
|
|
50
|
-
...options,
|
|
51
|
-
"/work/main.c", "-o", "/work/main.s",
|
|
52
|
-
];
|
|
53
|
-
const r = await runIsolated({
|
|
54
|
-
gluePath: shGlue("cc1.mjs"),
|
|
55
|
-
argv, inputFiles,
|
|
56
|
-
outputFiles: [{ vfsPath: "/work/main.s", encoding: "utf8" }],
|
|
57
|
-
});
|
|
58
|
-
return { log: r.log, exitCode: r.exitCode, asmSource: getOutputText(r, "/work/main.s") || null,
|
|
59
|
-
...(r.crash ? { crash: r.crash, stage: "crash" } : {}) };
|
|
60
|
-
}
|
|
41
|
+
/**
|
|
42
|
+
* Compile a C source to SH assembly via cc1.
|
|
43
|
+
* @param {{source:string, headers?:Record<string,string>, options?:string[]}} args
|
|
44
|
+
* @returns {Promise<{log:string, exitCode:number, asmSource:string|null, crash?:any}>}
|
|
45
|
+
*/
|
|
46
|
+
export const runCc1sh = runCc1;
|
|
61
47
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
for (const [name, content] of Object.entries(includes)) inputFiles.push(textFile("/work/" + name, content));
|
|
69
|
-
for (const [name, bytes] of Object.entries(binaryIncludes)) inputFiles.push(binaryFile("/work/" + name, bytes));
|
|
70
|
-
const argv = [...AS_ARCH, "-I", "/work", ...options, "/work/main.s", "-o", "/work/main.o"];
|
|
71
|
-
const r = await runIsolated({
|
|
72
|
-
gluePath: shGlue("sh-elf-as.mjs"),
|
|
73
|
-
argv, inputFiles,
|
|
74
|
-
outputFiles: [{ vfsPath: "/work/main.o", encoding: "base64" }],
|
|
75
|
-
});
|
|
76
|
-
return { log: r.log, exitCode: r.exitCode, object: getOutputBytes(r, "/work/main.o"),
|
|
77
|
-
...(r.crash ? { crash: r.crash, stage: "crash" } : {}) };
|
|
78
|
-
}
|
|
48
|
+
/**
|
|
49
|
+
* Assemble SH assembly with sh-elf-as.
|
|
50
|
+
* @param {{source:string, includes?:Record<string,string>, binaryIncludes?:Record<string,Uint8Array>, options?:string[]}} args
|
|
51
|
+
* @returns {Promise<{log:string, exitCode:number, object:Uint8Array|null, crash?:any}>}
|
|
52
|
+
*/
|
|
53
|
+
export const runShAs = runAs;
|
|
79
54
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
for (const [name, bytes] of Object.entries(archives)) inputFiles.push(binaryFile("/work/" + name, bytes));
|
|
87
|
-
const argv = [
|
|
88
|
-
"-EL",
|
|
89
|
-
"-T", "/work/link.ld",
|
|
90
|
-
"-o", "/work/main.elf",
|
|
91
|
-
"-Map=/work/main.map",
|
|
92
|
-
...libraryPaths.flatMap((p) => ["-L", p]),
|
|
93
|
-
...Object.keys(objects).map((n) => "/work/" + n),
|
|
94
|
-
...libraries.map((l) => `-l${l}`),
|
|
95
|
-
...options,
|
|
96
|
-
];
|
|
97
|
-
const r = await runIsolated({
|
|
98
|
-
gluePath: shGlue("sh-elf-ld.mjs"),
|
|
99
|
-
argv, inputFiles,
|
|
100
|
-
outputFiles: [
|
|
101
|
-
{ vfsPath: "/work/main.elf", encoding: "base64" },
|
|
102
|
-
{ vfsPath: "/work/main.map", encoding: "utf8" },
|
|
103
|
-
],
|
|
104
|
-
});
|
|
105
|
-
return { log: r.log, exitCode: r.exitCode, elf: getOutputBytes(r, "/work/main.elf"),
|
|
106
|
-
map: getOutputText(r, "/work/main.map") || null,
|
|
107
|
-
...(r.crash ? { crash: r.crash, stage: "crash" } : {}) };
|
|
108
|
-
}
|
|
55
|
+
/**
|
|
56
|
+
* Link SH object files into an ELF executable (+ linker map).
|
|
57
|
+
* @param {{objects:Record<string,Uint8Array>, linkScript:string, libraries?:string[], libraryPaths?:string[], archives?:Record<string,Uint8Array>, options?:string[]}} args
|
|
58
|
+
* @returns {Promise<{log:string, exitCode:number, elf:Uint8Array|null, map:string|null, crash?:any}>}
|
|
59
|
+
*/
|
|
60
|
+
export const runShLd = runLd;
|
|
109
61
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
gluePath: shGlue("sh-elf-objcopy.mjs"),
|
|
117
|
-
argv, inputFiles,
|
|
118
|
-
outputFiles: [{ vfsPath: "/work/main.bin", encoding: "base64" }],
|
|
119
|
-
});
|
|
120
|
-
return { log: r.log, exitCode: r.exitCode, binary: getOutputBytes(r, "/work/main.bin"),
|
|
121
|
-
...(r.crash ? { crash: r.crash, stage: "crash" } : {}) };
|
|
122
|
-
}
|
|
62
|
+
/**
|
|
63
|
+
* Strip an ELF down to a raw .bin.
|
|
64
|
+
* @param {{elf:Uint8Array, options?:string[]}} args
|
|
65
|
+
* @returns {Promise<{log:string, exitCode:number, binary:Uint8Array|null, crash?:any}>}
|
|
66
|
+
*/
|
|
67
|
+
export const runShObjcopy = runObjcopy;
|
|
@@ -5,25 +5,21 @@
|
|
|
5
5
|
// #include that generated .h. Built by scripts/build-sjasm.sh.
|
|
6
6
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
-
import { existsSync } from "node:fs";
|
|
9
8
|
import path from "node:path";
|
|
10
9
|
|
|
10
|
+
import { makeGlueResolver } from "../common/wasm-tool.js";
|
|
11
|
+
|
|
11
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
13
|
const __dirname = path.dirname(__filename);
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} catch { /* package not resolvable — fall through to local */ }
|
|
23
|
-
const local = path.join(__dirname, "wasm", name);
|
|
24
|
-
if (existsSync(local)) return local;
|
|
25
|
-
throw new Error(`sjasm WASM not found: ${name} — install romdev-toolchain-m68k-gcc (or run scripts/build-sjasm.sh)`);
|
|
26
|
-
}
|
|
15
|
+
// sjasm + bintos ship in romdev-toolchain-m68k-gcc (the Genesis toolchain
|
|
16
|
+
// package — same place as the m68k compiler they pair with); a local src/ copy
|
|
17
|
+
// is the dev fallback. Lazy + memoized: resolve only on first use.
|
|
18
|
+
const resolveGlue = makeGlueResolver({
|
|
19
|
+
pkg: "romdev-toolchain-m68k-gcc",
|
|
20
|
+
localDir: __dirname,
|
|
21
|
+
label: "sjasm",
|
|
22
|
+
});
|
|
27
23
|
|
|
28
24
|
let _sjasmFactory, _bintosFactory;
|
|
29
25
|
async function loadSjasm() {
|