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.
Files changed (105) hide show
  1. package/AGENTS.md +19 -16
  2. package/CHANGELOG.md +184 -0
  3. package/README.md +5 -5
  4. package/examples/README.md +1 -0
  5. package/examples/gametank/templates/gt_draw.h +91 -0
  6. package/examples/gametank/templates/gt_hud.h +89 -0
  7. package/examples/gametank/templates/gt_palette.h +59 -0
  8. package/examples/gametank/templates/gt_sound.h +90 -0
  9. package/examples/gametank/templates/gt_sprites.h +53 -0
  10. package/examples/gametank/templates/platformer.c +318 -0
  11. package/examples/gametank/templates/puzzle.c +295 -0
  12. package/examples/gametank/templates/racing.c +139 -0
  13. package/examples/gametank/templates/shmup.c +277 -0
  14. package/examples/gametank/templates/sports.c +119 -0
  15. package/package.json +24 -22
  16. package/src/cores/capabilities.js +24 -0
  17. package/src/cores/registry.js +7 -1
  18. package/src/host/LibretroHost.js +90 -17
  19. package/src/host/callbacks.js +20 -2
  20. package/src/host/cpu-state.js +17 -0
  21. package/src/host/gametank-acp-state.js +53 -0
  22. package/src/http/skill-doc.js +10 -6
  23. package/src/mcp/tools/audio.js +1 -1
  24. package/src/mcp/tools/cart-parts.js +76 -0
  25. package/src/mcp/tools/lifecycle.js +1 -1
  26. package/src/mcp/tools/platform-tools.js +9 -1
  27. package/src/platforms/gametank/lib/gt/audio/acp_image.bin +0 -0
  28. package/src/platforms/gametank/lib/gt/audio/audio_coprocessor.c +55 -0
  29. package/src/platforms/gametank/lib/gt/audio/audio_coprocessor.h +34 -0
  30. package/src/platforms/gametank/lib/gt/audio/audio_fw.asm +223 -0
  31. package/src/platforms/gametank/lib/gt/audio/audio_fw_blob.s +8 -0
  32. package/src/platforms/gametank/lib/gt/audio/instruments.c +79 -0
  33. package/src/platforms/gametank/lib/gt/audio/instruments.h +25 -0
  34. package/src/platforms/gametank/lib/gt/audio/music.c +370 -0
  35. package/src/platforms/gametank/lib/gt/audio/music.h +35 -0
  36. package/src/platforms/gametank/lib/gt/audio/note_numbers.h +132 -0
  37. package/src/platforms/gametank/lib/gt/audio/scaled_sines.raw +0 -0
  38. package/src/platforms/gametank/lib/gt/audio/sine_256_-63_63.bin +0 -0
  39. package/src/platforms/gametank/lib/gt/banking.c +29 -0
  40. package/src/platforms/gametank/lib/gt/banking.h +8 -0
  41. package/src/platforms/gametank/lib/gt/banking2.s +41 -0
  42. package/src/platforms/gametank/lib/gt/crt0.s +102 -0
  43. package/src/platforms/gametank/lib/gt/draw_logo.s +55 -0
  44. package/src/platforms/gametank/lib/gt/feature/persist/persist.c +103 -0
  45. package/src/platforms/gametank/lib/gt/feature/persist/persist.h +13 -0
  46. package/src/platforms/gametank/lib/gt/feature/random/random.c +40 -0
  47. package/src/platforms/gametank/lib/gt/feature/random/random.h +18 -0
  48. package/src/platforms/gametank/lib/gt/feature/text/text.c +111 -0
  49. package/src/platforms/gametank/lib/gt/feature/text/text.h +25 -0
  50. package/src/platforms/gametank/lib/gt/gametank.c +12 -0
  51. package/src/platforms/gametank/lib/gt/gametank.h +80 -0
  52. package/src/platforms/gametank/lib/gt/gametank_logo.inc +393 -0
  53. package/src/platforms/gametank/lib/gt/gen/assets/assets_index.h +9 -0
  54. package/src/platforms/gametank/lib/gt/gen/assets/sdk_default.h +5 -0
  55. package/src/platforms/gametank/lib/gt/gen/bank_nums.h +11 -0
  56. package/src/platforms/gametank/lib/gt/gen/modules_enabled.h +4 -0
  57. package/src/platforms/gametank/lib/gt/gen/modules_enabled.inc +4 -0
  58. package/src/platforms/gametank/lib/gt/gfx/draw_direct.c +132 -0
  59. package/src/platforms/gametank/lib/gt/gfx/draw_direct.h +75 -0
  60. package/src/platforms/gametank/lib/gt/gfx/draw_queue.c +177 -0
  61. package/src/platforms/gametank/lib/gt/gfx/draw_queue.h +35 -0
  62. package/src/platforms/gametank/lib/gt/gfx/draw_util.s +157 -0
  63. package/src/platforms/gametank/lib/gt/gfx/gfx_sys.c +43 -0
  64. package/src/platforms/gametank/lib/gt/gfx/gfx_sys.h +46 -0
  65. package/src/platforms/gametank/lib/gt/gfx/sprites.c +216 -0
  66. package/src/platforms/gametank/lib/gt/gfx/sprites.h +41 -0
  67. package/src/platforms/gametank/lib/gt/init.c +9 -0
  68. package/src/platforms/gametank/lib/gt/input.c +26 -0
  69. package/src/platforms/gametank/lib/gt/input.h +20 -0
  70. package/src/platforms/gametank/lib/gt/interrupt.s +67 -0
  71. package/src/platforms/gametank/lib/gt/vectors.s +14 -0
  72. package/src/platforms/gametank/lib/gt/wait.s +53 -0
  73. package/src/playtest/playtest.js +174 -26
  74. package/src/toolchains/arm-none-eabi-gcc/gcc.js +39 -188
  75. package/src/toolchains/asar/asar.js +10 -15
  76. package/src/toolchains/cc65/cc65.js +82 -92
  77. package/src/toolchains/cc65/da65.js +12 -17
  78. package/src/toolchains/cc65/preset-resolver.js +13 -2
  79. package/src/toolchains/cc65/presets/gametank/gametank.h +80 -0
  80. package/src/toolchains/cc65/presets/gametank/sdk.cfg +32 -0
  81. package/src/toolchains/cc65/presets/gametank/sdk.crt0.s +61 -0
  82. package/src/toolchains/cc65/presets/gametank/sdk.vectors.s +11 -0
  83. package/src/toolchains/cc65/presets/gametank/single-bank.cfg +28 -0
  84. package/src/toolchains/cc65/presets/gametank/single-bank.crt0.s +71 -0
  85. package/src/toolchains/cc65/presets/gametank/single-bank.vectors.s +12 -0
  86. package/src/toolchains/common/c-build.js +109 -0
  87. package/src/toolchains/common/gcc-toolchain.js +164 -0
  88. package/src/toolchains/common/wasm-tool.js +101 -0
  89. package/src/toolchains/dasm/dasm.js +12 -18
  90. package/src/toolchains/gba-c/gba-c.js +253 -305
  91. package/src/toolchains/genesis-c/genesis-c.js +196 -225
  92. package/src/toolchains/index.js +58 -4
  93. package/src/toolchains/m68k-elf-gcc/gcc.js +39 -202
  94. package/src/toolchains/mips-c/mips-c.js +68 -78
  95. package/src/toolchains/mips-elf-gcc/gcc.js +55 -118
  96. package/src/toolchains/rgbds/rgbds.js +7 -19
  97. package/src/toolchains/sdcc/sdcc.js +35 -26
  98. package/src/toolchains/sh-c/sh-c.js +44 -52
  99. package/src/toolchains/sh-elf-gcc/gcc.js +55 -110
  100. package/src/toolchains/sjasm/sjasm.js +10 -14
  101. package/src/toolchains/snes-c/snes-c.js +125 -150
  102. package/src/toolchains/tcc816/tcc816.js +10 -15
  103. package/src/toolchains/vasm68k/vasm68k.js +11 -16
  104. package/src/toolchains/wladx/wladx.js +5 -17
  105. package/src/toolchains/z80/binutils.js +5 -11
