858e5755ad
- big commit.
692 lines
20 KiB
C
692 lines
20 KiB
C
/*
|
||
* conio.c — console I/O wrappers around ESTEX kbd/screen syscalls.
|
||
*
|
||
* $30 WAITKEY — blocking read, returns scan / ASCII / modifiers
|
||
* $31 SCANKEY — non-blocking poll
|
||
* $32 ECHOKEY — blocking read + auto-echo to the screen
|
||
* $52 LOCATE — set cursor to (D=row, E=col)
|
||
* $56 CLEAR — fill a window with (A=char, B=attr)
|
||
* $5B PUTCHAR — write single character (CR/LF/scroll handled by ESTEX)
|
||
*
|
||
* Every RST 10h is bracketed with push/pop IX (caller's frame pointer).
|
||
*/
|
||
|
||
#include <conio.h>
|
||
#include <stdint.h>
|
||
#include <errno.h>
|
||
|
||
/* Forward extern — definition is further down (after putch/cputs which
|
||
* reference it from asm by linker-symbol name). */
|
||
extern int16_t g_text_attr;
|
||
|
||
char kbhit(void) __naked
|
||
{
|
||
__asm
|
||
push ix
|
||
ld c, #0x33 ; ESTEX CTRLKEY — peeks without consuming
|
||
rst #0x10
|
||
pop ix
|
||
;; A=0 → no key waiting; non-zero → there is one.
|
||
or a, a
|
||
ret z
|
||
ld a, #0x01
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
char getch(void) __naked
|
||
{
|
||
__asm
|
||
push ix
|
||
ld c, #0x30 ; ESTEX WAITKEY (no echo)
|
||
rst #0x10
|
||
pop ix
|
||
;; ESTEX returns ASCII in E (and copy in A)
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
char getche(void) __naked
|
||
{
|
||
__asm
|
||
push ix
|
||
ld c, #0x32 ; ESTEX ECHOKEY (echo to console)
|
||
rst #0x10
|
||
pop ix
|
||
;; ESTEX returns ASCII in E (and copy in A)
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
/* getkey — like getch() but exposes BOTH the ASCII value and the
|
||
* positional scan code, so callers can distinguish extended keys
|
||
* (arrows, F1..F12, PgUp/PgDn, Home/End, Ins/Del — all of which carry
|
||
* ASCII == 0 from ESTEX) from plain ASCII keys.
|
||
*
|
||
* return = (scan << 8) | ascii
|
||
*
|
||
* For plain keys: ascii holds the character, scan holds the positional
|
||
* code (bit 7 set when Ctrl/Alt/Shift is held).
|
||
* For extended keys: ascii == 0, scan identifies the key (see KEY_* in
|
||
* <conio.h>).
|
||
*/
|
||
uint16_t getkey(void) __naked
|
||
{
|
||
__asm
|
||
push ix
|
||
ld c, #0x30 ; ESTEX WAITKEY: A=ASCII, D=scan, E=ASCII
|
||
rst #0x10
|
||
pop ix
|
||
ld e, a ; E = ASCII (defensive: A is the canonical copy)
|
||
ret ; __sdcccall(1) returns uint16_t in DE
|
||
__endasm;
|
||
}
|
||
|
||
/* ---- putch / cputs: Turbo-C conio convention ---------------------- *
|
||
* Both APPLY the current text attribute (g_text_attr). When attr is
|
||
* KEEP_EXIST_ATTR (>0xFF), they short-circuit to the FAST stdio path
|
||
* (putchar / puts-like raw PCHARS).
|
||
*
|
||
* No '\n' to CR LF translation here — Turbo-C cputs/putch require the
|
||
* caller to use "\r\n" explicitly. Stdio puts/putchar do translate.
|
||
*/
|
||
|
||
static uint8_t pc_ch = 0;
|
||
static uint8_t pc_attr = 0;
|
||
static uint8_t pc_row = 0;
|
||
static uint8_t pc_col = 0;
|
||
|
||
/* Controls how _raw_putch treats control characters (< 0x20):
|
||
* 0 (default) — BS/TAB/LF/CR are interpreted (no glyph output);
|
||
* other chars print as glyphs via WRCHAR.
|
||
* 1 — all characters print as glyphs, no interpretation.
|
||
*
|
||
* Only takes effect on the WRCHAR (attr ≤ 0xFF) path. When
|
||
* g_text_attr is KEEP_EXIST_ATTR, ESTEX's own PUTCHAR/PCHARS handle
|
||
* cursor and control chars — pc_raw_mode is irrelevant. */
|
||
static uint8_t pc_raw_mode = 0;
|
||
|
||
void set_putch_raw_mode(uint8_t mode) { pc_raw_mode = mode; }
|
||
uint8_t get_putch_raw_mode(void) { return pc_raw_mode; }
|
||
|
||
/* ---- Internal helpers ------------------------------------------- */
|
||
|
||
/* Read current cursor into pc_row / pc_col via ESTEX CURSOR ($53). */
|
||
static void _get_cursor(void) __naked
|
||
{
|
||
__asm
|
||
push ix
|
||
ld c, #0x53 ; ESTEX CURSOR
|
||
rst #0x10
|
||
ld a, d
|
||
ld (_pc_row), a
|
||
ld a, e
|
||
ld (_pc_col), a
|
||
pop ix
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
/* Move cursor to (pc_col, pc_row) via ESTEX LOCATE ($52). */
|
||
static void _set_cursor(void) __naked
|
||
{
|
||
__asm
|
||
push ix
|
||
ld a, (_pc_row)
|
||
ld d, a
|
||
ld a, (_pc_col)
|
||
ld e, a
|
||
ld c, #0x52 ; ESTEX LOCATE
|
||
rst #0x10
|
||
pop ix
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
/* ESTEX PUTCHAR ($5B) — fast no-attr path; ESTEX handles CR/LF/scroll
|
||
* and cursor itself. Used when g_text_attr = KEEP_EXIST_ATTR. */
|
||
static char _bios_putchar(char ch) __naked
|
||
{
|
||
(void)ch;
|
||
__asm
|
||
;; c in A. push af stashes it across the RST (which clobbers A).
|
||
;; push ix ; PUTCHAR не меняет IX
|
||
push af
|
||
ld c, #0x5B ; ESTEX PUTCHAR
|
||
rst #0x10
|
||
pop af
|
||
;; pop ix
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
/* Raw putch: low-level WRCHAR-based output at (pc_col, pc_row) using
|
||
* the given attribute byte (caller has already verified that the high
|
||
* byte of g_text_attr is zero — this function takes only the low byte).
|
||
* Updates pc_col / pc_row per pc_raw_mode:
|
||
*
|
||
* pc_raw_mode == 0: BS/TAB/LF/CR are INTERPRETED:
|
||
* 0x08 BS → pc_col-- (if not already 0)
|
||
* 0x09 TAB → pc_col rounded up to next multiple of 8 (capped 80)
|
||
* 0x0A LF → pc_row++ (capped at 32; no glyph)
|
||
* 0x0D CR → pc_col = 0
|
||
* other → WRCHAR + pc_col++
|
||
*
|
||
* pc_raw_mode == 1: ALL characters print as glyphs via WRCHAR
|
||
* + pc_col++ (including 0x08, 0x09, 0x0A, 0x0D — they render as
|
||
* their CP437 glyphs).
|
||
*
|
||
* WRCHAR itself is suppressed when pc_col ≥ 80 or pc_row ≥ 32 (off-
|
||
* screen) — coordinates [0..79] × [0..31] only.
|
||
*
|
||
* Does NOT call CURSOR / LOCATE — caller is expected to fetch cursor
|
||
* once before a sequence of _raw_putch calls and write it back once
|
||
* after, so we pay the BIOS overhead per OPERATION instead of per CHAR. */
|
||
/* Mode-0 worker: interprets BS/TAB/LF/CR, outputs other chars as glyphs. */
|
||
static void _raw_putch_raw0(char ch, uint8_t attr) __naked
|
||
{
|
||
(void)ch; (void)attr;
|
||
__asm
|
||
;; __sdcccall(1): ch in A, attr in L.
|
||
;; Dispatch on control chars while A still holds ch (cp does not
|
||
;; modify A). B/C only get loaded on the output path so the
|
||
;; ctrl-char paths are cheaper.
|
||
cp #0x08
|
||
jr z, _rp0_bs
|
||
cp #0x09
|
||
jr z, _rp0_tab
|
||
cp #0x0A
|
||
jr z, _rp0_lf
|
||
cp #0x0D
|
||
jr z, _rp0_cr
|
||
;; Anything else (printable or unrecognised ctrl) → glyph.
|
||
|
||
ld c, a ; C = ch (save before A is clobbered)
|
||
ld a, (_pc_row)
|
||
cp #32
|
||
ret nc ; off-screen bottom — silently skip
|
||
ld d, a ; D = row (ESTEX WRCHAR convention)
|
||
ld a, (_pc_col)
|
||
cp #80
|
||
ret nc ; off-screen right — silently skip
|
||
ld e, a ; E = col
|
||
inc a
|
||
ld (_pc_col), a ; pc_col++
|
||
|
||
ld b, l ; B = attr
|
||
ld a, c ; A = ch
|
||
push ix
|
||
ld c, #0x58 ; ESTEX WRCHAR
|
||
rst #0x10
|
||
pop ix
|
||
ret
|
||
|
||
_rp0_bs:
|
||
ld a, (_pc_col)
|
||
or a, a
|
||
ret z ; already at col 0 — no change
|
||
dec a
|
||
ld (_pc_col), a
|
||
ret
|
||
|
||
_rp0_tab:
|
||
ld a, (_pc_col)
|
||
and #0xF8 ; floor to mult of 8
|
||
add a, #8 ; → next mult of 8
|
||
cp #80
|
||
jr c, _rp0_tab_store
|
||
ld a, #80 ; cap at off-screen right
|
||
_rp0_tab_store:
|
||
ld (_pc_col), a
|
||
ret
|
||
|
||
_rp0_lf:
|
||
ld a, (_pc_row)
|
||
cp #32
|
||
ret nc ; already at bottom edge
|
||
inc a
|
||
ld (_pc_row), a
|
||
ret
|
||
|
||
_rp0_cr:
|
||
xor a, a
|
||
ld (_pc_col), a
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
/* Mode-1 worker: every byte goes through WRCHAR as a glyph. */
|
||
static void _raw_putch_raw1(char ch, uint8_t attr) __naked
|
||
{
|
||
(void)ch; (void)attr;
|
||
__asm
|
||
;; __sdcccall(1): ch in A, attr in L.
|
||
ld c, a ; C = ch (save)
|
||
ld a, (_pc_row)
|
||
cp #32
|
||
ret nc ; off-screen bottom — silently skip
|
||
ld d, a ; D = row
|
||
ld a, (_pc_col)
|
||
cp #80
|
||
ret nc ; off-screen right — silently skip
|
||
ld e, a ; E = col
|
||
inc a
|
||
ld (_pc_col), a ; pc_col++
|
||
|
||
ld b, l ; B = attr
|
||
ld a, c ; A = ch
|
||
push ix
|
||
ld c, #0x58 ; ESTEX WRCHAR
|
||
rst #0x10
|
||
pop ix
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
/* PCHARS (no attr) — used by cputs when KEEP_EXIST_ATTR is in effect. */
|
||
static void _cputs_pchars(const char *s) __naked
|
||
{
|
||
(void)s;
|
||
__asm
|
||
push ix
|
||
ld c, #0x5C ; ESTEX PCHARS
|
||
rst #0x10
|
||
pop ix
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
/* ---- Public putch / cputs --------------------------------------- *
|
||
*
|
||
* KEEP_EXIST_ATTR (high byte != 0) → fast PUTCHAR/PCHARS through
|
||
* ESTEX, which manages its own cursor.
|
||
*
|
||
* Otherwise → fetch cursor ONCE via CURSOR ($53), run one or many
|
||
* _raw_putch calls, write cursor back ONCE via LOCATE ($52). This
|
||
* folds the per-char CURSOR/LOCATE pair from the old design into a
|
||
* single pair per operation. */
|
||
|
||
char putch(char ch) __naked
|
||
{
|
||
(void)ch;
|
||
__asm
|
||
;; A = ch on entry; char return → A.
|
||
ld (_pc_ch), a ; stash c (for both return and re-load)
|
||
|
||
;; KEEP_EXIST_ATTR? high byte of g_text_attr != 0
|
||
ld a, (_g_text_attr + 1)
|
||
or a, a
|
||
jr nz, _putch_fast
|
||
|
||
;; --- WRCHAR path: cursor → worker → cursor ---
|
||
call __get_cursor
|
||
|
||
;; Worker ABI (2 char/uint8 args): ch in A, attr in L.
|
||
ld a, (_g_text_attr) ; A = low byte = attr
|
||
ld l, a ; L = attr
|
||
|
||
ld a, (_pc_raw_mode)
|
||
or a, a ; Z = (mode == 0)
|
||
ld a, (_pc_ch) ; A = ch (`ld a,(nn)` does not touch flags)
|
||
jr nz, _putch_use_raw1
|
||
call __raw_putch_raw0
|
||
jr _putch_after_raw
|
||
_putch_use_raw1:
|
||
call __raw_putch_raw1
|
||
_putch_after_raw:
|
||
call __set_cursor
|
||
ld a, (_pc_ch) ; return value
|
||
ret
|
||
|
||
_putch_fast:
|
||
ld a, (_pc_ch)
|
||
call __bios_putchar ; __bios_putchar keeps AF
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
char cputs(const char *s) __naked
|
||
{
|
||
(void)s;
|
||
__asm
|
||
;; HL = s on entry; char return → A.
|
||
|
||
;; NULL-check: cputs(NULL) → return 0 immediately.
|
||
ld a, h
|
||
or a, l
|
||
ret z
|
||
|
||
;; IX is callee-saved under SDCC's z80 ABI. We use it as a
|
||
;; function pointer below (ld ix, #__raw_putch_raw0/1) so save
|
||
;; the caller's value up front and restore before every ret.
|
||
push ix
|
||
|
||
;; KEEP_EXIST_ATTR? high byte of g_text_attr != 0
|
||
ld a, (_g_text_attr + 1)
|
||
or a, a
|
||
jr nz, _cputs_fast
|
||
|
||
;; --- WRCHAR path: cursor → worker → cursor ---
|
||
call __get_cursor
|
||
|
||
;; Worker walks the string via DE; HL is used to carry attr in L
|
||
;; across iterations (RST 10 inside worker clobbers it, so we
|
||
;; push/pop hl around each call).
|
||
ex de, hl ; DE = s
|
||
ld a, (_g_text_attr) ; A = attr (low byte)
|
||
ld l, a ; L = attr (worker ABI: arg2 in L)
|
||
|
||
;; Pick worker once based on pc_raw_mode; IX = function pointer.
|
||
ld a, (_pc_raw_mode)
|
||
or a, a
|
||
jr z, _cputs_use_raw0
|
||
ld ix, #__raw_putch_raw1
|
||
jr _cputs_loop
|
||
_cputs_use_raw0:
|
||
ld ix, #__raw_putch_raw0
|
||
|
||
_cputs_loop:
|
||
ld a, (de)
|
||
or a, a
|
||
jr z, _cputs_loop_end
|
||
inc de
|
||
;; Z80 has no "call (ix)" — emulate via push-of-ret + jp (ix).
|
||
push de ; save string pointer
|
||
push hl ; save attr (in L)
|
||
ld de, #_cputs_after_worker
|
||
push de ; push return address
|
||
jp (ix) ; "call" worker
|
||
_cputs_after_worker:
|
||
pop hl
|
||
pop de
|
||
jr _cputs_loop
|
||
_cputs_loop_end:
|
||
|
||
call __set_cursor
|
||
pop ix ; restore caller's IX
|
||
xor a, a ; return 0
|
||
ret
|
||
|
||
_cputs_fast:
|
||
call __cputs_pchars
|
||
pop ix ; restore caller's IX
|
||
xor a, a ; return 0
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
void clrscr(void) __naked
|
||
{
|
||
__asm
|
||
ld a, #0x0F
|
||
jp _clrscr_attr
|
||
__endasm;
|
||
}
|
||
|
||
void clrscr_attr(uint8_t attr) __naked
|
||
{
|
||
(void)attr;
|
||
__asm
|
||
push ix
|
||
;; SDCC __sdcccall(1): uint8_t 1st arg is in A.
|
||
ld b, a ; B = attribute (mode-fill colour)
|
||
ld de, #0x0000 ; top-left
|
||
ld hl, #0x2050 ; H=32 rows, L=80 cols
|
||
ld a, #0x20 ; space fill
|
||
ld c, #0x56 ; ESTEX CLEAR
|
||
rst #0x10
|
||
pop ix
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
void gotoxy(uint8_t x, uint8_t y) __naked
|
||
{
|
||
(void)x; (void)y;
|
||
__asm
|
||
;; __sdcccall(1) 2 uint8 args: x in A, y in L.
|
||
;; ESTEX LOCATE ($52) wants: D = row, E = col.
|
||
push ix
|
||
ld d, l ; D = row (y)
|
||
ld e, a ; E = col (x)
|
||
ld c, #0x52
|
||
rst #0x10
|
||
pop ix
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
uint8_t wherex(void) __naked
|
||
{
|
||
__asm
|
||
;; ESTEX CURSOR ($53): D = row, E = col. Return col in DE.
|
||
push ix
|
||
ld c, #0x53
|
||
rst #0x10
|
||
pop ix
|
||
ld a, e
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
uint8_t wherey(void) __naked
|
||
{
|
||
__asm
|
||
push ix
|
||
ld c, #0x53
|
||
rst #0x10
|
||
pop ix
|
||
ld a, d
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
uint16_t wherexy(void) __naked
|
||
{
|
||
__asm
|
||
;; ESTEX CURSOR ($53): D = row, E = col. Return col in DE.
|
||
push ix
|
||
ld c, #0x53
|
||
rst #0x10
|
||
pop ix
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
/* wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr)
|
||
*
|
||
* SDCC __sdcccall(1): x in A, y in L (2 uint8 → A, L); ch and attr
|
||
* packed and pushed on the stack as a single 16-bit value (caller does
|
||
* `ld hl, #(attr<<8)|ch; push hl`). Layout after CALL:
|
||
* [SP+0..1] = return address
|
||
* [SP+2] = ch (low half of pushed pair)
|
||
* [SP+3] = attr (high half)
|
||
* Void return → callee-pops the 2 stack-arg bytes via `pop bc` + jp (iy).
|
||
*/
|
||
void scroll(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t direction, uint8_t clear) __naked
|
||
{
|
||
(void)x; (void)y; (void)w; (void)h; (void)direction; (void)clear;
|
||
__asm
|
||
pop iy ; return address
|
||
ld d, l ; D = row (y)
|
||
ld e, a ; E = col (x)
|
||
pop hl ; H = heigth(h), L = width(w)
|
||
pop bc ; C = direction, B = clear
|
||
ld a, b ; A = clear(B)
|
||
ld b, c ; B = direction(C)
|
||
push ix
|
||
ld c, #0x55 ; ESTEX SCROLL
|
||
rst #0x10
|
||
pop ix
|
||
jp (iy)
|
||
__endasm;
|
||
}
|
||
|
||
|
||
|
||
|
||
/* wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr)
|
||
*
|
||
* SDCC __sdcccall(1): x in A, y in L (2 uint8 → A, L); ch and attr
|
||
* packed and pushed on the stack as a single 16-bit value (caller does
|
||
* `ld hl, #(attr<<8)|ch; push hl`). Layout after CALL:
|
||
* [SP+0..1] = return address
|
||
* [SP+2] = ch (low half of pushed pair)
|
||
* [SP+3] = attr (high half)
|
||
* Void return → callee-pops the 2 stack-arg bytes via `pop bc` + jp (iy).
|
||
*/
|
||
void wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr) __naked
|
||
{
|
||
(void)x; (void)y; (void)ch; (void)attr;
|
||
__asm
|
||
pop iy ; return address
|
||
pop bc ; C = ch, B = attr
|
||
push ix
|
||
ld d, l ; D = row (y)
|
||
ld e, a ; E = col (x)
|
||
ld a, c ; A = ch
|
||
ld c, #0x58 ; ESTEX WRCHAR
|
||
rst #0x10
|
||
pop ix
|
||
jp (iy)
|
||
__endasm;
|
||
}
|
||
|
||
/* rdchar(int x, int y) → (attr << 8) | ch */
|
||
uint16_t rdchar(uint8_t x, uint8_t y) __naked
|
||
{
|
||
(void)x; (void)y;
|
||
__asm
|
||
push ix
|
||
ld d, l ; D = row
|
||
ld e, a ; E = col
|
||
ld c, #0x57 ; ESTEX RDCHAR
|
||
rst #0x10
|
||
;; A = ch, B = attr
|
||
ld d, b ; high byte → attr
|
||
ld e, a ; low byte → ch
|
||
pop ix
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
/* Public text-mode video API — defined here so it's pulled in with the
|
||
* rest of conio. The raw setters/getters live in videomode_raw.c so
|
||
* pure graphics programs can pick them up without conio's other
|
||
* dependencies. */
|
||
extern uint8_t _videomode_raw_get(void);
|
||
extern int _videomode_raw_set(uint8_t mode);
|
||
|
||
uint8_t get_videotextmode(void)
|
||
{
|
||
return _videomode_raw_get();
|
||
}
|
||
|
||
int set_videotextmode(uint8_t mode)
|
||
{
|
||
/* Refuse anything that isn't a known text mode — otherwise a stray
|
||
* GFX_MODE_* value could swap the screen out from under text I/O. */
|
||
if (mode != TEXT_MODE_40x32 && mode != TEXT_MODE_80x32) {
|
||
errno = EINVAL;
|
||
return -1;
|
||
}
|
||
return _videomode_raw_set(mode);
|
||
}
|
||
|
||
/* ---- text attribute state ----------------------------------------
|
||
* g_text_attr is owned by conio.c now (Turbo-C-style: stdio putchar/puts
|
||
* are fast and attribute-free; only conio's putch/cputs/cprintf apply
|
||
* the attribute). Default = 0x0F (bright white on black).
|
||
*
|
||
* 0x00..0xFF — real attribute (4-bit FG | 3-bit BG | 1-bit blink)
|
||
* KEEP_EXIST_ATTR (0xFFFF) — putch/cputs fall back to fast no-attr path */
|
||
int16_t g_text_attr = 0x0F;
|
||
|
||
int16_t set_text_attr(int16_t attr)
|
||
{
|
||
int16_t prev = g_text_attr;
|
||
g_text_attr = attr;
|
||
return prev;
|
||
}
|
||
|
||
int16_t get_text_attr(void)
|
||
{
|
||
return g_text_attr;
|
||
}
|
||
|
||
/* ---- Turbo-C-style palette helpers --------------------------------
|
||
* textcolor / textbackground touch only their nibble; the other nibble
|
||
* (and the blink bit) are preserved. textattr replaces the whole byte. */
|
||
|
||
void textcolor(uint8_t fg)
|
||
{
|
||
/* If we were KEEP_EXIST_ATTR, switch to a real attr first. */
|
||
uint8_t cur = ((uint16_t)g_text_attr > 0xFF) ? 0x00 : (uint8_t)g_text_attr;
|
||
g_text_attr = (int16_t)((cur & 0xF0) | (fg & 0x0F));
|
||
}
|
||
|
||
void textbackground(uint8_t bg)
|
||
{
|
||
uint8_t cur = ((uint16_t)g_text_attr > 0xFF) ? 0x00 : (uint8_t)g_text_attr;
|
||
/* Background uses 3 bits (4..6); preserve blink (bit 7) too. */
|
||
g_text_attr = (int16_t)((cur & 0x8F) | ((bg & 0x07) << 4));
|
||
}
|
||
|
||
void textattr(uint8_t attr)
|
||
{
|
||
g_text_attr = (int16_t)attr;
|
||
}
|
||
|
||
/* ---- Solid-C compatibility ---------------------------------------- */
|
||
|
||
/* Direct port I/O. Z80 has 256 IN/OUT ports; we wrap the Z80 IN/OUT
|
||
* opcodes with a stable C API. Names match Solid-C / MS-DOS Turbo-C. */
|
||
uint8_t z80_inp(uint8_t port) __naked
|
||
{
|
||
(void)port;
|
||
__asm
|
||
;; SDCC __sdcccall(1): single uint8_t arg in A; uint8_t return in A.
|
||
ld c, a
|
||
in a, (c)
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
void z80_outp(uint8_t port, uint8_t value) __naked
|
||
{
|
||
(void)port; (void)value;
|
||
__asm
|
||
;; __sdcccall(1): 2 uint8 args → arg1 in A, arg2 in L.
|
||
ld c, a ; C = port
|
||
out (c), l ; out (port), value
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
/* cgets — Solid-C / Turbo-C style line input.
|
||
* buf[0] = max characters (in)
|
||
* buf[1] = actual count (out)
|
||
* buf[2..] = chars + NUL
|
||
* Returns &buf[2]. */
|
||
char *cgets(char *buf)
|
||
{
|
||
uint8_t maxlen = (uint8_t)buf[0];
|
||
uint8_t n = 0;
|
||
while (n < maxlen) {
|
||
int ch = getche();
|
||
if (ch == '\n' || ch == '\r') {
|
||
putch('\r'); putch('\n');
|
||
break;
|
||
}
|
||
if (ch == 8) { /* backspace */
|
||
if (n > 0) { n--; }
|
||
continue;
|
||
}
|
||
buf[2 + n] = (char)ch;
|
||
n++;
|
||
}
|
||
buf[1] = (char)n;
|
||
buf[2 + n] = 0;
|
||
return &buf[2];
|
||
}
|