cc65: upgraded to V2.19-3867

apple2: added hires tgi driver, patches lores tgi driver in cc65
This commit is contained in:
Steven Hugg committed 2026-10-03 09:27:49 +02:00
1 parent 410b063c15
commit 5c813c2526
32 files changed
+1007 -35

No files matched your search

+7 -1
View File
@@ -9,6 +9,7 @@ See [extension/CHANGELOG.md](extension/CHANGELOG.md).
### Debugging and the `8bws` CLI
- New `8bws` command-line tool (`npm run cli`) to build and run programs headlessly: `build`, `run`, `verify-replay`, `profile`, `detect`, `list-platforms`, `list-tools`; scripted emulator control; `--png`, `--json`, `--symbols`, `--memdump`, `--info`; platform and main file detected from a project directory.
- Platform detection no longer confuses the Atari 8-bit targets: `atari.h` means the 800 (it is guarded by `__ATARI__`), `atari5200.h` means the 5200, and a folder named for a platform breaks a near-tie.
- Emulators now run on a timeline: step back, seek, reverse run, `rbreak`, and deterministic replay with a replay-verification test that found determinism bugs in several platforms.
- Step Over for C and assembly sources, a Breakpoints pane, and more breakpoint conditions.
- Call Stack view renamed Call Graph, with clocks and more detail per line.
@@ -16,6 +17,7 @@ See [extension/CHANGELOG.md](extension/CHANGELOG.md).
- Click the hex offset in the gutter to run to that line.
- Waveform viewer for Verilog, with help and shortcuts.
- Faster line lookups in listings.
- Atari 8-bit: the XEX loader now calls each INIT vector before loading the next chunk (cc65's system-check chunk overlaps the main program), fixing a crash on startup of `tgidemo.c`.
### Editor and UI
@@ -41,7 +43,7 @@ See [extension/CHANGELOG.md](extension/CHANGELOG.md).
- **Game Boy**: native emulator with GBC double speed, RTC and banking; C via a partial gbdk-2020 header set; presets ported from GBDK, plus Chase and Paku Paku; ghosting on the LCD.
- **Atari 8-bit**: `.atr` disk images, 5th player, ANTIC and POKEY fixes, a8lib common library, scrolling and libdemo examples, oscar64 support.
- **Atari 2600**: a Javatari-derived `vcs.jt4` machine that also runs headlessly; updated `vcs.h`/`macro.h`.
- **Apple II**: paddle/joystick and SW0-2 support, dynamic origin, DOS 3.3 and AppleSingle samples.
- **Apple II**: paddle/joystick and SW0-2 support, dynamic origin, DOS 3.3 and AppleSingle samples. New self-contained hi-res TGI driver (`a2hires.s`, 280x192, 8 colors, no ROM calls) used by the TGI demos.
- **NES**: updated jsnes and a fix for a write bug.
- **SMS/Game Gear/ColecoVision**: shared code, SMS Solarian port, more presets.
- **Arcade**: Pac-Man hardware, Williams, Galaxian and Astrocade accuracy updates; early Exidy, MCR and Channel F work.
@@ -57,6 +59,10 @@ See [extension/CHANGELOG.md](extension/CHANGELOG.md).
- New build directives replace the legacy CFGFILE-style ones; build parameters no longer leak between builds.
- `#pragma compile("...")` and oscar64 symbol/listing parsing.
- Tool metadata centralized in `src/common/toolmeta.ts`; `npm run toolversions` checks tool versions.
- cc65/ca65/ld65 warnings no longer fail the build; only errors are reported (the WASI tools print non-fatal `Warning:` lines such as duplicate `const` qualifiers).
- cc65 toolchain (cc65/ca65/ld65 WASI builds and the per-platform include/lib zips) updated from the 2021 build (`6ac4aa4`) to cc65 `d8a486a` (V2.19-3867, 2026-09-26). The compiler runs with `--disable-opt OptLoadStore1` (the new step drops stores after loads of the same address, which breaks hardware registers and inline asm) and `-unreachable-code`.
- cc65 `a2.lo.s` (Apple II lo-res TGI) patched in `8bitworkshop-compilers/patches/cc65-apple2-lo-tgi.patch`: a stray `bpl :+` in `INSTALL` made `tgi_install(a2_lo_tgi)` crash on a non-enhanced Apple II.
- Presets adjusted for the newer cc65: NES `chase` closes its zero-page `#pragma` region, C64 `side_scroller` uses an unsigned fixed-point constant, and the Atari vector presets no longer recurse into `main()`. `neslib.h` no longer redefines `NULL` if `stddef.h` already did.
- Build is now only `esbuild` through `scripts/build.mjs`; code split into a headless `src/common/projectcore.ts` / `toolselect.ts`.
- New `buildpresets` script that builds and runs every preset.
+2
View File
@@ -0,0 +1,2 @@
/* Self-contained hi-res TGI driver (280x192, 8 colors); see a2hires.s */
extern unsigned char a2hires_tgi[];
+499
View File
@@ -0,0 +1,499 @@
;
; Self-contained TGI driver for the Apple ][ hi-res screen (280x192, 8 colors)
;
; The stock cc65 hi-res driver calls Applesoft ROM routines, which the
; emulator's ROM doesn't have. This one draws everything itself.
; Usage: //#link "a2hires.s" then tgi_install(a2hires_tgi);
; (no text output, no palette changes)
;
; The picture lives on hi-res page 2 ($4000-$5FFF), so programs may use
; everything below $4000 (the default config puts code at $0803 up).
;
.include "zeropage.inc"
.include "tgi-kernel.inc"
.include "tgi-error.inc"
.export _a2hires_tgi
; Soft switches
TXTCLR := $C050
TXTSET := $C051
MIXCLR := $C052
MIXSET := $C053 ; MIXCLR+1
LOWSCR := $C054
HISCR := $C055 ; LOWSCR+1
LORES := $C056
HIRES := $C057
WNDTOP := $22
; Parameters passed by the TGI kernel
X1 := ptr1
Y1 := ptr2
X2 := ptr3
Y2 := ptr4
; Scratch (never live across a driver call)
SCR := sreg ; pointer to screen byte
MASK := tmp1 ; pixel mask within byte
TMP := tmp2
; ------------------------------------------------------------------------
.rodata
; Driver header: signature, capabilities, then the jump table.
; Entries are absolute since this driver is linked statically.
_a2hires_tgi:
.byte $74, $67, $69 ; "tgi"
.byte TGI_API_VERSION
.addr $0000 ; Library reference
.word 280 ; X resolution
.word 192 ; Y resolution
.byte 8 ; Number of drawing colors
.byte 1 ; Number of screens
.byte 7 ; System font X size
.byte 8 ; System font Y size
.word $00EA ; Aspect ratio (based on 4/3 display)
.byte 0 ; TGI driver flags
.addr INSTALL
.addr UNINSTALL
.addr INIT
.addr DONE
.addr GETERROR
.addr CONTROL
.addr CLEAR
.addr SETVIEWPAGE
.addr SETDRAWPAGE
.addr SETCOLOR
.addr SETPALETTE
.addr GETPALETTE
.addr GETDEFPALETTE
.addr SETPIXEL
.addr GETPIXEL
.addr LINE
.addr BAR
.addr TEXTSTYLE
.addr OUTTEXT
; Colors: 0 black, 1 green, 2 violet, 3 white, 4 black2, 5 orange, 6 blue, 7 white2
DEFPALETTE:
.byte 0, 1, 2, 3, 4, 5, 6, 7
; Per-color pixel behavior, indexed by color & 3
EVENSET: .byte 0, 0, 1, 1 ; set the pixel in even columns?
ODDSET: .byte 0, 1, 0, 1 ; set the pixel in odd columns?
ROWOFS: .byte 0, 40, 80 ; 40 bytes per row of 8 scanlines, 3 groups
; ------------------------------------------------------------------------
.bss
ERROR: .res 1
SETEVEN: .res 1 ; current color: set pixel in even columns
SETODD: .res 1 ; set pixel in odd columns
PALBIT: .res 1 ; palette bit ($00 or $80)
XDIV: .res 256 ; x / 7
XMASK: .res 256 ; 1 << (x % 7)
ROWLO: .res 192 ; address of scanline y on page 1
ROWHI: .res 192
; Pixel position, also used as the running point of LINE
PX: .res 2
PY: .res 1
; Line state
SXDIR: .res 1 ; $00 = x increases, $FF = decreases
SYDIR: .res 1
MAJVEC: .res 2 ; routine that steps the major axis
MINVEC: .res 2 ; ... and the minor axis
LEN: .res 2 ; major axis length (steps remaining)
TOT: .res 2 ; copy of LEN
MINOR: .res 2 ; minor axis length
ERR: .res 2
; ------------------------------------------------------------------------
.code
INSTALL:
UNINSTALL:
SETVIEWPAGE:
SETDRAWPAGE:
TEXTSTYLE:
OUTTEXT:
rts
; INIT: build lookup tables, then switch on page 1 of hi-res
INIT:
; x -> byte offset and bit mask, for x = 0..255
ldx #0
stx tmp3 ; div
stx tmp4 ; mod
lda #1
sta MASK
@xloop: lda tmp3
sta XDIV,x
lda MASK
sta XMASK,x
asl MASK
inc tmp4
lda tmp4
cmp #7
bcc @nx
lda #0
sta tmp4
lda #1
sta MASK
inc tmp3
@nx: inx
bne @xloop
; y -> address of scanline: $2000 + (y&7)*$400 + ((y>>3)&7)*$80 + (y>>6)*40
ldy #0
@yloop: tya
and #7
asl
asl
ora #$20
sta TMP
tya
lsr
lsr
lsr
and #7
lsr ; carry = bit 0 of the 8-line group
ora TMP
sta TMP
lda #0
ror ; $80 if carry
sta MASK
tya
asl
rol
rol
and #3
tax
lda ROWOFS,x
clc
adc MASK
sta ROWLO,y
lda TMP
adc #0
sta ROWHI,y
iny
cpy #192
bne @yloop
bit MIXCLR
bit HISCR
bit HIRES
bit TXTCLR
lda #TGI_ERR_OK
sta ERROR
rts
DONE:
bit TXTSET
bit LOWSCR
bit LORES
lda #0
sta WNDTOP
rts
GETERROR:
lda ERROR
ldx #TGI_ERR_OK
stx ERROR
rts
; CONTROL: data = 0 for full screen, 1 for 4 lines of text
CONTROL:
ora ptr1+1
bne @err
lda ptr1
cmp #2
bcs @err
tax
beq @top
lda #20
@top: sta WNDTOP
lda MIXCLR,x
lda #TGI_ERR_OK
.byte $2C ; BIT absolute
@err: lda #TGI_ERR_INV_ARG
sta ERROR
rts
; CLEAR: fill the screen with zeros
CLEAR:
lda #0
sta SCR
lda #$40
sta SCR+1
ldx #32 ; 32 pages of 256 bytes
ldy #0
tya
@loop: sta (SCR),y
iny
bne @loop
inc SCR+1
dex
bne @loop
rts
; SETCOLOR: A = 0..7
SETCOLOR:
tax
and #3
tay
lda EVENSET,y
sta SETEVEN
lda ODDSET,y
sta SETODD
txa
and #4
beq @lo
lda #$80
@lo: sta PALBIT
rts
SETPALETTE:
lda #TGI_ERR_INV_FUNC
sta ERROR
rts
GETPALETTE:
GETDEFPALETTE:
lda #<DEFPALETTE
ldx #>DEFPALETTE
rts
; ADDR: locate the pixel at PX/PY. Returns the byte address in (SCR),y
; and the pixel's bit in MASK.
ADDR:
ldx PY
lda ROWLO,x
sta SCR
lda ROWHI,x
clc
adc #$20 ; page 2
sta SCR+1
ldy #0 ; byte offset of the 256 column page
ldx PX
lda PX+1
beq @lo
txa ; x >= 256: use x-252, which is 36 bytes on
sec
sbc #252
tax
ldy #36
@lo: tya
clc
adc XDIV,x
tay
lda XMASK,x
sta MASK
rts
; PLOT: draw the pixel at PX/PY in the current color
PLOT:
jsr ADDR
ldx SETEVEN
lda PX
lsr ; carry = odd column
bcc @even
ldx SETODD
@even: lda MASK
ora #$80 ; the palette bit is always rewritten
eor #$FF
and (SCR),y
ora PALBIT
cpx #0
beq @store
ora MASK
@store: sta (SCR),y
rts
SETPIXEL:
lda X1
sta PX
lda X1+1
sta PX+1
lda Y1
sta PY
jmp PLOT
; GETPIXEL: returns black/white (+4 for the second palette)
GETPIXEL:
lda X1
sta PX
lda X1+1
sta PX+1
lda Y1
sta PY
jsr ADDR
lda (SCR),y
tax ; remember the palette bit
and MASK
beq @black
lda #3
@black: cpx #$80
bcc @done
adc #3 ; carry is set: +4
@done: ldx #0
rts
; LINE: DDA between X1/Y1 and X2/Y2. The major axis is stepped every pixel,
; and the minor axis whenever the accumulated error passes the major length.
LINE:
lda X1
sta PX
lda X1+1
sta PX+1
lda Y1
sta PY
; dx = |x2 - x1|
lda #0
sta SXDIR
sec
lda X2
sbc X1
sta LEN
lda X2+1
sbc X1+1
sta LEN+1
bcs @dxpos
dec SXDIR ; $FF
sec ; negate
lda #0
sbc LEN
sta LEN
lda #0
sbc LEN+1
sta LEN+1
@dxpos:
; dy = |y2 - y1|
lda #0
sta SYDIR
sta MINOR+1
sec
lda Y2
sbc Y1
bcs @dypos
dec SYDIR
eor #$FF
adc #1
@dypos: sta MINOR
; the major axis is the longer one
lda MINOR
cmp LEN
lda MINOR+1
sbc LEN+1
bcs @ymajor ; dy >= dx
lda #<stepx
sta MAJVEC
lda #>stepx
sta MAJVEC+1
lda #<stepy
sta MINVEC
lda #>stepy
sta MINVEC+1
bne @go ; branch always
@ymajor:
lda LEN ; swap: LEN <-> MINOR
ldx MINOR
sta MINOR
stx LEN
lda LEN+1
ldx MINOR+1
sta MINOR+1
stx LEN+1
lda #<stepy
sta MAJVEC
lda #>stepy
sta MAJVEC+1
lda #<stepx
sta MINVEC
lda #>stepx
sta MINVEC+1
@go: lda LEN
sta TOT
lda LEN+1
sta TOT+1
lsr ; error starts at half the length
sta ERR+1
lda LEN
ror
sta ERR
@loop: jsr PLOT
lda LEN
ora LEN+1
beq @done
lda LEN
bne @nb
dec LEN+1
@nb: dec LEN
jsr @major
clc
lda ERR
adc MINOR
sta ERR
lda ERR+1
adc MINOR+1
sta ERR+1
lda ERR ; error >= length?
cmp TOT
lda ERR+1
sbc TOT+1
bcc @loop
sec ; yes: subtract it and step the minor axis
lda ERR
sbc TOT
sta ERR
lda ERR+1
sbc TOT+1
sta ERR+1
jsr @minor
jmp @loop
@done: rts
@major: jmp (MAJVEC)
@minor: jmp (MINVEC)
stepx: lda SXDIR
bne @dec
inc PX
bne @ret
inc PX+1
@ret: rts
@dec: lda PX
bne @nb
dec PX+1
@nb: dec PX
rts
stepy: lda SYDIR
bne @dec
inc PY
rts
@dec: dec PY
rts
; BAR: horizontal lines from Y1 to Y2 (the kernel sorts and clips the corners)
BAR:
inc Y2
@row: lda Y2
pha
lda Y1
sta Y2
jsr LINE
pla
sta Y2
inc Y1
cmp Y1
bne @row
rts
+165
View File
@@ -0,0 +1,165 @@
/*
Hi-res (280x192, 6 colors) TGI demo for the Apple ][.
Four full-screen animations; press a key to switch to the next (ESC quits):
a Lissajous line figure, a swirl of dots, random lines, random bars.
Each shape is remembered in a ring buffer; when the buffer wraps, the
oldest shape is erased, so the picture keeps moving.
Uses our own hi-res TGI driver, a2hires.s (the stock one needs
Applesoft ROM routines). apple2-hgr2.cfg keeps $2000-$5FFF free.
*/
#include <stdlib.h>
#include <cc65.h>
#include <conio.h>
#include <tgi.h>
#include "a2hires.h"
//#link "a2hires.s"
//#tooldef ld cfgfile=apple2-hgr2.cfg
//#resource "apple2-hgr2.cfg"
#define W 280 // screen size
#define H 192
#define R (H/2 - 4) // radius of the figures
#define MAXSEG 240
// hi-res colors: 1 green, 2 violet, 5 orange, 6 blue
static const unsigned char Colors[4] = { 1, 2, 5, 6 };
#define SIN(a) _sin((unsigned)(a) % 360)
#define COS(a) _sin(((unsigned)(a) + 90) % 360)
typedef struct {
int x1, y1, x2, y2;
unsigned char color;
} Seg;
static Seg ring[MAXSEG]; // the shapes currently on screen
static unsigned head; // next slot to overwrite
static unsigned count; // slots in use
static unsigned T; // time, advances every step
static unsigned char mode;
static const unsigned ringsize[4] = { 40, 240, 70, 14 };
// scale a sine value (-256..256) to -R..R
static int scale(int v)
{
return (int)(((long)v * R) / 256);
}
static unsigned char randcolor(void)
{
return Colors[rand() & 3];
}
// draw (or, with color 0, erase) one shape for the current mode
static void draw(const Seg* s, unsigned char color)
{
int cx = W/2, cy = H/2;
tgi_setcolor(color);
switch (mode) {
case 0: // line, mirrored four ways
tgi_line(cx + s->x1, cy + s->y1, cx + s->x2, cy + s->y2);
tgi_line(cx - s->x1, cy + s->y1, cx - s->x2, cy + s->y2);
tgi_line(cx + s->x1, cy - s->y1, cx + s->x2, cy - s->y2);
tgi_line(cx - s->x1, cy - s->y1, cx - s->x2, cy - s->y2);
break;
case 1: // dot
tgi_setpixel(s->x1, s->y1);
break;
case 2: // line
tgi_line(s->x1, s->y1, s->x2, s->y2);
break;
case 3: // bar
tgi_bar(s->x1, s->y1, s->x2, s->y2);
break;
}
}
// Lissajous lines
static void make_lissajous(Seg* s)
{
unsigned a = T * 2;
s->x1 = scale(SIN(a * 3)) * 3/2;
s->y1 = scale(COS(a * 2));
s->x2 = scale(COS(a * 5 + T)) * 3/2;
s->y2 = scale(SIN(a * 4));
s->color = Colors[(T >> 4) & 3];
}
// dots on a wandering rose curve
static void make_swirl(Seg* s)
{
unsigned a = T * 7;
int r = scale(SIN(a * 5 + T / 2)) + scale(COS(a * 3)) / 2;
s->x1 = W/2 + r * COS(a) / 128;
s->y1 = H/2 + r * SIN(a) / 256;
s->color = Colors[(a / 90) & 3];
}
// random horizontal and vertical lines
static void make_hatch(Seg* s)
{
int x = rand() % W;
int y = rand() % H;
int len = 4 + rand() % 60;
s->x1 = s->x2 = x;
s->y1 = s->y2 = y;
if (rand() & 1) {
s->x2 = x + len < W ? x + len : W - 1;
} else {
s->y2 = y + len < H ? y + len : H - 1;
}
s->color = randcolor();
}
// random filled bars
static void make_bar(Seg* s)
{
int x = rand() % (W - 8);
int y = rand() % (H - 4);
int w = 8 + rand() % (W/2);
int h = 4 + rand() % 24;
s->x1 = x;
s->y1 = y;
s->x2 = x + w < W ? x + w : W - 1;
s->y2 = y + h < H ? y + h : H - 1;
s->color = randcolor();
}
// add one shape, erasing the oldest if the ring is full
static void step(void)
{
Seg* s = &ring[head];
unsigned size = ringsize[mode];
if (count == size) {
draw(s, TGI_COLOR_BLACK);
} else {
count++;
}
switch (mode) {
case 0: make_lissajous(s); break;
case 1: make_swirl(s); break;
case 2: make_hatch(s); break;
case 3: make_bar(s); break;
}
draw(s, s->color);
if (++head == size) head = 0;
T++;
}
int main(void)
{
tgi_install(a2hires_tgi);
tgi_init();
for (;;) {
tgi_clear();
head = count = T = 0;
while (!kbhit()) {
step();
if (mode == 1) { step(); step(); step(); }
}
if (cgetc() == 27) break;
mode = (mode + 1) & 3;
}
tgi_uninstall();
return 0;
}
+8 -3
View File
@@ -1,7 +1,8 @@
/*
NOTE: The alternate Apple ][ ROM used by 8bitworkshop
is not compatible with the hi-res TGI driver.
So we use the lo-res driver here.
has no Applesoft routines, so the stock hi-res TGI driver
doesn't work. We use our own hi-res driver, a2hires.s.
apple2-hgr2.cfg keeps $2000-$5FFF free for the picture.
*/
#include <stdio.h>
#include <stdlib.h>
@@ -10,6 +11,10 @@ NOTE: The alternate Apple ][ ROM used by 8bitworkshop
#include <ctype.h>
#include <modload.h>
#include <tgi.h>
#include "a2hires.h"
//#link "a2hires.s"
//#tooldef ld cfgfile=apple2-hgr2.cfg
//#resource "apple2-hgr2.cfg"
#ifndef DYN_DRV
# define DYN_DRV 0
@@ -189,7 +194,7 @@ int main (void)
CheckError ("tgi_load_driver");
#else
/* Install the driver */
tgi_install (a2_lo_tgi);
tgi_install (a2hires_tgi);
CheckError ("tgi_install");
#endif
+170
View File
@@ -0,0 +1,170 @@
/*
Hi-res (280x192, 6 colors) TGI demo for the Apple ][.
Four full-screen animations; press a key to switch to the next (ESC quits):
a Lissajous line figure, a swirl of dots, random lines, random bars.
Each shape is remembered in a ring buffer; when the buffer wraps, the
oldest shape is erased, so the picture keeps moving.
Uses our own hi-res TGI driver, a2hires.s (the stock one needs
Applesoft ROM routines). apple2-hgr2.cfg keeps $2000-$5FFF free.
*/
#include <stdlib.h>
#include <cc65.h>
#include <conio.h>
#include <tgi.h>
//#tooldef ld cfgfile=atari.cfg
//#symbol ld __RESERVED_MEMORY__=8192
#define W 80 // screen size
#define H 192
#define R (H/2 - 4) // radius of the figures
#define MAXSEG 240
#define MAXCOL 7
#define SIN(a) _sin((unsigned)(a) % 360)
#define COS(a) _sin(((unsigned)(a) + 90) % 360)
typedef struct {
int x1, y1, x2, y2;
unsigned char color;
} Seg;
static Seg ring[MAXSEG]; // the shapes currently on screen
static unsigned head; // next slot to overwrite
static unsigned count; // slots in use
static unsigned T; // time, advances every step
static unsigned char mode;
static const unsigned ringsize[4] = { 40, 240, 70, 14 };
// scale a sine value (-256..256) to -R..R
static int scale(int v)
{
return (int)(((long)v * R) / 256);
}
static unsigned char randcolor(void)
{
return rand() & MAXCOL;
}
// draw (or, with color 0, erase) one shape for the current mode
static void draw(const Seg* s, unsigned char color)
{
int cx = W/2, cy = H/2;
tgi_setcolor(color);
switch (mode) {
case 0: // line, mirrored four ways
tgi_line(cx + s->x1, cy + s->y1, cx + s->x2, cy + s->y2);
tgi_line(cx - s->x1, cy + s->y1, cx - s->x2, cy + s->y2);
tgi_line(cx + s->x1, cy - s->y1, cx + s->x2, cy - s->y2);
tgi_line(cx - s->x1, cy - s->y1, cx - s->x2, cy - s->y2);
break;
case 1: // dot
tgi_setpixel(s->x1, s->y1);
break;
case 2: // line
tgi_line(s->x1, s->y1, s->x2, s->y2);
break;
case 3: // bar
tgi_bar(s->x1, s->y1, s->x2, s->y2);
break;
}
}
// Lissajous lines
static void make_lissajous(Seg* s)
{
unsigned a = T * 2;
s->x1 = scale(SIN(a * 3)) * 3/2;
s->y1 = scale(COS(a * 2));
s->x2 = scale(COS(a * 5 + T)) * 3/2;
s->y2 = scale(SIN(a * 4));
s->color = (T >> 4) & MAXCOL;
}
// dots on a wandering rose curve
static void make_swirl(Seg* s)
{
unsigned a = T * 7;
int r = scale(SIN(a * 5 + T / 2)) + scale(COS(a * 3)) / 2;
s->x1 = W/2 + r * COS(a) / 128;
s->y1 = H/2 + r * SIN(a) / 256;
s->color = (a / 90) & MAXCOL;
}
// random horizontal and vertical lines
static void make_hatch(Seg* s)
{
int x = rand() % W;
int y = rand() % H;
int len = 4 + rand() % 60;
s->x1 = s->x2 = x;
s->y1 = s->y2 = y;
if (rand() & 1) {
s->x2 = x + len < W ? x + len : W - 1;
} else {
s->y2 = y + len < H ? y + len : H - 1;
}
s->color = randcolor();
}
// random filled bars
static void make_bar(Seg* s)
{
int x = rand() % (W - 8);
int y = rand() % (H - 4);
int w = 8 + rand() % (W/2);
int h = 4 + rand() % 24;
s->x1 = x;
s->y1 = y;
s->x2 = x + w < W ? x + w : W - 1;
s->y2 = y + h < H ? y + h : H - 1;
s->color = randcolor();
}
// add one shape, erasing the oldest if the ring is full
static void step(void)
{
Seg* s = &ring[head];
unsigned size = ringsize[mode];
if (count == size) {
draw(s, TGI_COLOR_BLACK);
} else {
count++;
}
switch (mode) {
case 0: make_lissajous(s); break;
case 1: make_swirl(s); break;
case 2: make_hatch(s); break;
case 3: make_bar(s); break;
}
draw(s, s->color);
if (++head == size) head = 0;
T++;
}
void set_palette(void) {
int i;
for (i=0; i<=MAXCOL; i++) {
_setcolor(i, i*2, 15);
}
}
int main(void)
{
tgi_install(atr10_tgi);
tgi_init();
set_palette();
for (;;) {
tgi_clear();
head = count = T = 0;
while (!kbhit()) {
step();
if (mode == 1) { step(); step(); step(); }
}
if (cgetc() == 27) break;
mode = (mode + 1) & 3;
}
tgi_uninstall();
return 0;
}
+1 -1
View File
@@ -71,7 +71,7 @@ const char SPRITE_DATA[64*NUM_SPRITE_PATTERNS] = {
#define OBSTACLE_INDEX 2
#define CENTER_X 172 // sprite start X coord.
#define FLOOR_Y (128 << 8) // sprite start Y (fixed 8.8)
#define FLOOR_Y (128u << 8) // sprite start Y (fixed 8.8)
#define JUMP_VELOCITY (-900) // jump velocity (fixed 8.8)
#define GRAVITY 32 // gravity (fixed 8.8)
+6
View File
@@ -287,6 +287,12 @@ static unsigned char bright;
static unsigned char update_list[7*3+1];
// close the zero-page region. (Without the matching pop, cc65 V2.19-3867 also
// moves the map[] declared above into ZEROPAGE, overflowing the 256-byte area.)
#pragma bss-name(pop)
#pragma data-name(pop)
//smoothly fade current bright to the given value
+18 -17
View File
@@ -376,22 +376,23 @@ byte just_one_actor_left() {
}
void main() {
dvgclear();
dvgwrofs = 0x800; //write into the middle of VEC ROM (TODO)
make_cached_shapes();
create_obstacles(5);
new_player_ship();
while (!just_one_actor_left()) {
dvgreset();
control_player();
draw_and_update_actors();
CNTR();
HALT();
dvgstart();
remove_expired_actors();
frame++;
watchdog=0;
// while (vidframe == (frame & 0x3)) {}
for (;;) {
dvgclear();
dvgwrofs = 0x800; //write into the middle of VEC ROM (TODO)
make_cached_shapes();
create_obstacles(5);
new_player_ship();
while (!just_one_actor_left()) {
dvgreset();
control_player();
draw_and_update_actors();
CNTR();
HALT();
dvgstart();
remove_expired_actors();
frame++;
watchdog=0;
// while (vidframe == (frame & 0x3)) {}
}
}
main();
}
-1
View File
@@ -17,5 +17,4 @@ void main() {
HALT();
dvgstart();
}
main();
}
+4 -2
View File
@@ -65,7 +65,7 @@ const SYSTEM_HEADERS: { [header: string]: string[] } = {
'cbm.h': ['c64', 'vic20'],
'apple2.h': ['apple2'],
'apple2enh.h': ['apple2'],
'atari.h': ['atari8-800', 'atari8-5200'],
'atari.h': ['atari8-800'],
'atari5200.h': ['atari8-5200'],
'atari7800.h': ['atari7800'],
'pce.h': ['pce'],
@@ -292,7 +292,9 @@ export async function detectProject(input: DetectInput): Promise<Detection[]> {
export function isClearWinner(detections: Detection[]): boolean {
if (!detections.length || detections[0].score < 0.5) return false;
var w = (d: Detection) => d.weight ?? d.score;
return detections.length == 1 || w(detections[0]) - w(detections[1]) >= 0.2;
// weights are rounded for display, so allow for the rounding error when the
// lead lands exactly on the threshold (e.g. a 0.2 folder-name hint)
return detections.length == 1 || w(detections[0]) - w(detections[1]) >= 0.2 - 1e-9;
}
/**
+39
View File
@@ -0,0 +1,39 @@
# cc65 Toolchain Upgrade
The cc65 family (`cc65`, `ca65`, `ld65`) used by the 6502 platforms was upgraded
for 8bitworkshop 4.0.
| | Old build | Current build |
| --- | --- | --- |
| cc65 commit | `6ac4aa4` (2021-12-24) | `d8a486a` (V2.19-3867, 2026-09-26) |
| Runtime | Emscripten | WASI (`WASIRunner`) |
| Includes/libs | hand-mixed 2019–2023 objects | one set built from the same revision |
| Files | `fs65-*.data` (~22 MB) | `cc65-fs-<platform>.zip` (~10 MB) |
## Pitfalls
There are a few potential incompatibilities in the new compiler:
- **Inline assembly.** cc65 now optimizes non-volatile inline assembly.
8bitworkshop runs with `--disable-opt OptLoadStore1` to prevent unexpected changes.
- **`NULL` is now `((void*)0)`** from `<stddef.h>`, so headers that
defined their own `NULL` had to stop (see `neslib.h`).
- **Signed constant overflow warning**. For example, `128 << 8`
is now an error-adjacent warning and needs an unsigned constant (`128u << 8`).
- **Recursing into `main()` is an error.** Programs that tail-called
`main()` need an explicit loop instead.
- **`#pragma bss-name`/`data-name` regions must be closed.** Without a
matching `pop`, the newer compiler could place earlier globals into
`ZEROPAGE` and overflow the 256-byte area. (See `nes/chase.c`.)
- **`sp` was renamed to `c_sp`.** The old alias still works but emits an
`ld65` deprecation warning. Assembly code may need to be modified.
## Warnings vs. errors
8bitworkshop treats only **errors** as build failures; `Warning:` lines are
ignored so a clean build does not fail on style or version noise.
## Apple II graphics
The stock lo-res driver (`a2.lo.s`) required a small upstream
patch (a stray branch in `INSTALL`).
+2
View File
@@ -52,6 +52,8 @@ multi-file projects, compiler/linker directives, and file extensions.
See [Toolchains & Platforms](toolchains.md) for the full list of tools and
the complete platform × extension → tool reference.
CC65 was updated in 8bitworkshop 4.0 -- for more information see
[cc65 Toolchain Upgrade](cc65-upgrade.md).
## Debugger
+2
View File
@@ -22,6 +22,7 @@ import breakpointsMd from "../../docs/breakpoints.md";
import asseteditorMd from "../../docs/asseteditor.md";
import managingFilesMd from "../../docs/managing-files.md";
import buildDirectivesMd from "../../docs/build-directives.md";
import cc65upgrade from "../../docs/cc65-upgrade.md";
import toolchainsMd from "../../docs/toolchains.md";
import assetHeadersMD from "../../docs/asset-headers.md";
import embeddingIdeMd from "../../docs/embedding-ide.md";
@@ -57,6 +58,7 @@ export const HELP_TOPICS: HelpTopic[] = [
{ id: "asseteditor", title: "Asset Editor", html: asseteditorMd },
{ id: "managing-files", title: "Managing Files", html: managingFilesMd },
{ id: "build-directives", title: "Build Directives", html: buildDirectivesMd },
{ id: "cc65-upgrade", title: "CC65 Upgrade", html: cc65upgrade },
{ id: "toolchains", title: "Toolchains & Platforms", html: toolchainsMd },
{ id: "asset-headers", title: "Asset Headers", html: assetHeadersMD },
{ id: "embedding-ide", title: "Embedding the IDE", html: embeddingIdeMd },
+1
View File
@@ -12,6 +12,7 @@ const APPLE2_PRESETS: Preset[] = [
{ id: 'keyboardtest.c', name: 'Keyboard Test' },
{ id: 'mandel.c', name: 'Mandelbrot' },
{ id: 'tgidemo.c', name: 'TGI Graphics Demo' },
{ id: 'icepiano.c', name: 'Ice Piano (Hires Demo)' },
{ id: 'Eliza.c', name: 'Eliza' },
{ id: 'siegegame.c', name: 'Siege Game' },
{ id: 'cosmic.c', name: 'Cosmic Impalas' },
+1
View File
@@ -16,6 +16,7 @@ var Atari8_PRESETS : Preset[] = [
var Atari800_PRESETS = Atari8_PRESETS.concat([
//{id:'testmusic.c', name:'POKEY Music'},
{id:'tgidemo.c', name:'TGI Demo'},
{id:'icepiano.c', name:'Ice Piano (TGI Demo)'},
{id:'libdemo.c', name:'Library Demo'},
{id:'scrolldemo.c', name:'Scrolling'},
{id:'hello.cpp', name:'Hello Oscar64', category:'Oscar64'},
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4 -1
View File
@@ -297,7 +297,10 @@ void __fastcall__ nmi_set_callback(void (*callback)(void));
#define NAMETABLE_C 0x2800
#define NAMETABLE_D 0x2c00
#define NULL 0
// newer cc65 stddef.h defines NULL as ((void*)0); don't clash with it
#ifndef NULL
#define NULL ((void*)0)
#endif
#define TRUE 1
#define FALSE 0
+21 -6
View File
@@ -12,6 +12,11 @@ import { wasiFSAdapter } from "../wasiutils";
// cc65-fs-<platform>.zip, preopened at '.'
const CC65_SHARE = 'share/cc65';
// cc65/ca65/ld65 report both errors and non-fatal warnings as
// "<file>:<line>: <Warning|Error>: <msg>" on stderr; skip the warnings so a
// clean build with only style/deprecation warnings doesn't fail.
const re_cc65_warning = /:\s*Warning:/;
const wasiModules: { [tool: string]: WebAssembly.Module } = {};
/**
@@ -187,7 +192,7 @@ export async function assembleCA65(step: BuildStep): Promise<BuildStepResult> {
.concat(extraArgsFor('ca65', step.params.buildArgs));
args.splice(args.length - 1, 0, ...extra);
const { wasi, errno, stderr } = await runCC65Tool(step, 'ca65', args, (fs) => populateFiles(step, fs));
stderr.forEach(makeErrorMatcher(errors, /(.+?):(\d+): (.+)/, 2, 3, step.path, 1));
stderr.filter(s => !re_cc65_warning.test(s)).forEach(makeErrorMatcher(errors, /(.+?):(\d+): (.+)/, 2, 3, step.path, 1));
checkExitCode('ca65', errno, stderr, errors);
if (errors.length) {
let listings : CodeListingMap = {};
@@ -231,8 +236,13 @@ export async function linkLD65(step: BuildStep): Promise<BuildStepResult> {
populateEntry(fs, params.cfgfile, store.getFileEntry(params.cfgfile), null);
}
});
// any ld65 message (even a warning) fails the build
for (let s of stderr) errors.push({ msg: s, line: 0 });
// any non-warning ld65 message fails the build; again, "Warning:"-severity
// output (e.g. the sp->c_sp deprecation from our prebuilt crt0.o/neslib2.lib)
// is non-fatal noise the user can't act on.
for (let s of stderr) {
if (re_cc65_warning.test(s)) continue;
errors.push({ msg: s, line: 0 });
}
checkExitCode('ld65', errno, stderr, errors);
if (errors.length)
return { errors: errors };
@@ -356,8 +366,13 @@ export async function compileCC65(step: BuildStep): Promise<BuildStepResult> {
// //#symbol c / //#flag c
args.push.apply(args, defineArgs('cc65', params.symbols && params.symbols.compiler));
args.push.apply(args, extraArgsFor('cc65', params.buildArgs));
var customArgs = params.extra_compiler_args || ['-T', '-g', '-Oirs', '-Cl', '-W', '-pointer-sign,-no-effect'];
args = args.concat(customArgs, args);
var customArgs = params.extra_compiler_args || ['-T', '-g', '-Oirs', '-Cl', '-W', '-pointer-sign,-no-effect,-unreachable-code'];
// cc65 V2.19-3867 parses inline asm into code entries and a new
// optimizer step (OptLoadStore1) removes a store that follows a load
// from the same address -- wrong for hardware registers and
// self-modifying code. The old cc65 never touched inline asm; keep
// that behavior by disabling just this new step.
args = args.concat(customArgs, ['--disable-opt', 'OptLoadStore1'], args);
args.push(step.path);
const { wasi, errno, stderr } = await runCC65Tool(step, 'cc65', args, (fs) => {
populateFiles(step, fs, {
@@ -371,7 +386,7 @@ export async function compileCC65(step: BuildStep): Promise<BuildStepResult> {
});
populateExtraFiles(step, fs, params.extra_compile_files);
});
stderr.forEach(makeErrorMatcher(errors, /(.*?):(\d+): (.+)/, 2, 3, step.path, 1));
stderr.filter(s => !re_cc65_warning.test(s)).forEach(makeErrorMatcher(errors, /(.*?):(\d+): (.+)/, 2, 3, step.path, 1));
checkExitCode('cc65', errno, stderr, errors);
if (errors.length) return { errors };
putWorkFile(destpath, readWASIOutputString(wasi, destpath));
Binary file not shown.
Binary file not shown.
Binary file not shown.
+33
View File
@@ -284,6 +284,39 @@ describe('8bws CLI', function () {
});
});
describe('apple2 TGI drivers', function () {
// build a one-file program next to the preset driver, run it, return the script output
function runTGI(body, headers, script) {
var dir = fs.mkdtempSync(path.join(os.tmpdir(), '8bws-tgi-'));
for (var f of ['a2hires.s', 'a2hires.h']) {
fs.copyFileSync(path.join('presets/apple2', f), path.join(dir, f));
}
var src = path.join(dir, 'main.c');
fs.writeFileSync(src, headers + '\nint main(void) {\n' + body + '\nfor(;;);\n}\n');
return cli('run', '--platform', 'apple2', src, '-e', script);
}
it('hires driver should draw pixels and lines on page 2', function () {
var out = runTGI(
'tgi_install(a2hires_tgi); tgi_init(); tgi_clear();' +
'tgi_setcolor(3); tgi_setpixel(0, 0); tgi_setpixel(279, 191);' +
'tgi_setcolor(5); tgi_line(0, 8, 13, 8);',
'#include <tgi.h>\n#include "a2hires.h"\n//#link "a2hires.s"',
'run 20; mem $4000 2; mem $4080 2; mem $5FF0 8');
assert.ok(/^4000: 01 00/m.test(out), out); // white pixel at 0,0
assert.ok(/^4080: AA D5/m.test(out), out); // orange line: odd columns + palette bit
assert.ok(/^5FF0: 00 00 00 00 00 00 00 40/m.test(out), out); // last pixel
});
it('lores driver should install and draw (stray branch in cc65 a2.lo.s)', function () {
var out = runTGI(
'tgi_install(a2_lo_tgi); tgi_init(); tgi_clear();' +
'tgi_setcolor(15); tgi_bar(0, 0, 39, 9);',
'#include <tgi.h>',
'run 30; pc 1; mem $400 4');
assert.ok(!/PC=\$0000/.test(out), out); // not crashed into zero page
assert.ok(/^0400: (?!00 00 00 00)/m.test(out), out); // lo-res memory was written
});
});
describe('legacy aliases', function () {
it('should accept compile, check and compilerun', function () {
assert.equal(cliJSON('compile', '--platform', 'gb', 'presets/gb/hello.c', '--check').command, 'check');
+6 -3
View File
@@ -162,6 +162,9 @@ describe('Worker', function() {
it('should compile CC65', function(done) {
compile('cc65', '#if defined(__8BITWORKSHOP__) && defined(__MAIN__)\nint main() {\nint x=1;\nreturn x+2;\n}\n#endif', 'nes.mame', done, 40976, 3);
});
it('should compile CC65 with a warning (warnings are not errors)', function(done) {
compile('cc65', '#if defined(__8BITWORKSHOP__) && defined(__MAIN__)\nint main() {\nconst const int x=1;\nreturn x+2;\n}\n#endif', 'nes.mame', done, 40976, 3, 0);
});
it('should NOT compile CC65 (compile error)', function(done) {
compile('cc65', 'int main() {\nint x=1;\nprintf("%d",x);\nreturn x+2;\n}', 'nes', done, 0, 0, 1);
});
@@ -388,7 +391,7 @@ describe('Worker', function() {
});
it('should compile apple2 skeleton with CC65', function(done) {
var csource = ab2str(fs.readFileSync('presets/apple2/skeleton.cc65'));
compile('cc65', csource, 'apple2', done, 3023+58, 4, 0);
compile('cc65', csource, 'apple2', done, 3382, 4, 0);
});
// TODO: test if compile, errors, then compile same file
it('should compile CC65 banked', function(done) {
@@ -402,7 +405,7 @@ describe('Worker', function() {
it('should compile C64 cc65 skeleton', function(done) {
var csource = ab2str(fs.readFileSync('presets/c64/skeleton.cc65'));
csource = csource.replace('#include "','//');
compile('cc65', csource, 'c64.wasm', done, 3112, 3, 0);
compile('cc65', csource, 'c64.wasm', done, 3026, 3, 0);
});
it('should compile zmachine inform6 skeleton', function(done) {
var csource = ab2str(fs.readFileSync('presets/zmachine/skeleton.inform6'));
@@ -420,7 +423,7 @@ describe('Worker', function() {
doBuild(msgs, done2, 205, 0, 0);
});
it('should compile CC65 flags', function(done) {
compile('cc65', '#define CC65_FLAGS -Or,-g,-j\nint main() {\nint x=1;\nreturn x+2;\n}', 'apple2', done, 416+58, 3);
compile('cc65', '#define CC65_FLAGS -Or,-g,-j\nint main() {\nint x=1;\nreturn x+2;\n}', 'apple2', done, 489, 2);
});
/*
it('should compile gameboy', function(done) {
+18
View File
@@ -45,6 +45,24 @@ describe('detect', () => {
assert.strictEqual(d[0].platform, 'vcs');
});
it('tells the Atari 8-bit family apart by header', async () => {
// atari.h is guarded by __ATARI__ (800); atari5200.h by __ATARI5200__
var d = await detectProject(files({ 'game.c': '#include <atari.h>\nvoid main() {}\n' }));
assert.strictEqual(d[0].platform, 'atari8-800');
d = await detectProject(files({ 'game.c': '#include <atari5200.h>\nvoid main() {}\n' }));
assert.strictEqual(d[0].platform, 'atari8-5200');
});
it('prefers the folder name over a shared Atari library header', async () => {
var map: { [fn: string]: string } = {
'game.c': '#include <atari.h>\nvoid main() {}\n',
'a8lib.h': '#if defined(__ATARI5200__)\n#include <atari5200.h>\n#else\n#include <atari.h>\n#endif\n',
};
var d = await detectProject({ files: Object.keys(map), read: (fn) => map[fn], platforms: PLATFORMS, dirName: 'atari8-800' });
assert.strictEqual(d[0].platform, 'atari8-800');
assert.ok(isClearWinner(d));
});
it('says nothing about plain C', async () => {
var d = await detectProject(files({ 'util.c': 'int add(int a, int b) { return a + b; }\n', 'util.h': 'int add(int, int);\n' }));
assert.ok(d.every(x => x.score < 0.5), JSON.stringify(d));