@@ -15,6 +15,9 @@ import { fileURLToPath } from "node:url";
15
15
  import { existsSync } from "node:fs";
16
16
  import path from "node:path";
17
17
 
18
+ import { resolveToolBaseDir } from "../common/wasm-tool.js";
19
+ import { CBuild, BuildError } from "../common/c-build.js";
20
+
18
21
  const __filename = fileURLToPath(import.meta.url);
19
22
  const __dirname = path.dirname(__filename);
20
23
 
@@ -23,21 +26,18 @@ const __dirname = path.dirname(__filename);
23
26
  // needs to link a target, so they must come from the same place as the WASM.
24
27
  // Resolve the package's base dir once; fall back to a local copy under src/
25
28
  // if present (transition / dev). The package is a hard dep of romdev.
26
- function resolveCc65BaseDir() {
27
- try {
28
- const u = import.meta.resolve("romdev-toolchain-cc65");
29
- const dir = path.dirname(fileURLToPath(u));
30
- if (existsSync(path.join(dir, "wasm", "cc65.js"))) return dir;
31
- } catch { /* package not resolvable — fall through to local */ }
32
- if (existsSync(path.join(__dirname, "wasm", "cc65.js"))) return __dirname;
33
- throw new Error("cc65 WASM not found — install romdev-toolchain-cc65");
34
- }
35
29
  // Lazy + memoized: resolve (and possibly throw "not installed") only on the
