libc: сплит «1 функция = 1 модуль» — вся библиотека, wildcard-сборка
- bios/conio/env/errno/gfx/io/mem/mouse/stdio/stdlib/string/sys/time/ video разложены по модулям: общие статики и helpers — в internal _-модулях (_conio.h/_mouse.h/_gfx.h/_palette.h/_atexit.h/_time.h) - lib/Makefile: LIBC_C = wildcard libc/*/*.c — гранулярность файлов = гранулярность DCE линкера - эффект _CODE: gfx_text 6986→2568 Б, gfx_mous −1745, gfx_demo/d16 −542; ранее timedir −3270, ls −3098, stattest −2995 - комментарии оставшихся модулей переведены на русский; puts: убран мёртвый pchars; videomode_raw разложен на get/set - docs/libc-split-asm-cases.md — правила asm-связок между модулями; docs/libc-roadmap.md — план этапа - восстановлен examples/mdview/SAMPLE.MD (нужен make floppy) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
# libc — план работ (на рассмотрение, 2026-07-06)
|
||||
|
||||
Анализ после сплита четвёрки лидеров (time/stat/file/conio, итог −19.6 КБ
|
||||
суммарно по _CODE приложений). Ниже — что ещё стоит сделать, по приоритетам.
|
||||
|
||||
---
|
||||
|
||||
## П1. Досплит остальной libc — СДЕЛАНО 2026-07-06
|
||||
|
||||
Всё из таблицы ниже посплитано (кроме dec_print — осознанно оставлен).
|
||||
gfx: 40 модулей (внутренний заголовок `libc/gfx/_gfx.h`, скретчи в
|
||||
data-модулях `_gfx_state/_gfx_w3_state/_gfx_acc256/_gfx_g16_state/
|
||||
_gfx_font_state`, helpers `_gfx_hfill256/_gfx_hfill16/_gfx_rmw16/
|
||||
_gfx_text16`); video/palette → 7 модулей + `_palette.h`;
|
||||
conio/text_palette → 5 модулей. Эффект (_CODE, Б):
|
||||
gfx_demo 3769→3227, gfx_d16 3869→3327, gfx_text 6986→2568,
|
||||
gfx_mous 7287→5542. Не-gfx тесты не изменились.
|
||||
|
||||
Правило было: 1 публичная функция = 1 модуль, state/helpers — в отдельные
|
||||
internal-модули, комментарии на русском, без `= 0`.
|
||||
|
||||
| Файл | Ф-ий | _CODE | Замечания |
|
||||
|---|---|---|---|
|
||||
| gfx/gfx_256.c + gfx_16.c | 13+13 | 1277+1159 Б | самый жирный кусок; резать по примитивам (putpixel/line/hline/vline/rect/fill/clear/text). Учесть SMC-паттерны акселератора и кэш Port_Y — state в data-модули |
|
||||
| gfx/gfx_raw_16 / raw_256 / raw_common / core / palette / font / text_* | ~40 | ~2.7 КБ | вместе с предыдущим — весь gfx |
|
||||
| mouse/mouse.c | 16 | 308 Б | state mb_*/mc_* → data-модули |
|
||||
| bios/text.c | 14 | 264 Б | все `__naked`, сплит чистый |
|
||||
| io/open.c | 3+3 | 216 Б | open/creat/close; asm-хелперы `_estex_*_raw` у единственных потребителей |
|
||||
| errno/errno.c | 2 | 680 Б | strerror + таблица строк неразделимы (один модуль); perror — отдельно, зовёт strerror |
|
||||
| video/palette.c | 6 | 348 Б | |
|
||||
| conio/text_palette.c | 5 | 140 Б | |
|
||||
| io/read.c | 2 | 60 Б | read + write — обязательно врозь (write-only приложения) |
|
||||
| io/fsdir.c | 4 | 80 Б | mkdir/rmdir/chdir/getcwd |
|
||||
| env/env.c | 3 | 78 Б | getenv/putenv/sysenv + общий env_buf → data-модуль |
|
||||
| mem/mem_bios, mem_estex, bank_io_w1/w3 | 3–4 каждый | ~430 Б | |
|
||||
| sys/atexit.c | 3 | 106 Б | atexit/exit/_exit; общий стек хендлеров → data-модуль (exit тянется всегда из crt0, выигрыш небольшой но правильный) |
|
||||
| io/dir.c | 2 | 53 Б | ffirst/fnext |
|
||||
| stdlib/minmax.c | 2 | 27 Б | min/max врозь |
|
||||
| stdio/hex_print.c | 3 | 38 Б | сплит чистый (call/jp по именам) — см. docs/libc-split-asm-cases.md |
|
||||
| stdio/dec_print.c | 3 | 176 Б | НЕ резать (общее тело); открытое решение: вариант «3 независимых цикла» — п. отложен |
|
||||
|
||||
Ожидаемый эффект: графические приложения −1–2 КБ, mouse/BIOS-text — сотни байт.
|
||||
|
||||
## П2. FILE* — отложенные баги и недостающее
|
||||
|
||||
Из шапки бывшего file.c («PROVISIONAL», stdio-review issues 3/4/5):
|
||||
|
||||
- [x] fwrite: короткая запись ставит _F_ERROR (issue 3) — сделано 2026-07-06
|
||||
- [x] fgets(n=1): возвращает пустую строку по стандарту (issue 4) — сделано 2026-07-06
|
||||
- [x] mode_to_flags: проверено — парсер сканирует весь хвост режима, «rb+» работает (issue 5, уже был исправлен)
|
||||
- [x] **fprintf/vfprintf** — сделаны 2026-07-06 (vsprintf в статический 256-байтовый буфер + fwrite)
|
||||
- [x] ungetc — 1-байтный putback через поле hold; работает и на stdin — сделано 2026-07-06
|
||||
- [x] Буферизация FILE v2 — **реализован вариант B+** (2026-07-06): единый ленивый буфер BUFSIZ=512 на чтение и запись с автопереключением направления, статическая таблица OPEN_MAX=8 слотов, _fclosall через atexit, fflush(NULL) = все потоки. Дизайн: docs/file-buffering-design.md. Цена: filetest (использует всё) 7411→9929 Б _CODE; не-FILE программы не платят ничего. **Ждёт MAME-прогона: filetest, fdmax (лимит DSS), fbench (замер скорости)**
|
||||
- [ ] fdopen/freopen/fclosall/fgetpos/fsetpos — по мере надобности (Solid-C категория C)
|
||||
|
||||
## П3. Solid-C совместимость — остатки (docs/solid_c_compatibility.md)
|
||||
|
||||
Phase 1 (алиасы) фактически закрыт — home/inp/outp/enable/disable, ms_*,
|
||||
getc/putc, min/max, dec*/hex*, strlwr/strupr, cgets есть. Осталось:
|
||||
|
||||
- [ ] getdisk()/setdisk() — ESTEX $02 CURDISK / $01 CHDISK
|
||||
- [ ] getdate/gettime/setdate/settime — обёртки над getdatetime/setdatetime
|
||||
- [ ] seek/tell/ltell, setmem/movmem, isascii, abort(), div(), remove→unlink, _ffirst
|
||||
- [ ] errno-константы Solid-C (EZERO/EINVFNC/…) как алиасы в errno.h
|
||||
- [ ] `<sprinter_solid.h>` — зонтичный compat-заголовок
|
||||
- [ ] обновить статусы/history в solid_c_compatibility.md (Phase 1 done)
|
||||
|
||||
Research (Phase 3): absread/abswrite (найти ESTEX READ_SECT/WRITE_SECT),
|
||||
scanf-семейство (**в SDCC 4.5 z80 его НЕТ** — писать самим или задокументировать
|
||||
отказ), brk/sbrk vs SDCC heap, isatty, ioctl (скорее скип).
|
||||
|
||||
## П4. Недостающие POSIX-мелочи вне Solid-C списка
|
||||
|
||||
- [ ] **rename()** — в gap-анализе отсутствует, ESTEX почти наверняка имеет
|
||||
RENAME (~$0F, рядом с DELETE $0E) — проверить по докам ESTEX и добавить
|
||||
- [ ] isatty(fd) — тривиально (fd<0/флаги)
|
||||
|
||||
## П5. Заголовки и гигиена сборки
|
||||
|
||||
- [ ] Аудит затенения SDCC-заголовков: stdlib.h уже на `#include_next`;
|
||||
stdio.h/time.h — полные замены (задокументировать контракт «что обязаны
|
||||
объявлять»); подумать про собственный string.h (include_next + strlwr/
|
||||
strupr из sprinter_compat.h)
|
||||
- [ ] lib/build: wildcard не удаляет stale .rel при удалении исходника —
|
||||
либо чистка в Makefile (сверка списка), либо привычка `make clean`
|
||||
после переименований (это уже укусило: старый file.rel лежал в build)
|
||||
- [ ] Включить extra-тесты (conio2, dec_test, gets, hello2, simple, stest2,
|
||||
winrest, bios_text, text_palette, gfx_dbuf, mdview2) в top-level
|
||||
TESTS/APPS — иначе `make all` их не собирает и их baseline протухает
|
||||
(уже дало ложные «+17 Б» при сравнении)
|
||||
- [ ] Размерный регресс-тест: скрипт сравнения _CODE по .map с эталоном
|
||||
(ловить случайные разжирения при правках libc)
|
||||
|
||||
## П6. Верификация после сплита
|
||||
|
||||
- [ ] `make floppy` + прогон в MAME ключевых тестов (conio, filetest, ptime,
|
||||
stattest, mouse, gfx_demo) — линковка прошла, но поведение надо
|
||||
подтвердить на эмуляторе
|
||||
- [ ] Потом на железе (mdview2 и так ждёт проверки на железе — совместить)
|
||||
|
||||
## П7. Документация
|
||||
|
||||
- [ ] docs/libc-reference.md — сводный справочник API (по заголовкам:
|
||||
сигнатура + 1 строка описания + особенности ABI); сейчас знание
|
||||
размазано по memory/ и комментариям
|
||||
- [ ] TODO.md: этапы 5/6/8 закрыты — перенести в history, добавить «этап 9 —
|
||||
libc-оптимизация» (этот план)
|
||||
- [ ] В CLAUDE.md/README зафиксировать правила libc: 1 ф-я = 1 модуль,
|
||||
internal `_`-модули, русские комментарии, без `= 0`, asm-правила
|
||||
(docs/libc-split-asm-cases.md)
|
||||
|
||||
## П8. Смежное (не libc, из TODO.md — чтобы не потерялось)
|
||||
|
||||
- auto-banking Phase 1 (toolchain/auto_bank.py) — когда проект перерастёт ~30 КБ
|
||||
- IM2 ISR v2 (docs/im2_isr_design.md) — отложено решением 2026-06-01
|
||||
- font-quad для 640×256 (per-cell палитра)
|
||||
- factoring parse_argv из crt0/crt0_banked в общий argv.s
|
||||
- check_banks.py: разбивка code/const/bss per bank (косметика)
|
||||
|
||||
---
|
||||
|
||||
## Предлагаемый порядок
|
||||
|
||||
1. **П1-лайт**: io/env/errno/atexit/minmax/mem/dir/fsdir (мелкие, час работы,
|
||||
выигрыш для всех CLI-приложений) + mouse + bios/text.
|
||||
2. **П6**: MAME-смоук — подтвердить, что сплит ничего не сломал в рантайме,
|
||||
до того как менять что-то ещё.
|
||||
3. **П1-gfx**: разбор графики (самый большой кусок, отдельный заход).
|
||||
4. **П2**: баги FILE* (3 шт.) + fprintf + ungetc.
|
||||
5. **П3/П4**: solid-c остатки + rename/isatty.
|
||||
6. **П5/П7**: гигиена сборки и документация — фоном, по кусочку.
|
||||
@@ -0,0 +1,52 @@
|
||||
# libc split: asm-связки между функциями
|
||||
|
||||
Журнал случаев, найденных при разбиении libc на «1 публичная функция = 1 модуль»
|
||||
(2026-07-05). Сюда записывается каждый обнаруженный переход `jr _func` или
|
||||
`jr/jp/call` на метку **внутри другой функции** — такие связки нельзя разрывать
|
||||
механически, разбираем каждую отдельно.
|
||||
|
||||
## Правила (справка)
|
||||
|
||||
| Паттерн | Через границу модулей |
|
||||
|---|---|
|
||||
| `call/jp _func` (публичная C-функция) | работает — метка глобальная (`::`) |
|
||||
| `call/jp _label` на метку в чужой функции | работает, только если метка объявлена `_label::` |
|
||||
| `jr` / `djnz` в другой модуль | **запрещено** — ±128 байт, разложение модулей не гарантировано |
|
||||
| fall-through (без перехода, в надежде на соседство) | **не работает никогда** |
|
||||
|
||||
## Случаи
|
||||
|
||||
### 1. stdio/dec_print.c — dec8/dec16/dec32: разделяемое тело (НЕ разрывать)
|
||||
|
||||
Статус: **оставлены в одном файле, решение отдельно.**
|
||||
|
||||
- `dec8` → `jp __dec_entry3` — прыжок в середину тела `dec32`;
|
||||
- `dec16` → `jp __dec_entry5` — то же;
|
||||
- метки уже глобальные (`__dec_entry3::`, `__dec_entry5::`) — линковаться будет,
|
||||
но выигрыша от сплита нет: dec8 всё равно притянет модуль с телом dec32;
|
||||
- внутри хвоста: `_dec_get_d16` **fall-through** в `_dec_emit_or_skip`,
|
||||
`_dec_get_d32` → `jr _dec_emit_or_skip`, общий флаг `_dec_flag`.
|
||||
|
||||
Это осознанный дизайн из solid-c: тройка делит per-digit код. Варианты на потом:
|
||||
(а) оставить как есть (176 Б тянутся целиком — терпимо);
|
||||
(б) развести на 3 независимых цикла — dec8 станет ~40 Б, но исходник длиннее
|
||||
и суммарно в exe, использующем dec8+dec32, станет хуже. Решение отложено.
|
||||
|
||||
### 2. stdio/hex_print.c — чист
|
||||
|
||||
`hex16` → `call _hex8` / `jp _hex8` (tail), `hex32` → `_hex16`: переходы по
|
||||
именам публичных функций. Разъезжается на hex8.c/hex16.c/hex32.c без правок.
|
||||
`_hex8_digit` — self-call внутри hex8, не мешает.
|
||||
|
||||
### 3. conio/conio.c — jp _clrscr_attr (чист)
|
||||
|
||||
`clrscr` → `jp _clrscr_attr` — tail-call публичной функции, работает через
|
||||
модули как есть.
|
||||
|
||||
### 4. mem/mem_bios.c, mem/mem_estex.c — jp __errno_set (чист)
|
||||
|
||||
Tail-call публичного internal-хелпера `_errno_set` — кросс-модульный уже сейчас.
|
||||
|
||||
---
|
||||
Все прочие `jr`-переходы в libc (проверены все `jr`, включая условные формы,
|
||||
2026-07-05) ведут на метки внутри своей же функции — сплиту не мешают.
|
||||
@@ -0,0 +1,5 @@
|
||||
MDVIEW Sample Document
|
||||
|
||||
This is a sample Markdown file for testing the Sprinter *mdview text
|
||||
viewer*. Phase 3 adds inline emphasis: bold, italic and underscore
|
||||
runs render with distinct background colours.
|
||||
+4
-46
@@ -20,52 +20,10 @@ CC_FLAGS := -mz80 --no-std-crt0 --std-c99 --opt-code-size $(INC)
|
||||
|
||||
BUILD := $(PROJ_ROOT)/lib/build
|
||||
|
||||
# All libc C modules.
|
||||
LIBC_C := \
|
||||
libc/sys/atexit.c \
|
||||
libc/conio/conio.c \
|
||||
libc/conio/cprintf.c \
|
||||
libc/conio/text_palette.c \
|
||||
libc/bios/text.c \
|
||||
libc/io/dir.c \
|
||||
libc/video/videomode_raw.c \
|
||||
libc/video/palette.c \
|
||||
libc/errno/_errno_set.c \
|
||||
libc/env/env.c \
|
||||
libc/errno/errno.c \
|
||||
libc/io/fsdir.c \
|
||||
libc/io/lseek.c \
|
||||
libc/mouse/mouse.c \
|
||||
libc/io/open.c \
|
||||
libc/io/read.c \
|
||||
libc/time/sleep.c \
|
||||
libc/time/time.c \
|
||||
libc/time/posix_time.c \
|
||||
libc/io/unlink.c \
|
||||
libc/io/stat.c \
|
||||
libc/mem/bank_io_w3.c \
|
||||
libc/mem/bank_io_w1.c \
|
||||
libc/mem/mem_estex.c \
|
||||
libc/mem/mem_bios.c \
|
||||
libc/gfx/gfx_core.c \
|
||||
libc/gfx/gfx_palette.c \
|
||||
libc/gfx/gfx_raw_common.c \
|
||||
libc/gfx/gfx_raw_256.c \
|
||||
libc/gfx/gfx_raw_16.c \
|
||||
libc/gfx/gfx_256.c \
|
||||
libc/gfx/gfx_16.c \
|
||||
libc/gfx/gfx_font.c \
|
||||
libc/gfx/gfx_text_256.c \
|
||||
libc/gfx/gfx_text_16.c \
|
||||
libc/stdio/getchar.c \
|
||||
libc/stdio/putchar.c \
|
||||
libc/stdio/puts.c \
|
||||
libc/stdlib/minmax.c \
|
||||
libc/file/file.c \
|
||||
libc/stdio/hex_print.c \
|
||||
libc/stdio/dec_print.c \
|
||||
libc/string/strlwr.c \
|
||||
libc/string/strupr.c
|
||||
# All libc C modules — every .c under libc/<area>/ becomes its own .rel.
|
||||
# One PUBLIC function per file (see docs/libc-split-asm-cases.md): the
|
||||
# linker pulls whole .rel modules, so file granularity == DCE granularity.
|
||||
LIBC_C := $(patsubst $(PROJ_ROOT)/%,%,$(wildcard $(PROJ_ROOT)/libc/*/*.c))
|
||||
|
||||
# Runtime modules to bundle (pulled by symbol references from libc-using code).
|
||||
# NOTE: runtime/bank.s is NOT bundled — its trampoline depends on the banking
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* bios_clearwin — очистить окно (row, col, height, width) пробелами с
|
||||
* атрибутом attr. BIOS LP_CLS_WIN ($89, rst 8): D=row, E=col,
|
||||
* H=height, L=width, B=attr.
|
||||
*/
|
||||
|
||||
#include <bios/text.h>
|
||||
|
||||
void bios_clearwin(uint8_t row, uint8_t col, uint8_t height, uint8_t width,
|
||||
uint8_t attr) __naked
|
||||
{
|
||||
(void)row; (void)col; (void)height; (void)width; (void)attr;
|
||||
__asm
|
||||
;; row→A, col→L, height/width/attr на стеке [SP+2..4].
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld h, 0 (iy) ; H = height
|
||||
ld c, 1 (iy) ; спрятать width (L пока держит col)
|
||||
ld e, l ; E = col
|
||||
ld d, a ; D = row
|
||||
ld l, c ; L = width
|
||||
ld b, 2 (iy) ; B = attr
|
||||
push ix
|
||||
ld c, #0x89
|
||||
rst #0x08
|
||||
pop ix
|
||||
pop hl
|
||||
inc sp
|
||||
inc sp
|
||||
inc sp ; съесть height + width + attr
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* bios_clearwin_ch — залить окно (row, col, height, width) символом
|
||||
* fillch с атрибутом attr. BIOS LP_CLS_WIN2 ($8D, rst 8): D=row,
|
||||
* E=col, H=height, L=width, B=attr, A=fillch.
|
||||
*/
|
||||
|
||||
#include <bios/text.h>
|
||||
|
||||
void bios_clearwin_ch(uint8_t row, uint8_t col, uint8_t height,
|
||||
uint8_t width, uint8_t attr, char fillch) __naked
|
||||
{
|
||||
(void)row; (void)col; (void)height; (void)width; (void)attr; (void)fillch;
|
||||
__asm
|
||||
;; row→A, col→L, height/width/attr/fillch на стеке [SP+2..5].
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld d, a ; D = row (освободить A под fillch)
|
||||
ld e, l ; E = col (освободить L под width)
|
||||
ld h, 0 (iy) ; H = height
|
||||
ld c, 1 (iy) ; спрятать width
|
||||
ld b, 2 (iy) ; B = attr
|
||||
ld a, 3 (iy) ; A = fillch
|
||||
ld l, c ; L = width
|
||||
push ix
|
||||
ld c, #0x8D
|
||||
rst #0x08
|
||||
pop ix
|
||||
pop hl
|
||||
inc sp
|
||||
inc sp
|
||||
inc sp
|
||||
inc sp ; съесть height + width + attr + fillch
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* bios_fillattr — залить count позиций атрибутом attr (символы не
|
||||
* трогаются) от текущего place. BIOS LP_PRINT_ATR ($83, rst 8):
|
||||
* E=attr, B=count. Сохраняет HL и IX.
|
||||
*/
|
||||
|
||||
#include <bios/text.h>
|
||||
|
||||
void bios_fillattr(uint8_t attr, uint8_t count) __naked
|
||||
{
|
||||
(void)attr; (void)count;
|
||||
__asm
|
||||
;; attr→A, count→L.
|
||||
ld e, a
|
||||
ld b, l
|
||||
ld c, #0x83
|
||||
rst #0x08
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* bios_fillchar — вывести символ ch count раз (атрибут не трогается)
|
||||
* от текущего place. BIOS LP_PRINT_SYM ($82, rst 8): A=ch, B=count.
|
||||
* Сохраняет HL и IX.
|
||||
*/
|
||||
|
||||
#include <bios/text.h>
|
||||
|
||||
void bios_fillchar(char ch, uint8_t count) __naked
|
||||
{
|
||||
(void)ch; (void)count;
|
||||
__asm
|
||||
;; ch→A, count→L.
|
||||
ld b, l
|
||||
ld c, #0x82
|
||||
rst #0x08
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* bios_fillcharattr — вывести символ ch с атрибутом attr count раз от
|
||||
* текущего place. BIOS LP_PRINT_ALL ($81, rst 8): A=ch, E=attr,
|
||||
* B=count. Сохраняет HL и IX — push/pop ix не нужен.
|
||||
*
|
||||
* SDCC __sdcccall(1): ch→A, attr→L, count на стеке [SP+2]; стековый
|
||||
* байт съедает callee (inc sp), IY — скретч для чтения без порчи IX.
|
||||
*/
|
||||
|
||||
#include <bios/text.h>
|
||||
|
||||
void bios_fillcharattr(char ch, uint8_t attr, uint8_t count) __naked
|
||||
{
|
||||
(void)ch; (void)attr; (void)count;
|
||||
__asm
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld b, 0 (iy) ; B = count
|
||||
ld e, l ; E = attr
|
||||
ld c, #0x81
|
||||
rst #0x08
|
||||
pop hl ; адрес возврата
|
||||
inc sp ; съесть байт count
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* bios_get_place — текущая позиция BIOS-вывода: (row << 8) | col.
|
||||
* BIOS LP_GET_PLACE ($8E, rst 8) возвращает D=row, E=col — это уже
|
||||
* конвенция возврата uint16_t (DE), перестановок не нужно.
|
||||
*/
|
||||
|
||||
#include <bios/text.h>
|
||||
|
||||
uint16_t bios_get_place(void) __naked
|
||||
{
|
||||
__asm
|
||||
ld c, #0x8E
|
||||
rst #0x08
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* bios_scrollwin — прокрутить экран от строки row на count строк.
|
||||
* BIOS LP_SCROLL_UD ($8A, rst 8): B=dir (1=вверх, 2=вниз), D=row,
|
||||
* E=count.
|
||||
*/
|
||||
|
||||
#include <bios/text.h>
|
||||
|
||||
void bios_scrollwin(uint8_t dir, uint8_t row, uint8_t count) __naked
|
||||
{
|
||||
(void)dir; (void)row; (void)count;
|
||||
__asm
|
||||
;; dir→A, row→L, count на стеке [SP+2].
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld e, 0 (iy) ; E = count
|
||||
ld d, l ; D = row
|
||||
ld b, a ; B = dir
|
||||
push ix
|
||||
ld c, #0x8A
|
||||
rst #0x08
|
||||
pop ix
|
||||
pop hl
|
||||
inc sp
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* bios_set_place — установить позицию вывода BIOS-текста (place).
|
||||
* BIOS LP_SET_PLACE ($84, rst 8): D=row, E=col. Используется без
|
||||
* push/pop ix (как gotoxy в conio — эмпирически безопасно).
|
||||
*/
|
||||
|
||||
#include <bios/text.h>
|
||||
|
||||
void bios_set_place(uint8_t row, uint8_t col) __naked
|
||||
{
|
||||
(void)row; (void)col;
|
||||
__asm
|
||||
;; row→A, col→L.
|
||||
ld d, a
|
||||
ld e, l
|
||||
ld c, #0x84
|
||||
rst #0x08
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* bios_write — вывести len символов строки s (атрибут не трогается).
|
||||
* BIOS LP_PRINT_LN2 ($86, rst 8): HL=s, B=len. Place продвигается.
|
||||
* s должен лежать в #4000-#BFFF.
|
||||
*/
|
||||
|
||||
#include <bios/text.h>
|
||||
|
||||
void bios_write(const char *s, uint8_t len) __naked
|
||||
{
|
||||
(void)s; (void)len;
|
||||
__asm
|
||||
;; s→HL, len на стеке [SP+2].
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld b, 0 (iy) ; B = len
|
||||
push ix
|
||||
ld c, #0x86
|
||||
rst #0x08
|
||||
pop ix
|
||||
pop hl
|
||||
inc sp
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* bios_write_stop — вывести строку s до разделителя sep БЕЗ добивки
|
||||
* (максимум maxlen символов, атрибут не трогается).
|
||||
* BIOS LP_PRINT_LN6 ($8C, rst 8): HL=s, B=maxlen, D=sep.
|
||||
*/
|
||||
|
||||
#include <bios/text.h>
|
||||
|
||||
void bios_write_stop(const char *s, uint8_t maxlen, char sep) __naked
|
||||
{
|
||||
(void)s; (void)maxlen; (void)sep;
|
||||
__asm
|
||||
;; s→HL, maxlen/sep на стеке [SP+2..3].
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld b, 0 (iy) ; B = maxlen
|
||||
ld d, 1 (iy) ; D = sep
|
||||
push ix
|
||||
ld c, #0x8C
|
||||
rst #0x08
|
||||
pop ix
|
||||
pop hl
|
||||
inc sp
|
||||
inc sp
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* bios_write_until — вывести строку s до разделителя sep, ДОБИВ
|
||||
* пробелами до len (атрибут не трогается). BIOS LP_PRINT_LN4 ($88,
|
||||
* rst 8): HL=s, B=len, D=sep.
|
||||
*/
|
||||
|
||||
#include <bios/text.h>
|
||||
|
||||
void bios_write_until(const char *s, uint8_t len, char sep) __naked
|
||||
{
|
||||
(void)s; (void)len; (void)sep;
|
||||
__asm
|
||||
;; s→HL, len/sep на стеке [SP+2..3].
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld b, 0 (iy) ; B = len
|
||||
ld d, 1 (iy) ; D = sep
|
||||
push ix
|
||||
ld c, #0x88
|
||||
rst #0x08
|
||||
pop ix
|
||||
pop hl
|
||||
inc sp
|
||||
inc sp
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* bios_writeattr — вывести len символов строки s с атрибутом attr.
|
||||
* BIOS LP_PRINT_LN ($85, rst 8): HL=s, B=len, E=attr. Place
|
||||
* продвигается после печати. s должен лежать в #4000-#BFFF.
|
||||
*
|
||||
* SDCC __sdcccall(1): s→HL, len/attr на стеке [SP+2]/[SP+3];
|
||||
* стековые байты съедает callee.
|
||||
*/
|
||||
|
||||
#include <bios/text.h>
|
||||
|
||||
void bios_writeattr(const char *s, uint8_t len, uint8_t attr) __naked
|
||||
{
|
||||
(void)s; (void)len; (void)attr;
|
||||
__asm
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld b, 0 (iy) ; B = len
|
||||
ld e, 1 (iy) ; E = attr
|
||||
push ix
|
||||
ld c, #0x85
|
||||
rst #0x08
|
||||
pop ix
|
||||
pop hl ; адрес возврата
|
||||
inc sp
|
||||
inc sp ; съесть len + attr
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* bios_writeattr_stop — вывести строку s с атрибутом attr до
|
||||
* разделителя sep БЕЗ добивки пробелами (максимум maxlen символов).
|
||||
* BIOS LP_PRINT_LN5 ($8B, rst 8): HL=s, B=maxlen, E=attr, D=sep.
|
||||
*/
|
||||
|
||||
#include <bios/text.h>
|
||||
|
||||
void bios_writeattr_stop(const char *s, uint8_t maxlen, uint8_t attr,
|
||||
char sep) __naked
|
||||
{
|
||||
(void)s; (void)maxlen; (void)attr; (void)sep;
|
||||
__asm
|
||||
;; s→HL, maxlen/attr/sep на стеке [SP+2..4].
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld b, 0 (iy) ; B = maxlen
|
||||
ld e, 1 (iy) ; E = attr
|
||||
ld d, 2 (iy) ; D = sep
|
||||
push ix
|
||||
ld c, #0x8B
|
||||
rst #0x08
|
||||
pop ix
|
||||
pop hl
|
||||
inc sp
|
||||
inc sp
|
||||
inc sp
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* bios_writeattr_until — вывести строку s с атрибутом attr до
|
||||
* разделителя sep, ДОБИВ пробелами до len. BIOS LP_PRINT_LN3 ($87,
|
||||
* rst 8): HL=s, B=len, E=attr, D=sep.
|
||||
*/
|
||||
|
||||
#include <bios/text.h>
|
||||
|
||||
void bios_writeattr_until(const char *s, uint8_t len, uint8_t attr, char sep) __naked
|
||||
{
|
||||
(void)s; (void)len; (void)attr; (void)sep;
|
||||
__asm
|
||||
;; s→HL, len/attr/sep на стеке [SP+2..4].
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld b, 0 (iy) ; B = len
|
||||
ld e, 1 (iy) ; E = attr
|
||||
ld d, 2 (iy) ; D = sep
|
||||
push ix
|
||||
ld c, #0x87
|
||||
rst #0x08
|
||||
pop ix
|
||||
pop hl
|
||||
inc sp
|
||||
inc sp
|
||||
inc sp ; съесть len + attr + sep
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
/*
|
||||
* text.c — wrappers for the Sprinter BIOS text-output calls (RST 8,
|
||||
* 081h..08Eh "LP_*"). See <bios/text.h> for the full opcode -> name
|
||||
* mapping and semantics.
|
||||
*
|
||||
* ABI recap (docs/converted/bios.txt):
|
||||
* - 081h/082h/083h explicitly preserve HL and IX -> no push/pop ix
|
||||
* needed, and the result is the smallest/fastest of the set.
|
||||
* - 084h/08Eh (place get/set) are used without push/pop ix elsewhere
|
||||
* in this libc (libc/conio/conio.c gotoxy/wherex/wherey via the same
|
||||
* opcodes) — empirically safe, followed here too.
|
||||
* - Everything else clobbers IX like any other BIOS/ESTEX call, so
|
||||
* every RST 8 below is bracketed with push ix / pop ix.
|
||||
*
|
||||
* Stack-argument layout: SDCC __sdcccall(1) places the first 8-bit arg
|
||||
* in A, the second (only if the first was also 8-bit) in L; every arg
|
||||
* beyond that lands on the stack in left-to-right declaration order,
|
||||
* starting at [SP+2] (right after the 2-byte return address) — verified
|
||||
* empirically via `sdcc -S` for 2..6 uint8 args and for pointer+N uint8
|
||||
* args. IY is used as a scratch index register to read those without
|
||||
* disturbing IX (the caller's frame pointer).
|
||||
*/
|
||||
|
||||
#include <bios/text.h>
|
||||
|
||||
void bios_fillcharattr(char ch, uint8_t attr, uint8_t count) __naked
|
||||
{
|
||||
(void)ch; (void)attr; (void)count;
|
||||
__asm
|
||||
;; ch->A, attr->L, count on stack at [SP+2].
|
||||
;; LP_PRINT_ALL (081h): A=ch, E=attr, B=count. Preserves HL,IX.
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld b, 0 (iy) ; B = count
|
||||
ld e, l ; E = attr
|
||||
ld c, #0x81
|
||||
rst #0x08
|
||||
pop hl ; return address
|
||||
inc sp ; consume count byte
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void bios_fillchar(char ch, uint8_t count) __naked
|
||||
{
|
||||
(void)ch; (void)count;
|
||||
__asm
|
||||
;; ch->A, count->L. LP_PRINT_SYM (082h): A=ch, B=count.
|
||||
;; Preserves HL,IX.
|
||||
ld b, l
|
||||
ld c, #0x82
|
||||
rst #0x08
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void bios_fillattr(uint8_t attr, uint8_t count) __naked
|
||||
{
|
||||
(void)attr; (void)count;
|
||||
__asm
|
||||
;; attr->A, count->L. LP_PRINT_ATR (083h): E=attr, B=count.
|
||||
;; Preserves HL,IX.
|
||||
ld e, a
|
||||
ld b, l
|
||||
ld c, #0x83
|
||||
rst #0x08
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void bios_set_place(uint8_t row, uint8_t col) __naked
|
||||
{
|
||||
(void)row; (void)col;
|
||||
__asm
|
||||
;; row->A, col->L. LP_SET_PLACE (084h): D=row, E=col.
|
||||
ld d, a
|
||||
ld e, l
|
||||
ld c, #0x84
|
||||
rst #0x08
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
uint16_t bios_get_place(void) __naked
|
||||
{
|
||||
__asm
|
||||
;; LP_GET_PLACE (08Eh): returns D=row, E=col — already the
|
||||
;; uint16_t return convention (DE); nothing left to rearrange.
|
||||
ld c, #0x8E
|
||||
rst #0x08
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void bios_writeattr(const char *s, uint8_t len, uint8_t attr) __naked
|
||||
{
|
||||
(void)s; (void)len; (void)attr;
|
||||
__asm
|
||||
;; s->HL, len/attr on stack at [SP+2]/[SP+3].
|
||||
;; LP_PRINT_LN (085h): HL=s, B=len, E=attr.
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld b, 0 (iy) ; B = len
|
||||
ld e, 1 (iy) ; E = attr
|
||||
push ix
|
||||
ld c, #0x85
|
||||
rst #0x08
|
||||
pop ix
|
||||
pop hl ; return address
|
||||
inc sp
|
||||
inc sp ; consume len + attr
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void bios_write(const char *s, uint8_t len) __naked
|
||||
{
|
||||
(void)s; (void)len;
|
||||
__asm
|
||||
;; s->HL, len on stack at [SP+2].
|
||||
;; LP_PRINT_LN2 (086h): HL=s, B=len.
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld b, 0 (iy) ; B = len
|
||||
push ix
|
||||
ld c, #0x86
|
||||
rst #0x08
|
||||
pop ix
|
||||
pop hl
|
||||
inc sp
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void bios_writeattr_until(const char *s, uint8_t len, uint8_t attr, char sep) __naked
|
||||
{
|
||||
(void)s; (void)len; (void)attr; (void)sep;
|
||||
__asm
|
||||
;; s->HL, len/attr/sep on stack at [SP+2..4].
|
||||
;; LP_PRINT_LN3 (087h): HL=s, B=len, E=attr, D=sep.
|
||||
;; Pads with spaces after sep up to len.
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld b, 0 (iy) ; B = len
|
||||
ld e, 1 (iy) ; E = attr
|
||||
ld d, 2 (iy) ; D = sep
|
||||
push ix
|
||||
ld c, #0x87
|
||||
rst #0x08
|
||||
pop ix
|
||||
pop hl
|
||||
inc sp
|
||||
inc sp
|
||||
inc sp ; consume len + attr + sep
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void bios_write_until(const char *s, uint8_t len, char sep) __naked
|
||||
{
|
||||
(void)s; (void)len; (void)sep;
|
||||
__asm
|
||||
;; s->HL, len/sep on stack at [SP+2..3].
|
||||
;; LP_PRINT_LN4 (088h): HL=s, B=len, D=sep. Pads with spaces.
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld b, 0 (iy) ; B = len
|
||||
ld d, 1 (iy) ; D = sep
|
||||
push ix
|
||||
ld c, #0x88
|
||||
rst #0x08
|
||||
pop ix
|
||||
pop hl
|
||||
inc sp
|
||||
inc sp
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void bios_clearwin(uint8_t row, uint8_t col, uint8_t height, uint8_t width,
|
||||
uint8_t attr) __naked
|
||||
{
|
||||
(void)row; (void)col; (void)height; (void)width; (void)attr;
|
||||
__asm
|
||||
;; row->A, col->L, height/width/attr on stack at [SP+2..4].
|
||||
;; LP_CLS_WIN (089h): D=row, E=col, H=height, L=width, B=attr.
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld h, 0 (iy) ; H = height
|
||||
ld c, 1 (iy) ; stash width (L is still col for now)
|
||||
ld e, l ; E = col
|
||||
ld d, a ; D = row
|
||||
ld l, c ; L = width
|
||||
ld b, 2 (iy) ; B = attr
|
||||
push ix
|
||||
ld c, #0x89
|
||||
rst #0x08
|
||||
pop ix
|
||||
pop hl
|
||||
inc sp
|
||||
inc sp
|
||||
inc sp ; consume height + width + attr
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void bios_scrollwin(uint8_t dir, uint8_t row, uint8_t count) __naked
|
||||
{
|
||||
(void)dir; (void)row; (void)count;
|
||||
__asm
|
||||
;; dir->A, row->L, count on stack at [SP+2].
|
||||
;; LP_SCROLL_UD (08Ah): B=dir(1 up/2 down), D=row, E=count.
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld e, 0 (iy) ; E = count
|
||||
ld d, l ; D = row
|
||||
ld b, a ; B = dir
|
||||
push ix
|
||||
ld c, #0x8A
|
||||
rst #0x08
|
||||
pop ix
|
||||
pop hl
|
||||
inc sp
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void bios_writeattr_stop(const char *s, uint8_t maxlen, uint8_t attr,
|
||||
char sep) __naked
|
||||
{
|
||||
(void)s; (void)maxlen; (void)attr; (void)sep;
|
||||
__asm
|
||||
;; s->HL, maxlen/attr/sep on stack at [SP+2..4].
|
||||
;; LP_PRINT_LN5 (08Bh): HL=s, B=maxlen, E=attr, D=sep.
|
||||
;; Stops at sep (no padding).
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld b, 0 (iy) ; B = maxlen
|
||||
ld e, 1 (iy) ; E = attr
|
||||
ld d, 2 (iy) ; D = sep
|
||||
push ix
|
||||
ld c, #0x8B
|
||||
rst #0x08
|
||||
pop ix
|
||||
pop hl
|
||||
inc sp
|
||||
inc sp
|
||||
inc sp
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void bios_write_stop(const char *s, uint8_t maxlen, char sep) __naked
|
||||
{
|
||||
(void)s; (void)maxlen; (void)sep;
|
||||
__asm
|
||||
;; s->HL, maxlen/sep on stack at [SP+2..3].
|
||||
;; LP_PRINT_LN6 (08Ch): HL=s, B=maxlen, D=sep. Stops at sep.
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld b, 0 (iy) ; B = maxlen
|
||||
ld d, 1 (iy) ; D = sep
|
||||
push ix
|
||||
ld c, #0x8C
|
||||
rst #0x08
|
||||
pop ix
|
||||
pop hl
|
||||
inc sp
|
||||
inc sp
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void bios_clearwin_ch(uint8_t row, uint8_t col, uint8_t height,
|
||||
uint8_t width, uint8_t attr, char fillch) __naked
|
||||
{
|
||||
(void)row; (void)col; (void)height; (void)width; (void)attr; (void)fillch;
|
||||
__asm
|
||||
;; row->A, col->L, height/width/attr/fillch on stack [SP+2..5].
|
||||
;; LP_CLS_WIN2 (08Dh): D=row, E=col, H=height, L=width, B=attr,
|
||||
;; A=fillch.
|
||||
ld iy, #2
|
||||
add iy, sp
|
||||
ld d, a ; D = row (free A for fillch later)
|
||||
ld e, l ; E = col (free L for width later)
|
||||
ld h, 0 (iy) ; H = height
|
||||
ld c, 1 (iy) ; stash width
|
||||
ld b, 2 (iy) ; B = attr
|
||||
ld a, 3 (iy) ; A = fillch
|
||||
ld l, c ; L = width
|
||||
push ix
|
||||
ld c, #0x8D
|
||||
rst #0x08
|
||||
pop ix
|
||||
pop hl
|
||||
inc sp
|
||||
inc sp
|
||||
inc sp
|
||||
inc sp ; consume height + width + attr + fillch
|
||||
jp (hl)
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* _bios_putchar — быстрый вывод символа через ESTEX PUTCHAR ($5B):
|
||||
* без атрибута, CR/LF/скролл и курсор ведёт сам ESTEX. Используется
|
||||
* (задуман) для пути g_text_attr == KEEP_EXIST_ATTR.
|
||||
* PUTCHAR не меняет IX (проверено), поэтому push/pop ix не нужен.
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
char _bios_putchar(char ch) __naked
|
||||
{
|
||||
(void)ch;
|
||||
__asm
|
||||
;; ch в A. push af сохраняет его через RST (который портит A).
|
||||
push af
|
||||
ld c, #0x5B ; ESTEX PUTCHAR
|
||||
rst #0x10
|
||||
pop af
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* _conio.h — внутренние состояние и хелперы conio.
|
||||
*
|
||||
* НЕ публичный заголовок: живёт рядом с исходниками. После сплита
|
||||
* «1 функция = 1 модуль» каждый элемент лежит в своём модуле, чтобы
|
||||
* приложение тянуло из архива только то, что реально вызывает:
|
||||
*
|
||||
* _g_text_attr.c — g_text_attr (текущий атрибут, KEEP_EXIST_ATTR)
|
||||
* _pc_place.c — pc_place (курсор col:row; global — читают тесты)
|
||||
* _pc_raw_mode.c — pc_raw_mode (режим управляющих символов putch)
|
||||
* _get_cursor.c — чтение курсора BIOS → pc_place
|
||||
* _set_cursor.c — запись pc_place → курсор BIOS
|
||||
* _putch_wrchar.c — воркер вывода mode-0 (BS/TAB/LF/CR интерпретируются)
|
||||
* _putch_wrchar_raw.c — воркер mode-1 (всё как глифы)
|
||||
* _bios_putchar.c — быстрый вывод через ESTEX PUTCHAR (без атрибута)
|
||||
*/
|
||||
#ifndef _CONIO_INTERNAL_H
|
||||
#define _CONIO_INTERNAL_H
|
||||
|
||||
#include <conio.h> /* two_bytes, KEEP_EXIST_ATTR */
|
||||
#include <stdint.h>
|
||||
|
||||
/* Текущий текстовый атрибут: 0x00..0xFF — реальный байт атрибута,
|
||||
* KEEP_EXIST_ATTR (0xFFFF) — быстрый путь без атрибута. */
|
||||
extern two_bytes g_text_attr;
|
||||
|
||||
/* Позиция курсора: .byte.low = col, .byte.high = row.
|
||||
* Поддерживается gotoxy/wherex/wherey/wherexy и воркерами putch. */
|
||||
extern two_bytes pc_place;
|
||||
|
||||
/* 0 — BS/TAB/LF/CR интерпретируются; 1 — печатаются как глифы. */
|
||||
extern uint8_t pc_raw_mode;
|
||||
|
||||
/* Прочитать курсор BIOS (rst 8, $8E) в pc_place. */
|
||||
void _get_cursor(void);
|
||||
|
||||
/* Установить курсор BIOS (rst 8, $84) из pc_place. */
|
||||
void _set_cursor(void);
|
||||
|
||||
/* Быстрый вывод символа через ESTEX PUTCHAR ($5B) — атрибут не
|
||||
* трогается, CR/LF/скролл делает ESTEX. */
|
||||
char _bios_putchar(char ch);
|
||||
|
||||
/* Воркеры вывода в (pc_place) атрибутом attr; курсор BIOS НЕ трогают —
|
||||
* вызывающий делает _get_cursor()/_set_cursor() один раз на операцию. */
|
||||
void _putch_wrchar(char ch, uint8_t attr);
|
||||
void _putch_wrchar_raw(char ch, uint8_t attr);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* g_text_attr — текущий текстовый атрибут conio (Turbo-C-стиль: stdio
|
||||
* putchar/puts быстрые и без атрибута; putch/cputs/cprintf применяют
|
||||
* этот атрибут). Модуль только с данными.
|
||||
*
|
||||
* 0x00..0xFF — реальный атрибут (4 бита FG | 3 бита BG | 1 бит blink)
|
||||
* KEEP_EXIST_ATTR (0xFFFF) — putch/cputs уходят на быстрый путь
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
two_bytes g_text_attr;
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* _get_cursor — прочитать текущий курсор BIOS GetCursor (rst 8, $8E)
|
||||
* в pc_place (D=row, E=col → сохраняются парой).
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
void _get_cursor(void) __naked
|
||||
{
|
||||
__asm
|
||||
ld c, #0x8e ; BIOS GetCursor
|
||||
rst #0x08
|
||||
ld (_pc_place), de
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* pc_place — кэш позиции курсора conio: .byte.low = col (0..79),
|
||||
* .byte.high = row (0..31). Обновляется gotoxy/wherex/wherey/wherexy
|
||||
* и воркерами putch; глобальный (не `_`-имя) — читается и тестами
|
||||
* (tests/conio2). Модуль только с данными.
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
two_bytes pc_place;
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* pc_raw_mode — режим обработки управляющих символов (< 0x20) в
|
||||
* воркерах putch/cputs:
|
||||
* 0 (по умолчанию) — BS/TAB/LF/CR интерпретируются (без глифа);
|
||||
* 1 — все символы печатаются как глифы CP437.
|
||||
* Действует только на WRCHAR-пути (attr ≤ 0xFF); при KEEP_EXIST_ATTR
|
||||
* курсором и управляющими символами занимается сам ESTEX.
|
||||
* Модуль только с данными.
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
uint8_t pc_raw_mode;
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* _putch_wrchar — воркер вывода mode-0: пишет символ в (pc_place)
|
||||
* через ESTEX WRCHAR ($58) атрибутом attr, интерпретируя управляющие:
|
||||
*
|
||||
* 0x08 BS → pc_col-- (если не 0)
|
||||
* 0x09 TAB → pc_col к следующему кратному 8 (потолок 80)
|
||||
* 0x0A LF → pc_row++ (потолок 32; без глифа)
|
||||
* 0x0D CR → pc_col = 0
|
||||
* прочее → WRCHAR + pc_col++
|
||||
*
|
||||
* WRCHAR подавляется при pc_col ≥ 80 или pc_row ≥ 32 (вне экрана) —
|
||||
* координаты только [0..79] × [0..31].
|
||||
*
|
||||
* Курсор BIOS НЕ трогает — вызывающий читает его один раз до серии
|
||||
* вызовов и записывает один раз после: плата BIOS за ОПЕРАЦИЮ,
|
||||
* а не за символ.
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
void _putch_wrchar(char ch, uint8_t attr) __naked
|
||||
{
|
||||
(void)ch; (void)attr;
|
||||
__asm
|
||||
;; __sdcccall(1): ch в A, attr в L.
|
||||
;; Диспетчеризация по управляющим, пока A держит ch (cp не
|
||||
;; трогает A). B/C грузятся только на пути вывода, поэтому
|
||||
;; ветки управляющих символов дешевле.
|
||||
cp #0x08
|
||||
jr z, _rp0_bs
|
||||
cp #0x09
|
||||
jr z, _rp0_tab
|
||||
cp #0x0A
|
||||
jr z, _rp0_lf
|
||||
cp #0x0D
|
||||
jr z, _rp0_cr
|
||||
;; Всё остальное (печатное или незнакомый ctrl) → глиф.
|
||||
|
||||
_rp0_pri:
|
||||
|
||||
ld c, a ; C = ch (сохранить до порчи A)
|
||||
ld a, (_pc_place + 1)
|
||||
cp #32
|
||||
ret nc ; за нижним краем — молча пропустить
|
||||
ld d, a ; D = row (конвенция ESTEX WRCHAR)
|
||||
ld a, (_pc_place)
|
||||
cp #80
|
||||
ret nc ; за правым краем — молча пропустить
|
||||
ld e, a ; E = col
|
||||
inc a
|
||||
ld (_pc_place), a ; pc_col++
|
||||
|
||||
_rp0_wr:
|
||||
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_place)
|
||||
or a, a
|
||||
ret z ; уже в колонке 0 — без изменений
|
||||
dec a
|
||||
ld (_pc_place), a
|
||||
ld e, a ; E = col
|
||||
ld a, (_pc_place + 1)
|
||||
ld d, a ; D = row (конвенция ESTEX WRCHAR)
|
||||
ld c, #0x20
|
||||
jr _rp0_wr
|
||||
|
||||
_rp0_tab:
|
||||
ld a, (_pc_place)
|
||||
or #0x07 ; округлить вниз к кратному 8
|
||||
inc a ; → следующее кратное 8
|
||||
cp #81 ; сравнить A с 81 (0x51)
|
||||
jr c, _rp0_tab_skip ; если A < 81 (т.е. A ≤ 80), пропустить загрузку
|
||||
ld a, #80 ; иначе A > 80 → установить A = 80
|
||||
_rp0_tab_skip:
|
||||
ld (_pc_place), a
|
||||
ret
|
||||
|
||||
_rp0_lf:
|
||||
ld a, (_pc_place + 1)
|
||||
cp #32
|
||||
ret nc ; уже у нижнего края
|
||||
inc a
|
||||
ld (_pc_place + 1), a
|
||||
ret
|
||||
|
||||
_rp0_cr:
|
||||
xor a, a
|
||||
ld (_pc_place), a
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* _putch_wrchar_raw — воркер вывода mode-1: КАЖДЫЙ байт идёт через
|
||||
* ESTEX WRCHAR ($58) как глиф CP437 (включая 0x08/0x09/0x0A/0x0D),
|
||||
* атрибутом attr, в позицию (pc_place) с pc_col++.
|
||||
* Вне экрана (col ≥ 80 / row ≥ 32) вывод молча подавляется.
|
||||
* Курсор BIOS не трогает — см. _putch_wrchar.
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
void _putch_wrchar_raw(char ch, uint8_t attr) __naked
|
||||
{
|
||||
(void)ch; (void)attr;
|
||||
__asm
|
||||
;; __sdcccall(1): ch в A, attr в L.
|
||||
ld c, a ; C = ch (сохранить)
|
||||
|
||||
ld a, (_pc_place + 1)
|
||||
cp #32
|
||||
ret nc ; за нижним краем — молча пропустить
|
||||
ld d, a ; D = row
|
||||
ld a, (_pc_place)
|
||||
cp #80
|
||||
ret nc ; за правым краем — молча пропустить
|
||||
ld e, a ; E = col
|
||||
inc a
|
||||
ld (_pc_place), 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;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* _set_cursor — установить курсор BIOS SetCursor (rst 8, $84) из
|
||||
* pc_place (D=row, E=col).
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
void _set_cursor(void) __naked
|
||||
{
|
||||
__asm
|
||||
ld de, (_pc_place)
|
||||
ld c, #0x84 ; BIOS SetCursor
|
||||
rst #0x08
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* cgets — построчный ввод в стиле Solid-C / Turbo-C:
|
||||
* buf[0] = максимум символов (вход)
|
||||
* buf[1] = фактическое количество (выход)
|
||||
* buf[2..] = символы + NUL
|
||||
* Ввод через getche() (с эхом), Enter завершает строку (выводится
|
||||
* "\r\n"), Backspace откатывает символ. Возвращает &buf[2].
|
||||
*/
|
||||
|
||||
#include <conio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
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];
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* clrscr — очистить экран атрибутом по умолчанию 0x0F (ярко-белый на
|
||||
* чёрном). Tail-call в clrscr_attr (jp по глобальному имени — работает
|
||||
* через границу модулей).
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
void clrscr(void) __naked
|
||||
{
|
||||
__asm
|
||||
ld a, #0x0F
|
||||
jp _clrscr_attr
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* clrscr_attr — очистить экран (80×32) пробелами с заданным атрибутом.
|
||||
* ESTEX CLEAR ($56): A=символ-заполнитель, B=атрибут, DE=верхний левый
|
||||
* угол, H=строк, L=колонок.
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
void clrscr_attr(uint8_t attr) __naked
|
||||
{
|
||||
(void)attr;
|
||||
__asm
|
||||
push ix
|
||||
;; SDCC __sdcccall(1): первый uint8_t аргумент в A.
|
||||
ld b, a ; B = атрибут (цвет заливки)
|
||||
ld de, #0x0000 ; верхний левый угол
|
||||
ld hl, #0x2050 ; H=32 строки, L=80 колонок
|
||||
ld a, #0x20 ; заполнение пробелом
|
||||
ld c, #0x56 ; ESTEX CLEAR
|
||||
rst #0x10
|
||||
pop ix
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -1,678 +0,0 @@
|
||||
/*
|
||||
* 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). */
|
||||
static two_bytes g_text_attr = {0};
|
||||
static uint8_t pc_ch = 0;
|
||||
extern two_bytes pc_place = {0};
|
||||
static uint8_t pc_raw_mode = 0;
|
||||
|
||||
|
||||
// TODO - проверить - ф-ии 30h-33h (kbhit/getch/getche/getkey)
|
||||
// не должны менять IX и им можно не делать push ix / pop ix
|
||||
//
|
||||
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.
|
||||
*/
|
||||
|
||||
|
||||
/* 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. */
|
||||
|
||||
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, #0x8e ; BIOS GetCursor
|
||||
rst #0x08
|
||||
ld (_pc_place), de
|
||||
; 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 de, (_pc_place)
|
||||
ld c, #0x84 ; BIOS SetCursor
|
||||
rst #0x08
|
||||
; 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 _putch_wrchar(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.
|
||||
|
||||
;; ld c, a
|
||||
;; ld a, (_pc_ch) ; A = ch (`ld a,(nn)` does not touch flags)
|
||||
;; jr nz, _rp0_pri
|
||||
|
||||
;; ld a, c
|
||||
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.
|
||||
|
||||
_rp0_pri:
|
||||
|
||||
ld c, a ; C = ch (save before A is clobbered)
|
||||
ld a, (_pc_place + 1)
|
||||
cp #32
|
||||
ret nc ; off-screen bottom — silently skip
|
||||
ld d, a ; D = row (ESTEX WRCHAR convention)
|
||||
ld a, (_pc_place)
|
||||
cp #80
|
||||
ret nc ; off-screen right — silently skip
|
||||
ld e, a ; E = col
|
||||
inc a
|
||||
ld (_pc_place), a ; pc_col++
|
||||
|
||||
_rp0_wr:
|
||||
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_place)
|
||||
or a, a
|
||||
ret z ; already at col 0 — no change
|
||||
dec a
|
||||
ld (_pc_place), a
|
||||
ld e, a ; E = col
|
||||
ld a, (_pc_place + 1)
|
||||
ld d, a ; D = row (ESTEX WRCHAR convention)
|
||||
ld c, #0x20
|
||||
jr _rp0_wr
|
||||
|
||||
_rp0_tab:
|
||||
ld a, (_pc_place)
|
||||
or #0x07 ; floor to mult of 8
|
||||
inc a ; → next mult of 8
|
||||
cp #81 ; сравнить A с 81 (0x51)
|
||||
jr c, _rp0_tab_skip ; если A < 81 (т.е. A ≤ 80), пропустить загрузку
|
||||
ld a, #80 ; иначе A > 80 → установить A = 80
|
||||
_rp0_tab_skip:
|
||||
ld (_pc_place), a
|
||||
ret
|
||||
|
||||
_rp0_lf:
|
||||
ld a, (_pc_place + 1)
|
||||
cp #32
|
||||
ret nc ; already at bottom edge
|
||||
inc a
|
||||
ld (_pc_place + 1), a
|
||||
ret
|
||||
|
||||
_rp0_cr:
|
||||
xor a, a
|
||||
ld (_pc_place), a
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
/* Mode-1 worker: every byte goes through WRCHAR as a glyph. */
|
||||
static void _putch_wrchar_raw(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_place + 1)
|
||||
cp #32
|
||||
ret nc ; off-screen bottom — silently skip
|
||||
ld d, a ; D = row
|
||||
ld a, (_pc_place)
|
||||
cp #80
|
||||
ret nc ; off-screen right — silently skip
|
||||
ld e, a ; E = col
|
||||
inc a
|
||||
ld (_pc_place), 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;
|
||||
}
|
||||
|
||||
|
||||
/* ---- 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)
|
||||
|
||||
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_raw
|
||||
call __putch_wrchar
|
||||
jr _putch_after_raw
|
||||
_putch_use_raw:
|
||||
call __putch_wrchar_raw
|
||||
_putch_after_raw:
|
||||
call __set_cursor
|
||||
ld a, (_pc_ch) ; return value
|
||||
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
|
||||
|
||||
push ix
|
||||
|
||||
call __get_cursor
|
||||
|
||||
;; KEEP_EXIST_ATTR? high byte of g_text_attr != 0
|
||||
ld a, (_g_text_attr + 1)
|
||||
or a, a
|
||||
jr nz, _cputs_fast
|
||||
|
||||
ld a, (_pc_raw_mode)
|
||||
or a, a
|
||||
jr nz, _cputs_bios
|
||||
|
||||
push hl
|
||||
pop de
|
||||
|
||||
_cputs_wrloop:
|
||||
ld a, (_g_text_attr)
|
||||
ld l, a
|
||||
ld a, (de) ; загрузить байт
|
||||
or a ; установить флаг Z, если A == 0
|
||||
jr z, _cputs_ex ; завершить подпрограмму (конец строки)
|
||||
push de
|
||||
call __putch_wrchar ; вывести символ (A передаётся как аргумент)
|
||||
pop de
|
||||
inc de ; перейти к следующему байту
|
||||
jr _cputs_wrloop ; повторить
|
||||
|
||||
_cputs_bios:
|
||||
ld a, (_g_text_attr)
|
||||
ld b, #0xFF
|
||||
ld d, #0x0
|
||||
ld e, a
|
||||
ld c, #0x8B ; BIOS LP_PRINT_LN5
|
||||
rst #0x08
|
||||
jr _cputs_ex
|
||||
_cputs_fast:
|
||||
ld c, #0x5C ; ESTEX PCHARS
|
||||
rst #0x10
|
||||
_cputs_ex:
|
||||
call __set_cursor
|
||||
pop ix ; restore callers 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 (_pc_place), de
|
||||
ld c, #0x84 ; BIOS SetCursor
|
||||
rst #0x08
|
||||
;; 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, #0x8e ; BIOS GetCursor
|
||||
rst #0x08
|
||||
;; ld c, #0x53
|
||||
;; rst #0x10
|
||||
;; pop ix
|
||||
ld a, e
|
||||
ld (_pc_place), de
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
uint8_t wherey(void) __naked
|
||||
{
|
||||
__asm
|
||||
;; push ix
|
||||
ld c, #0x8e ; BIOS GetCursor
|
||||
rst #0x08
|
||||
;; ld c, #0x53
|
||||
;; rst #0x10
|
||||
;; pop ix
|
||||
ld a, d
|
||||
ld (_pc_place), de
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
uint16_t wherexy(void) __naked
|
||||
{
|
||||
__asm
|
||||
;; ESTEX CURSOR ($53): D = row, E = col. Return col in DE.
|
||||
;; push ix
|
||||
ld c, #0x8e ; BIOS GetCursor
|
||||
rst #0x08
|
||||
;; ld c, #0x53
|
||||
;; rst #0x10
|
||||
;; pop ix
|
||||
ld (_pc_place), de
|
||||
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 gettextmode(void)
|
||||
{
|
||||
return _videomode_raw_get();
|
||||
}
|
||||
|
||||
int settextmode(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 set_text_attr(int16_t attr)
|
||||
{
|
||||
int16_t prev = g_text_attr.value;
|
||||
g_text_attr.value = attr;
|
||||
return prev;
|
||||
}
|
||||
|
||||
int16_t get_text_attr(void)
|
||||
{
|
||||
return g_text_attr.value;
|
||||
}
|
||||
|
||||
/* ---- 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)
|
||||
{
|
||||
fg = (fg & 0x07);
|
||||
g_text_attr.value = (g_text_attr.byte.low & 0xF0) | fg;
|
||||
// g_text_attr.byte.high = 0;
|
||||
}
|
||||
|
||||
void textbackground(uint8_t bg)
|
||||
{
|
||||
bg = (bg & 0x07) << 4;
|
||||
g_text_attr.value = (g_text_attr.byte.low & 0x0F) | bg;
|
||||
// g_text_attr.byte.high = 0;
|
||||
}
|
||||
|
||||
void textattr(uint8_t attr)
|
||||
{
|
||||
g_text_attr.value = (uint16_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];
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
/*
|
||||
* cprintf — printf for the conio output set. Formats into a static
|
||||
* buffer with vsprintf (from SDCC's stdlib), then emits via cputs which
|
||||
* applies the current text attribute per character.
|
||||
* cprintf — printf для conio-набора вывода. Форматирует в статический
|
||||
* буфер через vsprintf (из stdlib SDCC), затем выводит cputs'ом,
|
||||
* который применяет текущий текстовый атрибут посимвольно.
|
||||
*
|
||||
* No '\n' to CR LF translation — Turbo-C convention: callers write
|
||||
* "\r\n" explicitly in the format string for line breaks.
|
||||
* '\n' в CR LF НЕ транслируется — конвенция Turbo-C: перевод строки
|
||||
* пишется в format-строке явно как "\r\n".
|
||||
*
|
||||
* Not reentrant (single static buffer) but Z80 single-threaded is fine.
|
||||
* Не реентерабельно (один статический буфер), но на однопоточном Z80
|
||||
* это не проблема.
|
||||
*/
|
||||
|
||||
#include <conio.h>
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* cputs — вывод ASCIIZ-строки с текущим атрибутом (Turbo-C conio).
|
||||
*
|
||||
* Курсор BIOS читается один раз до строки и записывается один раз
|
||||
* после. Три пути:
|
||||
* g_text_attr == KEEP_EXIST_ATTR → быстрый ESTEX PCHARS ($5C);
|
||||
* pc_raw_mode == 1 → BIOS LP_PRINT_LN5 (глифы с атрибутом);
|
||||
* иначе → цикл _putch_wrchar (интерпретация
|
||||
* BS/TAB/LF/CR).
|
||||
* Трансляции '\n' → CR LF нет — вызывающий пишет "\r\n" сам.
|
||||
* cputs(NULL) — no-op. Возвращает 0.
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
char cputs(const char *s) __naked
|
||||
{
|
||||
(void)s;
|
||||
__asm
|
||||
;; HL = s на входе; возврат char → A.
|
||||
|
||||
;; NULL-check: cputs(NULL) → сразу вернуть 0.
|
||||
ld a, h
|
||||
or a, l
|
||||
ret z
|
||||
|
||||
push ix
|
||||
|
||||
call __get_cursor
|
||||
|
||||
;; KEEP_EXIST_ATTR? старший байт g_text_attr != 0
|
||||
ld a, (_g_text_attr + 1)
|
||||
or a, a
|
||||
jr nz, _cputs_fast
|
||||
|
||||
ld a, (_pc_raw_mode)
|
||||
or a, a
|
||||
jr nz, _cputs_bios
|
||||
|
||||
push hl
|
||||
pop de
|
||||
|
||||
_cputs_wrloop:
|
||||
ld a, (_g_text_attr)
|
||||
ld l, a
|
||||
ld a, (de) ; загрузить байт
|
||||
or a ; установить флаг Z, если A == 0
|
||||
jr z, _cputs_ex ; завершить подпрограмму (конец строки)
|
||||
push de
|
||||
call __putch_wrchar ; вывести символ (A передаётся как аргумент)
|
||||
pop de
|
||||
inc de ; перейти к следующему байту
|
||||
jr _cputs_wrloop ; повторить
|
||||
|
||||
_cputs_bios:
|
||||
ld a, (_g_text_attr)
|
||||
ld b, #0xFF
|
||||
ld d, #0x0
|
||||
ld e, a
|
||||
ld c, #0x8B ; BIOS LP_PRINT_LN5
|
||||
rst #0x08
|
||||
jr _cputs_ex
|
||||
_cputs_fast:
|
||||
ld c, #0x5C ; ESTEX PCHARS
|
||||
rst #0x10
|
||||
_cputs_ex:
|
||||
call __set_cursor
|
||||
pop ix ; восстановить IX вызывающего
|
||||
xor a, a ; вернуть 0
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* get_putch_raw_mode — текущий режим обработки управляющих символов
|
||||
* putch/cputs (см. set_putch_raw_mode).
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
uint8_t get_putch_raw_mode(void)
|
||||
{
|
||||
return pc_raw_mode;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* get_text_attr — текущий 16-битный атрибут conio (см. set_text_attr).
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
int16_t get_text_attr(void)
|
||||
{
|
||||
return g_text_attr.value;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* getch — блокирующее чтение клавиши БЕЗ эха на экран.
|
||||
* ESTEX WAITKEY ($30); ASCII возвращается в A.
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
char getch(void) __naked
|
||||
{
|
||||
__asm
|
||||
push ix
|
||||
ld c, #0x30 ; ESTEX WAITKEY (без эха)
|
||||
rst #0x10
|
||||
pop ix
|
||||
;; ESTEX возвращает ASCII в E (и копию в A)
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* getche — блокирующее чтение клавиши С эхом на экран.
|
||||
* ESTEX ECHOKEY ($32); ASCII возвращается в A.
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
char getche(void) __naked
|
||||
{
|
||||
__asm
|
||||
push ix
|
||||
ld c, #0x32 ; ESTEX ECHOKEY (эхо на консоль)
|
||||
rst #0x10
|
||||
pop ix
|
||||
;; ESTEX возвращает ASCII в E (и копию в A)
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* getkey — как getch(), но возвращает И ASCII, И позиционный скан-код,
|
||||
* чтобы различать расширенные клавиши (стрелки, F1..F12, PgUp/PgDn,
|
||||
* Home/End, Ins/Del — у них ASCII == 0 из ESTEX) и обычные символы.
|
||||
*
|
||||
* возврат = (scan << 8) | ascii
|
||||
*
|
||||
* Обычные клавиши: ascii — символ, scan — позиционный код (бит 7
|
||||
* взведён при Ctrl/Alt/Shift).
|
||||
* Расширенные: ascii == 0, клавишу определяет scan (см. KEY_* в
|
||||
* <conio.h>).
|
||||
*/
|
||||
|
||||
#include "_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 (защитно: канонична копия в A)
|
||||
ret ; __sdcccall(1) возвращает uint16_t в DE
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* gettextmode — текущий видеорежим (обёртка _videomode_raw_get из
|
||||
* video/videomode_raw.c: сырые геттер/сеттер живут отдельно, чтобы
|
||||
* чисто графические программы не тянули conio).
|
||||
*/
|
||||
|
||||
#include <conio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
extern uint8_t _videomode_raw_get(void);
|
||||
|
||||
uint8_t gettextmode(void)
|
||||
{
|
||||
return _videomode_raw_get();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* gotoxy — установить курсор в (x=col, y=row), 0-based.
|
||||
* Обновляет pc_place и ставит курсор BIOS SetCursor (rst 8, $84).
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
void gotoxy(uint8_t x, uint8_t y) __naked
|
||||
{
|
||||
(void)x; (void)y;
|
||||
__asm
|
||||
;; __sdcccall(1), 2 аргумента uint8: x в A, y в L.
|
||||
ld d, l ; D = row (y)
|
||||
ld e, a ; E = col (x)
|
||||
ld (_pc_place), de
|
||||
ld c, #0x84 ; BIOS SetCursor
|
||||
rst #0x08
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* kbhit — 1, если в буфере клавиатуры есть символ, иначе 0.
|
||||
* ESTEX CTRLKEY ($33) подглядывает, не забирая символ из буфера.
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
// TODO - проверить - ф-ии 30h-33h (kbhit/getch/getche/getkey)
|
||||
// не должны менять IX и им можно не делать push ix / pop ix
|
||||
char kbhit(void) __naked
|
||||
{
|
||||
__asm
|
||||
push ix
|
||||
ld c, #0x33 ; ESTEX CTRLKEY — подглядывает без изъятия
|
||||
rst #0x10
|
||||
pop ix
|
||||
;; A=0 → клавиши нет; ненулевой → есть.
|
||||
or a, a
|
||||
ret z
|
||||
ld a, #0x01
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* putch — вывод символа с текущим атрибутом (Turbo-C conio).
|
||||
*
|
||||
* Применяет g_text_attr; при pc_raw_mode == 1 управляющие символы
|
||||
* печатаются как глифы (воркер _putch_wrchar_raw), иначе
|
||||
* интерпретируются (_putch_wrchar). После вывода записывает курсор
|
||||
* BIOS один раз (_set_cursor). Трансляции '\n' → CR LF нет —
|
||||
* Turbo-C-конвенция, вызывающий пишет "\r\n" сам.
|
||||
* Возвращает выведенный символ.
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
/* Стэш символа на время вызовов воркеров (только для putch). */
|
||||
static uint8_t pc_ch;
|
||||
|
||||
char putch(char ch) __naked
|
||||
{
|
||||
(void)ch;
|
||||
__asm
|
||||
;; A = ch на входе; возврат char → A.
|
||||
ld (_pc_ch), a ; спрятать ch (для возврата и перезагрузки)
|
||||
|
||||
ld a, (_g_text_attr) ; A = младший байт = 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)` не трогает флаги)
|
||||
jr nz, _putch_use_raw
|
||||
call __putch_wrchar
|
||||
jr _putch_after_raw
|
||||
_putch_use_raw:
|
||||
call __putch_wrchar_raw
|
||||
_putch_after_raw:
|
||||
call __set_cursor
|
||||
ld a, (_pc_ch) ; возвращаемое значение
|
||||
ret
|
||||
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* rdchar — прочитать символ и атрибут из позиции (x, y):
|
||||
* возврат (attr << 8) | ch. ESTEX RDCHAR ($57).
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
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 ; старший байт → attr
|
||||
ld e, a ; младший байт → ch
|
||||
pop ix
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* scroll — прокрутка окна (x, y, w, h) на 1 позицию в направлении
|
||||
* direction, с очисткой освободившейся строки/колонки атрибутом clear.
|
||||
* ESTEX SCROLL ($55).
|
||||
*
|
||||
* SDCC __sdcccall(1): x в A, y в L; w,h и direction,clear запушены
|
||||
* парами на стек. Void-возврат → callee снимает стековые аргументы
|
||||
* (pop до RST) и уходит через jp (iy).
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
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 ; адрес возврата
|
||||
ld d, l ; D = row (y)
|
||||
ld e, a ; E = col (x)
|
||||
pop hl ; H = высота (h), L = ширина (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;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* set_putch_raw_mode — режим обработки управляющих символов в
|
||||
* putch/cputs: 0 — BS/TAB/LF/CR интерпретируются, 1 — все байты
|
||||
* печатаются как глифы CP437 (см. pc_raw_mode).
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
void set_putch_raw_mode(uint8_t mode)
|
||||
{
|
||||
pc_raw_mode = mode;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* set_text_attr — установить полный 16-битный атрибут conio
|
||||
* (0x00..0xFF — реальный атрибут; KEEP_EXIST_ATTR — быстрый путь без
|
||||
* атрибута). Возвращает прежнее значение.
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
int16_t set_text_attr(int16_t attr)
|
||||
{
|
||||
int16_t prev = g_text_attr.value;
|
||||
g_text_attr.value = attr;
|
||||
return prev;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* settextmode — установить ТЕКСТОВЫЙ видеорежим (TEXT_MODE_40x32 /
|
||||
* TEXT_MODE_80x32). Любое другое значение отвергается с EINVAL —
|
||||
* иначе случайный GFX_MODE_* мог бы выдернуть экран из-под текстового
|
||||
* I/O. Возвращает 0 или -1 + errno.
|
||||
*/
|
||||
|
||||
#include <conio.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
|
||||
extern int _videomode_raw_set(uint8_t mode);
|
||||
|
||||
int settextmode(uint8_t mode)
|
||||
{
|
||||
/* Отвергаем всё, что не известный текстовый режим. */
|
||||
if (mode != TEXT_MODE_40x32 && mode != TEXT_MODE_80x32) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
return _videomode_raw_set(mode);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* text_pal_get — прочитать блок записей текстовой палитры;
|
||||
* план 0..3 транслируется в палитровую страницу BIOS 4..7.
|
||||
*/
|
||||
|
||||
#include <conio.h>
|
||||
#include <palette.h>
|
||||
|
||||
void text_pal_get(uint8_t plane, uint8_t start, uint8_t count, uint8_t *data)
|
||||
{
|
||||
pal_get((uint8_t)(plane + 4u), start, count, data);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* text_pal_get_color — прочитать одну запись текстовой палитры в R/G/B;
|
||||
* план 0..3 транслируется в палитровую страницу BIOS 4..7.
|
||||
*/
|
||||
|
||||
#include <conio.h>
|
||||
#include <palette.h>
|
||||
|
||||
void text_pal_get_color(uint8_t plane, uint8_t attr,
|
||||
uint8_t *r, uint8_t *g, uint8_t *b)
|
||||
{
|
||||
pal_get_color((uint8_t)(plane + 4u), attr, r, g, b);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* text_pal_load — загрузить блок записей текстовой палитры.
|
||||
* Текстовый «план» 0..3 (paper/ink/blink-paper/blink-ink) транслируется
|
||||
* в палитровую страницу BIOS 4..7 (см. <palette.h>).
|
||||
*/
|
||||
|
||||
#include <conio.h>
|
||||
#include <palette.h>
|
||||
|
||||
void text_pal_load(uint8_t plane, uint8_t start, uint8_t count,
|
||||
const uint8_t *data)
|
||||
{
|
||||
pal_load((uint8_t)(plane + 4u), start, count, data);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* text_pal_reset — восстановить системную текстовую палитру
|
||||
* (BIOS $A6, type = PAL_CGA, страницы 4..7).
|
||||
*/
|
||||
|
||||
#include <conio.h>
|
||||
#include <palette.h>
|
||||
|
||||
void text_pal_reset(void)
|
||||
{
|
||||
pal_reset(PAL_CGA);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* text_pal_set_color — одна запись текстовой палитры из RGB;
|
||||
* план 0..3 транслируется в палитровую страницу BIOS 4..7.
|
||||
*/
|
||||
|
||||
#include <conio.h>
|
||||
#include <palette.h>
|
||||
|
||||
void text_pal_set_color(uint8_t plane, uint8_t attr,
|
||||
uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
pal_set_color((uint8_t)(plane + 4u), attr, r, g, b);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* text_palette.c — text-mode palette wrappers.
|
||||
*
|
||||
* Thin layer over libc/video/palette.c. Translates the text "plane
|
||||
* number" 0..3 (paper/ink/blink-paper/blink-ink) into the underlying
|
||||
* BIOS palette page 4..7 used by $A4.
|
||||
*
|
||||
* For an introduction to the four-plane text colour model see
|
||||
* <palette.h> (top-of-file doc-block).
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <conio.h>
|
||||
#include <palette.h>
|
||||
|
||||
void text_pal_load(uint8_t plane, uint8_t start, uint8_t count,
|
||||
const uint8_t *data)
|
||||
{
|
||||
pal_load((uint8_t)(plane + 4u), start, count, data);
|
||||
}
|
||||
|
||||
void text_pal_set_color(uint8_t plane, uint8_t attr,
|
||||
uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
pal_set_color((uint8_t)(plane + 4u), attr, r, g, b);
|
||||
}
|
||||
|
||||
void text_pal_get(uint8_t plane, uint8_t start, uint8_t count, uint8_t *data)
|
||||
{
|
||||
pal_get((uint8_t)(plane + 4u), start, count, data);
|
||||
}
|
||||
|
||||
void text_pal_get_color(uint8_t plane, uint8_t attr,
|
||||
uint8_t *r, uint8_t *g, uint8_t *b)
|
||||
{
|
||||
pal_get_color((uint8_t)(plane + 4u), attr, r, g, b);
|
||||
}
|
||||
|
||||
void text_pal_reset(void)
|
||||
{
|
||||
pal_reset(PAL_CGA);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* textattr — заменить атрибут целиком одним байтом (fg | bg | blink).
|
||||
* Turbo-C-стиль. Сбрасывает KEEP_EXIST_ATTR (старший байт = 0).
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
void textattr(uint8_t attr)
|
||||
{
|
||||
g_text_attr.value = (uint16_t)attr;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* textbackground — установить цвет фона (биты 4..6 атрибута),
|
||||
* цвет символов и бит blink сохраняются. Turbo-C-стиль. Попутно
|
||||
* сбрасывает KEEP_EXIST_ATTR (старший байт становится 0).
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
void textbackground(uint8_t bg)
|
||||
{
|
||||
bg = (bg & 0x07) << 4;
|
||||
g_text_attr.value = (g_text_attr.byte.low & 0x0F) | bg;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* textcolor — установить цвет символов (нижний нибл атрибута),
|
||||
* фон и бит blink сохраняются. Turbo-C-стиль. Попутно сбрасывает
|
||||
* KEEP_EXIST_ATTR (старший байт становится 0).
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
void textcolor(uint8_t fg)
|
||||
{
|
||||
fg = (fg & 0x07);
|
||||
g_text_attr.value = (g_text_attr.byte.low & 0xF0) | fg;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* wherex — текущая колонка курсора (0..79).
|
||||
* Читает курсор BIOS GetCursor (rst 8, $8E), попутно обновляя pc_place.
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
uint8_t wherex(void) __naked
|
||||
{
|
||||
__asm
|
||||
ld c, #0x8e ; BIOS GetCursor: D = row, E = col
|
||||
rst #0x08
|
||||
ld a, e
|
||||
ld (_pc_place), de
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* wherexy — обе координаты курсора одним вызовом:
|
||||
* (row << 8) | col в DE. Читает курсор BIOS GetCursor (rst 8, $8E),
|
||||
* попутно обновляя pc_place.
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
uint16_t wherexy(void) __naked
|
||||
{
|
||||
__asm
|
||||
ld c, #0x8e ; BIOS GetCursor: D = row, E = col
|
||||
rst #0x08
|
||||
ld (_pc_place), de
|
||||
ret ; __sdcccall(1): uint16_t в DE
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* wherey — текущая строка курсора (0..31).
|
||||
* Читает курсор BIOS GetCursor (rst 8, $8E), попутно обновляя pc_place.
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
uint8_t wherey(void) __naked
|
||||
{
|
||||
__asm
|
||||
ld c, #0x8e ; BIOS GetCursor: D = row, E = col
|
||||
rst #0x08
|
||||
ld a, d
|
||||
ld (_pc_place), de
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* wrchar — записать символ ch с атрибутом attr в позицию (x, y), не
|
||||
* трогая курсор. ESTEX WRCHAR ($58).
|
||||
*
|
||||
* SDCC __sdcccall(1): x в A, y в L (2 uint8 → A, L); ch и attr
|
||||
* упакованы и запушены на стек одной 16-битной парой (вызывающий
|
||||
* делает `ld hl, #(attr<<8)|ch; push hl`). Раскладка после CALL:
|
||||
* [SP+0..1] = адрес возврата
|
||||
* [SP+2] = ch (младшая половина пары)
|
||||
* [SP+3] = attr (старшая половина)
|
||||
* Void-возврат → callee снимает 2 байта стековых аргументов через
|
||||
* `pop bc` + jp (iy).
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
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 ; адрес возврата
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* z80_inp — прочитать байт из порта Z80 (опкод IN). Обёртка с
|
||||
* стабильным C API; имя как в Solid-C / MS-DOS Turbo-C.
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
uint8_t z80_inp(uint8_t port) __naked
|
||||
{
|
||||
(void)port;
|
||||
__asm
|
||||
;; SDCC __sdcccall(1): единственный uint8_t аргумент в A; возврат в A.
|
||||
ld c, a
|
||||
in a, (c)
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* z80_outp — записать байт value в порт Z80 (опкод OUT). Обёртка с
|
||||
* стабильным C API; имя как в Solid-C / MS-DOS Turbo-C.
|
||||
*/
|
||||
|
||||
#include "_conio.h"
|
||||
|
||||
void z80_outp(uint8_t port, uint8_t value) __naked
|
||||
{
|
||||
(void)port; (void)value;
|
||||
__asm
|
||||
;; __sdcccall(1): 2 аргумента uint8 → arg1 в A, arg2 в L.
|
||||
ld c, a ; C = port
|
||||
out (c), l ; out (port), value
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
Vendored
-93
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* getenv / putenv via ESTEX ENVIRON ($46).
|
||||
*
|
||||
* ESTEX $46 subfn 1 (getenv):
|
||||
* in: HL = name (ASCIIZ), DE = output buffer (caller-owned)
|
||||
* out: CF=0 + A != 0 → variable found, value written into [DE..end-1]
|
||||
* CF=0 + A == 0 → variable not present (note: this is the
|
||||
* opposite of what DiskSyscalls.txt v1.6 docs
|
||||
* claim — verified empirically and against
|
||||
* solid-c's IO.ASM:763-766 implementation)
|
||||
* CF=1 → error, A = code
|
||||
* DE on exit points at one past the last byte written
|
||||
*
|
||||
* ESTEX $46 subfn 2 (putenv):
|
||||
* in: HL = "NAME=value" (NUL-terminated)
|
||||
* out: CF=1 / A = error code on failure
|
||||
*
|
||||
* We hand back a pointer into a private 128-byte buffer for getenv().
|
||||
* Caller must copy the bytes before the next getenv() call if they
|
||||
* need to outlive it.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sprinter.h>
|
||||
|
||||
static char env_buf[128];
|
||||
|
||||
char *getenv(const char *name) __naked
|
||||
{
|
||||
(void)name;
|
||||
__asm
|
||||
push ix
|
||||
;; HL = name on entry; we need to also load DE = env_buf.
|
||||
ld de, #_env_buf
|
||||
ld bc, #0x0146 ; ESTEX ENVIRON; subfn: getenv
|
||||
rst #0x10
|
||||
pop ix
|
||||
jr c, _getenv_err
|
||||
or a, a
|
||||
jr Z, _getenv_miss
|
||||
ld de, #_env_buf
|
||||
ret
|
||||
_getenv_err:
|
||||
call __errno_set
|
||||
_getenv_miss:
|
||||
ld de, #0
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
int putenv(const char *namevalue) __naked
|
||||
{
|
||||
(void)namevalue;
|
||||
__asm
|
||||
push ix
|
||||
ld bc, #0x0246 ; ESTEX ENVIRON; subfn: setenv
|
||||
rst #0x10
|
||||
pop ix
|
||||
jr c, _putenv_err
|
||||
ld de, #0
|
||||
ret
|
||||
_putenv_err:
|
||||
call __errno_set
|
||||
ld de, #-1
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
/* ESTEX $46 subfn 0 (sysenv):
|
||||
* in: HL = caller-owned buffer
|
||||
* out: caller's buffer is filled with one NUL-terminated "NAME=value"
|
||||
* per env var, then a trailing extra NUL marks the end:
|
||||
* "PATH=...\0SOLID=H\0\0"
|
||||
* return: buf on success, -1 on error (errno set).
|
||||
* Buffer must be large enough for the whole environment.
|
||||
*/
|
||||
char *sysenv(char *buf) __naked
|
||||
{
|
||||
(void)buf;
|
||||
__asm
|
||||
push ix
|
||||
push hl ; stash buffer pointer (= return value)
|
||||
ld bc, #0x0046 ; ESTEX ENVIRON; subfn 0 = sysenv
|
||||
rst #0x10
|
||||
pop de ; DE = buffer pointer
|
||||
pop ix
|
||||
ret nc ; success: DE already has buf
|
||||
;; CF=1 → A = ESTEX error code.
|
||||
call __errno_set
|
||||
ld de, #-1
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* getenv — значение переменной окружения через ESTEX ENVIRON ($46, subfn 1):
|
||||
* in: HL = имя (ASCIIZ), DE = выходной буфер
|
||||
* out: CF=0 + A != 0 → найдена, значение записано в [DE..end-1]
|
||||
* CF=0 + A == 0 → переменной нет (примечание: это ПРОТИВОПОЛОЖНО
|
||||
* тому, что пишет DiskSyscalls.txt v1.6 — проверено
|
||||
* эмпирически и по solid-c IO.ASM:763-766)
|
||||
* CF=1 → ошибка, A = код
|
||||
*
|
||||
* Возвращает указатель в приватный 128-байтный буфер — скопируй до
|
||||
* следующего вызова getenv(), если значение должно пережить его.
|
||||
* NULL — нет переменной или ошибка (errno).
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sprinter.h>
|
||||
|
||||
static char env_buf[128];
|
||||
|
||||
char *getenv(const char *name) __naked
|
||||
{
|
||||
(void)name;
|
||||
__asm
|
||||
push ix
|
||||
;; HL = имя на входе; дополнительно DE = env_buf.
|
||||
ld de, #_env_buf
|
||||
ld bc, #0x0146 ; ESTEX ENVIRON; subfn: getenv
|
||||
rst #0x10
|
||||
pop ix
|
||||
jr c, _getenv_err
|
||||
or a, a
|
||||
jr Z, _getenv_miss
|
||||
ld de, #_env_buf
|
||||
ret
|
||||
_getenv_err:
|
||||
call __errno_set
|
||||
_getenv_miss:
|
||||
ld de, #0
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* putenv — установить переменную окружения строкой "NAME=value" (ASCIIZ).
|
||||
* ESTEX ENVIRON ($46, subfn 2): HL = строка; CF=1 / A = код при ошибке.
|
||||
* Возвращает 0 или -1 + errno.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sprinter.h>
|
||||
|
||||
int putenv(const char *namevalue) __naked
|
||||
{
|
||||
(void)namevalue;
|
||||
__asm
|
||||
push ix
|
||||
ld bc, #0x0246 ; ESTEX ENVIRON; subfn: setenv
|
||||
rst #0x10
|
||||
pop ix
|
||||
jr c, _putenv_err
|
||||
ld de, #0
|
||||
ret
|
||||
_putenv_err:
|
||||
call __errno_set
|
||||
ld de, #-1
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* sysenv — снять всё окружение в буфер вызывающего.
|
||||
* ESTEX ENVIRON ($46, subfn 0): HL = буфер; на выходе буфер заполнен
|
||||
* ASCIIZ-строками "NAME=value" подряд, конец — дополнительный NUL:
|
||||
* "PATH=...\0SOLID=H\0\0"
|
||||
* Буфер должен вмещать всё окружение. Возвращает buf или -1 + errno.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sprinter.h>
|
||||
|
||||
char *sysenv(char *buf) __naked
|
||||
{
|
||||
(void)buf;
|
||||
__asm
|
||||
push ix
|
||||
push hl ; спрятать указатель (= возврат)
|
||||
ld bc, #0x0046 ; ESTEX ENVIRON; subfn 0 = sysenv
|
||||
rst #0x10
|
||||
pop de ; DE = указатель буфера
|
||||
pop ix
|
||||
ret nc ; успех: DE уже держит buf
|
||||
;; CF=1 → A = код ошибки ESTEX.
|
||||
call __errno_set
|
||||
ld de, #-1
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
+22
-22
@@ -1,40 +1,40 @@
|
||||
/*
|
||||
* _errno_set — set `errno` from an ESTEX error code (0..255 in A).
|
||||
* _errno_set — установить `errno` из кода ошибки ESTEX (0..255 в A).
|
||||
*
|
||||
* Replaces the inline pattern
|
||||
* Заменяет inline-паттерн
|
||||
* ld (_errno), a
|
||||
* xor a, a
|
||||
* ld (_errno+1), a ; 7 bytes per error path
|
||||
* with a single
|
||||
* call __errno_set ; 3 bytes per error path
|
||||
* ld (_errno+1), a ; 7 байт на каждый error path
|
||||
* одним
|
||||
* call __errno_set ; 3 байта на error path
|
||||
*
|
||||
* Saves ~4 bytes at every libc error handler that converts an ESTEX
|
||||
* code into the C-side `errno`. Helper itself is 7 bytes; with 10+
|
||||
* error paths in our libc the size win is net-positive.
|
||||
* Экономит ~4 байта в каждом обработчике ошибок libc, конвертирующем
|
||||
* код ESTEX в C-шный errno. Сам хелпер — 7 байт; при 10+ error path
|
||||
* в нашей libc выигрыш по размеру суммарно положительный.
|
||||
*
|
||||
* ABI:
|
||||
* in: A = ESTEX error code (0..255)
|
||||
* out: HL = A (zero-extended); errno fully overwritten so a prior
|
||||
* large value (e.g. errno = -1) can't leak its high byte.
|
||||
* clobbers: HL, AF flags. Caller must not depend on HL afterwards.
|
||||
* вход: A = код ошибки ESTEX (0..255)
|
||||
* выход: errno перезаписан целиком, чтобы прежнее большое значение
|
||||
* (например errno = -1) не оставило свой старший байт.
|
||||
* клоббер: A, флаги. HL/BC/DE/IX/IY не трогаются.
|
||||
*
|
||||
* Defensive 16-bit store — see chat 2026-06-02: if anyone ever assigns
|
||||
* errno via C (`errno = -1`), the high byte becomes 0xFF, and a partial
|
||||
* 8-bit write here would leave that 0xFF in place. Always writing the
|
||||
* full word keeps errno honest regardless of who set it last.
|
||||
* Защитная 16-битная запись — см. чат 2026-06-02: если кто-то присвоит
|
||||
* errno из C (`errno = -1`), старший байт станет 0xFF, и частичная
|
||||
* 8-битная запись здесь оставила бы этот 0xFF на месте. Полное слово
|
||||
* держит errno честным независимо от того, кто писал последним.
|
||||
*/
|
||||
|
||||
void _errno_set(unsigned char code) __naked
|
||||
{
|
||||
(void)code;
|
||||
__asm
|
||||
;; __sdcccall(1): single uint8_t arg already in A.
|
||||
;; Write the two bytes separately so HL/BC/DE/IX/IY remain
|
||||
;; untouched. Only A is clobbered: it is the input register,
|
||||
;; and ABI does not require preserving it across a void call.
|
||||
ld (_errno), a ; low byte = code
|
||||
;; __sdcccall(1): единственный uint8_t-аргумент уже в A.
|
||||
;; Пишем два байта по отдельности, чтобы HL/BC/DE/IX/IY остались
|
||||
;; нетронутыми. Клобберится только A: это входной регистр, и
|
||||
;; ABI не требует сохранять его через void-вызов.
|
||||
ld (_errno), a ; младший байт = код
|
||||
xor a, a
|
||||
ld (_errno+1), a ; high byte = 0
|
||||
ld (_errno+1), a ; старший байт = 0
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* perror — напечатать "prefix: <strerror(errno)>\r\n" в stderr.
|
||||
* Пустой/NULL prefix — печатается только текст ошибки.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
void perror(const char *prefix)
|
||||
{
|
||||
if (prefix && *prefix) {
|
||||
fputs(prefix, stderr);
|
||||
fputs(": ", stderr);
|
||||
}
|
||||
fputs(strerror(errno), stderr);
|
||||
fputs("\r\n", stderr);
|
||||
}
|
||||
@@ -1,24 +1,20 @@
|
||||
/*
|
||||
* errno.c — strerror / perror over the SDCC-provided `errno` global.
|
||||
* strerror — текст ошибки по коду ESTEX (0..32); вне диапазона —
|
||||
* "Unknown error". Таблица повторяет solid-c IO.ASM (английские
|
||||
* формулировки сохранены для grep-ability). Таблица и функция
|
||||
* неразделимы — живут одним модулем.
|
||||
*
|
||||
* The message table mirrors the one in solid-c's IO.ASM (we kept the
|
||||
* English wording for grep-ability). ESTEX returns codes 0..32 in the
|
||||
* meaningful range; anything beyond gets "Unknown error".
|
||||
*
|
||||
* Note: we deliberately do NOT define `_errno` here — SDCC's
|
||||
* z80.lib/errno.rel provides it (a single int in _DATA), and our libc
|
||||
* wrappers (read.c, open.c, etc.) just assign to `errno`. This
|
||||
* removes the "multiple definition of _errno" link warning.
|
||||
* Примечание: `_errno` здесь сознательно НЕ определяется — его даёт
|
||||
* SDCC z80.lib/errno.rel (int в _DATA), наши обёртки просто пишут в
|
||||
* `errno`. Это убирает варнинг "multiple definition of _errno".
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* Stored verbatim — pointers in the lookup table cost 2 bytes each plus
|
||||
* the message bytes themselves. Sentinel "" entries pad out gaps so
|
||||
* indexing stays direct.
|
||||
* Хранится как есть — указатель в таблице стоит 2 байта плюс сами
|
||||
* байты сообщения. Заглушки "RESERVED" затыкают дыры, чтобы
|
||||
* индексирование оставалось прямым.
|
||||
*/
|
||||
static const char *const messages[] = {
|
||||
/* 0 */ "No error",
|
||||
@@ -65,13 +61,3 @@ const char *strerror(int err)
|
||||
}
|
||||
return messages[err];
|
||||
}
|
||||
|
||||
void perror(const char *prefix)
|
||||
{
|
||||
if (prefix && *prefix) {
|
||||
fputs(prefix, stderr);
|
||||
fputs(": ", stderr);
|
||||
}
|
||||
fputs(strerror(errno), stderr);
|
||||
fputs("\r\n", stderr);
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* _gfx.h — внутренности графической подсистемы (НЕ публичный заголовок).
|
||||
*
|
||||
* Общее состояние лежит в data-модулях (_gfx_state.c, _gfx_w3_state.c,
|
||||
* _gfx_acc256.c, _gfx_g16_state.c, _gfx_font_state.c), helpers — в
|
||||
* отдельных internal-модулях; см. docs/libc-split-asm-cases.md.
|
||||
*/
|
||||
#ifndef _GFX_INTERNAL_H
|
||||
#define _GFX_INTERNAL_H
|
||||
|
||||
#include <gfx.h>
|
||||
|
||||
/* ---- Общее состояние (_gfx_state.c) ------------------------------ */
|
||||
|
||||
/* Кэш SELPAGE: видимая и рисуемая страницы (0/1). */
|
||||
extern uint8_t _gfx_visible_page;
|
||||
extern uint8_t _gfx_draw_page;
|
||||
|
||||
/* Байт страницы W3 (0x50..0x5F) — какой видеобанк мапится при записи. */
|
||||
extern uint8_t _gfx_bank;
|
||||
|
||||
/* CPU-адрес колонки 0 текущей draw-страницы: 0xC000 (page 0) или
|
||||
* 0xC140 (page 1); каждый примитив использует его вместо константы. */
|
||||
extern uint16_t _gfx_addr_base;
|
||||
|
||||
/* ---- Низкоуровневый видеорежим (video/videomode_raw.c) ----------- */
|
||||
|
||||
uint8_t _videomode_raw_get(void);
|
||||
int _videomode_raw_set(uint8_t mode);
|
||||
|
||||
/* ---- W3-скобки (_gfx_w3_video_begin/end.c, _gfx_w3_state.c) ------ *
|
||||
* begin: DI + сохранить текущий W3 и замапить _gfx_bank;
|
||||
* end: восстановить W3 + EI. Не реентерабельно (сейв в статике). */
|
||||
|
||||
extern uint8_t _gfx_saved_w3;
|
||||
|
||||
void _gfx_w3_video_begin(void);
|
||||
void _gfx_w3_video_end(void);
|
||||
|
||||
/* ---- Raw-примитивы 256 цветов (mode 0x81) ------------------------- *
|
||||
* "Raw" = W3-naive: вызывающий обязан обернуть последовательность в
|
||||
* одну пару _gfx_w3_video_begin/_gfx_w3_video_end. */
|
||||
|
||||
void _gfx_putpixel256_raw(int x, int y, uint8_t color);
|
||||
void _gfx_hline256_raw (int x, int y, int len, uint8_t color);
|
||||
void _gfx_vline256_raw (int x, int y, int len, uint8_t color);
|
||||
void _gfx_clear256_raw (uint8_t color);
|
||||
|
||||
/* Скретч акселератора (_gfx_acc256.c) — общий для hline/vline/clear;
|
||||
* однопоточно, IRQ выключены между begin/end. */
|
||||
extern uint8_t _gfx_acc_color;
|
||||
extern uint8_t _gfx_acc_y;
|
||||
extern uint8_t _gfx_acc_len; /* 0 = 256 — конвенция акселератора */
|
||||
extern uint16_t _gfx_acc_addr;
|
||||
|
||||
/* Горизонтальный Fill-burst: _gfx_acc_len байт с _gfx_acc_addr на
|
||||
* строке _gfx_acc_y (W3 замаплен, DI активен). */
|
||||
void _gfx_hfill256_chunk(void);
|
||||
|
||||
/* ---- Raw-примитивы 16 цветов (mode 0x82) -------------------------- */
|
||||
|
||||
void _gfx_putpixel16_raw(int x, int y, uint8_t color);
|
||||
void _gfx_hline16_raw (int x, int y, int len, uint8_t color);
|
||||
void _gfx_vline16_raw (int x, int y, int len, uint8_t color);
|
||||
void _gfx_clear16_raw (uint8_t color);
|
||||
|
||||
/* Скретч 16-цветных примитивов (_gfx_g16_state.c). */
|
||||
extern uint8_t _gfx_g16_y;
|
||||
extern uint8_t _gfx_g16_byte; /* nibble | (nibble<<4) */
|
||||
extern uint8_t _gfx_g16_nibble; /* цвет в нужной половине байта */
|
||||
extern uint8_t _gfx_g16_mask; /* маска, сохраняющая ДРУГУЮ половину */
|
||||
extern uint8_t _gfx_g16_len; /* размер accel-блока (0 = 256) */
|
||||
extern uint16_t _gfx_g16_addr;
|
||||
|
||||
/* Горизонтальный Fill-burst байтами _gfx_g16_byte (_gfx_hfill16.c). */
|
||||
void _gfx_hfill16_chunk(void);
|
||||
|
||||
/* RMW одного нибла по (_gfx_g16_addr, _gfx_g16_y) (_gfx_rmw16.c). */
|
||||
void _gfx_rmw16_pixel(void);
|
||||
|
||||
/* ---- Шрифт (_gfx_font_state.c, _gfx_font_ensure.c) ---------------- *
|
||||
* Формат: 256 глифов × 8 рядов, INTERLEAVED — offset = row*256 + char;
|
||||
* биты MSB-first. См. memory/sprinter_font_format. */
|
||||
|
||||
#define _GFX_FONT_BYTES 2048
|
||||
|
||||
extern uint8_t _gfx_font_buf[_GFX_FONT_BYTES];
|
||||
extern const uint8_t *_gfx_font_ptr;
|
||||
extern uint8_t _gfx_font_loaded;
|
||||
|
||||
/* Ленивая инициализация: грузит системный шрифт при первом использовании. */
|
||||
void _gfx_font_ensure(void);
|
||||
|
||||
/* ---- Текст 16 цветов: общая машинерия ----------------------------- *
|
||||
* Данные в _gfx_t16_state.c, рендер ряда в _gfx_render_row16.c,
|
||||
* pair-таблица в _gfx_t16_pair_build.c. */
|
||||
|
||||
extern uint8_t _gfx_t16_y;
|
||||
extern uint8_t _gfx_t16_row; /* битовый паттерн ряда глифа */
|
||||
extern uint16_t _gfx_t16_addr; /* VRAM-адрес ряда */
|
||||
extern uint8_t _gfx_t16_pair[4]; /* LEFT*2+RIGHT → (LEFT<<4)|RIGHT */
|
||||
|
||||
/* Отрисовать один 8-пиксельный ряд по pair-таблице (4 записи байт). */
|
||||
void _gfx_render_row16(void);
|
||||
|
||||
/* Заполнить pair-таблицу для пары цветов fg/bg. */
|
||||
void _gfx_t16_pair_build(uint8_t fg, uint8_t bg);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* _gfx_acc256 — скретч акселератора для 256-цветных raw-примитивов
|
||||
* (модуль только с данными). Общий для hline/vline/clear; однопоточно —
|
||||
* GFX работает с выключенными прерываниями между W3-begin/end.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
uint8_t _gfx_acc_color;
|
||||
uint8_t _gfx_acc_y;
|
||||
uint8_t _gfx_acc_len; /* 0 = 256 — конвенция акселератора */
|
||||
uint16_t _gfx_acc_addr;
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* _gfx_clear16_raw — залить всё поле 640×256 цветом color, mode 0x82.
|
||||
* Построчный hfill: 256 строк × 2 burst'а (256 + 64 байта). W3-naive.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void _gfx_clear16_raw(uint8_t color)
|
||||
{
|
||||
_gfx_g16_byte = (uint8_t)((color & 0x0F) | ((color & 0x0F) << 4));
|
||||
for (int y = 0; y < GFX_HEIGHT_16; y++) {
|
||||
_gfx_g16_y = (uint8_t)y;
|
||||
_gfx_g16_addr = _gfx_addr_base;
|
||||
_gfx_g16_len = 0;
|
||||
_gfx_hfill16_chunk();
|
||||
_gfx_g16_addr = (uint16_t)(_gfx_addr_base + 0x100);
|
||||
_gfx_g16_len = 64;
|
||||
_gfx_hfill16_chunk();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* _gfx_clear256_raw — залить всё поле 320×256 цветом color, mode 0x81.
|
||||
* Построчный hfill: 256 строк × 2 burst'а (256 + 64 байта) = 512.
|
||||
* W3-naive: вызывающий обрамляет begin/end.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void _gfx_clear256_raw(uint8_t color)
|
||||
{
|
||||
_gfx_acc_color = color;
|
||||
for (int y = 0; y < GFX_HEIGHT; y++) {
|
||||
_gfx_acc_y = (uint8_t)y;
|
||||
_gfx_acc_addr = _gfx_addr_base; /* burst 256 байт */
|
||||
_gfx_acc_len = 0;
|
||||
_gfx_hfill256_chunk();
|
||||
_gfx_acc_addr = (uint16_t)(_gfx_addr_base + 256); /* burst 64 байта */
|
||||
_gfx_acc_len = 64;
|
||||
_gfx_hfill256_chunk();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* _gfx_font_ensure — ленивая инициализация шрифта: текстовые рендеры
|
||||
* зовут её при каждом входе, системный шрифт грузится при первом
|
||||
* использовании (чисто графическая программа BIOS-вызов не платит).
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void _gfx_font_ensure(void)
|
||||
{
|
||||
if (!_gfx_font_loaded) gfx_load_default_font();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* _gfx_font_state — буфер шрифта, указатель и флаг загрузки (данные).
|
||||
*
|
||||
* Формат (ZX-Spectrum-совместимый): 256 глифов × 8 рядов × 1 байт = 2 КБ,
|
||||
* INTERLEAVED — offset = row*256 + char (ряд 0 всех глифов занимает
|
||||
* 0x000..0x0FF и т.д.), биты MSB-first (бит 7 = левый пиксель).
|
||||
*
|
||||
* _gfx_font_ptr всегда указывает на валидные данные после
|
||||
* _gfx_font_ensure(); программы могут подменить шрифт gfx_set_font().
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
uint8_t _gfx_font_buf[_GFX_FONT_BYTES];
|
||||
const uint8_t *_gfx_font_ptr = _gfx_font_buf;
|
||||
uint8_t _gfx_font_loaded;
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* _gfx_g16_state — скретч 16-цветных raw-примитивов (только данные).
|
||||
* Общий для putpixel/hline/vline/clear; однопоточно, IRQ выключены
|
||||
* между W3-begin/end.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
uint8_t _gfx_g16_y;
|
||||
uint8_t _gfx_g16_byte; /* nibble | (nibble<<4) — цвет в обе половины */
|
||||
uint8_t _gfx_g16_nibble; /* цвет в нужной половине (low или high) */
|
||||
uint8_t _gfx_g16_mask; /* маска, сохраняющая ДРУГУЮ половину */
|
||||
uint8_t _gfx_g16_len; /* размер accel-блока (0 = 256) */
|
||||
uint16_t _gfx_g16_addr;
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* _gfx_hfill16_chunk — горизонтальный Fill-burst акселератора для
|
||||
* mode 0x82: _gfx_g16_len байт значения _gfx_g16_byte с _gfx_g16_addr
|
||||
* на строке _gfx_g16_y. Один burst красит до 256 байт = 512 пикселей.
|
||||
* Pre: W3 замаплен, DI активен.
|
||||
*
|
||||
* SMC только у байта длины. Цвет предзагружается в C и уходит через
|
||||
* `ld a, c` (0x79) — вставить ещё один `ld a,#n` между LD C,C и
|
||||
* стреляющим LD (HL),A нельзя: FSM акселератора перечитает immediate
|
||||
* как новый размер блока.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void _gfx_hfill16_chunk(void) __naked
|
||||
{
|
||||
__asm
|
||||
ld a, (__gfx_g16_len)
|
||||
ld (_hfill16_len_imm), a
|
||||
|
||||
ld a, (__gfx_g16_byte)
|
||||
ld c, a
|
||||
|
||||
ld a, (__gfx_g16_y)
|
||||
out (#0x89), a
|
||||
|
||||
ld hl, (__gfx_g16_addr)
|
||||
|
||||
ld d, d ; 0x52 — режим размера блока
|
||||
ld a, #0 ; 0x3E nn — длина (патчится)
|
||||
_hfill16_len_imm = . - 1
|
||||
ld c, c ; 0x49 — горизонтальный Fill
|
||||
ld a, c ; 0x79 — A = байт цвета
|
||||
ld (hl), a ; выстрел
|
||||
ld b, b ; 0x40 — выключить
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* _gfx_hfill256_chunk — горизонтальный Fill-burst акселератора:
|
||||
* _gfx_acc_len байт цвета _gfx_acc_color с адреса _gfx_acc_addr на
|
||||
* строке _gfx_acc_y. Pre: W3 замаплен на видеобанк, DI активен.
|
||||
*
|
||||
* Опкоды акселератора (docs/converted/accel_r.txt):
|
||||
* LD D,D (0x52) режим «задать размер блока»; СЛЕДУЮЩИЙ байт — длина,
|
||||
* ОБЯЗАН быть immediate-операндом LD A,n (0x3E)
|
||||
* LD C,C (0x49) горизонтальный Fill (LD (HL),A заливает n байт)
|
||||
* LD B,B (0x40) выключить акселератор
|
||||
*
|
||||
* Байт длины патчится SMC — HOME после загрузки DSS это RAM, патчить
|
||||
* собственный .EXE безопасно. Вставлять другой `ld a,#n` между LD C,C
|
||||
* и стреляющим LD (HL),A нельзя — FSM акселератора перечитает immediate
|
||||
* как новый размер блока.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void _gfx_hfill256_chunk(void) __naked
|
||||
{
|
||||
__asm
|
||||
;; Пропатчить immediate LD A,#n байтом длины.
|
||||
ld a, (__gfx_acc_len)
|
||||
ld (_hfill256_len_imm), a
|
||||
|
||||
ld a, (__gfx_acc_y)
|
||||
out (#0x89), a ; Port_Y = y
|
||||
|
||||
;; Цвет в C и адрес в HL готовим ДО взведения акселератора.
|
||||
ld a, (__gfx_acc_color)
|
||||
ld c, a
|
||||
ld hl, (__gfx_acc_addr)
|
||||
|
||||
;; --- ПОСЛЕДОВАТЕЛЬНОСТЬ АКСЕЛЕРАТОРА ---
|
||||
ld d, d ; 0x52 — режим размера блока
|
||||
ld a, #0 ; 0x3E nn — размер (nn патчится выше)
|
||||
_hfill256_len_imm = . - 1
|
||||
ld c, c ; 0x49 — горизонтальный Fill
|
||||
ld a, c ; 0x79 — A = цвет (НЕ ещё один ld a,#n)
|
||||
ld (hl), a ; выстрел; заливает _gfx_acc_len байт
|
||||
ld b, b ; 0x40 — выключить
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* _gfx_hline16_raw — горизонтальная линия len пикселей от (x,y),
|
||||
* mode 0x82: невыровненные крайние ниблы через RMW, середина —
|
||||
* Fill-burst'ами акселератора по целым байтам. W3-naive.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void _gfx_hline16_raw(int x, int y, int len, uint8_t color)
|
||||
{
|
||||
if ((unsigned)y >= GFX_HEIGHT_16) return;
|
||||
if (x < 0) { len += x; x = 0; }
|
||||
if (x >= GFX_WIDTH_16) return;
|
||||
if (x + len > GFX_WIDTH_16) len = GFX_WIDTH_16 - x;
|
||||
if (len <= 0) return;
|
||||
|
||||
_gfx_g16_y = (uint8_t)y;
|
||||
uint8_t cnib = color & 0x0F;
|
||||
_gfx_g16_byte = (uint8_t)(cnib | (cnib << 4));
|
||||
|
||||
/* Ведущий невыровненный пиксель: x нечётный — ПРАВАЯ половина
|
||||
* крайнего левого байта. */
|
||||
if (x & 1) {
|
||||
_gfx_g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1));
|
||||
_gfx_g16_nibble = cnib;
|
||||
_gfx_g16_mask = 0xF0;
|
||||
_gfx_rmw16_pixel();
|
||||
x++;
|
||||
len--;
|
||||
if (len <= 0) return;
|
||||
}
|
||||
|
||||
/* x чётный; len/2 целых байтов через accel hfill. */
|
||||
int full = len >> 1;
|
||||
_gfx_g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1));
|
||||
while (full > 0) {
|
||||
int chunk = full > 256 ? 256 : full;
|
||||
_gfx_g16_len = (chunk == 256) ? 0 : (uint8_t)chunk;
|
||||
_gfx_hfill16_chunk();
|
||||
full -= chunk;
|
||||
_gfx_g16_addr += chunk;
|
||||
}
|
||||
|
||||
/* Хвостовой пиксель нечётной длины: ЛЕВАЯ половина след. байта. */
|
||||
if (len & 1) {
|
||||
_gfx_g16_nibble = (uint8_t)(cnib << 4);
|
||||
_gfx_g16_mask = 0x0F;
|
||||
_gfx_rmw16_pixel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* _gfx_hline256_raw — горизонтальная линия len пикселей от (x,y),
|
||||
* mode 0x81, через Fill-burst'ы акселератора (до 256 байт за выстрел).
|
||||
* W3-naive: вызывающий обрамляет begin/end.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void _gfx_hline256_raw(int x, int y, int len, uint8_t color)
|
||||
{
|
||||
if ((unsigned)y >= GFX_HEIGHT) return;
|
||||
if (x < 0) { len += x; x = 0; }
|
||||
if (x >= GFX_WIDTH) return;
|
||||
if (x + len > GFX_WIDTH) len = GFX_WIDTH - x;
|
||||
if (len <= 0) return;
|
||||
|
||||
_gfx_acc_color = color;
|
||||
_gfx_acc_y = (uint8_t)y;
|
||||
_gfx_acc_addr = (uint16_t)(_gfx_addr_base + (unsigned)x);
|
||||
|
||||
while (len > 0) {
|
||||
int chunk = len > 256 ? 256 : len;
|
||||
_gfx_acc_len = (chunk == 256) ? 0 : (uint8_t)chunk;
|
||||
_gfx_hfill256_chunk();
|
||||
len -= chunk;
|
||||
_gfx_acc_addr += chunk;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* _gfx_putpixel16_raw — пиксель (x,y) цветом 0..15, mode 0x82.
|
||||
* Байт по адресу _gfx_addr_base + x/2 держит два пикселя: high nibble =
|
||||
* ЛЕВЫЙ (чётный x), low = ПРАВЫЙ (нечётный). W3-naive.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void _gfx_putpixel16_raw(int x, int y, uint8_t color)
|
||||
{
|
||||
if ((unsigned)x >= GFX_WIDTH_16 || (unsigned)y >= GFX_HEIGHT_16) return;
|
||||
_gfx_g16_y = (uint8_t)y;
|
||||
_gfx_g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1));
|
||||
if (x & 1) {
|
||||
_gfx_g16_nibble = (uint8_t)(color & 0x0F);
|
||||
_gfx_g16_mask = 0xF0;
|
||||
} else {
|
||||
_gfx_g16_nibble = (uint8_t)((color & 0x0F) << 4);
|
||||
_gfx_g16_mask = 0x0F;
|
||||
}
|
||||
_gfx_rmw16_pixel();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* _gfx_putpixel256_raw — пиксель (x,y) цветом color, mode 0x81.
|
||||
*
|
||||
* "Raw" = W3-naive: вызывающий уже сделал _gfx_w3_video_begin (W3 на
|
||||
* видеобанке, DI). Адресация: CPU _gfx_addr_base + x, Port_Y (0x89) = y.
|
||||
*
|
||||
* Скретч отдельный от _gfx_acc_* — чтобы putpixel внутри цикла
|
||||
* Брезенхэма не топтал внешнее состояние акселератора.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
static uint8_t _gfx_pp_y;
|
||||
static uint16_t _gfx_pp_addr;
|
||||
static uint8_t _gfx_pp_color;
|
||||
|
||||
void _gfx_putpixel256_raw(int x, int y, uint8_t color)
|
||||
{
|
||||
if ((unsigned)x >= GFX_WIDTH || (unsigned)y >= GFX_HEIGHT) return;
|
||||
_gfx_pp_y = (uint8_t)y;
|
||||
_gfx_pp_addr = (uint16_t)(_gfx_addr_base + (unsigned)x);
|
||||
_gfx_pp_color = color;
|
||||
__asm
|
||||
ld a, (__gfx_pp_y)
|
||||
out (#0x89), a
|
||||
ld hl, (__gfx_pp_addr)
|
||||
ld a, (__gfx_pp_color)
|
||||
ld (hl), a
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* _gfx_render_row16 — один 8-пиксельный ряд глифа в mode 0x82
|
||||
* (640×256×16): 4 выходных байта, каждый пакует два соседних пикселя
|
||||
* как (LEFT<<4) | RIGHT через прекомпилированную _gfx_t16_pair —
|
||||
* 2-битный паттерн источника (LEFT*2 + RIGHT) сразу даёт выходной
|
||||
* байт, без mask/OR на пиксель. Сам делает DI / W3 save+restore —
|
||||
* вызывать можно из любого контекста без скобок.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void _gfx_render_row16(void) __naked
|
||||
{
|
||||
__asm
|
||||
di
|
||||
in a, (#0xE2)
|
||||
push af
|
||||
ld a, #0x50
|
||||
out (#0xE2), a
|
||||
ld a, (__gfx_t16_y)
|
||||
out (#0x89), a
|
||||
|
||||
ld hl, (__gfx_t16_addr)
|
||||
ld a, (__gfx_t16_row)
|
||||
ld c, a ; C = сдвигаемые биты источника
|
||||
ld b, #4 ; 4 выходных байта
|
||||
rr16_loop:
|
||||
;; Два верхних бита C в индекс A = LEFT*2 + RIGHT.
|
||||
sla c ; CY = бит LEFT; C <<= 1
|
||||
ld a, #0
|
||||
adc a, a ; A = LEFT
|
||||
sla c ; CY = бит RIGHT; C <<= 1
|
||||
adc a, a ; A = LEFT*2 + RIGHT (0..3)
|
||||
|
||||
;; A = _gfx_t16_pair[A]; HL сохраняем через стек.
|
||||
push hl
|
||||
ld e, a
|
||||
ld d, #0
|
||||
ld hl, #__gfx_t16_pair
|
||||
add hl, de
|
||||
ld a, (hl)
|
||||
pop hl
|
||||
|
||||
ld (hl), a
|
||||
inc hl
|
||||
djnz rr16_loop
|
||||
|
||||
pop af
|
||||
out (#0xE2), a
|
||||
ei
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* _gfx_rmw16_pixel — RMW одного нибла по (_gfx_g16_addr, _gfx_g16_y):
|
||||
* прочитать байт, стереть целевой нибл маской _gfx_g16_mask, вписать
|
||||
* _gfx_g16_nibble. ~10 инструкций на пиксель — медленно, но байт
|
||||
* покрывает два горизонтальных пикселя, иначе никак.
|
||||
* Pre: W3 замаплен, DI активен.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void _gfx_rmw16_pixel(void) __naked
|
||||
{
|
||||
__asm
|
||||
ld a, (__gfx_g16_y)
|
||||
out (#0x89), a
|
||||
|
||||
ld hl, (__gfx_g16_addr)
|
||||
ld a, (__gfx_g16_mask)
|
||||
ld b, a ; B = маска сохранения
|
||||
ld a, (__gfx_g16_nibble)
|
||||
ld c, a ; C = новый нибл (в нужной половине)
|
||||
ld a, (hl)
|
||||
and a, b ; стереть целевой нибл
|
||||
or a, c ; вписать новый
|
||||
ld (hl), a
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* _gfx_state — общее состояние графики (модуль только с данными).
|
||||
*
|
||||
* Пишут gfx_init / gfx_set_visible_page / gfx_set_draw_page /
|
||||
* gfx_set_bank; читают все mode-специфичные примитивы.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
uint8_t _gfx_visible_page;
|
||||
uint8_t _gfx_draw_page;
|
||||
|
||||
/* См. memory/sprinter_vram_transparency.md про биты 0x50..0x5F
|
||||
* (0x50 normal, 0x54 temp, 0x58 transparent, 0x5C оба). */
|
||||
uint8_t _gfx_bank = 0x50;
|
||||
|
||||
/* Каждая VRAM-строка — 1024 байта: page 0 занимает байты 0..319
|
||||
* (CPU 0xC000+), page 1 — 320..639 (CPU 0xC140+), остаток — дескрипторы
|
||||
* режима/палитра, их не трогаем. */
|
||||
uint16_t _gfx_addr_base = 0xC000;
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* _gfx_t16_pair_build — заполнить pair-таблицу текстового рендера
|
||||
* mode 0x82 для пары цветов fg/bg.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void _gfx_t16_pair_build(uint8_t fg, uint8_t bg)
|
||||
{
|
||||
uint8_t f = fg & 0x0F;
|
||||
uint8_t b = bg & 0x0F;
|
||||
/* HIGH nibble = LEFT, LOW = RIGHT (конвенция sprinter_graphics). */
|
||||
_gfx_t16_pair[0] = (uint8_t)((b << 4) | b); /* 00 BG BG */
|
||||
_gfx_t16_pair[1] = (uint8_t)((b << 4) | f); /* 01 BG FG */
|
||||
_gfx_t16_pair[2] = (uint8_t)((f << 4) | b); /* 10 FG BG */
|
||||
_gfx_t16_pair[3] = (uint8_t)((f << 4) | f); /* 11 FG FG */
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* _gfx_t16_state — скретч текстового рендера mode 0x82 (только данные).
|
||||
* Пишут gfx_putchar16/gfx_text16 и _gfx_t16_pair_build, читает
|
||||
* _gfx_render_row16.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
uint8_t _gfx_t16_y;
|
||||
uint8_t _gfx_t16_row;
|
||||
uint16_t _gfx_t16_addr;
|
||||
|
||||
/* index = LEFT*2 + RIGHT, value = (LEFT<<4)|RIGHT. */
|
||||
uint8_t _gfx_t16_pair[4];
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* _gfx_vline16_raw — вертикальная линия len пикселей от (x,y),
|
||||
* mode 0x82. Вертикального accel-пути нет: байт покрывает два
|
||||
* горизонтальных пикселя, Fill затёр бы соседний нибл — поэтому
|
||||
* RMW на каждую строку. W3-naive.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void _gfx_vline16_raw(int x, int y, int len, uint8_t color)
|
||||
{
|
||||
if ((unsigned)x >= GFX_WIDTH_16) return;
|
||||
if (y < 0) { len += y; y = 0; }
|
||||
if (y >= GFX_HEIGHT_16) return;
|
||||
if (y + len > GFX_HEIGHT_16) len = GFX_HEIGHT_16 - y;
|
||||
if (len <= 0) return;
|
||||
|
||||
_gfx_g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1));
|
||||
if (x & 1) {
|
||||
_gfx_g16_nibble = (uint8_t)(color & 0x0F);
|
||||
_gfx_g16_mask = 0xF0;
|
||||
} else {
|
||||
_gfx_g16_nibble = (uint8_t)((color & 0x0F) << 4);
|
||||
_gfx_g16_mask = 0x0F;
|
||||
}
|
||||
for (int i = 0; i < len; i++) {
|
||||
_gfx_g16_y = (uint8_t)(y + i);
|
||||
_gfx_rmw16_pixel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* _gfx_vline256_raw — вертикальная линия len пикселей от (x,y),
|
||||
* mode 0x81. Вертикальный Fill акселератора сам инкрементирует Port_Y,
|
||||
* так что вся колонка (высота 256) — один burst. W3-naive.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
/* Вертикальный Fill: _gfx_acc_len пикселей в колонке _gfx_acc_addr,
|
||||
* верхняя строка _gfx_acc_y. Про SMC и правила FSM акселератора —
|
||||
* см. _gfx_hfill256.c. */
|
||||
static void vfill_chunk(void) __naked
|
||||
{
|
||||
__asm
|
||||
ld a, (__gfx_acc_len)
|
||||
ld (_vfill256_len_imm), a
|
||||
|
||||
ld a, (__gfx_acc_y)
|
||||
out (#0x89), a ; стартовый Y
|
||||
|
||||
ld a, (__gfx_acc_color)
|
||||
ld c, a
|
||||
ld hl, (__gfx_acc_addr)
|
||||
|
||||
ld d, d ; 0x52 — режим размера блока
|
||||
ld a, #0 ; immediate-длина (патчится)
|
||||
_vfill256_len_imm = . - 1
|
||||
ld e, e ; 0x5B — вертикальный Fill
|
||||
ld a, c ; A = цвет
|
||||
ld (hl), a ; выстрел
|
||||
ld b, b ; 0x40 — выключить
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
void _gfx_vline256_raw(int x, int y, int len, uint8_t color)
|
||||
{
|
||||
if ((unsigned)x >= GFX_WIDTH) return;
|
||||
if (y < 0) { len += y; y = 0; }
|
||||
if (y >= GFX_HEIGHT) return;
|
||||
if (y + len > GFX_HEIGHT) len = GFX_HEIGHT - y;
|
||||
if (len <= 0) return;
|
||||
|
||||
/* GFX_HEIGHT = 256, полная колонка — один accel-burst. */
|
||||
_gfx_acc_color = color;
|
||||
_gfx_acc_y = (uint8_t)y;
|
||||
_gfx_acc_addr = (uint16_t)(_gfx_addr_base + (unsigned)x);
|
||||
_gfx_acc_len = (len == 256) ? 0 : (uint8_t)len;
|
||||
vfill_chunk();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* _gfx_w3_state — сохранённый байт W3 между begin и end (данные).
|
||||
*
|
||||
* НЕ реентерабельно — безопасно только потому, что GFX держит
|
||||
* прерывания выключенными между _gfx_w3_video_begin и _end.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
uint8_t _gfx_saved_w3;
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* _gfx_w3_video_begin — DI, сохранить текущую страницу W3 и замапить
|
||||
* _gfx_bank (видеобанк 0x50..0x5F). Композитные примитивы обрамляют
|
||||
* одной парой begin/end весь внутренний цикл — W3-танец оплачивается
|
||||
* один раз на операцию, а не на пиксель.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void _gfx_w3_video_begin(void) __naked
|
||||
{
|
||||
__asm
|
||||
di
|
||||
in a, (#0xE2)
|
||||
ld (__gfx_saved_w3), a
|
||||
ld a, (__gfx_bank)
|
||||
out (#0xE2), a
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* _gfx_w3_video_end — восстановить сохранённую страницу W3 и EI.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void _gfx_w3_video_end(void) __naked
|
||||
{
|
||||
__asm
|
||||
ld a, (__gfx_saved_w3)
|
||||
out (#0xE2), a
|
||||
ei
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* gfx_16.c — 16-colour (mode 0x82) public drawing API.
|
||||
*
|
||||
* Raw primitives live in gfx_raw_16.c. Single-shot wrappers do one
|
||||
* W3-begin / one W3-end around their raw call; composites wrap many
|
||||
* raw calls in a single begin/end pair so the W3 dance is amortised.
|
||||
*
|
||||
* No vertical-accelerator path: in 16-colour mode each byte spans two
|
||||
* horizontal pixels, so a vertical Fill burst would also affect the
|
||||
* other column's nibble — vline16 falls back to per-row RMW.
|
||||
*/
|
||||
|
||||
#include <gfx.h>
|
||||
#include <stdint.h>
|
||||
|
||||
extern void _gfx_w3_video_begin(void);
|
||||
extern void _gfx_w3_video_end(void);
|
||||
|
||||
extern void _gfx_putpixel16_raw(int x, int y, uint8_t color);
|
||||
extern void _gfx_hline16_raw (int x, int y, int len, uint8_t color);
|
||||
extern void _gfx_vline16_raw (int x, int y, int len, uint8_t color);
|
||||
extern void _gfx_clear16_raw (uint8_t color);
|
||||
|
||||
void gfx_clear16(uint8_t color)
|
||||
{
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_clear16_raw(color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_putpixel16(int x, int y, uint8_t color)
|
||||
{
|
||||
if ((unsigned)x >= GFX_WIDTH_16 || (unsigned)y >= GFX_HEIGHT_16) return;
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_putpixel16_raw(x, y, color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_hline16(int x, int y, int len, uint8_t color)
|
||||
{
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_hline16_raw(x, y, len, color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_vline16(int x, int y, int len, uint8_t color)
|
||||
{
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_vline16_raw(x, y, len, color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_rect16(int x, int y, int w, int h, uint8_t color)
|
||||
{
|
||||
if (w <= 0 || h <= 0) return;
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_hline16_raw(x, y, w, color);
|
||||
_gfx_hline16_raw(x, y + h - 1, w, color);
|
||||
if (h > 2) {
|
||||
_gfx_vline16_raw(x, y + 1, h - 2, color);
|
||||
_gfx_vline16_raw(x + w - 1, y + 1, h - 2, color);
|
||||
}
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_fill_rect16(int x, int y, int w, int h, uint8_t color)
|
||||
{
|
||||
if (w <= 0 || h <= 0) return;
|
||||
/* Column-major vlines — user's earlier request: 16-color rect_fill
|
||||
* via vertical lines. Each column does its own per-row RMW; the
|
||||
* outer begin/end wraps the whole rect so W3 is mapped once. */
|
||||
_gfx_w3_video_begin();
|
||||
for (int xx = 0; xx < w; xx++)
|
||||
_gfx_vline16_raw(x + xx, y, h, color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_line16(int x0, int y0, int x1, int y1, uint8_t color)
|
||||
{
|
||||
if (y0 == y1) {
|
||||
int x = x0 <= x1 ? x0 : x1;
|
||||
int w = (x0 <= x1 ? x1 - x0 : x0 - x1) + 1;
|
||||
gfx_hline16(x, y0, w, color);
|
||||
return;
|
||||
}
|
||||
if (x0 == x1) {
|
||||
int y = y0 <= y1 ? y0 : y1;
|
||||
int h = (y0 <= y1 ? y1 - y0 : y0 - y1) + 1;
|
||||
gfx_vline16(x0, y, h, color);
|
||||
return;
|
||||
}
|
||||
/* Bresenham — single W3 setup around the whole loop. */
|
||||
int dx = x1 - x0; int sx = dx < 0 ? -1 : 1; if (dx < 0) dx = -dx;
|
||||
int dy = y1 - y0; int sy = dy < 0 ? -1 : 1; if (dy < 0) dy = -dy;
|
||||
int err = (dx > dy ? dx : -dy) / 2;
|
||||
int x = x0, y = y0;
|
||||
_gfx_w3_video_begin();
|
||||
for (;;) {
|
||||
_gfx_putpixel16_raw(x, y, color);
|
||||
if (x == x1 && y == y1) break;
|
||||
int e2 = err;
|
||||
if (e2 > -dx) { err -= dy; x += sx; }
|
||||
if (e2 < dy) { err += dx; y += sy; }
|
||||
}
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* gfx_256.c — 256-colour (mode 0x81) public drawing API.
|
||||
*
|
||||
* Single-shot wrappers do one W3-begin / one W3-end around their raw
|
||||
* call. Composites (rect, fill_rect, line) wrap a single begin/end
|
||||
* around many raw calls so the W3 dance is paid once per operation,
|
||||
* not once per pixel/byte.
|
||||
*/
|
||||
|
||||
#include <gfx.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* From gfx_raw_common.c — DI + map _gfx_bank into W3, save previous. */
|
||||
extern void _gfx_w3_video_begin(void);
|
||||
extern void _gfx_w3_video_end(void);
|
||||
|
||||
/* From gfx_raw_256.c — W3-naive primitives. */
|
||||
extern void _gfx_putpixel256_raw(int x, int y, uint8_t color);
|
||||
extern void _gfx_hline256_raw (int x, int y, int len, uint8_t color);
|
||||
extern void _gfx_vline256_raw (int x, int y, int len, uint8_t color);
|
||||
extern void _gfx_clear256_raw (uint8_t color);
|
||||
|
||||
void gfx_clear256(uint8_t color)
|
||||
{
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_clear256_raw(color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_putpixel256(int x, int y, uint8_t color)
|
||||
{
|
||||
if ((unsigned)x >= GFX_WIDTH || (unsigned)y >= GFX_HEIGHT) return;
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_putpixel256_raw(x, y, color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_hline256(int x, int y, int len, uint8_t color)
|
||||
{
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_hline256_raw(x, y, len, color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_vline256(int x, int y, int len, uint8_t color)
|
||||
{
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_vline256_raw(x, y, len, color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_rect256(int x, int y, int w, int h, uint8_t color)
|
||||
{
|
||||
if (w <= 0 || h <= 0) return;
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_hline256_raw(x, y, w, color);
|
||||
_gfx_hline256_raw(x, y + h - 1, w, color);
|
||||
if (h > 2) {
|
||||
_gfx_vline256_raw(x, y + 1, h - 2, color);
|
||||
_gfx_vline256_raw(x + w - 1, y + 1, h - 2, color);
|
||||
}
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_fill_rect256(int x, int y, int w, int h, uint8_t color)
|
||||
{
|
||||
if (w <= 0 || h <= 0) return;
|
||||
|
||||
/* Pick the orientation with fewer accelerator bursts. Each burst
|
||||
* paints up to 256 contiguous bytes (horizontal) or up to 256
|
||||
* vertical pixels in one column.
|
||||
* row-major (hlines): h × ceil(w/256) bursts
|
||||
* col-major (vlines): w × ceil(h/256) bursts — for h ≤ 256 = w
|
||||
* Vertical wins for tall-narrow rects; horizontal for short-wide. */
|
||||
int h_bursts = h * ((w + 255) >> 8);
|
||||
int v_bursts = w * ((h + 255) >> 8);
|
||||
_gfx_w3_video_begin();
|
||||
if (h_bursts <= v_bursts) {
|
||||
for (int yy = 0; yy < h; yy++)
|
||||
_gfx_hline256_raw(x, y + yy, w, color);
|
||||
} else {
|
||||
for (int xx = 0; xx < w; xx++)
|
||||
_gfx_vline256_raw(x + xx, y, h, color);
|
||||
}
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
|
||||
void gfx_line256(int x0, int y0, int x1, int y1, uint8_t color)
|
||||
{
|
||||
/* Orthogonal lines route to the accelerator. */
|
||||
if (y0 == y1) {
|
||||
int x = x0 <= x1 ? x0 : x1;
|
||||
int w = (x0 <= x1 ? x1 - x0 : x0 - x1) + 1;
|
||||
gfx_hline256(x, y0, w, color);
|
||||
return;
|
||||
}
|
||||
if (x0 == x1) {
|
||||
int y = y0 <= y1 ? y0 : y1;
|
||||
int h = (y0 <= y1 ? y1 - y0 : y0 - y1) + 1;
|
||||
gfx_vline256(x0, y, h, color);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Bresenham — single W3-setup around the whole loop. */
|
||||
int dx = x1 - x0; int sx = dx < 0 ? -1 : 1; if (dx < 0) dx = -dx;
|
||||
int dy = y1 - y0; int sy = dy < 0 ? -1 : 1; if (dy < 0) dy = -dy;
|
||||
int err = (dx > dy ? dx : -dy) / 2;
|
||||
int x = x0, y = y0;
|
||||
_gfx_w3_video_begin();
|
||||
for (;;) {
|
||||
_gfx_putpixel256_raw(x, y, color);
|
||||
if (x == x1 && y == y1) break;
|
||||
int e2 = err;
|
||||
if (e2 > -dx) { err -= dy; x += sx; }
|
||||
if (e2 < dy) { err += dx; y += sy; }
|
||||
}
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* gfx_clear16 — залить экран цветом (mode 0x82); одна W3-скобка.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void gfx_clear16(uint8_t color)
|
||||
{
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_clear16_raw(color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* gfx_clear256 — залить экран цветом (mode 0x81); одна W3-скобка.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void gfx_clear256(uint8_t color)
|
||||
{
|
||||
_gfx_w3_video_begin();
|
||||
_gfx_clear256_raw(color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* gfx_core.c — Sprinter graphics: common state + setup/teardown API.
|
||||
*
|
||||
* This module owns the variables that select which graphics page is
|
||||
* visible vs drawn into, which W3 bank is mapped during gfx writes,
|
||||
* and the row-base CPU address. All mode-specific primitives (256-
|
||||
* color in gfx_raw_256.c / gfx_256.c, 16-color in gfx_raw_16.c /
|
||||
* gfx_16.c, text in gfx_text_*.c) read this state via extern.
|
||||
*
|
||||
* Public API:
|
||||
* gfx_init / gfx_done — switch video mode, restore previous
|
||||
* gfx_set_visible_page / get — ESTEX $54 SELPAGE wrapper + cached value
|
||||
* gfx_set_draw_page / get — updates _gfx_addr_base for the new page
|
||||
* gfx_set_bank / get — sets the W3 page byte (0x50..0x5F)
|
||||
* gfx_wait_vsync — EI; HALT until next frame interrupt
|
||||
* (palette wrappers live in gfx_palette.c, backed by libc/video/palette.c)
|
||||
*
|
||||
* Shared state (extern from this file):
|
||||
* _gfx_addr_base — 0xC000 for page 0, 0xC140 for page 1. Every
|
||||
* mode-specific primitive uses this instead of a
|
||||
* hard-coded 0xC000 so the same code targets the
|
||||
* currently-selected draw page.
|
||||
* _gfx_bank — read by _gfx_w3_video_begin in gfx_raw_common.c
|
||||
* to map the right W3 page.
|
||||
*/
|
||||
|
||||
#include <gfx.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* From conio's videomode_raw.c — bypasses set_videotextmode's text-only
|
||||
* validation so gfx_init can move INTO graphics modes. */
|
||||
extern uint8_t _videomode_raw_get(void);
|
||||
extern int _videomode_raw_set(uint8_t mode);
|
||||
|
||||
/* ---- Shared graphics state --------------------------------------- */
|
||||
|
||||
/* Cached values of the SELPAGE state. Page numbers are 0 or 1. */
|
||||
static uint8_t _gfx_visible_page = 0;
|
||||
static uint8_t _gfx_draw_page = 0;
|
||||
|
||||
/* The W3 page byte (0x50..0x5F) — see memory/sprinter_vram_transparency.md
|
||||
* for the bit-meanings (0x50 normal, 0x54 temp, 0x58 transparent, 0x5C
|
||||
* both). Read by _gfx_w3_video_begin in gfx_raw_common.c. */
|
||||
uint8_t _gfx_bank = 0x50;
|
||||
|
||||
/* CPU address of column 0 in the current draw page. Each VRAM row is
|
||||
* 1024 bytes wide — page 0 occupies bytes 0..319 (CPU 0xC000+), page 1
|
||||
* occupies 320..639 (CPU 0xC140+), and the remaining 384 bytes hold
|
||||
* mode descriptors / palette data we don't touch. Updated by
|
||||
* gfx_set_draw_page; read by every primitive's raw helper. */
|
||||
uint16_t _gfx_addr_base = 0xC000;
|
||||
|
||||
/* ---- gfx_init / gfx_done ----------------------------------------- */
|
||||
|
||||
uint8_t gfx_init(uint8_t mode, uint8_t page)
|
||||
{
|
||||
uint8_t prev = _videomode_raw_get();
|
||||
_videomode_raw_set(mode);
|
||||
_gfx_bank = 0x50;
|
||||
gfx_set_visible_page(page);
|
||||
gfx_set_draw_page(page);
|
||||
return prev;
|
||||
}
|
||||
|
||||
void gfx_done(uint8_t mode)
|
||||
{
|
||||
_videomode_raw_set(mode);
|
||||
}
|
||||
|
||||
/* ---- Visible page (ESTEX $54 SELPAGE) ---------------------------- *
|
||||
*
|
||||
* Direct OUT to port 0xC9 bit 0 toggles the "screen mode page" register
|
||||
* but doesn't notify DSS's display bookkeeping, leaving the screen in
|
||||
* an inconsistent state (one of the swaps shows as black). Going
|
||||
* through the syscall keeps DSS happy.
|
||||
*/
|
||||
void gfx_set_visible_page(uint8_t page)
|
||||
{
|
||||
_gfx_visible_page = page & 1;
|
||||
__asm
|
||||
push ix
|
||||
ld a, (__gfx_visible_page)
|
||||
ld b, a ; B = page 0/1
|
||||
ld c, #0x54 ; ESTEX SELPAGE
|
||||
rst #0x10
|
||||
pop ix
|
||||
__endasm;
|
||||
}
|
||||
|
||||
uint8_t gfx_get_visible_page(void)
|
||||
{
|
||||
return _gfx_visible_page;
|
||||
}
|
||||
|
||||
/* ---- Draw page --------------------------------------------------- */
|
||||
|
||||
void gfx_set_draw_page(uint8_t page)
|
||||
{
|
||||
_gfx_draw_page = page & 1;
|
||||
/* Direct constants beat (0xC000 + (cond ? 0x140 : 0)) by 3 Z80
|
||||
* instructions — SDCC doesn't fold the constant addition. */
|
||||
_gfx_addr_base = _gfx_draw_page ? 0xC140 : 0xC000;
|
||||
}
|
||||
|
||||
uint8_t gfx_get_draw_page(void)
|
||||
{
|
||||
return _gfx_draw_page;
|
||||
}
|
||||
|
||||
/* ---- W3 bank (0x50..0x5F) ---------------------------------------- */
|
||||
|
||||
void gfx_set_bank(uint8_t bank)
|
||||
{
|
||||
_gfx_bank = bank;
|
||||
}
|
||||
|
||||
uint8_t gfx_get_bank(void)
|
||||
{
|
||||
return _gfx_bank;
|
||||
}
|
||||
|
||||
/* ---- Frame sync -------------------------------------------------- *
|
||||
*
|
||||
* Block until the next IM2 frame interrupt (50 Hz, programmed by DSS
|
||||
* for keyboard / cursor maintenance). The Z80's HALT instruction
|
||||
* sleeps the CPU until the next IRQ, which DSS handles and returns
|
||||
* to the instruction after HALT — that's the start of the vertical
|
||||
* retrace window, ideal for a tear-free page swap.
|
||||
*/
|
||||
void gfx_wait_vsync(void) __naked
|
||||
{
|
||||
__asm
|
||||
ei
|
||||
halt
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
/* Palette wrappers (gfx_pal_load / gfx_pal_set / gfx_pal_get /
|
||||
* gfx_pal_get_color / gfx_pal_reset) moved to gfx_palette.c — see also
|
||||
* libc/video/palette.c for the shared low-level $A4 / $A6 implementation. */
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* gfx_done — восстановить ранее сохранённый видеорежим (из gfx_init).
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void gfx_done(uint8_t mode)
|
||||
{
|
||||
_videomode_raw_set(mode);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* gfx_fill_rect16 — залитый прямоугольник w×h от (x,y) (mode 0x82).
|
||||
* Поколоночные vlines (per-row RMW в каждой колонке); внешняя
|
||||
* begin/end-скобка мапит W3 один раз на весь прямоугольник.
|
||||
*/
|
||||
|
||||
#include "_gfx.h"
|
||||
|
||||
void gfx_fill_rect16(int x, int y, int w, int h, uint8_t color)
|
||||
{
|
||||
if (w <= 0 || h <= 0) return;
|
||||
_gfx_w3_video_begin();
|
||||
for (int xx = 0; xx < w; xx++)
|
||||
_gfx_vline16_raw(x + xx, y, h, color);
|
||||
_gfx_w3_video_end();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user