From f50aa23d24fe507af767c72db91592283bc702ad Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Sat, 7 Mar 2026 11:30:12 +0100 Subject: [PATCH] cli: added --symbols, --save flags gb: use codeseg_start parameter --- src/machine/gb.ts | 23 ++++++------ src/tools/8bws.ts | 80 +++++++++++++++++++++++++++++++++------- src/tools/testlib.ts | 2 + src/worker/platforms.ts | 3 +- src/worker/tools/sdcc.ts | 18 +++++---- 5 files changed, 93 insertions(+), 33 deletions(-) diff --git a/src/machine/gb.ts b/src/machine/gb.ts index d0e31e12..c472714c 100644 --- a/src/machine/gb.ts +++ b/src/machine/gb.ts @@ -1,7 +1,7 @@ import { SM83, SM83State } from "../common/cpu/SM83"; import { BasicScanlineMachine, Bus } from "../common/devices"; -import { newAddressDecoder, padBytes, Keys, makeKeycodeMap, newKeyboardHandler } from "../common/emu"; +import { newAddressDecoder, padBytes, Keys, makeKeycodeMap, newKeyboardHandler, EmuHalt } from "../common/emu"; import { hex } from "../common/util"; // Game Boy DMG palette: 4 shades of green (darkest to lightest) @@ -1246,9 +1246,9 @@ export class GameBoyMachine extends BasicScanlineMachine { switch (cartType) { case 0x00: this.mbcType = 0; break; // ROM only case 0x01: case 0x02: case 0x03: this.mbcType = 1; break; // MBC1 - default: this.mbcType = 1; break; // Default to MBC1 for other types + default: console.log(`Invalid cartridge type @ 0x147: ${data[0x147]}`); break; } - } + } else throw new EmuHalt("ROM not long enough for header"); // Determine ROM size and bank mask this.rom = new Uint8Array(Math.max(data.length, 0x8000)); @@ -1257,15 +1257,14 @@ export class GameBoyMachine extends BasicScanlineMachine { this.romBankMask = numBanks - 1; // Determine RAM size from header - if (data.length > 0x149) { - switch (data[0x149]) { - case 0x00: break; // No RAM - case 0x01: this.extram = new Uint8Array(0x800); break; // 2KB - case 0x02: this.extram = new Uint8Array(0x2000); break; // 8KB - case 0x03: this.extram = new Uint8Array(0x8000); break; // 32KB - case 0x04: this.extram = new Uint8Array(0x20000); break; // 128KB - case 0x05: this.extram = new Uint8Array(0x10000); break; // 64KB - } + switch (data[0x149]) { + case 0x00: break; // No RAM + case 0x01: this.extram = new Uint8Array(0x800); break; // 2KB + case 0x02: this.extram = new Uint8Array(0x2000); break; // 8KB + case 0x03: this.extram = new Uint8Array(0x8000); break; // 32KB + case 0x04: this.extram = new Uint8Array(0x20000); break; // 128KB + case 0x05: this.extram = new Uint8Array(0x10000); break; // 64KB + default: console.log(`Invalid RAM size code @ 0x149: ${data[0x149]}`); break; } this.reset(); diff --git a/src/tools/8bws.ts b/src/tools/8bws.ts index 9351cada..3bef7a37 100644 --- a/src/tools/8bws.ts +++ b/src/tools/8bws.ts @@ -3,7 +3,8 @@ // 8bws - 8bitworkshop CLI tool for compilation, ROM execution, and platform info import * as fs from 'fs'; -import { initialize, compile, compileSourceFile, preload, listTools, listPlatforms, getToolForFilename, PLATFORM_PARAMS, TOOLS, TOOL_PRELOADFS } from './testlib'; +import * as path from 'path'; +import { initialize, compile, compileSourceFile, preload, listTools, listPlatforms, getToolForFilename, PLATFORM_PARAMS, TOOLS, TOOL_PRELOADFS, store } from './testlib'; import { isDebuggable } from '../common/baseplatform'; import { hex } from '../common/util'; @@ -91,7 +92,8 @@ function formatHelp(data: any): void { console.log(` ${c.green}${cmd}${c.reset}${c.dim} - ${usage}${c.reset}`); } console.log(`\n${c.bold}Global options:${c.reset}`); - console.log(` ${c.yellow}--json${c.reset}${c.dim} Output raw JSON instead of formatted text${c.reset}`); + console.log(` ${c.yellow}--json${c.reset}${c.dim} Output raw JSON instead of formatted text${c.reset}`); + console.log(` ${c.yellow}--save${c.reset}${c.dim} Save all intermediate build files to /tmp/8bws-${c.reset}`); console.log(); } } @@ -114,6 +116,31 @@ function formatCompile(data: any): void { if (data.outputFile) console.log(` ${c.dim}Output:${c.reset} ${c.cyan}${data.outputFile}${c.reset}`); if (data.hasListings) console.log(` ${c.dim}Listings:${c.reset} ${c.green}yes${c.reset}`); if (data.hasSymbolmap) console.log(` ${c.dim}Symbols:${c.reset} ${c.green}yes${c.reset}`); + + // --symbols: dump symbol map + if (data.symbolmap) { + console.log(`\n${c.bold}Symbols${c.reset} ${c.dim}(${Object.keys(data.symbolmap).length})${c.reset}`); + var sorted = Object.entries(data.symbolmap).sort((a: any, b: any) => a[1] - b[1]); + for (var [name, addr] of sorted) { + console.log(` ${c.cyan}$${hex(addr as number, 4)}${c.reset} ${name}`); + } + } + + // --save: show saved files + if (data.saveDir) { + console.log(`\n${c.bold}Saved to${c.reset} ${c.cyan}${data.saveDir}${c.reset} ${c.dim}(${data.savedFiles.length} files)${c.reset}`); + for (var f of data.savedFiles) { + console.log(` ${c.dim}●${c.reset} ${f}`); + } + } + + // --symbols: dump segments + if (data.segments) { + console.log(`\n${c.bold}Segments${c.reset} ${c.dim}(${data.segments.length})${c.reset}`); + for (var seg of data.segments) { + console.log(` ${c.green}${seg.name.padEnd(16)}${c.reset} ${c.cyan}$${hex(seg.start, 4)}${c.reset} ${c.dim}size${c.reset} ${c.yellow}${seg.size}${c.reset}`); + } + } } function formatListTools(data: any): void { @@ -152,7 +179,7 @@ function formatGeneric(data: any): void { } } -var BOOLEAN_FLAGS = new Set(['json', 'info']); +var BOOLEAN_FLAGS = new Set(['json', 'info', 'symbols', 'save']); function parseArgs(argv: string[]): { command: string; args: { [key: string]: string }; positional: string[] } { var command = argv[2]; @@ -183,7 +210,7 @@ function usage(): void { command: 'help', data: { commands: { - 'compile': 'compile --platform [--tool ] [--output ] ', + 'compile': 'compile --platform [--tool ] [--output ] [--symbols] [--save] ', 'check': 'check --platform [--tool ] ', 'run': 'run (--platform | --machine ) [--frames N] [--output ] [--memdump start,end] [--info] ', 'list-tools': 'list-tools', @@ -273,18 +300,45 @@ async function doCompile(args: { [key: string]: string }, positional: string[], outputSize = result.output.code ? result.output.code.length : result.output.length; } + var compileData: any = { + tool: tool, + platform: platform, + source: sourceFile, + outputSize: outputSize, + outputFile: outputFile || null, + hasListings: result.listings ? Object.keys(result.listings).length > 0 : false, + hasSymbolmap: !!result.symbolmap, + }; + + if (args['symbols'] === 'true') { + if (result.symbolmap) compileData.symbolmap = result.symbolmap; + if (result.segments) compileData.segments = result.segments; + } + + // --save: write all intermediate build files to /tmp/ + if (args['save'] === 'true') { + var baseName = path.basename(sourceFile, path.extname(sourceFile)); + var saveDir = path.join('/tmp', `8bws-${baseName}`); + fs.mkdirSync(saveDir, { recursive: true }); + var savedFiles: string[] = []; + for (var [filePath, entry] of Object.entries(store.workfs)) { + var outPath = path.join(saveDir, filePath); + fs.mkdirSync(path.dirname(outPath), { recursive: true }); + if (entry.data instanceof Uint8Array) { + fs.writeFileSync(outPath, entry.data); + } else { + fs.writeFileSync(outPath, entry.data); + } + savedFiles.push(filePath); + } + compileData.saveDir = saveDir; + compileData.savedFiles = savedFiles; + } + output({ success: true, command: 'compile', - data: { - tool: tool, - platform: platform, - source: sourceFile, - outputSize: outputSize, - outputFile: outputFile || null, - hasListings: result.listings ? Object.keys(result.listings).length > 0 : false, - hasSymbolmap: !!result.symbolmap, - } + data: compileData, }); } diff --git a/src/tools/testlib.ts b/src/tools/testlib.ts index 1325a5e0..d02b17fb 100644 --- a/src/tools/testlib.ts +++ b/src/tools/testlib.ts @@ -30,6 +30,7 @@ export interface CompileResult { errors?: { line: number; msg: string; path?: string }[]; listings?: any; symbolmap?: any; + segments?: any; params?: any; unchanged?: boolean; } @@ -381,6 +382,7 @@ function workerResultToCompileResult(result: WorkerResult): CompileResult { output: result.output, listings: (result as any).listings, symbolmap: (result as any).symbolmap, + segments: (result as any).segments, params: (result as any).params, }; } diff --git a/src/worker/platforms.ts b/src/worker/platforms.ts index 60b69826..2e618c5c 100644 --- a/src/worker/platforms.ts +++ b/src/worker/platforms.ts @@ -364,7 +364,8 @@ export var PLATFORM_PARAMS = { }, 'gb': { arch: 'gbz80', - code_start: 0x0, + code_start: 0x0, // ROM starts @ 0x0, header @ 0x100, etc. + codeseg_start: 0x200, // _CODE area starts here rom_size: 0x8000, data_start: 0xc0a0, data_size: 0x1f60, diff --git a/src/worker/tools/sdcc.ts b/src/worker/tools/sdcc.ts index c2c9ee6b..d91ef865 100644 --- a/src/worker/tools/sdcc.ts +++ b/src/worker/tools/sdcc.ts @@ -14,22 +14,25 @@ function hexToArray(s, ofs) { return arr; } -function parseIHX(ihx, rom_start, rom_size, errors) { +function parseIHX(ihx: string, rom_start: number, rom_size: number, errors: WorkerError[]) { var output = new Uint8Array(new ArrayBuffer(rom_size)); var high_size = 0; for (var s of ihx.split("\n")) { if (s[0] == ':') { var arr = hexToArray(s, 1); var count = arr[0]; - var address = (arr[1] << 8) + arr[2] - rom_start; + var offset = (arr[1] << 8) + arr[2] - rom_start; var rectype = arr[3]; //console.log(rectype,address.toString(16),count,arr); if (rectype == 0) { + if (output[offset] !== 0) { + errors.push({line:0,msg:`IHX overlap offset 0x${(offset).toString(16)}`}); + } for (var i = 0; i < count; i++) { var b = arr[4 + i]; - output[i + address] = b; + output[i + offset] = b; } - if (i + address > high_size) high_size = i + address; + if (i + offset > high_size) high_size = i + offset; } else if (rectype == 1) { break; } else { @@ -144,6 +147,7 @@ export async function assembleSDASGB(step: BuildStep): Promise export function linkSDLDZ80(step: BuildStep) { loadNative("sdldz80"); + const arch = step.params.arch || 'z80'; var errors = []; gatherFiles(step); var binpath = "main.ihx"; @@ -177,10 +181,10 @@ export function linkSDLDZ80(step: BuildStep) { FS.writeFile('crt0.lst', '\n'); // TODO: needed so -u flag works } var args = ['-mjwxyu', - '-i', 'main.ihx', // TODO: main? - '-b', '_CODE=0x' + params.code_start.toString(16), + '-i', 'main.ihx', + '-b', '_CODE=0x' + (params.codeseg_start||params.code_start).toString(16), '-b', '_DATA=0x' + params.data_start.toString(16), - '-k', '/share/lib/z80', + '-k', `/share/lib/z80`, // TODO: $arch for gbz80 '-l', 'z80']; if (params.extra_link_args) args.push.apply(args, params.extra_link_args);