36
30
  // first cc65 build (NES/C64/Atari7800/Lynx), not at module load — so booting
37
31
  // the server never touches this package unless cc65 is actually used. Resolve
38
32
  // the base dir once; derive the wasm + share dirs from it on demand.
39
33
  let _cc65Base;
40
- const cc65Base = () => (_cc65Base ??= resolveCc65BaseDir());
34
+ const cc65Base = () =>
35
+ (_cc65Base ??= resolveToolBaseDir({
36
+ pkg: "romdev-toolchain-cc65",
37
+ sentinel: "wasm/cc65.js",
38
+ localDir: __dirname,
39
+ label: "cc65",
40
+ }));
41
41
 
42
42
  /** True if the cc65 build toolchain WASM (cc65 + ld65, in romdev-toolchain-cc65)
43
43
  * is installed/resolvable, without throwing — for the catalog(status) capability
@@ -45,7 +45,7 @@ const cc65Base = () => (_cc65Base ??= resolveCc65BaseDir());
45
45
  * reflects ld65 (the linker) availability. */
46
46
  export function cc65Available() {
47
47
  try {
48
- const dir = resolveCc65BaseDir();
48
+ const dir = cc65Base();
49
49
  return existsSync(path.join(dir, "wasm", "cc65.js")) && existsSync(path.join(dir, "wasm", "ld65.js"));
50
50
  } catch { return false; }
51
51
  }
