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
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
} from "../arm-none-eabi-gcc/gcc.js";
|
|
38
38
|
import { packAr } from "../common/ar.js";
|
|
39
39
|
import { resolveSdkArchive } from "../common/sdk-cache.js";
|
|
40
|
+
import { CBuild, BuildError } from "../common/c-build.js";
|
|
40
41
|
|
|
41
42
|
const __filename = fileURLToPath(import.meta.url);
|
|
42
43
|
const __dirname = path.dirname(__filename);
|
|
@@ -139,7 +140,7 @@ function normalizeGbaSources(args) {
|
|
|
139
140
|
* 6. objcopy -O binary → final .gba ROM
|
|
140
141
|
*/
|
|
141
142
|
async function buildWithLibtonc({ sources, headers, cc1Options, binaryIncludes = {}, maxmod = false, rebuildSdk = false, writeSeed = false }) {
|
|
142
|
-
|
|
143
|
+
const cb = new CBuild();
|
|
143
144
|
|
|
144
145
|
const crt0Src = await readFile(path.join(LIBTONC_DIR, "gba_crt0.s"), "utf-8");
|
|
145
146
|
const linkScript = await readFile(path.join(LIBTONC_DIR, "gba_cart.ld"), "utf-8");
|
|
@@ -152,7 +153,7 @@ async function buildWithLibtonc({ sources, headers, cc1Options, binaryIncludes =
|
|
|
152
153
|
// ourselves so users don't need to author it by hand.
|
|
153
154
|
const hasSoundbank = maxmod && Object.prototype.hasOwnProperty.call(binaryIncludes, "soundbank.bin");
|
|
154
155
|
if (hasSoundbank) {
|
|
155
|
-
|
|
156
|
+
cb.note(`--- soundbank stub auto-emitted (.incbin "soundbank.bin") ---`);
|
|
156
157
|
}
|
|
157
158
|
|
|
158
159
|
const libtoncHeaders = await loadLibtoncHeaders();
|
|
@@ -166,45 +167,34 @@ async function buildWithLibtonc({ sources, headers, cc1Options, binaryIncludes =
|
|
|
166
167
|
"-mthumb-interwork",
|
|
167
168
|
];
|
|
168
169
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
const asm = await runArmAs({ source: cc1.asmSource });
|
|
183
|
-
log += `--- as (${name}) ---\n${asm.log || "(ok)"}\n`;
|
|
184
|
-
if (asm.exitCode !== 0 || !asm.object) {
|
|
185
|
-
return { ok: false, binary: null, log, exitCode: asm.exitCode || 1, stage: `as (${name})`, runtime: "libtonc", ...(asm.crash ? { crash: asm.crash } : {}) };
|
|
170
|
+
try {
|
|
171
|
+
// ── Stage A: compile each user .c → .s ─────────────────────────
|
|
172
|
+
const objects = {};
|
|
173
|
+
for (const [name, src] of Object.entries(sources)) {
|
|
174
|
+
if (!name.endsWith(".c")) continue;
|
|
175
|
+
const cc1 = await cb.stage(`cc1 (${name})`, () => runCc1arm({
|
|
176
|
+
source: src,
|
|
177
|
+
headers: { ...sysHeaders, ...libtoncHeaders, ...maxmodHeaders, ...headers },
|
|
178
|
+
options: libtoncCc1Options,
|
|
179
|
+
}), (r) => r.asmSource);
|
|
180
|
+
const asm = await cb.stage(`as (${name})`, () => runArmAs({ source: cc1.asmSource }), (r) => r.object);
|
|
181
|
+
objects[name.replace(/\.c$/, ".o")] = asm.object;
|
|
186
182
|
}
|
|
187
|
-
objects[name.replace(/\.c$/, ".o")] = asm.object;
|
|
188
|
-
}
|
|
189
183
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
// track it dynamically — this stub gives a fixed bound which is
|
|
205
|
-
// fine for the small allocations newlib does internally.
|
|
206
|
-
const fakeHeapEndStub = await runArmAs({
|
|
207
|
-
source: `
|
|
184
|
+
// ── Stage B: assemble gba_crt0.s ─────────────────────────────────
|
|
185
|
+
const crt0As = await cb.stage("as (gba_crt0.s)", () => runArmAs({ source: crt0Src }), (r) => r.object);
|
|
186
|
+
objects["gba_crt0.o"] = crt0As.object;
|
|
187
|
+
|
|
188
|
+
// fake_heap_end + `end` stubs. The gba_cart.ld linker script
|
|
189
|
+
// defines `__end__` (top of bss) but not the bare `end` symbol that
|
|
190
|
+
// newlib's sbrk() references. We provide both `fake_heap_end` AND
|
|
191
|
+
// `end` here as tiny data-section labels. Points at end of EWRAM.
|
|
192
|
+
//
|
|
193
|
+
// For real games you'd want sbrk to start the heap at __end__ and
|
|
194
|
+
// track it dynamically — this stub gives a fixed bound which is
|
|
195
|
+
// fine for the small allocations newlib does internally.
|
|
196
|
+
const fakeHeapEndStub = await cb.stage("as (fake_heap_end stub)", () => runArmAs({
|
|
197
|
+
source: `
|
|
208
198
|
.section .data
|
|
209
199
|
.global fake_heap_end
|
|
210
200
|
.global end
|
|
@@ -214,17 +204,13 @@ async function buildWithLibtonc({ sources, headers, cc1Options, binaryIncludes =
|
|
|
214
204
|
end:
|
|
215
205
|
.word 0x02000000 /* start of EWRAM — sbrk grows from here */
|
|
216
206
|
`,
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (fakeHeapEndStub.exitCode !== 0 || !fakeHeapEndStub.object) {
|
|
220
|
-
return { ok: false, binary: null, log, exitCode: fakeHeapEndStub.exitCode || 1, stage: "as (fake_heap_end stub)", runtime: "libtonc", ...(fakeHeapEndStub.crash ? { crash: fakeHeapEndStub.crash } : {}) };
|
|
221
|
-
}
|
|
222
|
-
objects["fake_heap_end.o"] = fakeHeapEndStub.object;
|
|
207
|
+
}), (r) => r.object);
|
|
208
|
+
objects["fake_heap_end.o"] = fakeHeapEndStub.object;
|
|
223
209
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
210
|
+
// ── Stage B3: maxmod soundbank embedding stub ───────────────────
|
|
211
|
+
if (hasSoundbank) {
|
|
212
|
+
const soundbankStub = await cb.stage("as (soundbank.s)", () => runArmAs({
|
|
213
|
+
source: `
|
|
228
214
|
.section .rodata
|
|
229
215
|
.align 2
|
|
230
216
|
.global soundbank_bin
|
|
@@ -236,117 +222,109 @@ async function buildWithLibtonc({ sources, headers, cc1Options, binaryIncludes =
|
|
|
236
222
|
soundbank_bin_size:
|
|
237
223
|
.word soundbank_bin_end - soundbank_bin
|
|
238
224
|
`,
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (soundbankStub.exitCode !== 0 || !soundbankStub.object) {
|
|
243
|
-
return { ok: false, binary: null, log, exitCode: soundbankStub.exitCode || 1, stage: "as (soundbank.s)", runtime: "libtonc", ...(soundbankStub.crash ? { crash: soundbankStub.crash } : {}) };
|
|
225
|
+
binaryIncludes: { "soundbank.bin": binaryIncludes["soundbank.bin"] },
|
|
226
|
+
}), (r) => r.object);
|
|
227
|
+
objects["soundbank.o"] = soundbankStub.object;
|
|
244
228
|
}
|
|
245
|
-
objects["soundbank.o"] = soundbankStub.object;
|
|
246
|
-
}
|
|
247
229
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
rebuild: rebuildSdk, writeSeed,
|
|
258
|
-
compile: async () => {
|
|
259
|
-
const r = await compileSdkObjects({
|
|
260
|
-
key: "libtonc",
|
|
261
|
-
srcDirs: [path.join(LIBTONC_DIR, "src"), SYSBASE_DIR],
|
|
262
|
-
headers: { ...sysHeaders, ...libtoncHeaders, ...maxmodHeaders, ...headers },
|
|
263
|
-
});
|
|
264
|
-
return r.ok ? { ok: true, archive: packAr(r.objects) } : r;
|
|
265
|
-
},
|
|
266
|
-
});
|
|
267
|
-
if (!toncRes.ok) {
|
|
268
|
-
return { ok: false, binary: null, log: log + (toncRes.log || ""), exitCode: 1, stage: toncRes.stage, runtime: "libtonc" };
|
|
269
|
-
}
|
|
270
|
-
if (toncRes.sdkEditIgnored) sdkWarnings.push(toncRes.sdkEditIgnored);
|
|
271
|
-
log += `--- libtonc ${toncRes.fromSource ? "compiled from source" : "from prebuilt seed"} ---\n`;
|
|
272
|
-
|
|
273
|
-
let maxmodAr = null;
|
|
274
|
-
if (maxmod) {
|
|
275
|
-
const mmHeaders = await loadMaxmodAsmHeaders();
|
|
276
|
-
const mmRes = await sdkArchive({
|
|
277
|
-
name: "maxmod",
|
|
278
|
-
srcDirs: [path.join(MAXMOD_DIR, "source"), path.join(MAXMOD_DIR, "source_gba")],
|
|
279
|
-
seedBase: path.join(MAXMOD_DIR, "maxmod"),
|
|
230
|
+
// ── Stage B4: resolve libtonc (and maxmod) — seed by default, or compile
|
|
231
|
+
// from source when rebuildSdk is set. Edits to the vendored SDK source take
|
|
232
|
+
// effect with rebuildSdk:true; without it, the fast prebuilt seed is used and
|
|
233
|
+
// an edit is flagged (sdkEditIgnored), never silently dropped.
|
|
234
|
+
const sdkWarnings = [];
|
|
235
|
+
const toncRes = await sdkArchive({
|
|
236
|
+
name: "libtonc",
|
|
237
|
+
srcDirs: [path.join(LIBTONC_DIR, "src"), SYSBASE_DIR],
|
|
238
|
+
seedBase: path.join(LIBTONC_DIR, "libtonc"),
|
|
280
239
|
rebuild: rebuildSdk, writeSeed,
|
|
281
240
|
compile: async () => {
|
|
282
241
|
const r = await compileSdkObjects({
|
|
283
|
-
key: "
|
|
284
|
-
srcDirs: [path.join(
|
|
285
|
-
headers: { ...sysHeaders, ...maxmodHeaders, ...
|
|
286
|
-
cppDefines: ["SYS_GBA=1"],
|
|
242
|
+
key: "libtonc",
|
|
243
|
+
srcDirs: [path.join(LIBTONC_DIR, "src"), SYSBASE_DIR],
|
|
244
|
+
headers: { ...sysHeaders, ...libtoncHeaders, ...maxmodHeaders, ...headers },
|
|
287
245
|
});
|
|
288
246
|
return r.ok ? { ok: true, archive: packAr(r.objects) } : r;
|
|
289
247
|
},
|
|
290
248
|
});
|
|
291
|
-
if (!
|
|
292
|
-
return { ok: false, binary: null, log: log + (
|
|
249
|
+
if (!toncRes.ok) {
|
|
250
|
+
return { ok: false, binary: null, log: cb.log + (toncRes.log || ""), exitCode: 1, stage: toncRes.stage, runtime: "libtonc" };
|
|
251
|
+
}
|
|
252
|
+
if (toncRes.sdkEditIgnored) sdkWarnings.push(toncRes.sdkEditIgnored);
|
|
253
|
+
cb.note(`--- libtonc ${toncRes.fromSource ? "compiled from source" : "from prebuilt seed"} ---`);
|
|
254
|
+
|
|
255
|
+
let maxmodAr = null;
|
|
256
|
+
if (maxmod) {
|
|
257
|
+
const mmHeaders = await loadMaxmodAsmHeaders();
|
|
258
|
+
const mmRes = await sdkArchive({
|
|
259
|
+
name: "maxmod",
|
|
260
|
+
srcDirs: [path.join(MAXMOD_DIR, "source"), path.join(MAXMOD_DIR, "source_gba")],
|
|
261
|
+
seedBase: path.join(MAXMOD_DIR, "maxmod"),
|
|
262
|
+
rebuild: rebuildSdk, writeSeed,
|
|
263
|
+
compile: async () => {
|
|
264
|
+
const r = await compileSdkObjects({
|
|
265
|
+
key: "maxmod",
|
|
266
|
+
srcDirs: [path.join(MAXMOD_DIR, "source"), path.join(MAXMOD_DIR, "source_gba")],
|
|
267
|
+
headers: { ...sysHeaders, ...maxmodHeaders, ...mmHeaders },
|
|
268
|
+
cppDefines: ["SYS_GBA=1"],
|
|
269
|
+
});
|
|
270
|
+
return r.ok ? { ok: true, archive: packAr(r.objects) } : r;
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
if (!mmRes.ok) {
|
|
274
|
+
return { ok: false, binary: null, log: cb.log + (mmRes.log || ""), exitCode: 1, stage: mmRes.stage, runtime: "libtonc" };
|
|
275
|
+
}
|
|
276
|
+
if (mmRes.sdkEditIgnored) sdkWarnings.push(mmRes.sdkEditIgnored);
|
|
277
|
+
maxmodAr = mmRes.archive;
|
|
278
|
+
cb.note(`--- maxmod ${mmRes.fromSource ? "compiled from source" : "from prebuilt seed"} ---`);
|
|
293
279
|
}
|
|
294
|
-
if (mmRes.sdkEditIgnored) sdkWarnings.push(mmRes.sdkEditIgnored);
|
|
295
|
-
maxmodAr = mmRes.archive;
|
|
296
|
-
log += `--- maxmod ${mmRes.fromSource ? "compiled from source" : "from prebuilt seed"} ---\n`;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// ── Stage C: link ───────────────────────────────────────────────
|
|
300
|
-
// crt*.o + libgcc/libc/libnosys are gcc/newlib toolchain runtime.
|
|
301
|
-
const archives = {
|
|
302
|
-
"libtonc.a": toncRes.archive,
|
|
303
|
-
"crti.o": new Uint8Array(await readFile(path.join(LIBTONC_DIR, "crti.o"))),
|
|
304
|
-
"crtn.o": new Uint8Array(await readFile(path.join(LIBTONC_DIR, "crtn.o"))),
|
|
305
|
-
"crtbegin.o": new Uint8Array(await readFile(path.join(LIBTONC_DIR, "crtbegin.o"))),
|
|
306
|
-
"crtend.o": new Uint8Array(await readFile(path.join(LIBTONC_DIR, "crtend.o"))),
|
|
307
|
-
};
|
|
308
|
-
if (maxmodAr) archives["libmm.a"] = maxmodAr;
|
|
309
|
-
const targetLibs = await readTargetArchives();
|
|
310
|
-
Object.assign(archives, targetLibs);
|
|
311
|
-
|
|
312
|
-
// Wrap the libs in --start-group / --end-group so the linker
|
|
313
|
-
// re-scans them for cross-references between libc → libgcc → libc
|
|
314
|
-
// (libc/strtol uses __aeabi_uidiv from libgcc; libgcc may call back
|
|
315
|
-
// into libc helpers). Without re-scan we get undefined references.
|
|
316
|
-
const ld = await runArmLd({
|
|
317
|
-
objects,
|
|
318
|
-
linkScript,
|
|
319
|
-
archives,
|
|
320
|
-
libraryPaths: ["/work"],
|
|
321
|
-
libraries: [], // explicit -l flags moved into options for ordering control
|
|
322
|
-
options: [
|
|
323
|
-
"/work/crti.o",
|
|
324
|
-
"/work/crtbegin.o",
|
|
325
|
-
// libtonc/maxmod archives are packed from compiled-from-source objects.
|
|
326
|
-
"--start-group",
|
|
327
|
-
"-ltonc",
|
|
328
|
-
...(maxmod ? ["-lmm"] : []),
|
|
329
|
-
"-lc",
|
|
330
|
-
"-lgcc",
|
|
331
|
-
"-lnosys",
|
|
332
|
-
"--end-group",
|
|
333
|
-
"/work/crtend.o",
|
|
334
|
-
"/work/crtn.o",
|
|
335
|
-
],
|
|
336
|
-
});
|
|
337
|
-
log += `--- ld ---\n${ld.log || "(ok)"}\n`;
|
|
338
|
-
if (ld.exitCode !== 0 || !ld.elf) {
|
|
339
|
-
return { ok: false, binary: null, log, exitCode: ld.exitCode || 1, stage: "ld", runtime: "libtonc", ...(ld.crash ? { crash: ld.crash } : {}) };
|
|
340
|
-
}
|
|
341
280
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
281
|
+
// ── Stage C: link ───────────────────────────────────────────────
|
|
282
|
+
// crt*.o + libgcc/libc/libnosys are gcc/newlib toolchain runtime.
|
|
283
|
+
const archives = {
|
|
284
|
+
"libtonc.a": toncRes.archive,
|
|
285
|
+
"crti.o": new Uint8Array(await readFile(path.join(LIBTONC_DIR, "crti.o"))),
|
|
286
|
+
"crtn.o": new Uint8Array(await readFile(path.join(LIBTONC_DIR, "crtn.o"))),
|
|
287
|
+
"crtbegin.o": new Uint8Array(await readFile(path.join(LIBTONC_DIR, "crtbegin.o"))),
|
|
288
|
+
"crtend.o": new Uint8Array(await readFile(path.join(LIBTONC_DIR, "crtend.o"))),
|
|
289
|
+
};
|
|
290
|
+
if (maxmodAr) archives["libmm.a"] = maxmodAr;
|
|
291
|
+
const targetLibs = await readTargetArchives();
|
|
292
|
+
Object.assign(archives, targetLibs);
|
|
293
|
+
|
|
294
|
+
// Wrap the libs in --start-group / --end-group so the linker
|
|
295
|
+
// re-scans them for cross-references between libc → libgcc → libc
|
|
296
|
+
// (libc/strtol uses __aeabi_uidiv from libgcc; libgcc may call back
|
|
297
|
+
// into libc helpers). Without re-scan we get undefined references.
|
|
298
|
+
const ld = await cb.stage("ld", () => runArmLd({
|
|
299
|
+
objects,
|
|
300
|
+
linkScript,
|
|
301
|
+
archives,
|
|
302
|
+
libraryPaths: ["/work"],
|
|
303
|
+
libraries: [], // explicit -l flags moved into options for ordering control
|
|
304
|
+
options: [
|
|
305
|
+
"/work/crti.o",
|
|
306
|
+
"/work/crtbegin.o",
|
|
307
|
+
// libtonc/maxmod archives are packed from compiled-from-source objects.
|
|
308
|
+
"--start-group",
|
|
309
|
+
"-ltonc",
|
|
310
|
+
...(maxmod ? ["-lmm"] : []),
|
|
311
|
+
"-lc",
|
|
312
|
+
"-lgcc",
|
|
313
|
+
"-lnosys",
|
|
314
|
+
"--end-group",
|
|
315
|
+
"/work/crtend.o",
|
|
316
|
+
"/work/crtn.o",
|
|
317
|
+
],
|
|
318
|
+
}), (r) => r.elf);
|
|
319
|
+
|
|
320
|
+
// ── Stage D: objcopy ────────────────────────────────────────────
|
|
321
|
+
const objcopy = await cb.stage("objcopy", () => runArmObjcopy({ elf: ld.elf }), (r) => r.binary);
|
|
322
|
+
|
|
323
|
+
return { ok: true, binary: objcopy.binary, log: cb.log, exitCode: 0, stage: "done", runtime: "libtonc", ...(ld.map ? { symbols: ld.map } : {}), ...(sdkWarnings.length ? { sdkEditIgnored: sdkWarnings } : {}) };
|
|
324
|
+
} catch (e) {
|
|
325
|
+
if (e instanceof BuildError) return e.toResult({ runtime: "libtonc" });
|
|
326
|
+
throw e;
|
|
347
327
|
}
|
|
348
|
-
|
|
349
|
-
return { ok: true, binary: objcopy.binary, log, exitCode: 0, stage: "done", runtime: "libtonc", ...(ld.map ? { symbols: ld.map } : {}), ...(sdkWarnings.length ? { sdkEditIgnored: sdkWarnings } : {}) };
|
|
350
328
|
}
|
|
351
329
|
|
|
352
330
|
/**
|
|
@@ -360,7 +338,7 @@ async function buildWithLibtonc({ sources, headers, cc1Options, binaryIncludes =
|
|
|
360
338
|
* 5. objcopy -O binary → final .gba ROM
|
|
361
339
|
*/
|
|
362
340
|
async function buildWithLibgba({ sources, headers, cc1Options, rebuildSdk = false, writeSeed = false }) {
|
|
363
|
-
|
|
341
|
+
const cb = new CBuild();
|
|
364
342
|
|
|
365
343
|
// Read the libgba bundle once (crt0 + linker script; the SDK itself is
|
|
366
344
|
// compiled from source below, not linked from libgba.a).
|
|
@@ -381,43 +359,32 @@ async function buildWithLibgba({ sources, headers, cc1Options, rebuildSdk = fals
|
|
|
381
359
|
"-mthumb-interwork",
|
|
382
360
|
];
|
|
383
361
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
const asm = await runArmAs({ source: cc1.asmSource });
|
|
398
|
-
log += `--- as (${name}) ---\n${asm.log || "(ok)"}\n`;
|
|
399
|
-
if (asm.exitCode !== 0 || !asm.object) {
|
|
400
|
-
return { ok: false, binary: null, log, exitCode: asm.exitCode || 1, stage: `as (${name})`, runtime: "libgba", ...(asm.crash ? { crash: asm.crash } : {}) };
|
|
362
|
+
try {
|
|
363
|
+
// ── Stage A: compile each user .c → .s ─────────────────────────
|
|
364
|
+
const objects = {};
|
|
365
|
+
for (const [name, src] of Object.entries(sources)) {
|
|
366
|
+
if (!name.endsWith(".c")) continue;
|
|
367
|
+
const cc1 = await cb.stage(`cc1 (${name})`, () => runCc1arm({
|
|
368
|
+
source: src,
|
|
369
|
+
headers: { ...sysHeaders, ...libgbaHeaders, ...headers },
|
|
370
|
+
options: libgbaCc1Options,
|
|
371
|
+
}), (r) => r.asmSource);
|
|
372
|
+
const asm = await cb.stage(`as (${name})`, () => runArmAs({ source: cc1.asmSource }), (r) => r.object);
|
|
373
|
+
objects[name.replace(/\.c$/, ".o")] = asm.object;
|
|
401
374
|
}
|
|
402
|
-
objects[name.replace(/\.c$/, ".o")] = asm.object;
|
|
403
|
-
}
|
|
404
375
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
// both as tiny data labels — gba_cart.ld defines `__end__` but not
|
|
418
|
-
// bare `end` which newlib sbrk needs.
|
|
419
|
-
const fakeHeapEndStub = await runArmAs({
|
|
420
|
-
source: `
|
|
376
|
+
// ── Stage B: assemble gba_crt0.s ─────────────────────────────────
|
|
377
|
+
const crt0As = await cb.stage("as (gba_crt0.s)", () => runArmAs({ source: crt0Src }), (r) => r.object);
|
|
378
|
+
objects["gba_crt0.o"] = crt0As.object;
|
|
379
|
+
|
|
380
|
+
// ── Stage B2: assemble fake_heap_end + `end` stubs ──────────────
|
|
381
|
+
// devkitARM's libsysbase normally provides both `fake_heap_end`
|
|
382
|
+
// (used by sbrk's heap tracking) and the linker script defines
|
|
383
|
+
// `end` (start of heap). We excluded libsysbase so we provide
|
|
384
|
+
// both as tiny data labels — gba_cart.ld defines `__end__` but not
|
|
385
|
+
// bare `end` which newlib sbrk needs.
|
|
386
|
+
const fakeHeapEndStub = await cb.stage("as (fake_heap_end stub)", () => runArmAs({
|
|
387
|
+
source: `
|
|
421
388
|
.section .data
|
|
422
389
|
.global fake_heap_end
|
|
423
390
|
.global end
|
|
@@ -427,98 +394,90 @@ async function buildWithLibgba({ sources, headers, cc1Options, rebuildSdk = fals
|
|
|
427
394
|
end:
|
|
428
395
|
.word 0x02000000 /* start of EWRAM — sbrk grows from here */
|
|
429
396
|
`,
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
return
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
397
|
+
}), (r) => r.object);
|
|
398
|
+
objects["fake_heap_end.o"] = fakeHeapEndStub.object;
|
|
399
|
+
|
|
400
|
+
// ── Stage B3: compile libgba (+ libsysbase) FROM SOURCE ─────────
|
|
401
|
+
// Link libgba's own compiled objects, NOT a prebuilt libgba.a. Edit any
|
|
402
|
+
// libgba source and it takes effect. libsysbase (gba_iosupport.c) gives the
|
|
403
|
+
// devoptab routing so libgba's consoleInit() + iprintf work.
|
|
404
|
+
const sdkWarnings = [];
|
|
405
|
+
const gbaRes = await sdkArchive({
|
|
406
|
+
name: "libgba",
|
|
407
|
+
srcDirs: [path.join(LIBGBA_DIR, "src"), SYSBASE_DIR],
|
|
408
|
+
seedBase: path.join(LIBGBA_DIR, "libgba"),
|
|
409
|
+
rebuild: rebuildSdk, writeSeed,
|
|
410
|
+
compile: async () => {
|
|
411
|
+
const r = await compileSdkObjects({
|
|
412
|
+
key: "libgba",
|
|
413
|
+
srcDirs: [path.join(LIBGBA_DIR, "src"), SYSBASE_DIR],
|
|
414
|
+
headers: { ...sysHeaders, ...libgbaHeaders, ...headers },
|
|
415
|
+
});
|
|
416
|
+
return r.ok ? { ok: true, archive: packAr(r.objects) } : r;
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
if (!gbaRes.ok) {
|
|
420
|
+
return { ok: false, binary: null, log: cb.log + (gbaRes.log || ""), exitCode: 1, stage: gbaRes.stage, runtime: "libgba" };
|
|
421
|
+
}
|
|
422
|
+
if (gbaRes.sdkEditIgnored) sdkWarnings.push(gbaRes.sdkEditIgnored);
|
|
423
|
+
cb.note(`--- libgba ${gbaRes.fromSource ? "compiled from source" : "from prebuilt seed"} ---`);
|
|
424
|
+
|
|
425
|
+
// ── Stage C: link ───────────────────────────────────────────────
|
|
426
|
+
// libgba archive from seed/source; crt*.o + libgcc/libc/libnosys are toolchain.
|
|
427
|
+
const archives = {
|
|
428
|
+
"libgba.a": gbaRes.archive,
|
|
429
|
+
"crti.o": new Uint8Array(await readFile(path.join(LIBGBA_DIR, "crti.o"))),
|
|
430
|
+
"crtn.o": new Uint8Array(await readFile(path.join(LIBGBA_DIR, "crtn.o"))),
|
|
431
|
+
"crtbegin.o": new Uint8Array(await readFile(path.join(LIBGBA_DIR, "crtbegin.o"))),
|
|
432
|
+
"crtend.o": new Uint8Array(await readFile(path.join(LIBGBA_DIR, "crtend.o"))),
|
|
433
|
+
};
|
|
434
|
+
const targetLibs = await readTargetArchives();
|
|
435
|
+
Object.assign(archives, targetLibs);
|
|
436
|
+
|
|
437
|
+
// Link order matters for `-l` archives and crt*.o files. Standard
|
|
438
|
+
// gcc-driver order: crti.o + crtbegin.o → user objects → libgba →
|
|
439
|
+
// libc + libgcc + libnosys → crtend.o + crtn.o.
|
|
440
|
+
// --start-group / --end-group forces ld to re-scan the libs so
|
|
441
|
+
// libc → libgcc → libc cross-refs resolve (libc/strtol calls
|
|
442
|
+
// __aeabi_uidiv in libgcc; libgcc may call back into libc).
|
|
443
|
+
const ld = await cb.stage("ld", () => runArmLd({
|
|
444
|
+
objects,
|
|
445
|
+
linkScript,
|
|
446
|
+
archives,
|
|
447
|
+
libraryPaths: ["/work"],
|
|
448
|
+
libraries: [],
|
|
449
|
+
options: [
|
|
450
|
+
"/work/crti.o",
|
|
451
|
+
"/work/crtbegin.o",
|
|
452
|
+
// libgba archive packed from compiled-from-source objects.
|
|
453
|
+
"--start-group",
|
|
454
|
+
"-lgba",
|
|
455
|
+
"-lc",
|
|
456
|
+
"-lgcc",
|
|
457
|
+
"-lnosys",
|
|
458
|
+
"--end-group",
|
|
459
|
+
"/work/crtend.o",
|
|
460
|
+
"/work/crtn.o",
|
|
461
|
+
],
|
|
462
|
+
}), (r) => r.elf);
|
|
463
|
+
|
|
464
|
+
// ── Stage D: objcopy ELF → raw .gba ─────────────────────────────
|
|
465
|
+
const objcopy = await cb.stage("objcopy", () => runArmObjcopy({ elf: ld.elf }), (r) => r.binary);
|
|
466
|
+
|
|
467
|
+
return {
|
|
468
|
+
ok: true,
|
|
469
|
+
binary: objcopy.binary,
|
|
470
|
+
log: cb.log,
|
|
471
|
+
exitCode: 0,
|
|
472
|
+
stage: "done",
|
|
473
|
+
runtime: "libgba",
|
|
474
|
+
...(ld.map ? { symbols: ld.map } : {}),
|
|
475
|
+
...(sdkWarnings.length ? { sdkEditIgnored: sdkWarnings } : {}),
|
|
476
|
+
};
|
|
477
|
+
} catch (e) {
|
|
478
|
+
if (e instanceof BuildError) return e.toResult({ runtime: "libgba" });
|
|
479
|
+
throw e;
|
|
510
480
|
}
|
|
511
|
-
|
|
512
|
-
return {
|
|
513
|
-
ok: true,
|
|
514
|
-
binary: objcopy.binary,
|
|
515
|
-
log,
|
|
516
|
-
exitCode: 0,
|
|
517
|
-
stage: "done",
|
|
518
|
-
runtime: "libgba",
|
|
519
|
-
...(ld.map ? { symbols: ld.map } : {}),
|
|
520
|
-
...(sdkWarnings.length ? { sdkEditIgnored: sdkWarnings } : {}),
|
|
521
|
-
};
|
|
522
481
|
}
|
|
523
482
|
|
|
524
483
|
/**
|
|
@@ -530,44 +489,33 @@ async function buildWithLibgba({ sources, headers, cc1Options, rebuildSdk = fals
|
|
|
530
489
|
* must provide their own _start / main entry point.
|
|
531
490
|
*/
|
|
532
491
|
async function buildMinimal({ sources, headers, cc1Options }) {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
}
|
|
542
|
-
const asm = await runArmAs({ source: cc1.asmSource });
|
|
543
|
-
log += `--- as (${name}) ---\n${asm.log || "(ok)"}\n`;
|
|
544
|
-
if (asm.exitCode !== 0 || !asm.object) {
|
|
545
|
-
return { ok: false, binary: null, log, exitCode: asm.exitCode || 1, stage: `as (${name})`, runtime: "minimal", ...(asm.crash ? { crash: asm.crash } : {}) };
|
|
492
|
+
const cb = new CBuild();
|
|
493
|
+
try {
|
|
494
|
+
const objects = {};
|
|
495
|
+
for (const [name, src] of Object.entries(sources)) {
|
|
496
|
+
if (!name.endsWith(".c")) continue;
|
|
497
|
+
const cc1 = await cb.stage(`cc1 (${name})`, () => runCc1arm({ source: src, headers, options: cc1Options }), (r) => r.asmSource);
|
|
498
|
+
const asm = await cb.stage(`as (${name})`, () => runArmAs({ source: cc1.asmSource }), (r) => r.object);
|
|
499
|
+
objects[name.replace(/\.c$/, ".o")] = asm.object;
|
|
546
500
|
}
|
|
547
|
-
objects[name.replace(/\.c$/, ".o")] = asm.object;
|
|
548
|
-
}
|
|
549
501
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
502
|
+
// Minimum-viable linker script — single .text region at GBA cart base.
|
|
503
|
+
const ld = await cb.stage("ld", () => runArmLd({
|
|
504
|
+
objects,
|
|
505
|
+
linkScript: `OUTPUT_FORMAT("elf32-littlearm")
|
|
554
506
|
ENTRY(main)
|
|
555
507
|
MEMORY { ROM (rx) : ORIGIN = 0x08000000, LENGTH = 32M }
|
|
556
508
|
SECTIONS { .text : { *(.text*) *(.rodata*) } > ROM }
|
|
557
509
|
`,
|
|
558
|
-
|
|
559
|
-
log += `--- ld ---\n${ld.log || "(ok)"}\n`;
|
|
560
|
-
if (ld.exitCode !== 0 || !ld.elf) {
|
|
561
|
-
return { ok: false, binary: null, log, exitCode: ld.exitCode || 1, stage: "ld", runtime: "minimal", ...(ld.crash ? { crash: ld.crash } : {}) };
|
|
562
|
-
}
|
|
510
|
+
}), (r) => r.elf);
|
|
563
511
|
|
|
564
|
-
|
|
565
|
-
log += `--- objcopy ---\n${objcopy.log || "(ok)"}\n`;
|
|
566
|
-
if (objcopy.exitCode !== 0 || !objcopy.binary) {
|
|
567
|
-
return { ok: false, binary: null, log, exitCode: objcopy.exitCode || 1, stage: "objcopy", runtime: "minimal", ...(objcopy.crash ? { crash: objcopy.crash } : {}) };
|
|
568
|
-
}
|
|
512
|
+
const objcopy = await cb.stage("objcopy", () => runArmObjcopy({ elf: ld.elf }), (r) => r.binary);
|
|
569
513
|
|
|
570
|
-
|
|
514
|
+
return { ok: true, binary: objcopy.binary, log: cb.log, exitCode: 0, stage: "done", runtime: "minimal", ...(ld.map ? { symbols: ld.map } : {}) };
|
|
515
|
+
} catch (e) {
|
|
516
|
+
if (e instanceof BuildError) return e.toResult({ runtime: "minimal" });
|
|
517
|
+
throw e;
|
|
518
|
+
}
|
|
571
519
|
}
|
|
572
520
|
|
|
573
521
|
/**
|