From 344cdd21f98922fc656a049314d2a0bed88ae70b Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Sat, 3 Oct 2026 15:37:05 +0200 Subject: [PATCH] c64: fullscrollgame-sdcc.c for sdcc4, fixed source line offset bug, test --- CHANGELOG.md | 3 ++- presets/c64/fullscrollgame-sdcc.c | 17 +++++++++++++++++ presets/c64/fullscrollgame.c | 4 +++- presets/c64/scrolling2.c | 14 ++++++++++---- src/platform/c64.ts | 1 + src/worker/listingutils.ts | 5 ++++- src/worker/tools/sdcc.ts | 7 +++++++ test/cli/testcli.js | 2 +- test/unit/testsdcclisting.ts | 15 +++++++++++++++ 9 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 presets/c64/fullscrollgame-sdcc.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 288fcc19..2cf7fffd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,7 +55,8 @@ See [extension/CHANGELOG.md](extension/CHANGELOG.md). ### Build tools - Many toolchains are now WASI builds: cc65 (pinned to 2021 6ac4aa4), DASM 2.20.17, acme, zmac, yasm and merlin32. -- SDCC 4 mos6502 backend: C64 presets (`hello-sdcc.c`, `screen_ram-sdcc.c`, `joymove-sdcc.c`, `siegegame-sdcc.c`, `sprite_collision-sdcc.c`); `load_header` platform param wraps the image as a DOS 3.3 binary (apple2) or a PRG with a `SYS` stub (c64). +- SDCC 4 mos6502 backend: C64 presets (`hello-sdcc.c`, `screen_ram-sdcc.c`, `joymove-sdcc.c`, `siegegame-sdcc.c`, `sprite_collision-sdcc.c`, `fullscrollgame-sdcc.c`); `load_header` platform param wraps the image as a DOS 3.3 binary (apple2) or a PRG with a `SYS` stub (c64). +- C64 `fullscrollgame.c` now takes its level data from `level2-data.c` (the same data as `level2.s`, in C) so the SDCC build can link it; `scrolling2.c` uses plain C copies under SDCC. - Updated batari BASIC; sdcc now parses `#symbol` directives and fixes lost link errors. - New build directives replace the legacy CFGFILE-style ones; build parameters no longer leak between builds. - `#pragma compile("...")` and oscar64 symbol/listing parsing. diff --git a/presets/c64/fullscrollgame-sdcc.c b/presets/c64/fullscrollgame-sdcc.c new file mode 100644 index 00000000..5d9c6625 --- /dev/null +++ b/presets/c64/fullscrollgame-sdcc.c @@ -0,0 +1,17 @@ +// fullscrollgame.c built with SDCC's mos6502 backend instead of cc65. +// The program is fullscrollgame.c; common.h supplies the C64 definitions SDCC lacks. +// (The C files are #included, since cc65 and SDCC objects can't be linked together, +// and the level data from level2.s is converted to C in level2-data.c.) + +#include "common.h" +#include "common.c" + +#include "scrolling.h" +#include "scrolling2.c" + +#include "sprites.h" +#include "sprites.c" + +#include "level2-data.c" + +#include "fullscrollgame.c" diff --git a/presets/c64/fullscrollgame.c b/presets/c64/fullscrollgame.c index 4a3c7810..25ca32fc 100644 --- a/presets/c64/fullscrollgame.c +++ b/presets/c64/fullscrollgame.c @@ -1,4 +1,5 @@ +#ifndef __SDCC #include #include #include @@ -7,6 +8,7 @@ #include #include #include +#endif //#resource "c64-sid.cfg" #define CFGFILE c64-sid.cfg @@ -20,7 +22,7 @@ #include "sprites.h" //#link "sprites.c" -//#link "level2.s" +//#link "level2-data.c" #define CAMERA_OFFSET_X 158 #define CAMERA_OFFSET_Y 120 diff --git a/presets/c64/scrolling2.c b/presets/c64/scrolling2.c index 0c9cf0b4..e9d70fd2 100644 --- a/presets/c64/scrolling2.c +++ b/presets/c64/scrolling2.c @@ -43,6 +43,15 @@ void copy_color_ram_slow() { memcpy(COLOR_RAM, colorbuf, COLS*ROWS); } +void copy_to_hidden_buffer_slow() { + memcpy(hidbuf, visbuf, COLS*ROWS); +} + +#ifdef __SDCC +// (SDCC build: no cc65 inline assembly, so use the plain C copies) +void copy_color_ram_fast() { copy_color_ram_slow(); } +void copy_to_hidden_buffer_fast() { copy_to_hidden_buffer_slow(); } +#else void copy_color_ram_fast() { // fast copy loop for upper 1/2 of color ram asm("ldy #0"); @@ -63,10 +72,6 @@ void copy_color_ram_fast() { asm("bne @loop2"); } -void copy_to_hidden_buffer_slow() { - memcpy(hidbuf, visbuf, COLS*ROWS); -} - void copy_to_hidden_buffer_fast() { // self-modifying code asm("ldy %v+1", visbuf); @@ -101,6 +106,7 @@ void copy_to_hidden_buffer_fast() { asm("@skip: iny"); asm("bne @loop"); } +#endif void scroll_start(byte dir) { if (scroll_seq == 0 && dir) { diff --git a/src/platform/c64.ts b/src/platform/c64.ts index 26a3d221..68ab9162 100644 --- a/src/platform/c64.ts +++ b/src/platform/c64.ts @@ -48,6 +48,7 @@ const C64_PRESETS : Preset[] = [ {id:'lander.cpp', name:'Lunar Lander'}, {id:'hello-sdcc.c', name:'Hello World (SDCC)', category:'C (SDCC)'}, {id:'sprite_collision-sdcc.c', name:'Sprite Collision (SDCC)'}, + {id:'fullscrollgame-sdcc.c', name:'Full-Scrolling Game (SDCC)'}, ]; const C64_MEMORY_MAP = { main:[ diff --git a/src/worker/listingutils.ts b/src/worker/listingutils.ts index 5f75d9c3..8f43b956 100644 --- a/src/worker/listingutils.ts +++ b/src/worker/listingutils.ts @@ -102,6 +102,7 @@ export function parseListing(code: string, export function parseSourceLines(code: string, lineMatch, offsetMatch, funcMatch?, segMatch?) { var lines = []; var lastlinenum = 0; + var lastpath: string; var state = { segment: '', func: '', funcbase: 0 }; for (var line of code.split(re_crlf)) { parseSegFunc(line, segMatch, funcMatch, state); @@ -109,6 +110,7 @@ export function parseSourceLines(code: string, lineMatch, offsetMatch, funcMatch var linem = lineMatch.exec(line); if (linem && linem[1]) { lastlinenum = parseInt(linem[1]); + lastpath = linem[2]; // optional source file, for listings that mix files } else if (lastlinenum) { var linem = offsetMatch.exec(line); if (linem && linem[1]) { @@ -117,7 +119,8 @@ export function parseSourceLines(code: string, lineMatch, offsetMatch, funcMatch line: lastlinenum, offset: offset - state.funcbase, segment: state.segment, - func: state.func + func: state.func, + ...(lastpath ? { path: lastpath } : {}) }); lastlinenum = 0; } diff --git a/src/worker/tools/sdcc.ts b/src/worker/tools/sdcc.ts index 6613438a..7a93f1a0 100644 --- a/src/worker/tools/sdcc.ts +++ b/src/worker/tools/sdcc.ts @@ -559,6 +559,13 @@ export function parseRSTListing(rstout: string, srcprefix: string) { // the mos6502 backend writes `;file.c: 17: source`, with a space after the colon const srcre = new RegExp(`^\\s+\\d+ ;\\s*(?:|/*${name}\\.[^:\\s]+):\\s*(\\d+):`, 'i'); var srclines = parseSourceLines(rstout, srcre, /^\s*([0-9A-F]{4,8})/i); + if (!srclines.length) { + // the mos6502 backend names the file of every line, so a main file + // that #includes the others (the -sdcc.c wrappers) still gets a + // listing: tag each line with its file (see processListings); + // the pattern captures the line number first, then the file name + srclines = parseSourceLines(rstout, /^\s+\d+ ;\t(?=[^\s:]+: (\d+):)([^\s:]+)/, /^\s*([0-9A-F]{4,8})/i); + } // TODO: you have to get rid of all source lines to get asm listing return { asmlines: srclines.length ? asmlines : null, diff --git a/test/cli/testcli.js b/test/cli/testcli.js index dad9af08..eaa6e53d 100644 --- a/test/cli/testcli.js +++ b/test/cli/testcli.js @@ -132,7 +132,7 @@ describe('8bws CLI', function () { assert.equal(bin[2] | bin[3] << 8, bin.length - 4); assert.ok(bin.length > 4000); // the whole game, not just the wrapper }); - ['hello', 'screen_ram', 'joymove', 'siegegame', 'sprite_collision'].forEach(function (name) { + ['hello', 'sprite_collision'].forEach(function (name) { it('should build the c64 ' + name + ' SDCC preset as a PRG', function () { var out = path.join(os.tmpdir(), '8bws-c64-' + name + '-sdcc.prg'); cli('build', '-p', 'c64', 'presets/c64/' + name + '-sdcc.c', '-o', out); diff --git a/test/unit/testsdcclisting.ts b/test/unit/testsdcclisting.ts index 3b93844b..e5e8e9d9 100644 --- a/test/unit/testsdcclisting.ts +++ b/test/unit/testsdcclisting.ts @@ -28,8 +28,23 @@ const RST_MOS6502 = [ " 00000869 20 4E 08 [ 6] 138 \tjsr\t_putstr", ].join("\n"); +// a main file that only #includes the others (e.g. fullscrollgame-sdcc.c) +const RST_MOS6502_INCLUDES = [ + " 00000865 126 _main:", + " 128 ;\tgame.c: 18: putstr(message);", + " 00000865 A2 08 [ 2] 136 \tldx\t#>_message", + " 140 ;\tlib.c: 4: x++;", + " 00000869 E6 02 [ 5] 142 \tinc\t*_x", +].join("\n"); + describe("sdcc listings", function () { + it("tags mos6502 lines with the included file they came from", function () { + const l = parseRSTListing(RST_MOS6502_INCLUDES, "game-sdcc"); + assert.deepStrictEqual(l.lines.map((x) => [x.path, x.line, x.offset]), + [["game.c", 18, 0x865], ["lib.c", 4, 0x869]]); + }); + it("maps mos6502 source comments", function () { const l = parseRSTListing(RST_MOS6502, "hello"); assert.deepStrictEqual(l.lines.map((x) => [x.line, x.offset]), [[17, 0x865], [18, 0x865]]); // the function line maps to its label