@@ -275,63 +275,57 @@ export async function buildC(args) {
275
275
  // and these are pure upside. (NOTE: cc65 errors on an unknown -W name, so this
276
276
  // list is verified valid against the bundled cc65.)
277
277
  const ccWarn = ["-W", "unused-var,unused-func,unused-label,const-comparison,struct-param,pointer-sign"];
278
- const ccOpts = args.debug ? ["-g", ...ccWarn] : [...ccWarn];
279
- const caOpts = args.debug ? ["-g"] : [];
278
+ // ccOptions/caOptions = caller-supplied extra flags (e.g. GameTank's `--cpu 65c02`
279
+ // for the `-t none` path where the CPU isn't implied by a built-in cc65 target).
280
+ const ccOpts = [...(args.debug ? ["-g"] : []), ...ccWarn, ...(args.ccOptions ?? [])];
281
+ const caOpts = [...(args.debug ? ["-g"] : []), ...(args.caOptions ?? [])];
280
282
  const sources = normalizeSources(args, "main.c");
281
283
 
282
- let log = "";
283
- /** @type {Record<string, Uint8Array>} */
284
- const objects = {};
285
- for (const [name, src] of Object.entries(sources)) {
286
- const ext = path.extname(name).toLowerCase();
287
- let asmSource;
288
- let asmName;
289
- if (ext === ".s" || ext === ".asm") {
290
- asmSource = src;
291
- asmName = name;
292
- } else {
293
- const cc = await runCc65({
294
- source: src,
295
- headers: args.headers,
296
- target: args.target,
297
- options: ccOpts,
298
- });
299
- log += `--- cc65 (${name}) ---\n` + cc.log + "\n";
300
- if (cc.exitCode !== 0 || !cc.asmSource) {
301
- return { binary: null, log, exitCode: cc.exitCode || 1, stage: "cc65" };
284
+ const cb = new CBuild();
285
+ try {
286
+ /** @type {Record<string, Uint8Array>} */
287
+ const objects = {};
288
+ for (const [name, src] of Object.entries(sources)) {
289
+ const ext = path.extname(name).toLowerCase();
290
+ let asmSource;
291
+ let asmName;
292
+ if (ext === ".s" || ext === ".asm") {
293
+ asmSource = src;
294
+ asmName = name;
295
+ } else {
296
+ // failure stage is bare "cc65" (no name) but the log header carries the name.
297
+ const cc = await cb.stage("cc65",
298
+ () => runCc65({ source: src, headers: args.headers, target: args.target, options: ccOpts }),
299
+ (r) => r.asmSource, { logName: `cc65 (${name})` });
300
+ asmSource = cc.asmSource;
301
+ asmName = name.replace(/\.(c|h)$/i, ".s");
302
302
  }
303
- asmSource = cc.asmSource;
304
- asmName = name.replace(/\.(c|h)$/i, ".s");
303
+ const ca = await cb.stage("ca65",
304
+ () => runCa65({ source: asmSource, includes: args.asmIncludes, binaryIncludes: args.binaryIncludes, target: args.target, options: caOpts }),
305
+ (r) => r.object, { logName: `ca65 (${asmName})` });
306
+ objects[asmName.replace(/\.s$/, ".o")] = ca.object;
305
307
  }
306
- const ca = await runCa65({
307
- source: asmSource,
308
- includes: args.asmIncludes,
309
- binaryIncludes: args.binaryIncludes,
308
+ // ld65 is NOT throw-on-fail here: it returns its own success/failure inline with
309
+ // dbg + ramUsage, so keep it as a plain call (the cc65 contract has no `ok` field).
310
+ const ld = await runLd65({
311
+ objects,
310
312
  target: args.target,
311
- options: caOpts,
313
+ debug: args.debug,
314
+ linkerConfig: args.linkerConfig,
312
315
  });
313
- log += `--- ca65 (${asmName}) ---\n` + ca.log + "\n";
314
- if (ca.exitCode !== 0 || !ca.object) {
315
- return { binary: null, log, exitCode: ca.exitCode || 1, stage: "ca65" };
316
- }
317
- const objName = asmName.replace(/\.s$/, ".o");
318
- objects[objName] = ca.object;
316
+ cb.log += "--- ld65 ---\n" + ld.log; // exact: no trailing newline (matches pre-refactor)
317
+ return {
318
+ binary: ld.binary,
319
+ dbg: ld.dbg,
320
+ log: cb.log,
321
+ exitCode: ld.exitCode,
322
+ ramUsage: parseRamUsage(ld.map),
323
+ stage: ld.exitCode === 0 ? "done" : "ld65",
324
+ };
325
+ } catch (e) {
326
+ if (e instanceof BuildError) return e.fields(); // cc65 shape: no `ok` field
327
+ throw e;
319
328
  }
320
- const ld = await runLd65({
321
- objects,
322
- target: args.target,
323
- debug: args.debug,
324
- linkerConfig: args.linkerConfig,
325
- });
326
- log += "--- ld65 ---\n" + ld.log;
327
- return {
328
- binary: ld.binary,
329
- dbg: ld.dbg,
330
- log,
331
- exitCode: ld.exitCode,
332
- ramUsage: parseRamUsage(ld.map),
333
- stage: ld.exitCode === 0 ? "done" : "ld65",
334
- };
335
329
  }
336
330
 
337
331
  /**
@@ -351,42 +345,38 @@ export async function buildC(args) {
351
345
  * @param {string} [args.linkerConfig] custom ld65 .cfg
352
346
  */
353
347
  export async function buildAsm(args) {
354
- const caOpts = args.debug ? ["-g"] : [];
348
+ const caOpts = [...(args.debug ? ["-g"] : []), ...(args.caOptions ?? [])];
355
349
  const sources = normalizeSources(args, "main.s");
356
350
 
357
- let log = "";
358
- /** @type {Record<string, Uint8Array>} */
359
- const objects = {};
360
- for (const [name, src] of Object.entries(sources)) {
361
- const ca = await runCa65({
362
- source: src,
363
- includes: args.includes,
364
- binaryIncludes: args.binaryIncludes,
351
+ const cb = new CBuild();
352
+ try {
353
+ /** @type {Record<string, Uint8Array>} */
354
+ const objects = {};
355
+ for (const [name, src] of Object.entries(sources)) {
356
+ const ca = await cb.stage("ca65",
357
+ () => runCa65({ source: src, includes: args.includes, binaryIncludes: args.binaryIncludes, target: args.target, options: caOpts }),
358
+ (r) => r.object, { logName: `ca65 (${name})` });
359
+ objects[name.replace(/\.(s|asm)$/i, ".o")] = ca.object;
360
+ }
361
+ const ld = await runLd65({
362
+ objects,
365
363
  target: args.target,
366
- options: caOpts,
364
+ debug: args.debug,
365
+ linkerConfig: args.linkerConfig,
367
366
  });
368
- log += `--- ca65 (${name}) ---\n` + ca.log + "\n";
369
- if (ca.exitCode !== 0 || !ca.object) {
370
- return { binary: null, log, exitCode: ca.exitCode || 1, stage: "ca65" };
371
- }
372
- const objName = name.replace(/\.(s|asm)$/i, ".o");
373
- objects[objName] = ca.object;
367
+ cb.log += "--- ld65 ---\n" + ld.log;
368
+ return {
369
+ binary: ld.binary,
370
+ dbg: ld.dbg,
371
+ log: cb.log,
372
+ exitCode: ld.exitCode,
373
+ ramUsage: parseRamUsage(ld.map),
374
+ stage: ld.exitCode === 0 ? "done" : "ld65",
375
+ };
376
+ } catch (e) {
377
+ if (e instanceof BuildError) return e.fields(); // cc65 shape: no `ok` field
378
+ throw e;
374
379
  }
375
- const ld = await runLd65({
376
- objects,
377
- target: args.target,
378
- debug: args.debug,
379
- linkerConfig: args.linkerConfig,
380
- });
381
- log += "--- ld65 ---\n" + ld.log;
382
- return {
383
- binary: ld.binary,
384
- dbg: ld.dbg,
385
- log,
386
- exitCode: ld.exitCode,
387
- ramUsage: parseRamUsage(ld.map),
388
- stage: ld.exitCode === 0 ? "done" : "ld65",
389
- };
390
380
  }
391
381
 
392
382
  /**
@@ -3,36 +3,31 @@
3
3
  // Run with --cpu 6502 (default). For SNES/65816 use --cpu 65816.
4
4
 
5
5
  import { fileURLToPath } from "node:url";
6
- import { existsSync } from "node:fs";
7
6
  import path from "node:path";
8
7
 
9
8
  import { runIsolated, textFile, binaryFile } from "../_worker/run.js";
9
+ import { resolveGlueFile } from "../common/wasm-tool.js";
10
10
 
11
11
  const __filename = fileURLToPath(import.meta.url);
12
12
  const __dirname = path.dirname(__filename);
13
13
 
14
- // da65's WASM ships in romdev-toolchain-cc65 (alongside cc65 / ca65 / ld65).
15
- // Resolve from that package; fall back to a local copy under src/ if present
16
- // (transition / dev). The package is a hard dep of romdev.
17
- function resolveDa65Glue() {
18
- try {
19
- const u = import.meta.resolve("romdev-toolchain-cc65");
20
- const p = path.join(path.dirname(fileURLToPath(u)), "wasm", "da65.js");
21
- if (existsSync(p)) return p;
22
- } catch { /* package not resolvable — fall through to local */ }
23
- const local = path.join(__dirname, "wasm", "da65.js");
24
- if (existsSync(local)) return local;
25
- throw new Error("da65 WASM not found — install romdev-toolchain-cc65");
26
- }
27
- // Lazy + memoized: resolve only on the first da65 disassembly, not at boot.
14
+ // da65's WASM ships in romdev-toolchain-cc65 (alongside cc65 / ca65 / ld65); a
15
+ // local src/ copy is the dev fallback. Lazy + memoized: resolve only on the
16
+ // first da65 disassembly, not at boot.
28
17
  let _glue;
29
- const glue = () => (_glue ??= resolveDa65Glue());
18
+ const glue = () =>
19
+ (_glue ??= resolveGlueFile({
20
+ pkg: "romdev-toolchain-cc65",
21
+ file: "da65.js",
22
+ localDir: __dirname,
23
+ label: "da65",
24
+ }));
30
25
 
31
26
  /** True if the da65 WASM (romdev-toolchain-cc65) is installed/resolvable, without
32
27
  * throwing — for capability probes (catalog status) that must not crash when the
33
28
  * toolchain is absent. */
34
29
  export function da65Available() {
35
- try { return !!resolveDa65Glue(); } catch { return false; }
30
+ try { return !!glue(); } catch { return false; }
36
31
  }
37
32
 
38
33
  /**
@@ -40,12 +40,23 @@ export async function resolveLinkerConfig(platform, arg) {
40
40
  );
41
41
  }
42
42
  const supportSources = {};
43
+ // Bundled headers a preset makes available to the C compile (e.g. GameTank's
44
+ // `gametank.h` register defs) — keyed by their REAL name so `#include "gametank.h"`
45
+ // resolves, NOT renamed like the `.s` support sources. A bundled header is named
46
+ // `<name>.h` (no `<preset>.` prefix needed); the `.s`/`.asm` files follow the
47
+ // support-source convention.
48
+ /** @type {Record<string,string>} */
49
+ const headers = {};
43
50
  try {
44
51
  const entries = await readdir(presetDir);
45
52
  const prefix = `${preset}.`;
46
53
  for (const f of entries) {
47
- if (!f.startsWith(prefix) || f === `${preset}.cfg`) continue;
48
54
  const ext = path.extname(f).toLowerCase();
55
+ if (ext === ".h") {
56
+ headers[f] = await readFile(path.join(presetDir, f), "utf-8");
57
+ continue;
58
+ }
59
+ if (!f.startsWith(prefix) || f === `${preset}.cfg`) continue;
49
60
  if (ext === ".s" || ext === ".asm") {
50
61
  const contents = await readFile(path.join(presetDir, f), "utf-8");
51
62
  const suffix = f.slice(prefix.length);
@@ -55,5 +66,5 @@ export async function resolveLinkerConfig(platform, arg) {
55
66
  } catch {
56
67
  // No support files.
57
68
  }
58
- return { cfg, supportSources };
69
+ return { cfg, supportSources, headers };
59
70
  }
@@ -0,0 +1,80 @@
1
+ /*
2
+ * GameTank-specific defines
3
+ */
4
+
5
+ #ifndef GAMETANK_H
6
+ #define GAMETANK_H
7
+
8
+ typedef char bool;
9
+ #define true 1
10
+ #define false 0
11
+
12
+ #define audio_reset ((char *) 0x2000)
13
+ #define audio_nmi ((char *) 0x2001)
14
+ #define audio_rate ((char *) 0x2006)
15
+ #define dma_flags ((char *) 0x2007)
16
+ #define bank_reg ((char *) 0x2005)
17
+ #define via ((char*) 0x2800)
18
+ #define aram ((unsigned char *) 0x3000)
19
+ #define vram ((unsigned char *) 0x4000)
20
+ #define vram_VX ((char *) 0x4000)
21
+ #define vram_VY ((char *) 0x4001)
22
+ #define vram_GX ((char *) 0x4002)
23
+ #define vram_GY ((char *) 0x4003)
24
+ #define vram_WIDTH ((char *) 0x4004)
25
+ #define vram_HEIGHT ((char *) 0x4005)
26
+ #define vram_COLOR ((char *) 0x4007)
27
+ #define vram_START ((char *) 0x4006)
28
+
29
+ #define SCREEN_WIDTH 128
30
+ #define SCREEN_HEIGHT 128
31
+
32
+ #define DMA_ENABLE 1
33
+ #define DMA_PAGE_OUT 2
34
+ #define DMA_NMI 4
35
+ #define DMA_COLORFILL_ENABLE 8
36
+ #define DMA_GCARRY 16
37
+ #define DMA_CPU_TO_VRAM 32
38
+ #define DMA_IRQ 64
39
+ #define DMA_OPAQUE 128
40
+
41
+ #define BANK_GRAM_MASK 7
42
+ #define BANK_SECOND_FRAMEBUFFER 8
43
+ #define BANK_CLIP_X 16
44
+ #define BANK_CLIP_Y 32
45
+ #define BANK_RAM_MASK 192
46
+
47
+ #define GRAM_PAGE(x) x
48
+
49
+ #define VX 0
50
+ #define VY 1
51
+ #define GX 2
52
+ #define GY 3
53
+ #define WIDTH 4
54
+ #define HEIGHT 5
55
+ #define START 6
56
+ #define COLOR 7
57
+
58
+ #define gamepad_1 ((volatile char *) 0x2008)
59
+ #define gamepad_2 ((volatile char *) 0x2009)
60
+
61
+ #define ORB 0
62
+ #define ORA 1
63
+ #define DDRB 2
64
+ #define DDRA 3
65
+ #define T1C 5
66
+ #define ACR 11
67
+ #define PCR 12
68
+ #define IFR 13
69
+ #define IER 14
70
+
71
+ extern char frameflag, frameflip, flagsMirror, banksMirror, bankflip;
72
+
73
+ void wait();
74
+ void nop5();
75
+ void nop10();
76
+
77
+ #define PROFILER_START(x) via[ORB] = 0x80; via[ORB] = x;
78
+ #define PROFILER_END(x) via[ORB] = 0x80; via[ORB] = x | 0x40;
79
+
80
+ #endif
@@ -0,0 +1,32 @@
1
+ # GameTank SDK-runtime single-bank (EEPROM32K) ld65 config. Like single-bank.cfg
2
+ # but adds the segments the bundled SDK runtime uses (LOADERS / COMMON / SAVE /
3
+ # WAVES) — in the multi-bank SDK build these live in dedicated flash banks; for the
4
+ # single 32 KB cart they all fold into the one ROM region (SAVE → RAM). Flat 32 KB
5
+ # image = a valid .gtr (EEPROM32K). VECTORS pinned to $FFFA.
6
+ MEMORY {
7
+ ZP: start = $0000, size = $0100, type = rw, define = yes;
8
+ RAM: start = $0200, size = $1D00, define = yes;
9
+ ROM: start = $8000, size = $8000, file = %O, fill = yes, define = yes;
10
+ }
11
+ SEGMENTS {
12
+ ZEROPAGE: load = ZP, type = zp, define = yes;
13
+ DATA: load = ROM, type = rw, define = yes, run = RAM;
14
+ BSS: load = RAM, type = bss, define = yes;
15
+ HEAP: load = RAM, type = bss, optional = yes;
16
+ STARTUP: load = ROM, type = ro;
17
+ ONCE: load = ROM, type = ro, optional = yes;
18
+ CODE: load = ROM, type = ro;
19
+ RODATA: load = ROM, type = ro;
20
+ LOADERS: load = ROM, type = ro, optional = yes; # SDK sprite/blit loaders
21
+ COMMON: load = ROM, type = ro, optional = yes; # SDK shared code/data
22
+ WAVES: load = ROM, type = ro, optional = yes; # SDK audio wave tables
23
+ SAVE: load = RAM, type = bss, optional = yes; # persist (battery SRAM) — RAM-backed here
24
+ VECTORS: load = ROM, type = ro, start = $FFFA;
25
+ }
26
+ FEATURES {
27
+ CONDES: type = constructor, label = __CONSTRUCTOR_TABLE__, count = __CONSTRUCTOR_COUNT__, segment = ONCE;
28
+ CONDES: type = destructor, label = __DESTRUCTOR_TABLE__, count = __DESTRUCTOR_COUNT__, segment = RODATA;
29
+ }
30
+ SYMBOLS {
31
+ __STACKSIZE__: type = weak, value = $0200;
32
+ }
@@ -0,0 +1,61 @@
1
+ ; ---------------------------------------------------------------------------
2
+ ; GameTank SDK-runtime crt0 (single-bank EEPROM32K, with the bundled SDK gfx/
3
+ ; input/random/ACP runtime). Adapted from gametank_sdk src/gt/crt0.s, MIT — but
4
+ ; STRIPPED of flash banking + the audio-FW asset: a single 32 KB cart maps at
5
+ ; boot, so there's no flash bank to shift in, and the bare runtime has no
6
+ ; asset-pipeline audio firmware. Calls _sdk_init (init_graphics + ACP) then
7
+ ; _main. The SDK's interrupt.s provides _nmi_int / _irq_int, so this crt0 does
8
+ ; NOT define them (that was the duplicate-symbol clash with the bare crt0).
9
+ ; ---------------------------------------------------------------------------
10
+ .export _init, _exit
11
+ .import _main, _sdk_init
12
+ .export __STARTUP__ : absolute = 1
13
+ .import __RAM_START__, __RAM_SIZE__
14
+ .import copydata, zerobss, initlib, donelib
15
+
16
+ .PC02
17
+
18
+ BankReg = $2005
19
+ VIA = $2800
20
+ DDRA = 3
21
+ ORAr = 1
22
+
23
+ .include "zeropage.inc"
24
+
25
+ .segment "STARTUP"
26
+
27
+ _init: LDX #$FF
28
+ TXS
29
+ CLD
30
+
31
+ LDX #0
32
+ viaWakeup:
33
+ INX
34
+ BNE viaWakeup
35
+
36
+ STZ BankReg ; single 32K cart: fixed bank, nothing to shift
37
+ STZ $1FFF
38
+
39
+ LDA #%00000111 ; VIA DDRA: banking pins as output
40
+ STA VIA+DDRA
41
+ LDA #$FF
42
+ STA VIA+ORAr
43
+
44
+ ; cc65 C argument-stack pointer = top of work RAM
45
+ LDA #<(__RAM_START__ + __RAM_SIZE__)
46
+ STA c_sp
47
+ LDA #>(__RAM_START__ + __RAM_SIZE__)
48
+ STA c_sp+1
49
+
50
+ JSR zerobss
51
+ JSR copydata
52
+ JSR initlib
53
+
54
+ JSR _sdk_init ; init_graphics + ACP (the bundled runtime)
55
+ CLI ; enable IRQs — the draw queue + ACP are
56
+ ; INTERRUPT-DRIVEN (blit-done IRQ processes
57
+ ; queue_draw_box; without CLI nothing draws)
58
+ JSR _main
59
+
60
+ _exit: JSR donelib
61
+ BRK
@@ -0,0 +1,11 @@
1
+ ; GameTank SDK-runtime vector table at $FFFA. The interrupt handlers come from the
2
+ ; bundled SDK runtime (interrupt.s exports _nmi_int / _irq_int), so this just
3
+ ; imports + points the table at them. (The bare single-bank.vectors.s defines its
4
+ ; own rti handlers; the SDK preset uses the SDK's real ones.)
5
+ .import _init, _nmi_int, _irq_int
6
+
7
+ .segment "VECTORS"
8
+
9
+ .addr _nmi_int ; $FFFA NMI
10
+ .addr _init ; $FFFC RESET
11
+ .addr _irq_int ; $FFFE IRQ / BRK
@@ -0,0 +1,28 @@
1
+ # GameTank single-bank (EEPROM32K) ld65 config — the Tier-A "bare" path.
2
+ # Collapses the SDK's multi-bank 2 MB linkerConfig.js to ONE 32 KB ROM bank at
3
+ # $8000-$FFFF (no flash SPI, no Node bank-cat, no zopfli). ZP/RAM match the SDK
4
+ # (ZP $0, stack $0100, work RAM $0200-$1EFF). VECTORS at $FFFA. Output is a flat
5
+ # 32 KB image that IS a valid .gtr (the core size-detects the EEPROM32K mapper).
6
+ MEMORY {
7
+ ZP: start = $0000, size = $0100, type = rw, define = yes;
8
+ RAM: start = $0200, size = $1D00, define = yes;
9
+ ROM: start = $8000, size = $8000, file = %O, fill = yes, define = yes;
10
+ }
11
+ SEGMENTS {
12
+ ZEROPAGE: load = ZP, type = zp, define = yes;
13
+ DATA: load = ROM, type = rw, define = yes, run = RAM;
14
+ BSS: load = RAM, type = bss, define = yes;
15
+ HEAP: load = RAM, type = bss, optional = yes;
16
+ STARTUP: load = ROM, type = ro;
17
+ ONCE: load = ROM, type = ro, optional = yes;
18
+ CODE: load = ROM, type = ro;
19
+ RODATA: load = ROM, type = ro;
20
+ VECTORS: load = ROM, type = ro, start = $FFFA;
21
+ }
22
+ FEATURES {
23
+ CONDES: type = constructor, label = __CONSTRUCTOR_TABLE__, count = __CONSTRUCTOR_COUNT__, segment = ONCE;
24
+ CONDES: type = destructor, label = __DESTRUCTOR_TABLE__, count = __DESTRUCTOR_COUNT__, segment = RODATA;
25
+ }
26
+ SYMBOLS {
27
+ __STACKSIZE__: type = weak, value = $0200;
28
+ }
@@ -0,0 +1,71 @@
1
+ ; ---------------------------------------------------------------------------
2
+ ; GameTank Tier-A crt0 (single-bank EEPROM32K, no SDK runtime).
3
+ ; Adapted from gametank_sdk src/gt/crt0.s, MIT (Clyde Shaffer). STRIPPED for the
4
+ ; bare 32 KB path: NO flash bank shift-out (the whole game is in one bank that
5
+ ; maps at boot), NO _sdk_init, NO _AudioFWPkg .incbin. Just: stack init, VIA
6
+ ; wakeup, zerobss/copydata/constructors, set the cc65 C arg-stack pointer, call
7
+ ; main(). A default NMI/IRQ handler (rti) lives here so a bare game links without
8
+ ; defining its own.
9
+ ; ---------------------------------------------------------------------------
10
+ .export _init, _exit
11
+ .export _nmi_int, _irq_int
12
+ .import _main
13
+ .export __STARTUP__ : absolute = 1
14
+ .import __RAM_START__, __RAM_SIZE__
15
+ .import copydata, zerobss, initlib, donelib
16
+
17
+ .PC02 ; W65C02 opcode set (stz/bra/phx/…)
18
+
19
+ BankReg = $2005
20
+ VIA = $2800
21
+ DDRA = 3
22
+ ORAr = 1
23
+
24
+ .include "zeropage.inc"
25
+
26
+ .segment "STARTUP"
27
+
28
+ _init: LDX #$FF ; init stack pointer to $01FF
29
+ TXS
30
+ CLD
31
+
32
+ LDX #0 ; brief VIA wakeup delay
33
+ viaWakeup:
34
+ INX
35
+ BNE viaWakeup
36
+
37
+ ; Park the banking register at a known state. With a single 32 KB cart
38
+ ; the active bank is fixed at boot, so we don't shift a flash bank in.
39
+ STZ BankReg
40
+ STZ $1FFF
41
+
42
+ LDA #%00000111 ; VIA DDRA: low 3 bits output (banking pins)
43
+ STA VIA+DDRA
44
+ LDA #$FF
45
+ STA VIA+ORAr
46
+
47
+ ; ---------------------------------------------------------------------------
48
+ ; cc65 C argument-stack pointer = top of work RAM
49
+ LDA #<(__RAM_START__ + __RAM_SIZE__)
50
+ STA c_sp
51
+ LDA #>(__RAM_START__ + __RAM_SIZE__)
52
+ STA c_sp+1
53
+
54
+ ; ---------------------------------------------------------------------------
55
+ JSR zerobss ; clear BSS
56
+ JSR copydata ; copy initialized DATA to RAM
57
+ JSR initlib ; run constructors
58
+
59
+ JSR _main
60
+
61
+ _exit: JSR donelib ; run destructors
62
+ BRK
63
+
64
+ ; ---------------------------------------------------------------------------
65
+ ; Default interrupt handlers — a bare game that doesn't define its own still
66
+ ; links. (A game CAN provide _nmi_int/_irq_int; cc65's linker lets a strong
67
+ ; symbol from the game override these .export'd ones... so keep them weak by
68
+ ; only defining if absent — here they're plain rti fallbacks.)
69
+ _nmi_int:
70
+ _irq_int:
71
+ RTI
@@ -0,0 +1,12 @@
1
+ ; ---------------------------------------------------------------------------
2
+ ; GameTank 6502 vector table at $FFFA (NMI / RESET / IRQ-BRK).
3
+ ; Adapted from gametank_sdk src/gt/vectors.s, MIT. The handlers are exported by
4
+ ; the Tier-A crt0 (rti defaults) — a game can override _nmi_int/_irq_int.
5
+ ; ---------------------------------------------------------------------------
6
+ .import _init, _nmi_int, _irq_int
7
+
8
+ .segment "VECTORS"
9
+
10
+ .addr _nmi_int ; $FFFA NMI
11
+ .addr _init ; $FFFC RESET
12
+ .addr _irq_int ; $FFFE IRQ / BRK