Add full compiler toolchain, libc, examples and reference docs

First substantive commit: the entire Sprinter C compiler tree on top of
the bare README+gitignore initial commit.

What's in here:
  bin/sprinter-cc        — driver script invoking SDCC + linker + mkexe
  libc/                  — Sprinter-specific libc layer over ESTEX/BIOS
                           (conio, gfx, io, mem, stdio + headers)
  runtime/               — crt0 variants (default/small/banked/minimal)
                           + heap + bank trampolines
  toolchain/             — mkexe (SprintEXE packer, C + tests)
  examples/              — 30 demo programs (gfx, file I/O, env, time, …)
  lib/Makefile           — builds the libc archive (sprinter.lib)
  docs/                  — converted Sprinter manuals + asm reference samples
  third_party/           — solid-c reference compiler dump + sdcc setup script
  release_docs/          — packaging / release notes

gitignore overhaul:
  • Drop dangerous blanket patterns: *.asm (would hide docs/samples/*.asm)
    and *.exe (case-insensitive match was hiding third_party/solid-c/*.EXE
    on macOS APFS).  Replaced with examples/*/*.{asm,exe,…} and lib/*.lib.
  • Restore tracking of toolchain/mkexe/tests/{one,big}.bin — those are
    INPUT fixtures, not build outputs.
  • Collapse the duplicated SDCC/C/Sdcc sections into one section per
    concern (build outputs / vendored / OS-junk).
  • Add .sprinter-cc-*/, build/ (catches lib/build/ too), .claude/.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 16:13:21 +03:00
parent f542608b3f
commit c71e249a4e
404 changed files with 75155 additions and 58 deletions
+154
View File
@@ -0,0 +1,154 @@
/*
* conio.h — direct console I/O backed by ESTEX functions.
*
* Two-set output API following the Turbo-C convention:
*
* stdio (fast, no attr — system default colour):
* putchar(c) — one char + '\n' → CR LF translation
* puts(s) — string + trailing newline
* printf(...) — uses putchar internally
*
* conio (slower, applies g_text_attr — set via textcolor / textbackground /
* textattr / set_text_attr):
* putch(c) — one char, NO '\n' translation
* cputs(s) — string, NO trailing newline, NO '\n' translation
* (write "\r\n" yourself for line breaks)
* cprintf(...) — printf with attribute, via vsprintf+cputs internally
*
* The conio set short-circuits to the fast path when
* g_text_attr == KEEP_EXIST_ATTR — useful for "I usually want a
* specific colour but right now don't care".
*
* Other helpers (unchanged):
* kbhit / getch / getche — keyboard
* clrscr / clrscr_attr — clear screen
* gotoxy / wherex / wherey
* wrchar / rdchar — direct VRAM cell access
* get_videotextmode / set_videotextmode (text modes only — see gfx.h
* for graphics mode constants)
*
* Coordinates are 0-based to match ESTEX directly.
*/
#ifndef CONIO_H
#define CONIO_H
#include <stdint.h>
char kbhit (void);
char getch (void);
char getche(void);
char putch (char c);
char cputs (const char *s);
int cprintf(const char *fmt, ...);
void clrscr(void);
void gotoxy(uint8_t x, uint8_t y);
/* Solid-C compatibility helpers. */
#define home() gotoxy(0, 0)
#define inp(port) z80_inp(port)
#define outp(p, v) z80_outp((p), (v))
#define enable() __asm__("ei")
#define disable() __asm__("di")
uint8_t z80_inp(uint8_t port);
void z80_outp(uint8_t port, uint8_t value);
/* Read a line from the console (no echo control — uses getche).
* `buf[0]` must be the max length; on return `buf[1]` is the actual
* length and `buf[2..]` is the NUL-terminated string. */
char *cgets(char *buf);
/* Cursor query via ESTEX $53 CURSOR. Returns 0-based row/column. */
uint8_t wherex (void);
uint8_t wherey (void);
uint16_t wherexy(void); // high byte = Y, low byte = X coords.
/* Direct character/attribute screen access (ESTEX $57 / $58).
* wrchar — write char + attribute at (x, y); does NOT advance the cursor
* and does NOT interpret control characters. Useful for
* coloured text and for painting the last-column-last-row cell
* without triggering ESTEX's auto-scroll on PCHARS/PUTCHAR.
* rdchar — read both character and attribute back; returned as
* (attr<<8 | ch). */
void wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr);
uint16_t rdchar(uint8_t x, uint8_t y);
/* clrscr_attr — wipe the entire 80x32 screen using the given attribute
* byte (fill character = space). Companion to clrscr() which uses the
* default attr 0x0F (bright white on black). */
void clrscr_attr(uint8_t attr);
/* Text video-mode control (ESTEX $50 / $51). These constants and helpers
* cover ONLY text modes; graphics modes live in <gfx.h> as GFX_MODE_*.
* `set_videotextmode()` validates that the argument is a known text mode
* (so calling code that includes conio.h alone cannot accidentally switch
* the screen into graphics — that requires <gfx.h>). */
#define TEXT_MODE_40x32 0x02
#define TEXT_MODE_80x32 0x03
uint8_t get_videotextmode(void);
int set_videotextmode(uint8_t mode); /* 0 OK, -1 + errno on bad mode */
/* ------------------------------------------------------------------ *
* Text-output attribute (used by the conio set: putch / cputs / cprintf).
*
* textcolor(c) — set foreground (preserves background+blink)
* textbackground(c) — set background (preserves foreground+blink)
* textattr(a) — replace the whole attribute byte
* set_text_attr(a) — alias to textattr but returns the previous value
* get_text_attr() — read current attribute
*
* Range 0x00..0xFF is a real attribute byte; KEEP_EXIST_ATTR (0xFFFF)
* means "don't touch attributes — fall through to the fast no-attr path"
* (so putch becomes equivalent to putchar etc.).
*
* Default at startup = 0x0F (bright white on black).
*
* NOTE: stdio's putchar / puts / printf IGNORE this — they always use
* whatever ESTEX has cached for the cursor cell. Use the conio set
* (putch / cputs / cprintf) for coloured output.
* ------------------------------------------------------------------ */
#define KEEP_EXIST_ATTR 0xFFFF
void textcolor(uint8_t fg);
void textbackground(uint8_t bg);
void textattr(uint8_t attr);
int16_t set_text_attr(int16_t attr); /* returns the previous value */
int16_t get_text_attr(void);
/* Control how putch / cputs / cprintf (the WRCHAR path) treat
* control characters (< 0x20):
*
* mode = 0 (default) — interpret BS/TAB/LF/CR:
* 0x08 BS → pc_col-- (clamped at 0)
* 0x09 TAB → pc_col rounded up to next multiple of 8 (clamped 80)
* 0x0A LF → pc_row++ (clamped at 32; no glyph emitted)
* 0x0D CR → pc_col = 0
*
* mode = 1 — print EVERY character as a CP437 glyph (no
* interpretation; useful e.g. for drawing box-drawing
* characters that overlap the 0x00..0x1F range).
*
* Only affects the WRCHAR path (attr ≤ 0xFF). When attr is
* KEEP_EXIST_ATTR, ESTEX's own PUTCHAR/PCHARS rule the cursor and
* pc_raw_mode is irrelevant. */
void set_putch_raw_mode(uint8_t mode);
uint8_t get_putch_raw_mode(void);
/* Sprinter text-mode 03h attribute byte (verified via attr_probe):
* bits 0..3 = foreground (0..15)
* bits 4..6 = background (0..7)
* bit 7 = blink (toggles fg between fg-colour and bg-colour)
* Colour order is standard CGA / Borland-conio.h. Constants 0..7 are
* usable for both fg and bg; 8..15 are foreground-only. */
enum {
COLOR_BLACK = 0, COLOR_BLUE, COLOR_GREEN, COLOR_CYAN,
COLOR_RED, COLOR_MAGENTA, COLOR_BROWN, COLOR_LIGHTGRAY,
COLOR_DARKGRAY, COLOR_LIGHTBLUE, COLOR_LIGHTGREEN, COLOR_LIGHTCYAN,
COLOR_LIGHTRED, COLOR_LIGHTMAGENTA, COLOR_YELLOW, COLOR_WHITE
};
#define COLOR_BLINK 0x80u
#define COLOR(fg, bg) ((uint8_t)((((bg) & 0x07) << 4) | ((fg) & 0x0F)))
#endif
+51
View File
@@ -0,0 +1,51 @@
/*
* dir.h — directory iteration via ESTEX F_FIRST / F_NEXT ($19 / $1A).
*
* ffirst(pattern, &buf, attrib)
* Initialises the search. The pattern is a DOS wildcard like
* `*.TXT` or `DATA\\*`. attrib is the search mask (use FA_NORMAL
* to match plain files, OR in FA_DIREC to include subdirectories).
* Returns 0 on success (buf is populated), -1 on failure (errno set).
*
* fnext(&buf)
* Steps to the next matching entry, reusing the same buf. Returns
* 0 on success, -1 when no more files match (errno = ENOENT) or on
* other errors.
*
* The buffer fields below mirror ESTEX's layout exactly so we can hand
* it straight to the kernel. Use `buf.found_name` (NUL-terminated DOS
* "name.ext" form) to display results.
*/
#ifndef DIR_H
#define DIR_H
#include <stdint.h>
/* File attribute bits (ESTEX / DOS standard). */
#define FA_NORMAL 0x00
#define FA_RDONLY 0x01
#define FA_HIDDEN 0x02
#define FA_SYSTEM 0x04
#define FA_LABEL 0x08
#define FA_DIREC 0x10
#define FA_ARCH 0x20
/* 256-byte work buffer for ESTEX F_FIRST / F_NEXT (B=1 mode). */
typedef struct {
char name[8]; /* +0 pattern: 8-byte filename */
char ext[3]; /* +8 pattern: 3-byte extension */
uint8_t attrib; /* +11 search attribute */
char reserved[10]; /* +12 DSS internal state */
uint16_t time; /* +22 time of last write */
uint16_t date; /* +24 date of last write */
uint16_t first_cluster; /* +26 first cluster */
uint32_t size; /* +28 file size in bytes */
uint8_t found_attr; /* +32 attribute of the matched file */
char found_name[223]; /* +33 NUL-terminated "name.ext" */
} ffblk_t;
int ffirst(const char *pattern, ffblk_t *buf, uint8_t attrib);
int fnext (ffblk_t *buf);
#endif
+62
View File
@@ -0,0 +1,62 @@
/*
* errno.h — ESTEX error codes + global errno + strerror / perror.
*
* ESTEX functions report errors by setting CF=1 and returning the error
* code in A. Our libc wrappers stash that code into `errno` and return
* -1 (or 0 / NULL where the C type calls for it).
*
* Error numbers match ESTEX (so they round-trip through OS/BIOS calls
* untouched). Where the meaning lines up cleanly with POSIX we also
* expose the POSIX-style name as an alias.
*/
#ifndef ERRNO_H
#define ERRNO_H
/* Process-wide error state. Reset to 0 only by user code — libc never
* clears it. */
extern int errno;
/* Error numbers — direct ESTEX codes. */
#define EOK 0 /* No error */
#define EINVFN 1 /* Invalid function */
#define ENODRV 2 /* Invalid drive number */
#define ENOENT 3 /* File not found — POSIX */
#define ENOPATH 4 /* Path not found */
#define EBADF 5 /* Invalid handle — POSIX */
#define EMFILE 6 /* Too many open files — POSIX */
#define EEXIST 7 /* File already exists — POSIX */
#define EROFS 8 /* File is read-only — POSIX */
#define EROOTFULL 9 /* Root directory overflow */
#define ENOSPC 10 /* No free space — POSIX */
#define ENOTEMPTY 11 /* Directory not empty — POSIX */
#define EBUSY 12 /* Can't delete current directory — POSIX-ish */
#define EMEDIA 13 /* Invalid media */
#define EUNKOP 14 /* Unknown operation */
#define EISDIR 15 /* Directory exists — POSIX */
#define EINAME 16 /* Invalid filename */
#define EINVEXE 17 /* Invalid EXE file */
#define ENOEXEC 18 /* Not supported EXE — POSIX */
#define EACCES 19 /* Permission denied — POSIX */
#define ENOTREADY 20 /* Device not ready */
#define ESEEK 21 /* Seek error — POSIX (ESPIPE) */
#define ENOSECT 22 /* Sector not found */
#define ECRC 23 /* CRC error */
#define EWRPROT 24 /* Write protect */
#define EREAD 25 /* Read error */
#define EWRITE 26 /* Write error */
#define EDRVFAIL 27 /* Drive failure */
#define ENOMEM 30 /* Out of memory — POSIX */
#define EINVMEM 31 /* Invalid memory block */
#define EUNKERR 32 /* Unknown error */
/* POSIX aliases for codes ESTEX doesn't have a direct equivalent for.
* Folded onto the closest existing code so error strings stay sane. */
#define EINVAL EUNKOP /* "Invalid argument" → "Unknown operation" */
/* C99 perror / strerror surface. */
const char *strerror(int err);
void perror (const char *prefix);
#endif
+31
View File
@@ -0,0 +1,31 @@
/*
* fcntl.h — open / creat for Sprinter ESTEX.
*
* POSIX-style flag bits. The low two bits select access mode (matches
* POSIX numbering — RDONLY=0, WRONLY=1, RDWR=2 — and is translated to
* the ESTEX OPEN $11 convention inside open()).
*
* The other flags map onto ESTEX calls like this:
* O_CREAT + O_EXCL → $0B (CREATE_NEW, fails if exists)
* O_CREAT + O_TRUNC → $0A (CREATE, truncates existing)
* O_CREAT alone → try $11 (OPEN); on ENOENT fall back to $0A
* no O_CREAT → $11 (OPEN, fails if missing)
* O_APPEND → after open, $15 lseek(0, SEEK_END)
*/
#ifndef FCNTL_H
#define FCNTL_H
#define O_RDONLY 0
#define O_WRONLY 1
#define O_RDWR 2
#define O_CREAT 0x040
#define O_EXCL 0x080
#define O_TRUNC 0x200
#define O_APPEND 0x400
int open (const char *path, int flags);
int creat(const char *path, int mode); /* mode arg ignored on Sprinter */
#endif
+148
View File
@@ -0,0 +1,148 @@
/*
* gfx.h — Sprinter graphics primitives.
*
* Two main modes:
* GFX_MODE_320x256x256 (0x81) — one byte per pixel, palette of 256
* entries. API functions suffixed _256.
* GFX_MODE_640x256x16 (0x82) — 4 bits per pixel, palette of 16.
* API functions suffixed _16.
*
* Common API (no suffix) covers things that are mode-agnostic:
* gfx_init / gfx_done
* gfx_set_visible_page / gfx_set_draw_page / gfx_set_bank
* gfx_wait_vsync
* gfx_pal_load / gfx_pal_set
* gfx_load_default_font / gfx_set_font
*
* Addressing reminder:
* pixel (x, y) lives at CPU 0xC000 + (x or x/2) with Port_Y (0x89) = y;
* the gfx code maps a 16 KB VRAM page into W3 around every write.
* For double-buffering, page 1 starts 320 bytes later (0xC140).
*
* Palette: BIOS $A4 (RST 8); 4 bytes per entry — B, G, R, pad.
*/
#ifndef GFX_H
#define GFX_H
#include <stdint.h>
/* ESTEX SETVMOD codes — same values as the SETVMOD `A` register. */
#define GFX_MODE_TEXT_40x32 0x02
#define GFX_MODE_TEXT_80x32 0x03
#define GFX_MODE_320x256x256 0x81
#define GFX_MODE_640x256x16 0x82
/* Pixel dimensions of mode 0x81 (320×256, 256 colours). */
#define GFX_WIDTH 320
#define GFX_HEIGHT 256
/* Pixel dimensions of mode 0x82 (640×256, 16 colours). Each byte at
* 0xC000+x_byte holds two pixels: high nibble = LEFT (even-x), low
* nibble = RIGHT (odd-x) — see memory/sprinter_graphics.md. */
#define GFX_WIDTH_16 640
#define GFX_HEIGHT_16 256
#define GFX_COLORS_16 16
/* ---- Setup / teardown -------------------------------------------- *
*
* Switch to `mode`, returning the previous mode for restore. `page`
* (0 or 1) selects the initial graphics screen — both the visible and
* the draw page are set to it, and the W3 bank is reset to 0x50 (the
* canonical visible video page). Programs that don't double-buffer
* just pass 0. */
uint8_t gfx_init(uint8_t mode, uint8_t page);
/* Restore a previously-saved video mode. */
void gfx_done(uint8_t mode);
/* ---- Page selection (double-buffering) and bank control ---------- *
*
* The Sprinter hardware holds two graphics screens. The VISIBLE page
* is what's shown on screen; the DRAW page is where the gfx_* writes
* land. Render the next frame into the hidden page and flip when ready.
*
* "Bank" is the W3 page byte (0x50..0x5F) — bits 2,3 select
* normal/temp/transparent display modes. All gfx_* primitives map
* THIS bank into W3 before touching 0xC000+. Default is 0x50. */
void gfx_set_visible_page(uint8_t page); /* 0 or 1 */
uint8_t gfx_get_visible_page(void);
void gfx_set_draw_page(uint8_t page); /* 0 or 1 */
uint8_t gfx_get_draw_page(void);
void gfx_set_bank(uint8_t bank); /* 0x50..0x5F */
uint8_t gfx_get_bank(void);
/* Block until the next frame interrupt (50 Hz on Sprinter). Uses
* `EI; HALT` — the Z80 sleeps until the next IM2 tick that DSS programs
* for keyboard / cursor handling. Typical use:
*
* gfx_set_draw_page(hidden);
* draw_frame(...);
* gfx_wait_vsync(); // wait for vretrace
* gfx_set_visible_page(hidden); // tear-free flip
*/
void gfx_wait_vsync(void);
/* ---- 320×256×256 (mode 0x81) drawing API -------------------------- *
* Colour args are palette indices 0..255. */
void gfx_clear256 (uint8_t color);
void gfx_putpixel256 (int x, int y, uint8_t color);
void gfx_hline256 (int x, int y, int len, uint8_t color);
void gfx_vline256 (int x, int y, int len, uint8_t color);
void gfx_line256 (int x0, int y0, int x1, int y1, uint8_t color);
void gfx_rect256 (int x, int y, int w, int h, uint8_t color);
void gfx_fill_rect256(int x, int y, int w, int h, uint8_t color);
/* ---- 640×256×16 (mode 0x82) drawing API --------------------------- *
* Colour args are palette indices 0..15. The accelerator works
* byte-wise so vline16 falls back to per-row RMW (a byte spans two
* horizontal pixels). */
void gfx_clear16 (uint8_t color);
void gfx_putpixel16 (int x, int y, uint8_t color);
void gfx_hline16 (int x, int y, int len, uint8_t color);
void gfx_vline16 (int x, int y, int len, uint8_t color);
void gfx_line16 (int x0, int y0, int x1, int y1, uint8_t color);
void gfx_rect16 (int x, int y, int w, int h, uint8_t color);
void gfx_fill_rect16(int x, int y, int w, int h, uint8_t color);
/* ---- Bitmap-font text -------------------------------------------- *
* Font is 256 glyphs × 8 rows × 1 byte (ZX-Spectrum format), 2 KB.
* On first use the default system font is fetched via BIOS WIN_GET_ZG
* (fn 0xB8). gfx_set_font() lets you swap in a custom font (the
* pointer is held — keep the storage alive). */
void gfx_load_default_font(void);
void gfx_set_font(const uint8_t *font);
/* 320×256×256 text: one byte per pixel; advances x by 8 per char. */
void gfx_putchar256(int x, int y, char c, uint8_t fg, uint8_t bg);
void gfx_text256 (int x, int y, const char *s, uint8_t fg, uint8_t bg);
/* 640×256×16 text: 4 bits per pixel; x must be EVEN (byte-aligned). */
void gfx_putchar16 (int x, int y, char c, uint8_t fg, uint8_t bg);
void gfx_text16 (int x, int y, const char *s, uint8_t fg, uint8_t bg);
/* ---- Palette ----------------------------------------------------- *
* Each graphics page has its own palette page (page 0 → palette 0,
* page 1 → palette 1). For seamless double-buffering, load the same
* palette into both. */
/* Load a contiguous block of palette entries.
* pal_num: 0..3 (graphics palettes)
* start: first colour slot (0..255)
* count: number of slots (0 → 256)
* data: pointer to count entries, each formatted (B, G, R, 0). */
void gfx_pal_load(uint8_t pal_num, uint8_t start, uint8_t count,
const uint8_t *data);
/* Convenience: set one palette entry from RGB. Internally builds the
* BGR+pad triple and calls gfx_pal_load(pal_num, idx, 1, ...). */
void gfx_pal_set (uint8_t pal_num, uint8_t idx,
uint8_t r, uint8_t g, uint8_t b);
#endif
+113
View File
@@ -0,0 +1,113 @@
/*
* mouse.h — Sprinter mouse driver (RST 30h).
*
* The driver is installed in the system shell. After a successful
* mouse_init(), the driver tracks the hardware and you can query state
* or move/show/hide the cursor on demand.
*
* coordinate units:
* READ_STATE / GOTO take pixel coordinates. In text mode 03h
* (80x32) divide x by 8 and y by 8 (NOT 16) to get char-cell
* position.
*
* buttons bitmask:
* bit 0 = left, bit 1 = right
*/
#ifndef MOUSE_H
#define MOUSE_H
#include <stdint.h>
typedef struct {
uint16_t x; /* pixel coordinate */
uint16_t y; /* pixel coordinate */
uint8_t buttons; /* bit 0 = left, bit 1 = right */
} mouse_state_t;
int mouse_init (void); /* 0 OK, -1 if no driver */
void mouse_show (void);
void mouse_hide (void);
void mouse_refresh(void);
void mouse_read (mouse_state_t *st);
void mouse_goto (int x, int y);
void mouse_bounds_x(int xmin, int xmax);
void mouse_bounds_y(int ymin, int ymax);
/* Text-mode cursor shape (function $0A).
* sym_and / sym_xor — character glyph mask
* attr_and / attr_xor — attribute byte mask
* Default cursor is XOR'd inverse video at the cell. */
void mouse_text_cursor(uint8_t sym_and, uint8_t sym_xor,
uint8_t attr_and, uint8_t attr_xor);
/* ---- Graphics-mode cursor image (functions $09 / $0B) ----------- *
* The driver accepts an opaque bitmap for the cursor in graphics modes.
* Doc does not specify the byte layout — empirically size = width*height
* bytes (1 byte per pixel for 256-colour mode, presumably packed nibbles
* for 16-colour mode). hot_x / hot_y mark the "click point" within the
* cursor image (0,0 = top-left).
*/
typedef struct {
const void *image; /* bitmap data */
uint8_t width;
uint8_t height;
uint8_t hot_x;
uint8_t hot_y;
} mouse_cursor_t;
void mouse_load_cursor(const mouse_cursor_t *c);
/* Read back the current cursor.
* c->image must point to a buffer large enough to hold width*height bytes.
* width, height, hot_x, hot_y are written by the driver. */
void mouse_get_cursor(mouse_cursor_t *c);
/* ---- Sensitivity (functions $0E / $0F) ------------------------- *
* Verified empirically (2026-05-31): the value is a DIVIDER — the
* driver counts that many raw hardware steps from the mouse before
* advancing the cursor by one screen pixel. So **smaller value = more
* sensitive** (cursor moves faster), and larger value = slower cursor.
* (The doc statement "higher = less movement needed" appears to be
* inverted.) Useful starting point: 2 on both axes. */
uint8_t mouse_get_sensitivity_x(void);
uint8_t mouse_get_sensitivity_y(void);
void mouse_set_sensitivity(uint8_t horz, uint8_t vert);
/* ---- Solid-C compatibility ------------------------------------- *
* Solid-C uses shorter `ms_*` names + typed state structs. The semantics
* are identical to our `mouse_*` API. */
#define LEFT_BUTTON 1
#define RIGHT_BUTTON 2
/* MSGSTAT mirrors mouse_state_t — same field order and types. */
typedef mouse_state_t MSGSTAT;
#define ms_init mouse_init
#define ms_show mouse_show
#define ms_hide mouse_hide
#define ms_ref mouse_refresh
#define ms_xbnd mouse_bounds_x
#define ms_ybnd mouse_bounds_y
#define ms_spos mouse_goto
#define mssgpos mouse_goto
#define ms_tcur mouse_text_cursor
#define ms_scur mouse_load_cursor
#define ms_gcur mouse_get_cursor
#define ms_vmod mouse_video_mode_changed
#define ms_ssen mouse_set_sensitivity
#define msgstat(st) mouse_read((st))
/* ---- $81 CHANGE VIDEO MODE ------------------------------------- *
* Call after changing the video mode (set_videotextmode or gfx_init) so
* the driver re-syncs its internal coordinate ranges and cursor image.
*
* `mode` is the byte you switched to — TEXT_MODE_* (from <conio.h>) or
* GFX_MODE_* (from <gfx.h>), e.g. 0x03 for 80×32 text or 0x81 for
* 320×256×256. The driver REQUIRES the new mode in register A —
* passing garbage leaves the cursor mis-configured (text-mode XOR
* pattern applied in graphics, etc.). */
void mouse_video_mode_changed(uint8_t mode);
#endif
+156
View File
@@ -0,0 +1,156 @@
/*
* sprinter.h — low-level Sprinter platform definitions for C programs.
*
* Numbers and behaviour cross-checked against docs/converted/ (IvanMak.txt,
* DiskSyscalls.txt, BIOS_v3.txt, ProgrammerManual.txt).
*/
#ifndef SPRINTER_H
#define SPRINTER_H
#include <stdint.h>
/* ---- I/O ports ----------------------------------------------------- */
/* Memory window page-select ports (write 8-bit physical page number). */
#define PORT_PAGE_W0 0x82 /* window 0: 0x0000-0x3FFF (ESTEX system) */
#define PORT_PAGE_W1 0xA2 /* window 1: 0x4000-0x7FFF (HOME) */
#define PORT_PAGE_W2 0xC2 /* window 2: 0x8000-0xBFFF (stack+heap) */
#define PORT_PAGE_W3 0xE2 /* window 3: 0xC000-0xFFFF (paged) */
/* Graphics ports (used in stage 3+). */
#define PORT_RGADR 0x89 /* graphic Y / Spectrum-page selector */
#define PORT_RGMOD 0xC9 /* bit 0 = active screen page 0/1 */
/* ---- ESTEX RST 10h function numbers -------------------------------- */
/* Full list in docs/converted/DiskSyscalls.txt (DSS v1.6). */
#define ESTEX_VERSION 0x00
#define ESTEX_CHDISK 0x01
#define ESTEX_CURDISK 0x02
#define ESTEX_DSKINFO 0x03
#define ESTEX_CREATE 0x0A
#define ESTEX_CREATE_NEW 0x0B
#define ESTEX_DELETE 0x0E
#define ESTEX_RENAME 0x10
#define ESTEX_OPEN 0x11
#define ESTEX_CLOSE 0x12
#define ESTEX_READ 0x13
#define ESTEX_WRITE 0x14
#define ESTEX_MOVE_FP 0x15
#define ESTEX_ATTRIB 0x16
#define ESTEX_F_FIRST 0x19
#define ESTEX_F_NEXT 0x1A
#define ESTEX_MKDIR 0x1B
#define ESTEX_RMDIR 0x1C
#define ESTEX_CHDIR 0x1D
#define ESTEX_CURDIR 0x1E
#define ESTEX_SYSTIME 0x21
#define ESTEX_WAITKEY 0x30
#define ESTEX_SCANKEY 0x31
#define ESTEX_ECHOKEY 0x32
#define ESTEX_CTRLKEY 0x33
#define ESTEX_SETWIN 0x38
#define ESTEX_SETWIN1 0x39
#define ESTEX_SETWIN2 0x3A
#define ESTEX_SETWIN3 0x3B
#define ESTEX_INFOMEM 0x3C
#define ESTEX_GETMEM 0x3D
#define ESTEX_FREEMEM 0x3E
#define ESTEX_SETMEM 0x3F
#define ESTEX_EXEC 0x40
#define ESTEX_EXIT 0x41
#define ESTEX_WAIT 0x42
#define ESTEX_ENV 0x46 /* env API: B=0 sysenv, B=1 getenv, B=2 putenv */
#define ESTEX_SETVMOD 0x50
#define ESTEX_GETVMOD 0x51
#define ESTEX_LOCATE 0x52
#define ESTEX_CURSOR 0x53
#define ESTEX_SELPAGE 0x54
#define ESTEX_SCROLL 0x55
#define ESTEX_CLEAR 0x56
#define ESTEX_RDCHAR 0x57
#define ESTEX_WRCHAR 0x58
#define ESTEX_WINCOPY 0x59
#define ESTEX_WINREST 0x5A
#define ESTEX_PUTCHAR 0x5B
#define ESTEX_PCHARS 0x5C
#define ESTEX_PRINT 0x5F
/* ---- BIOS RST 8 function numbers ----------------------------------- */
/* Full list in docs/converted/BIOS_v3.txt (BIOS v3.00). */
#define BIOS_EMM_INFO 0xC0 /* HL=total pages, BC=free pages */
#define BIOS_EMM_INIT 0xC1
#define BIOS_EMM_ALLOC 0xC2 /* B=npages -> A=blk_id */
#define BIOS_EMM_FREE 0xC3 /* A=blk_id */
#define BIOS_EMM_GETPAGE 0xC4 /* A=blk_id, B=log -> A=physical */
#define BIOS_EMM_LIST 0xC5
#define BIOS_EMM_PORT_FOR 0xC6 /* A=window -> C=port, B=current page */
#define BIOS_EMM_NEXTPAGE 0xC7
/* ---- Inline paging intrinsics -------------------------------------- */
/*
* Each generates a single "OUT (port), A" + the load of the literal.
* Wrap in DI/EI yourself for graphics-grade timing; for one-shot setup
* the inline form is fine.
*/
__sfr __at PORT_PAGE_W0 _io_page_w0;
__sfr __at PORT_PAGE_W1 _io_page_w1;
__sfr __at PORT_PAGE_W2 _io_page_w2;
__sfr __at PORT_PAGE_W3 _io_page_w3;
static inline void sprinter_page_w0(uint8_t page) { _io_page_w0 = page; }
static inline void sprinter_page_w1(uint8_t page) { _io_page_w1 = page; }
static inline void sprinter_page_w2(uint8_t page) { _io_page_w2 = page; }
static inline void sprinter_page_w3(uint8_t page) { _io_page_w3 = page; }
/* ---- Sprinter-specific debug helpers ------------------------------ */
/* Print one byte as two uppercase hex digits via putchar(). */
void print_hex(uint8_t v);
#ifdef DEBUG_RT
/*
* Runtime diagnostics — exposed only when the program is built with
* `sprinter-cc --debug`. The flag below is set by crt0 before main().
*
* 0 — crt0 did NOT self-allocate a W2 page (tiny mode never needs to,
* and in small mode DSS itself maps W2 when the image > 16 KB).
* 1 — crt0 had to allocate and map a W2 page via ESTEX $3D + $3A
* (small mode with image ≤ 16 KB).
*/
extern uint8_t w2_self_allocated;
#endif
/* ---- Environment (ESTEX $46) -------------------------------------- */
/*
* getenv(name) — return pointer to value, or NULL if not set.
* Returned buffer is ESTEX-owned (shared static);
* copy if needed before the next getenv() call.
* putenv("name=value") — add/replace. Pass "name" with no '=' to remove.
* sysenv(buf) — copy the WHOLE environment into caller's buf,
* as a series of NUL-terminated "NAME=value"
* strings followed by an extra NUL marking end:
* "PATH=...\0SOLID=H\0\0"
* Caller is responsible for sizing buf large
* enough for the entire environment.
*
* Return values:
* getenv: pointer to value (NUL-terminated) on success,
* NULL when the variable is not present (errno unchanged),
* NULL with errno set on real error.
* putenv: 0 on success, -1 on error (errno set).
* sysenv: buf on success, (char*)-1 on error (errno set).
*/
char *getenv(const char *name);
int putenv(const char *namevalue);
char *sysenv(char *buf);
#endif /* SPRINTER_H */
+106
View File
@@ -0,0 +1,106 @@
/*
* sprinter_compat.h — Solid-C compatibility shims.
*
* Pulls in standard headers and adds aliases/macros that Solid-C
* programs expect. Programs targeting Sprinter from Solid-C source
* can `#include <sprinter_compat.h>` and most names "just work".
*/
#ifndef SPRINTER_COMPAT_H
#define SPRINTER_COMPAT_H
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <mouse.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <dir.h>
#include <sprinter_exit.h> /* _exit, atexit */
/* ---- Solid-C types ----------------------------------------------- */
typedef uint8_t BYTE;
typedef uint8_t TINY;
typedef uint8_t BOOL;
typedef uint8_t STATUS;
typedef uint16_t WORD;
typedef int FD;
#ifndef TRUE
#define TRUE 1
#define FALSE 0
#define YES 1
#define NO 0
#endif
#ifndef OK
#define OK 0
#endif
#ifndef ERROR
#define ERROR (-1)
#endif
#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1
#endif
/* Solid-C's uint typedef (we expose it explicitly). Note `uint` may
* already be defined in some environments; guard. */
#ifndef _SOLID_UINT_DEFINED
#define _SOLID_UINT_DEFINED
typedef unsigned uint;
#endif
/* fpoint — 32-bit split file position (used by lseek/tell). */
typedef struct fpoint {
uint16_t low;
uint16_t high;
} f_point;
/* ---- string.h aliases -------------------------------------------- */
/* setmem(ptr, n, byte) → memset(ptr, byte, n) — arg order swapped */
#define setmem(p, n, b) memset((p), (b), (n))
/* movmem(src, dst, n) → memcpy(dst, src, n) — arg order swapped */
#define movmem(s, d, n) memcpy((d), (s), (n))
/* In-place case conversion. */
char *strlwr(char *s);
char *strupr(char *s);
/* Solid-C calls perror's table 'strerr'. */
#define strerr(n) strerror((n))
/* ---- stdlib.h additions ------------------------------------------ */
#define abort() _exit(0xFF)
#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef max
#define max(a, b) ((a) > (b) ? (a) : (b))
#endif
/* div() and div_t come from SDCC's <stdlib.h> (already included above). */
/* sysenv() is now a real function (libc/io/env.c) that returns the WHOLE
* environment into a caller-provided buffer. Declared in <sprinter.h>. */
/* ---- ctype.h additions ------------------------------------------- */
#define isascii(c) (((unsigned)(c)) < 128)
/* ---- io.h aliases (Solid-C fd shortcuts) ------------------------- */
#define seek(fd, off) lseek((fd), (long)(off), SEEK_SET)
#define tell(fd) ((uint16_t)lseek((fd), 0, SEEK_CUR))
#define remove(name) unlink(name)
/* ---- dir.h alias ------------------------------------------------- */
#define _ffirst ffirst
#endif
+15
View File
@@ -0,0 +1,15 @@
/*
* sprinter_exit.h — exit() / _exit() / atexit() declarations.
*
* SDCC's z80 <stdlib.h> doesn't ship these. Include this header
* (in addition to <stdlib.h>) to get them. Link with libc/io/atexit.c.
*/
#ifndef SPRINTER_EXIT_H
#define SPRINTER_EXIT_H
int atexit(void (*fn)(void));
void exit (int code);
void _exit (int code);
#endif
+37
View File
@@ -0,0 +1,37 @@
/*
* sprinter_mem.h — banking-aware page allocator and bank-data accessors.
*
* For data that doesn't fit in window 2's heap, allocate physical pages
* directly through ESTEX EMM and access them via bank_read / bank_write
* (which swap window 3 internally).
*
* mem_alloc_pages(N) — reserve N contiguous 16 KB pages, return block id.
* mem_free_block(id) — release a previously-allocated block.
* mem_get_page(id, i) — translate (block, page-index) to physical page.
* mem_info(&total, &free) — query EMM about total/free 16 KB pages.
*
* bank_load_byte / bank_store_byte — single-byte access via W3.
* bank_read / bank_write — bulk copy between far page and a near buffer.
*
* The bank_* helpers live in HOME so they are always reachable; they save
* the previous W3 page, swap to the target, do the work, restore W3.
*/
#ifndef SPRINTER_MEM_H
#define SPRINTER_MEM_H
#include <stdint.h>
/* ESTEX EMM allocator wrappers. */
uint8_t mem_alloc_pages(uint8_t n); /* 0 = failure */
void mem_free_block (uint8_t blk_id);
uint8_t mem_get_page (uint8_t blk_id, uint8_t idx);
void mem_info (uint16_t *total, uint16_t *free_pages);
/* Far-page accessors (always-mapped HOME, swap W3 internally). */
uint8_t bank_load_byte (uint8_t phys_page, uint16_t off_in_window);
void bank_store_byte(uint8_t phys_page, uint16_t off_in_window, uint8_t v);
void bank_read (uint8_t phys_page, uint16_t off, void *dst, uint16_t n);
void bank_write(uint8_t phys_page, uint16_t off, const void *src, uint16_t n);
#endif
+102
View File
@@ -0,0 +1,102 @@
/*
* stdio.h — extends SDCC's z80 stdio with a minimal FILE * stream API.
*
* The bottom of the stack is the existing POSIX-style fd I/O (open/
* read/write/close/lseek). FILE * is a thin struct wrapping a fd plus
* a few flag bits. No buffering — every fread/fwrite/fputc maps 1:1
* to a syscall. Sprinter's I/O is already char-oriented at the ESTEX
* level, so this stays minimal.
*
* stdin / stdout / stderr are predefined "virtual" FILE * pointers
* that talk to the console via putchar()/getchar() (NOT through a fd).
* That keeps printf-routed output going through our CR/LF mapping.
*/
#ifndef STDIO_H
#define STDIO_H
#include <stdarg.h>
#include <stddef.h> /* size_t */
#include <stdint.h>
#ifndef EOF
#define EOF (-1)
#endif
/* ---- printf family (linked from SDCC's z80.lib) -------------------- */
int printf (const char *, ...);
int sprintf(char *, const char *, ...);
int vprintf(const char *, va_list);
int vsprintf(char *, const char *, va_list);
/* puts / putchar / getchar — overridden by our libc to use ESTEX. */
int puts (const char *);
int putchar(int);
int getchar(void);
/* ---- FILE * (minimal, unbuffered) ---------------------------------- */
/* Internal layout — opaque to user. fd == -1 marks the console
* pseudo-streams (stdin/stdout/stderr). */
typedef struct __FILE {
int fd; /* underlying POSIX fd, or -1 for console */
uint8_t flags; /* see _F_* below */
} FILE;
#define _F_READ 0x01
#define _F_WRITE 0x02
#define _F_APPEND 0x04
#define _F_EOF 0x10
#define _F_ERROR 0x20
#define _F_CONIN 0x40 /* console pseudo-stream — uses getchar() */
#define _F_CONOUT 0x80 /* console pseudo-stream — uses putchar() */
extern FILE *const stdin;
extern FILE *const stdout;
extern FILE *const stderr;
/* fseek whence — same numeric values as SEEK_SET/CUR/END in unistd.h. */
#ifndef SEEK_SET
#define SEEK_SET 0
#define SEEK_CUR 1
#define SEEK_END 2
#endif
FILE *fopen (const char *path, const char *mode);
int fclose(FILE *fp);
int fflush(FILE *fp);
int fputc (int c, FILE *fp);
int fgetc (FILE *fp);
int fputs (const char *s, FILE *fp);
char *fgets (char *buf, int n, FILE *fp);
size_t fread (void *ptr, size_t size, size_t nmemb, FILE *fp);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp);
int fseek (FILE *fp, long off, int whence);
long ftell (FILE *fp);
void rewind(FILE *fp);
int feof (FILE *fp);
int ferror(FILE *fp);
void clearerr(FILE *fp);
/* Aliases to fputc/fgetc — POSIX says they may be macros. */
#define putc(c, fp) fputc(c, fp)
#define getc(fp) fgetc(fp)
/* Read line from stdin into buf until '\n' or EOF; '\n' is not stored.
* Returns buf, or NULL on EOF with empty input. Solid-C/POSIX semantic.
* Note: dangerous, no length check — caller must size buf appropriately. */
char *gets(char *buf);
/* Solid-C decimal/hex helpers — print uint as decimal/hex without args. */
void hex8 (uint8_t v); /* prints two hex digits */
void hex16(uint16_t v); /* prints four hex digits */
void hex32(uint32_t v); /* prints eight hex digits */
void dec8 (uint8_t v); /* prints up to 3 decimal digits, no padding */
void dec16(uint16_t v); /* prints up to 5 decimal digits */
void dec32(uint32_t v); /* prints up to 10 decimal digits */
#endif
+42
View File
@@ -0,0 +1,42 @@
/*
* sys/stat.h — POSIX-style stat() / fstat() over ESTEX file metadata.
*
* stat(path, &st) queries via ESTEX F_FIRST ($19) using the exact
* name; gets size, attrib, and DOS-format date/time
* that we expand to a struct stat.
* fstat(fd, &st) queries an open handle: size via lseek(SEEK_END)
* trick + date/time via ESTEX GET_D_T ($17).
*
* Sprinter has no inodes / owners / groups, so st_ino / st_uid / st_gid
* are absent. S_ISREG and S_ISDIR are the only meaningful mode tests.
*/
#ifndef SYS_STAT_H
#define SYS_STAT_H
#include <stdint.h>
#include <time.h> /* time_t */
/* File mode bits — POSIX subset (octal as per convention). */
#define S_IFMT 0170000 /* file-type mask */
#define S_IFREG 0100000 /* regular file */
#define S_IFDIR 0040000 /* directory */
#define S_IRUSR 0000400 /* owner read */
#define S_IWUSR 0000200 /* owner write */
#define S_IXUSR 0000100 /* owner execute */
#define S_IRWXU 0000700
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
struct stat {
uint16_t st_mode; /* file type + perms */
uint32_t st_size; /* size in bytes */
time_t st_mtime; /* last-mod time (Unix epoch) */
};
int stat (const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
#endif
+70
View File
@@ -0,0 +1,70 @@
/*
* time.h — system clock access via ESTEX $21 / $22.
*
* getdatetime() — read current date/time into a datetime_t.
* setdatetime() — set system clock from a datetime_t.
* Returns 0 on success, -1 with errno set on failure.
*
* Date/time is the raw Sprinter clock — no epoch conversion, no time_t.
*/
#ifndef TIME_H
#define TIME_H
#include <stdint.h>
typedef struct {
uint8_t day; /* 1..31 */
uint8_t month; /* 1..12 */
uint16_t year; /* full year, e.g. 2026 */
uint8_t hour; /* 0..23 */
uint8_t minute; /* 0..59 */
uint8_t second; /* 0..59 */
uint8_t dow; /* day of week, 1-based: 1=Sun..7=Sat — see DOW_* */
} datetime_t;
/* Sprinter ESTEX SYSTIME dow encoding (verified empirically):
* 1-based, week starts on Sunday — matches DOS INT 21h AH=2A+1. */
#define DOW_SUN 1
#define DOW_MON 2
#define DOW_TUE 3
#define DOW_WED 4
#define DOW_THU 5
#define DOW_FRI 6
#define DOW_SAT 7
void getdatetime(datetime_t *dt);
int setdatetime(const datetime_t *dt);
/* ------------------------------------------------------------------
* POSIX <time.h> API — implemented by SDCC's z80.lib (time.rel etc.).
* We provide RtcRead() in libc/io/time.c which bridges to getdatetime().
*
* Layout of struct tm matches SDCC's z80 ABI exactly (__TIME_UNSIGNED=1
* variant): tm_sec/min/hour/mday/mon/wday/isdst/hundredth are 1 byte,
* tm_year/tm_yday are 16-bit ints. Total 12 bytes.
* ------------------------------------------------------------------ */
struct tm {
unsigned char tm_sec; /* 0..60 */
unsigned char tm_min; /* 0..59 */
unsigned char tm_hour; /* 0..23 */
unsigned char tm_mday; /* 1..31 */
unsigned char tm_mon; /* 0..11 (POSIX, not Sprinter native!) */
int tm_year; /* years since 1900 */
unsigned char tm_wday; /* 0..6 (Sunday=0, POSIX) */
int tm_yday; /* 0..365 */
unsigned char tm_isdst;
unsigned char tm_hundredth; /* SDCC extension; we leave 0 */
};
typedef unsigned long time_t;
time_t time (time_t *t);
struct tm *gmtime (time_t *timep);
struct tm *localtime(time_t *timep);
time_t mktime (struct tm *timeptr);
char *asctime (struct tm *timeptr);
char *ctime (time_t *timep);
#endif
+39
View File
@@ -0,0 +1,39 @@
/*
* unistd.h — POSIX-style file descriptor I/O for Sprinter ESTEX.
*
* Each call maps to a single ESTEX RST 10h function:
* read → $13 write → $14
* close → $12 unlink → $0E
*
* On error every call returns -1 (the ESTEX error code is not exposed yet;
* a future errno mechanism will surface it).
*/
#ifndef UNISTD_H
#define UNISTD_H
#include <stddef.h> /* size_t */
/* lseek whence values (POSIX). */
#define SEEK_SET 0
#define SEEK_CUR 1
#define SEEK_END 2
int read (int fd, void *buf, size_t n);
int write(int fd, const void *buf, size_t n);
int close(int fd);
int unlink(const char *path);
long lseek(int fd, long offset, int whence);
/* Block the calling task for `seconds` seconds (50 Hz IRQ-based timer). */
void sleep(unsigned int seconds);
/* Directory operations (ESTEX $1B-$1E). All return 0 on success and -1
* with errno set on failure; getcwd returns the buffer on success or NULL.
* size is ignored — ESTEX always wants a 256-byte buffer. */
int mkdir (const char *path);
int rmdir (const char *path);
int chdir (const char *path);
char *getcwd(char *buf, size_t size);
#endif