Подготовить автономную сборку и запуск перед разделением проектов
This commit is contained in:
@@ -4,12 +4,7 @@
|
|||||||
# make tools build only host tools (mkexe)
|
# make tools build only host tools (mkexe)
|
||||||
# make lib build lib/sprinter.lib (libc) + lib/bgi256.lib (libbgi)
|
# make lib build lib/sprinter.lib (libc) + lib/bgi256.lib (libbgi)
|
||||||
# make tests build all libc feature tests under tests/
|
# make tests build all libc feature tests under tests/
|
||||||
# make examples build all real applications under examples/ (НЕ входит
|
# make floppy пакет всех SDK-тестов в build/media/toolkit-tests.img
|
||||||
# в `make all`: это регрессная сборка, а examples/ —
|
|
||||||
# крупные приложения, которые её только замедляют
|
|
||||||
# (mdview компилируется минутами) и ничего нового про
|
|
||||||
# libc не показывают. Собирать явно перед `make floppy`.)
|
|
||||||
# make floppy package every .exe + test fixtures into mame/v306/IMG/mc.img
|
|
||||||
# make check run mkexe unit tests
|
# make check run mkexe unit tests
|
||||||
# make clean remove all build artefacts
|
# make clean remove all build artefacts
|
||||||
# make sdcc download/extract vendored SDCC
|
# make sdcc download/extract vendored SDCC
|
||||||
@@ -25,25 +20,20 @@ TESTS := hello hello2 simple banked bankedbg banktest strtest cat seek \
|
|||||||
gfx_demo gfx_dbuf bgitest bgi_img accfill
|
gfx_demo gfx_dbuf bgitest bgi_img accfill
|
||||||
# gfx_d16 / gfx_text / gfx_mous — 16-цветные; убраны до Фазы 2 (bgi16.lib
|
# gfx_d16 / gfx_text / gfx_mous — 16-цветные; убраны до Фазы 2 (bgi16.lib
|
||||||
# ещё не собирается). Вернуть мигрированными на BGI --gfx 16.
|
# ещё не собирается). Вернуть мигрированными на BGI --gfx 16.
|
||||||
# Larger end-user applications under examples/.
|
FLOPPY_IMG := build/media/toolkit-tests.img
|
||||||
APPS := mdview mdview2
|
MAKE_DISK := toolchain/make_disk.py
|
||||||
|
MAME_HOME ?= $(if $(wildcard mame/v306/mame.arm),$(CURDIR)/mame/v306,)
|
||||||
MAME_DIR := mame/v306
|
|
||||||
FLOPPY_IMG := $(MAME_DIR)/IMG/mc.img
|
|
||||||
MAKE_DISK := $(MAME_DIR)/make_disk.py
|
|
||||||
|
|
||||||
TEST_EXES := $(foreach t,$(TESTS),tests/$(t)/$(t).exe)
|
TEST_EXES := $(foreach t,$(TESTS),tests/$(t)/$(t).exe)
|
||||||
APP_EXES := $(foreach a,$(APPS),examples/$(a)/$(a).exe)
|
ALL_EXES := $(TEST_EXES)
|
||||||
ALL_EXES := $(TEST_EXES) $(APP_EXES)
|
|
||||||
|
|
||||||
DATA_FILES := \
|
DATA_FILES := \
|
||||||
tests/cat/test.txt \
|
tests/cat/test.txt \
|
||||||
tests/seek/big.txt \
|
tests/seek/big.txt \
|
||||||
tests/cblwav/speech.pcm \
|
tests/cblwav/speech.pcm
|
||||||
examples/mdview/SAMPLE.MD
|
|
||||||
|
|
||||||
.PHONY: all tools lib tests examples check clean sdcc floppy \
|
.PHONY: all tools lib tests check clean sdcc floppy run \
|
||||||
size-check size-baseline host-tests sdbg-tests mame-sdbg-patch mame-sdbg $(TESTS) $(APPS)
|
size-check size-baseline host-tests sdbg-tests $(TESTS)
|
||||||
|
|
||||||
all: tools lib tests
|
all: tools lib tests
|
||||||
|
|
||||||
@@ -58,31 +48,30 @@ check: tools
|
|||||||
$(MAKE) -C toolchain/mkexe check
|
$(MAKE) -C toolchain/mkexe check
|
||||||
|
|
||||||
tests: $(TESTS)
|
tests: $(TESTS)
|
||||||
examples: $(APPS)
|
|
||||||
|
|
||||||
$(TESTS): tools lib
|
$(TESTS): tools lib
|
||||||
$(MAKE) -C tests/$@
|
$(MAKE) -C tests/$@
|
||||||
|
|
||||||
$(APPS): tools lib
|
|
||||||
$(MAKE) -C examples/$@
|
|
||||||
|
|
||||||
# Generate big.txt if missing (gen_bigfile.py creates 100 KB marker file).
|
# Generate big.txt if missing (gen_bigfile.py creates 100 KB marker file).
|
||||||
tests/seek/big.txt:
|
tests/seek/big.txt:
|
||||||
cd tests/seek && python3 gen_bigfile.py big.txt 102400
|
cd tests/seek && python3 gen_bigfile.py big.txt 102400
|
||||||
|
|
||||||
# Re-pack the MAME floppy image with every built exe + needed data files.
|
# Собственный SDK-носитель: не изменяет диск MAME или приложения.
|
||||||
floppy: tests examples tests/seek/big.txt
|
floppy: tests tests/seek/big.txt
|
||||||
python3 $(MAKE_DISK) $(FLOPPY_IMG) $(ALL_EXES) $(DATA_FILES)
|
python3 $(MAKE_DISK) $(FLOPPY_IMG) $(ALL_EXES) $(DATA_FILES)
|
||||||
@echo
|
@echo
|
||||||
@echo "Floppy ready: $(FLOPPY_IMG)"
|
@echo "Floppy ready: $(FLOPPY_IMG)"
|
||||||
@echo "Run: cd $(MAME_DIR) && ./run_mame.sh"
|
@echo "Run: make run MAME_HOME=/путь/к/MAME/runtime"
|
||||||
|
|
||||||
|
run: floppy
|
||||||
|
python3 toolchain/run_sprinter_mame.py --mame-home "$(MAME_HOME)" --floppy "$(FLOPPY_IMG)"
|
||||||
|
|
||||||
# Модульные тесты под ucsim_z80. Обвязка — testkit/, сами наборы лежат
|
# Модульные тесты под ucsim_z80. Обвязка — testkit/, сами наборы лежат
|
||||||
# рядом с кодом, который проверяют. MAME не нужна, идут за секунды;
|
# рядом с кодом, который проверяют. MAME не нужна, идут за секунды;
|
||||||
# ucsim идёт в комплекте нашего SDCC.
|
# ucsim идёт в комплекте нашего SDCC.
|
||||||
# applications/PoP/roomtest заморожена (её ветка развития — SprPoP), поэтому
|
# Тесты продуктов запускаются в собственных репозиториях.
|
||||||
# её набор здесь больше не гоняется.
|
HOST_TEST_DIRS := testkit
|
||||||
HOST_TEST_DIRS := testkit applications/SprPoP/tests/host
|
|
||||||
|
|
||||||
host-tests:
|
host-tests:
|
||||||
@for d in $(HOST_TEST_DIRS); do $(MAKE) -C $$d || exit 1; done
|
@for d in $(HOST_TEST_DIRS); do $(MAKE) -C $$d || exit 1; done
|
||||||
@@ -92,16 +81,6 @@ host-tests:
|
|||||||
sdbg-tests: tools lib
|
sdbg-tests: tools lib
|
||||||
python3 -m unittest discover -s tests/sdbg -v
|
python3 -m unittest discover -s tests/sdbg -v
|
||||||
|
|
||||||
# Backend удерживает MAME в stopped-loop без Cocoa debugger; Lua bridge при
|
|
||||||
# этом обслуживается из periodic_check ядра. Повторный вызов безопасен.
|
|
||||||
mame-sdbg-patch:
|
|
||||||
sh toolchain/apply-mame-sdbg-patch.sh
|
|
||||||
|
|
||||||
# Инкрементально собирает patched checkout и устанавливает бинарник в v306.
|
|
||||||
mame-sdbg: mame-sdbg-patch
|
|
||||||
$(MAKE) -C mame/sources/MAME
|
|
||||||
cp mame/sources/MAME/mame $(MAME_DIR)/mame.arm
|
|
||||||
|
|
||||||
# Размерный регресс: сверить _CODE всех программ с docs/size_baseline.tsv.
|
# Размерный регресс: сверить _CODE всех программ с docs/size_baseline.tsv.
|
||||||
size-check:
|
size-check:
|
||||||
python3 toolchain/size_check.py
|
python3 toolchain/size_check.py
|
||||||
@@ -115,7 +94,6 @@ clean:
|
|||||||
$(MAKE) -C libc clean
|
$(MAKE) -C libc clean
|
||||||
$(MAKE) -C libbgi clean
|
$(MAKE) -C libbgi clean
|
||||||
@for t in $(TESTS); do $(MAKE) -C tests/$$t clean; done
|
@for t in $(TESTS); do $(MAKE) -C tests/$$t clean; done
|
||||||
@for a in $(APPS); do $(MAKE) -C examples/$$a clean; done
|
|
||||||
|
|
||||||
sdcc:
|
sdcc:
|
||||||
bash third_party/setup-sdcc.sh
|
bash third_party/setup-sdcc.sh
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ banked-call trampolines, graphics & accelerator API, mouse driver wrappers, and
|
|||||||
git clone <this repo> sprinter-c
|
git clone <this repo> sprinter-c
|
||||||
cd sprinter-c
|
cd sprinter-c
|
||||||
make sdcc # one-time: fetch SDCC 4.5 binary (~25 MB)
|
make sdcc # one-time: fetch SDCC 4.5 binary (~25 MB)
|
||||||
make all # build mkexe + libsprinter.lib + 27 examples
|
make all # build tools, libraries and SDK tests
|
||||||
make floppy # pack everything into mame/v306/IMG/mc.img
|
make floppy # pack tests into build/media/toolkit-tests.img
|
||||||
cd mame/v306 && ./run_mame.sh # boot Sprinter in MAME
|
MAME_HOME=/путь/к/MAME/runtime make run
|
||||||
```
|
```
|
||||||
|
|
||||||
Compile a single program:
|
Compile a single program:
|
||||||
@@ -70,7 +70,11 @@ Banked functions are declared with `__banked`:
|
|||||||
void engine_tick(int dt) __banked; // lives in BANK1, automatically swapped
|
void engine_tick(int dt) __banked; // lives in BANK1, automatically swapped
|
||||||
```
|
```
|
||||||
|
|
||||||
## Examples (27 total)
|
## SDK tests and separate examples
|
||||||
|
|
||||||
|
Programs listed below live in `tests/` and validate the SDK. Demonstration
|
||||||
|
applications live in the separate `Examples` repository. Its Makefiles use
|
||||||
|
`SPRINTER_ROOT` to find this SDK.
|
||||||
|
|
||||||
| Example | What it demonstrates |
|
| Example | What it demonstrates |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -128,8 +132,9 @@ Sprinter-specific:
|
|||||||
## Toolchain commands
|
## Toolchain commands
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make all # build mkexe + lib + every example
|
make all # build mkexe + libraries + SDK tests
|
||||||
make floppy # repack mame/v306/IMG/mc.img with all .exe files
|
make floppy # pack SDK tests into build/media/toolkit-tests.img
|
||||||
|
MAME_HOME=/путь/к/MAME/runtime make run
|
||||||
make check # 17 mkexe unit-tests
|
make check # 17 mkexe unit-tests
|
||||||
make clean # remove all build artefacts
|
make clean # remove all build artefacts
|
||||||
make sdcc # one-time: fetch SDCC 4.5 binary
|
make sdcc # one-time: fetch SDCC 4.5 binary
|
||||||
@@ -198,8 +203,8 @@ runtime/ crt0 variants (default, minimal, small, banked)
|
|||||||
libc/include/ headers
|
libc/include/ headers
|
||||||
libc/io|stdio|mem|gfx/ C and asm sources for libsprinter.lib
|
libc/io|stdio|mem|gfx/ C and asm sources for libsprinter.lib
|
||||||
lib/ Makefile that archives libsprinter.lib via sdar
|
lib/ Makefile that archives libsprinter.lib via sdar
|
||||||
examples/ 27 example programs
|
tests/ SDK regression programs
|
||||||
mame/v306/ MAME binary + Sprinter ROM/HDD images + floppy script
|
app.mk shared rules for independent applications
|
||||||
third_party/sdcc/ vendored SDCC 4.5 (fetched via `make sdcc`)
|
third_party/sdcc/ vendored SDCC 4.5 (fetched via `make sdcc`)
|
||||||
third_party/solid-c/ reference: original Sprinter native C (for compat target)
|
third_party/solid-c/ reference: original Sprinter native C (for compat target)
|
||||||
docs/ documentation
|
docs/ documentation
|
||||||
@@ -208,7 +213,7 @@ docs/ documentation
|
|||||||
## License
|
## License
|
||||||
|
|
||||||
This repository contains:
|
This repository contains:
|
||||||
* Original code in `bin/`, `toolchain/`, `runtime/`, `libc/`, `lib/`, `examples/` —
|
* Original code in `bin/`, `toolchain/`, `runtime/`, `libc/`, `libbgi/`, `lib/` —
|
||||||
MIT-licensed.
|
MIT-licensed.
|
||||||
* `third_party/sdcc/` — SDCC 4.5 under GPLv2 with linking exception
|
* `third_party/sdcc/` — SDCC 4.5 under GPLv2 with linking exception
|
||||||
(see `third_party/sdcc/COPYING.txt`)
|
(see `third_party/sdcc/COPYING.txt`)
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
# app.mk — shared Makefile fragment for any standalone Sprinter ESTEX
|
# app.mk — shared Makefile fragment for any standalone Sprinter ESTEX
|
||||||
# program — used both by libc feature tests under tests/ and by real
|
# program. Приложение может лежать вне репозитория тулкита.
|
||||||
# applications under examples/.
|
|
||||||
#
|
#
|
||||||
# Usage in a per-program Makefile:
|
# Usage in a per-program Makefile:
|
||||||
#
|
#
|
||||||
# PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
# SPRINTER_ROOT := /путь/к/C-Compiler
|
||||||
# EXAMPLE := my_program # base name (matches my_program.c)
|
# EXAMPLE := my_program # base name (matches my_program.c)
|
||||||
#
|
#
|
||||||
# # Optional overrides (any combination):
|
# # Optional overrides (any combination):
|
||||||
@@ -28,24 +27,43 @@
|
|||||||
# all build $(EXAMPLE).exe (default)
|
# all build $(EXAMPLE).exe (default)
|
||||||
# clean remove build artefacts
|
# clean remove build artefacts
|
||||||
# floppy build the example and pack it (alone, plus EXTRA_DATA) into
|
# floppy build the example and pack it (alone, plus EXTRA_DATA) into
|
||||||
# mame/v306/IMG/mc.img — useful for trying a single program
|
# build/media/$(EXAMPLE).img внутри приложения.
|
||||||
# without rebuilding every example. Top-level `make floppy`
|
|
||||||
# (in the repo root) still packs all examples.
|
|
||||||
# run floppy + launch MAME
|
# run floppy + launch MAME
|
||||||
|
|
||||||
|
ifeq ($(strip $(PROJ_ROOT)$(SPRINTER_ROOT)),)
|
||||||
|
$(error Задайте SPRINTER_ROOT — путь к установленному Sprinter-CC)
|
||||||
|
endif
|
||||||
|
ifeq ($(strip $(PROJ_ROOT)),)
|
||||||
|
PROJ_ROOT := $(SPRINTER_ROOT)
|
||||||
|
endif
|
||||||
|
ifeq ($(strip $(SPRINTER_ROOT)),)
|
||||||
|
SPRINTER_ROOT := $(PROJ_ROOT)
|
||||||
|
endif
|
||||||
|
|
||||||
PYTHON ?= python3
|
PYTHON ?= python3
|
||||||
SPRINTER_CC := $(PROJ_ROOT)/bin/sprinter-cc
|
SPRINTER_CC := $(PROJ_ROOT)/bin/sprinter-cc
|
||||||
MKEXE := $(PROJ_ROOT)/toolchain/mkexe/mkexe
|
MKEXE := $(PROJ_ROOT)/toolchain/mkexe/mkexe
|
||||||
LIB := $(PROJ_ROOT)/lib/sprinter.lib
|
LIB := $(PROJ_ROOT)/lib/sprinter.lib
|
||||||
|
|
||||||
MAME_DIR := $(PROJ_ROOT)/mame/v306
|
# Временный fallback для старого общего дерева. После переноса нет MAME
|
||||||
FLOPPY_IMG := $(MAME_DIR)/IMG/mc.img
|
# внутри SDK, поэтому внешний проект задаёт MAME_HOME или отдельные пути.
|
||||||
# ?= — приложение со своим каталогом выхода (applications/SprPoP) держит
|
MAME_HOME ?= $(if $(wildcard $(PROJ_ROOT)/mame/v306/mame.arm),$(PROJ_ROOT)/mame/v306,)
|
||||||
# образ у себя и связывает его с MAME символьной ссылкой.
|
MAME_BIN ?= $(if $(strip $(MAME_HOME)),$(MAME_HOME)/mame.arm,)
|
||||||
HDD_IMG ?= $(MAME_DIR)/IMG/test_hdd.chd
|
MAME_ROMPATH ?= $(if $(strip $(MAME_HOME)),$(MAME_HOME)/roms,)
|
||||||
MAKE_DISK := $(MAME_DIR)/make_disk.py
|
MAME_DSS_IMAGE ?= $(if $(strip $(MAME_HOME)),$(MAME_HOME)/IMG/dss171u.img,)
|
||||||
|
MAME_SYSTEM_HDD_IMAGE ?= $(if $(strip $(MAME_HOME)),$(MAME_HOME)/IMG/sp_hdd_sys.chd,)
|
||||||
|
MAME_BIOS ?= v3.06
|
||||||
|
FLOPPY_IMG ?= $(CURDIR)/build/media/$(EXAMPLE).img
|
||||||
|
HDD_IMG ?= $(CURDIR)/build/hdd/$(EXAMPLE).chd
|
||||||
|
MAKE_DISK := $(PROJ_ROOT)/toolchain/make_disk.py
|
||||||
MAKE_HDD ?= $(PROJ_ROOT)/toolchain/make_hdd.sh
|
MAKE_HDD ?= $(PROJ_ROOT)/toolchain/make_hdd.sh
|
||||||
RUN_MAME := $(MAME_DIR)/run_mame.sh
|
RUN_MAME := $(PROJ_ROOT)/toolchain/run_sprinter_mame.py
|
||||||
|
CHDMAN_BIN ?= chdman
|
||||||
|
MAME_PROFILE_ARGS = --mame-home "$(MAME_HOME)" --mame-bin "$(MAME_BIN)" \
|
||||||
|
--mame-rompath "$(MAME_ROMPATH)" \
|
||||||
|
--mame-dss-image "$(MAME_DSS_IMAGE)" \
|
||||||
|
--mame-system-hdd-image "$(MAME_SYSTEM_HDD_IMAGE)" \
|
||||||
|
--mame-bios "$(MAME_BIOS)"
|
||||||
|
|
||||||
# Optional knobs — see top of file.
|
# Optional knobs — see top of file.
|
||||||
MEMORY ?= tiny
|
MEMORY ?= tiny
|
||||||
@@ -141,26 +159,26 @@ $(LIB):
|
|||||||
clean:
|
clean:
|
||||||
rm -rf $(if $(strip $(BUILD_DIR)),$(BUILD_DIR),.sprinter-cc-* $(EXE))
|
rm -rf $(if $(strip $(BUILD_DIR)),$(BUILD_DIR),.sprinter-cc-* $(EXE))
|
||||||
|
|
||||||
# `make floppy` packs ONLY this program (+ optional EXTRA_DATA files) into
|
# `make floppy` создаёт носитель только этого приложения, без записи в MAME.
|
||||||
# the MAME floppy image, replacing whatever was there. Handy for trying a
|
|
||||||
# single program without rebuilding everything.
|
|
||||||
floppy: $(EXE)
|
floppy: $(EXE)
|
||||||
python3 $(MAKE_DISK) $(FLOPPY_IMG) $(EXE) $(EXTRA_DATA)
|
$(PYTHON) $(MAKE_DISK) "$(FLOPPY_IMG)" $(EXE) $(EXTRA_DATA)
|
||||||
@echo
|
@echo
|
||||||
@echo "Floppy ready: $(FLOPPY_IMG) (with $(EXAMPLE).exe$(if $(EXTRA_DATA), + $(EXTRA_DATA))) "
|
@echo "Floppy ready: $(FLOPPY_IMG) (with $(EXAMPLE).exe$(if $(EXTRA_DATA), + $(EXTRA_DATA))) "
|
||||||
@echo "Run: cd $(MAME_DIR) && ./run_mame.sh"
|
@echo "Run: make run MAME_HOME=/путь/к/MAME/runtime"
|
||||||
|
|
||||||
run: floppy
|
run: floppy
|
||||||
cd $(MAME_DIR) && ./run_mame.sh
|
$(PYTHON) $(RUN_MAME) $(MAME_PROFILE_ARGS) --floppy "$(FLOPPY_IMG)"
|
||||||
|
|
||||||
# `make hdd` packs this program (+ optional EXTRA_DATA files) into the MAME
|
# `make hdd` кладёт носитель в build/ приложения. Для нового inode после
|
||||||
# HDD image mounted as disk D: (-hard2 test_hdd.chd). Гораздо быстрее FDD —
|
# пересборки образа запущенный MAME должен быть перезапущен.
|
||||||
# используется MCP-мостом к MAME (run_bridge.sh). После пересборки образа
|
|
||||||
# MAME ОБЯЗАН полный рестарт (chdman -f = новый inode; см. memory).
|
|
||||||
hdd: $(EXE)
|
hdd: $(EXE)
|
||||||
$(MAKE_HDD) $(if $(strip $(HDD_DEST_DIR)),--dest "$(HDD_DEST_DIR)") $(HDD_IMG) $(HDD_PACK_ARGS)
|
mkdir -p "$(dir $(HDD_IMG))"
|
||||||
|
CHDMAN_BIN="$(CHDMAN_BIN)" $(MAKE_HDD) $(if $(strip $(HDD_DEST_DIR)),--dest "$(HDD_DEST_DIR)") "$(HDD_IMG)" $(HDD_PACK_ARGS)
|
||||||
@echo
|
@echo
|
||||||
@echo "HDD (D:) ready: $(HDD_IMG) (with $(EXAMPLE).exe$(if $(EXTRA_DATA), + $(EXTRA_DATA)))"
|
@echo "HDD (D:) ready: $(HDD_IMG) (with $(EXAMPLE).exe$(if $(EXTRA_DATA), + $(EXTRA_DATA)))"
|
||||||
@echo "ВНИМАНИЕ: перезапусти MAME (run_bridge.sh) — образ пересобран."
|
@echo "ВНИМАНИЕ: перезапусти MAME — образ пересобран."
|
||||||
|
|
||||||
.PHONY: all clean floppy run hdd
|
run-hdd: hdd
|
||||||
|
$(PYTHON) $(RUN_MAME) $(MAME_PROFILE_ARGS) --hdd "$(HDD_IMG)"
|
||||||
|
|
||||||
|
.PHONY: all clean floppy run hdd run-hdd
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Референсные клоны и оригинальные игровые данные — не часть порта.
|
||||||
|
SDLPoP/
|
||||||
|
PR/
|
||||||
|
mininim/
|
||||||
|
Prince-of-Persia-Apple-II/
|
||||||
|
MSDOS/
|
||||||
|
PoP1_DOS_music/
|
||||||
|
R1/
|
||||||
|
local.mk
|
||||||
|
build/
|
||||||
|
.sprinter-cc-*/
|
||||||
|
.resource-stamps/
|
||||||
|
*.exe
|
||||||
|
*.asm
|
||||||
|
*.lst
|
||||||
|
*.lk
|
||||||
|
*.ihx
|
||||||
|
*.noi
|
||||||
|
*.sym
|
||||||
|
*.map
|
||||||
|
*.rel
|
||||||
|
*.cdb
|
||||||
|
*.mem
|
||||||
|
*.rst
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.DS_Store
|
||||||
|
._*
|
||||||
|
.vscode/
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3.12
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Sprinter C Compiler contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
#
|
#
|
||||||
# Данные фона генерит toolchain/pop_pack_bg.py в ../poc/res/bg/.
|
# Данные фона генерит toolchain/pop_pack_bg.py в ../poc/res/bg/.
|
||||||
|
|
||||||
PROJ_ROOT := $(abspath $(CURDIR)/../../..)
|
|
||||||
EXAMPLE := bgtest
|
EXAMPLE := bgtest
|
||||||
MEMORY ?= huge
|
MEMORY ?= huge
|
||||||
EXTRA_FLAGS ?= --gfx 256
|
EXTRA_FLAGS ?= --gfx 256
|
||||||
@@ -17,11 +16,11 @@ BG_DATA := $(BG_DIR)/pop_env0.atl $(BG_DIR)/pop_env1.atl $(BG_DIR)/pop_env2.
|
|||||||
$(BG_DIR)/pop_wall.atl $(BG_DIR)/pop_fore.atl $(BG_DIR)/pop_bg.pal
|
$(BG_DIR)/pop_wall.atl $(BG_DIR)/pop_fore.atl $(BG_DIR)/pop_bg.pal
|
||||||
|
|
||||||
EXTRA_DATA := $(BG_DATA)
|
EXTRA_DATA := $(BG_DATA)
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/../sdk.mk
|
||||||
|
|
||||||
# Ассеты фона: пересобрать пакером, если исходники поменялись.
|
# Ассеты фона: пересобрать пакером, если исходники поменялись.
|
||||||
$(BG_DATA): $(PROJ_ROOT)/applications/PoP/toolchain/pop_pack_bg.py \
|
$(BG_DATA): $(POP_ROOT)/toolchain/pop_pack_bg.py \
|
||||||
$(PROJ_ROOT)/applications/PoP/toolchain/render_room.py
|
$(POP_ROOT)/toolchain/render_room.py
|
||||||
cd $(PROJ_ROOT)/applications/PoP/toolchain && python3 pop_pack_bg.py
|
cd $(POP_ROOT)/toolchain && python3 pop_pack_bg.py
|
||||||
|
|
||||||
$(EXAMPLE).exe: $(BG_DATA)
|
$(EXAMPLE).exe: $(BG_DATA)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# coltest — прототип вертикального accel-copy (gfx_blit_cols) + флип.
|
# coltest — прототип вертикального accel-copy (gfx_blit_cols) + флип.
|
||||||
PROJ_ROOT := $(abspath $(CURDIR)/../../..)
|
|
||||||
EXAMPLE := coltest
|
EXAMPLE := coltest
|
||||||
MEMORY ?= huge
|
MEMORY ?= huge
|
||||||
EXTRA_FLAGS ?= --gfx 256
|
EXTRA_FLAGS ?= --gfx 256
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/../sdk.mk
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
# бы уехал). sprintf() заменён на ручное hex-форматирование пути в
|
# бы уехал). sprintf() заменён на ручное hex-форматирование пути в
|
||||||
# tile_atlas_load() (единственный потребитель printf, ~2.9КБ).
|
# tile_atlas_load() (единственный потребитель printf, ~2.9КБ).
|
||||||
|
|
||||||
PROJ_ROOT := $(abspath $(CURDIR)/../../..)
|
|
||||||
EXAMPLE := poc
|
EXAMPLE := poc
|
||||||
MEMORY ?= huge
|
MEMORY ?= huge
|
||||||
EXTRA_FLAGS ?= --gfx 256
|
EXTRA_FLAGS ?= --gfx 256
|
||||||
@@ -28,9 +27,9 @@ EXTRA_SRCS := room.c
|
|||||||
TILE_ATLASES := res/tiles/tile01.atl res/tiles/tile14.atl res/tiles/tile03.atl \
|
TILE_ATLASES := res/tiles/tile01.atl res/tiles/tile14.atl res/tiles/tile03.atl \
|
||||||
res/tiles/tile13.atl res/tiles/tile0e.atl res/tiles/tile0b.atl
|
res/tiles/tile13.atl res/tiles/tile0e.atl res/tiles/tile0b.atl
|
||||||
EXTRA_DATA := tools/kid.atl tools/room.pal res/room1.dat $(TILE_ATLASES)
|
EXTRA_DATA := tools/kid.atl tools/room.pal res/room1.dat $(TILE_ATLASES)
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/../sdk.mk
|
||||||
|
|
||||||
LEVEL1_BIN := $(PROJ_ROOT)/applications/PoP/SDLPoP/data/LEVELS/res2001.bin
|
LEVEL1_BIN := $(POP_ROOT)/SDLPoP/data/LEVELS/res2001.bin
|
||||||
|
|
||||||
tools/kid.raw tools/kid.pal: tools/gen_kid_placeholder.py
|
tools/kid.raw tools/kid.pal: tools/gen_kid_placeholder.py
|
||||||
cd tools && python3 gen_kid_placeholder.py
|
cd tools && python3 gen_kid_placeholder.py
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ kid.pal).
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
|
||||||
HERE = Path(__file__).resolve().parent
|
HERE = Path(__file__).resolve().parent
|
||||||
PROJ_ROOT = HERE.parents[3]
|
PROJ_ROOT = Path(os.environ["SPRINTER_ROOT"]).resolve()
|
||||||
TILES_DIR = HERE.parent / "res" / "tiles"
|
TILES_DIR = HERE.parent / "res" / "tiles"
|
||||||
PNG_STRIP = PROJ_ROOT / "toolchain" / "png_strip.py"
|
PNG_STRIP = PROJ_ROOT / "toolchain" / "png_strip.py"
|
||||||
MKATLAS = PROJ_ROOT / "toolchain" / "mkatlas.py"
|
MKATLAS = PROJ_ROOT / "toolchain" / "mkatlas.py"
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ import re
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
|
||||||
HERE = Path(__file__).resolve().parent
|
HERE = Path(__file__).resolve().parent
|
||||||
PROJ_ROOT = HERE.parents[3] # .../C-Compiler
|
PROJ_ROOT = Path(os.environ["SPRINTER_ROOT"]).resolve()
|
||||||
TILES_DIR = HERE.parent / "res" / "tiles"
|
TILES_DIR = HERE.parent / "res" / "tiles"
|
||||||
OUT_DIR = HERE.parent / "res" / "tiles"
|
OUT_DIR = HERE.parent / "res" / "tiles"
|
||||||
PNG_STRIP = PROJ_ROOT / "toolchain" / "png_strip.py"
|
PNG_STRIP = PROJ_ROOT / "toolchain" / "png_strip.py"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ third_party/16x16-RPG-characters, один персонаж (индекс 0) т
|
|||||||
"""
|
"""
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
SRC = ("../../../../third_party/16x16-RPG-characters/sprites/"
|
SRC = ("../../third_party/16x16-RPG-characters/sprites/"
|
||||||
"old-style/02-bard.png")
|
"old-style/02-bard.png")
|
||||||
CHAR = 0 # какой из 8 персонажей листа берём
|
CHAR = 0 # какой из 8 персонажей листа берём
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# roomtest — проверка порта статического фона PoP (Шаг 3): реальная
|
# roomtest — проверка порта статического фона PoP (Шаг 3): реальная
|
||||||
# композиция слоёв (pop_bg.c) для комнаты 1. --memory huge --gfx 256.
|
# композиция слоёв (pop_bg.c) для комнаты 1. --memory huge --gfx 256.
|
||||||
|
|
||||||
PROJ_ROOT := $(abspath $(CURDIR)/../../..)
|
|
||||||
EXAMPLE := roomtest
|
EXAMPLE := roomtest
|
||||||
HDD_IMG := $(CURDIR)/build/hdd/roomtest.chd
|
HDD_IMG := $(CURDIR)/build/hdd/roomtest.chd
|
||||||
|
|
||||||
@@ -276,9 +275,9 @@ HDD_PACK_ARGS := $(EXAMPLE).exe \
|
|||||||
$(foreach f,$(PV_DATA),PV:$(f)) \
|
$(foreach f,$(PV_DATA),PV:$(f)) \
|
||||||
$(foreach f,$(LVL_DATA),LEVELS:$(f))
|
$(foreach f,$(LVL_DATA),LEVELS:$(f))
|
||||||
|
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/../sdk.mk
|
||||||
|
|
||||||
TC := $(PROJ_ROOT)/applications/PoP/toolchain
|
TC := $(POP_ROOT)/toolchain
|
||||||
|
|
||||||
# Упаковщики создают сразу ГРУППУ файлов. Обычное multi-output правило
|
# Упаковщики создают сразу ГРУППУ файлов. Обычное multi-output правило
|
||||||
# заставляло GNU make при `-B` запускать один и тот же упаковщик отдельно для
|
# заставляло GNU make при `-B` запускать один и тот же упаковщик отдельно для
|
||||||
@@ -389,25 +388,25 @@ $(KID_BIN_STAMP): $(TC)/pop_extract_kid_data.py | $(RESOURCE_STAMP_DIR)
|
|||||||
kid_data.h $(KID_BIN): $(KID_BIN_STAMP)
|
kid_data.h $(KID_BIN): $(KID_BIN_STAMP)
|
||||||
@test -f $@ || { $(MAKE) -B $(KID_BIN_STAMP); test -f $@; }
|
@test -f $@ || { $(MAKE) -B $(KID_BIN_STAMP); test -f $@; }
|
||||||
|
|
||||||
$(FONT_STAMP): $(PROJ_ROOT)/toolchain/pop_extract_font.py \
|
$(FONT_STAMP): $(POP_ROOT)/toolchain/pop_extract_font.py \
|
||||||
$(CURDIR)/../SDLPoP/src/menu.c $(CURDIR)/../SDLPoP/src/seg009.c | $(RESOURCE_STAMP_DIR)
|
$(CURDIR)/../SDLPoP/src/menu.c $(CURDIR)/../SDLPoP/src/seg009.c | $(RESOURCE_STAMP_DIR)
|
||||||
cd $(PROJ_ROOT) && python3 toolchain/pop_extract_font.py
|
cd $(POP_ROOT) && $(PYTHON) toolchain/pop_extract_font.py
|
||||||
touch $@
|
touch $@
|
||||||
$(FONT_ATL) pop_font.h: $(FONT_STAMP)
|
$(FONT_ATL) pop_font.h: $(FONT_STAMP)
|
||||||
@test -f $@ || { $(MAKE) -B $(FONT_STAMP); test -f $@; }
|
@test -f $@ || { $(MAKE) -B $(FONT_STAMP); test -f $@; }
|
||||||
|
|
||||||
$(TITLE_STAMP): $(PROJ_ROOT)/toolchain/pop_pack_title.py \
|
$(TITLE_STAMP): $(POP_ROOT)/toolchain/pop_pack_title.py \
|
||||||
$(CURDIR)/../SDLPoP/data/TITLE/res51.png \
|
$(CURDIR)/../SDLPoP/data/TITLE/res51.png \
|
||||||
$(CURDIR)/../SDLPoP/data/TITLE/res52.png \
|
$(CURDIR)/../SDLPoP/data/TITLE/res52.png \
|
||||||
$(CURDIR)/../SDLPoP/data/TITLE/res53.png \
|
$(CURDIR)/../SDLPoP/data/TITLE/res53.png \
|
||||||
$(CURDIR)/../SDLPoP/data/TITLE/res54.png \
|
$(CURDIR)/../SDLPoP/data/TITLE/res54.png \
|
||||||
$(CURDIR)/../SDLPoP/data/TITLE/res55.png | $(RESOURCE_STAMP_DIR)
|
$(CURDIR)/../SDLPoP/data/TITLE/res55.png | $(RESOURCE_STAMP_DIR)
|
||||||
cd $(PROJ_ROOT) && python3 toolchain/pop_pack_title.py
|
cd $(POP_ROOT) && $(PYTHON) toolchain/pop_pack_title.py
|
||||||
touch $@
|
touch $@
|
||||||
$(TITLE_ATL) $(TITLE_DIR)/title.pal: $(TITLE_STAMP)
|
$(TITLE_ATL) $(TITLE_DIR)/title.pal: $(TITLE_STAMP)
|
||||||
@test -f $@ || { $(MAKE) -B $(TITLE_STAMP); test -f $@; }
|
@test -f $@ || { $(MAKE) -B $(TITLE_STAMP); test -f $@; }
|
||||||
|
|
||||||
$(PV_STAMP): $(PROJ_ROOT)/toolchain/pop_pack_intro.py \
|
$(PV_STAMP): $(POP_ROOT)/toolchain/pop_pack_intro.py \
|
||||||
$(CURDIR)/../SDLPoP/data/TITLE/res41.png \
|
$(CURDIR)/../SDLPoP/data/TITLE/res41.png \
|
||||||
$(CURDIR)/../SDLPoP/data/TITLE/res42.png \
|
$(CURDIR)/../SDLPoP/data/TITLE/res42.png \
|
||||||
$(CURDIR)/../SDLPoP/data/TITLE/res43.png \
|
$(CURDIR)/../SDLPoP/data/TITLE/res43.png \
|
||||||
@@ -429,7 +428,7 @@ $(PV_STAMP): $(PROJ_ROOT)/toolchain/pop_pack_intro.py \
|
|||||||
$(foreach n,852 853 854 855 856 857 858 859 860 861 862 863 864,$(CURDIR)/../SDLPoP/data/PV/res$(n).png) \
|
$(foreach n,852 853 854 855 856 857 858 859 860 861 862 863 864,$(CURDIR)/../SDLPoP/data/PV/res$(n).png) \
|
||||||
$(CURDIR)/../SDLPoP/data/PV/res952.png \
|
$(CURDIR)/../SDLPoP/data/PV/res952.png \
|
||||||
$(foreach n,151 152 153 154 155 156 157 158 159,$(CURDIR)/../SDLPoP/data/PRINCE/res$(n).png) | $(RESOURCE_STAMP_DIR)
|
$(foreach n,151 152 153 154 155 156 157 158 159,$(CURDIR)/../SDLPoP/data/PRINCE/res$(n).png) | $(RESOURCE_STAMP_DIR)
|
||||||
cd $(PROJ_ROOT) && python3 toolchain/pop_pack_intro.py
|
cd $(POP_ROOT) && $(PYTHON) toolchain/pop_pack_intro.py
|
||||||
touch $@
|
touch $@
|
||||||
$(PV_ATL) $(PV_DIR)/story.pal $(PV_DIR)/pv.pal: $(PV_STAMP)
|
$(PV_ATL) $(PV_DIR)/story.pal $(PV_DIR)/pv.pal: $(PV_STAMP)
|
||||||
@test -f $@ || { $(MAKE) -B $(PV_STAMP); test -f $@; }
|
@test -f $@ || { $(MAKE) -B $(PV_STAMP); test -f $@; }
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
# Модульные тесты движка roomtest под ucsim_z80. Обвязка общая (testkit/),
|
# Модульные тесты движка roomtest под ucsim_z80. Обвязка общая (testkit/),
|
||||||
# здесь — только сами наборы и список модулей, которые в них линкуются.
|
# здесь — только сами наборы и список модулей, которые в них линкуются.
|
||||||
TESTKIT := $(abspath $(CURDIR)/../../../../testkit)
|
POP_ROOT := $(abspath $(CURDIR)/../..)
|
||||||
|
-include $(POP_ROOT)/local.mk
|
||||||
|
SPRINTER_ROOT ?= $(if $(wildcard $(POP_ROOT)/../../bin/sprinter-cc),$(abspath $(POP_ROOT)/../..),)
|
||||||
|
ifeq ($(strip $(SPRINTER_ROOT)),)
|
||||||
|
$(error Задайте SPRINTER_ROOT — путь к установленному Sprinter-CC)
|
||||||
|
endif
|
||||||
|
TESTKIT := $(SPRINTER_ROOT)/testkit
|
||||||
ENGINE_DIR := $(abspath $(CURDIR)/..)
|
ENGINE_DIR := $(abspath $(CURDIR)/..)
|
||||||
|
|
||||||
# t_phys гоняет НАСТОЯЩУЮ физику, а она ходит по таблицам анимации
|
# t_phys гоняет НАСТОЯЩУЮ физику, а она ходит по таблицам анимации
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Привязка архивных PoP-проб к установленному Sprinter-CC.
|
||||||
|
POP_ROOT := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
|
||||||
|
-include $(POP_ROOT)/local.mk
|
||||||
|
SPRINTER_ROOT ?= $(if $(wildcard $(POP_ROOT)/../../bin/sprinter-cc),$(abspath $(POP_ROOT)/../..),)
|
||||||
|
ifeq ($(strip $(SPRINTER_ROOT)),)
|
||||||
|
$(error Задайте SPRINTER_ROOT или создайте PoP/local.mk)
|
||||||
|
endif
|
||||||
|
PROJ_ROOT := $(SPRINTER_ROOT)
|
||||||
|
export SPRINTER_ROOT MAME_HOME MAME_BIN MAME_ROMPATH MAME_DSS_IMAGE
|
||||||
|
export MAME_SYSTEM_HDD_IMAGE MAME_BIOS CHDMAN_BIN
|
||||||
|
include $(PROJ_ROOT)/app.mk
|
||||||
@@ -0,0 +1,359 @@
|
|||||||
|
Creative Commons Legal Code
|
||||||
|
|
||||||
|
Attribution-ShareAlike 3.0 Unported
|
||||||
|
|
||||||
|
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||||
|
LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
|
||||||
|
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||||
|
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||||
|
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
|
||||||
|
DAMAGES RESULTING FROM ITS USE.
|
||||||
|
|
||||||
|
License
|
||||||
|
|
||||||
|
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
|
||||||
|
COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
|
||||||
|
COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
|
||||||
|
AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
|
||||||
|
|
||||||
|
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
|
||||||
|
TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
|
||||||
|
BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
|
||||||
|
CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
|
||||||
|
CONDITIONS.
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
|
||||||
|
a. "Adaptation" means a work based upon the Work, or upon the Work and
|
||||||
|
other pre-existing works, such as a translation, adaptation,
|
||||||
|
derivative work, arrangement of music or other alterations of a
|
||||||
|
literary or artistic work, or phonogram or performance and includes
|
||||||
|
cinematographic adaptations or any other form in which the Work may be
|
||||||
|
recast, transformed, or adapted including in any form recognizably
|
||||||
|
derived from the original, except that a work that constitutes a
|
||||||
|
Collection will not be considered an Adaptation for the purpose of
|
||||||
|
this License. For the avoidance of doubt, where the Work is a musical
|
||||||
|
work, performance or phonogram, the synchronization of the Work in
|
||||||
|
timed-relation with a moving image ("synching") will be considered an
|
||||||
|
Adaptation for the purpose of this License.
|
||||||
|
b. "Collection" means a collection of literary or artistic works, such as
|
||||||
|
encyclopedias and anthologies, or performances, phonograms or
|
||||||
|
broadcasts, or other works or subject matter other than works listed
|
||||||
|
in Section 1(f) below, which, by reason of the selection and
|
||||||
|
arrangement of their contents, constitute intellectual creations, in
|
||||||
|
which the Work is included in its entirety in unmodified form along
|
||||||
|
with one or more other contributions, each constituting separate and
|
||||||
|
independent works in themselves, which together are assembled into a
|
||||||
|
collective whole. A work that constitutes a Collection will not be
|
||||||
|
considered an Adaptation (as defined below) for the purposes of this
|
||||||
|
License.
|
||||||
|
c. "Creative Commons Compatible License" means a license that is listed
|
||||||
|
at https://creativecommons.org/compatiblelicenses that has been
|
||||||
|
approved by Creative Commons as being essentially equivalent to this
|
||||||
|
License, including, at a minimum, because that license: (i) contains
|
||||||
|
terms that have the same purpose, meaning and effect as the License
|
||||||
|
Elements of this License; and, (ii) explicitly permits the relicensing
|
||||||
|
of adaptations of works made available under that license under this
|
||||||
|
License or a Creative Commons jurisdiction license with the same
|
||||||
|
License Elements as this License.
|
||||||
|
d. "Distribute" means to make available to the public the original and
|
||||||
|
copies of the Work or Adaptation, as appropriate, through sale or
|
||||||
|
other transfer of ownership.
|
||||||
|
e. "License Elements" means the following high-level license attributes
|
||||||
|
as selected by Licensor and indicated in the title of this License:
|
||||||
|
Attribution, ShareAlike.
|
||||||
|
f. "Licensor" means the individual, individuals, entity or entities that
|
||||||
|
offer(s) the Work under the terms of this License.
|
||||||
|
g. "Original Author" means, in the case of a literary or artistic work,
|
||||||
|
the individual, individuals, entity or entities who created the Work
|
||||||
|
or if no individual or entity can be identified, the publisher; and in
|
||||||
|
addition (i) in the case of a performance the actors, singers,
|
||||||
|
musicians, dancers, and other persons who act, sing, deliver, declaim,
|
||||||
|
play in, interpret or otherwise perform literary or artistic works or
|
||||||
|
expressions of folklore; (ii) in the case of a phonogram the producer
|
||||||
|
being the person or legal entity who first fixes the sounds of a
|
||||||
|
performance or other sounds; and, (iii) in the case of broadcasts, the
|
||||||
|
organization that transmits the broadcast.
|
||||||
|
h. "Work" means the literary and/or artistic work offered under the terms
|
||||||
|
of this License including without limitation any production in the
|
||||||
|
literary, scientific and artistic domain, whatever may be the mode or
|
||||||
|
form of its expression including digital form, such as a book,
|
||||||
|
pamphlet and other writing; a lecture, address, sermon or other work
|
||||||
|
of the same nature; a dramatic or dramatico-musical work; a
|
||||||
|
choreographic work or entertainment in dumb show; a musical
|
||||||
|
composition with or without words; a cinematographic work to which are
|
||||||
|
assimilated works expressed by a process analogous to cinematography;
|
||||||
|
a work of drawing, painting, architecture, sculpture, engraving or
|
||||||
|
lithography; a photographic work to which are assimilated works
|
||||||
|
expressed by a process analogous to photography; a work of applied
|
||||||
|
art; an illustration, map, plan, sketch or three-dimensional work
|
||||||
|
relative to geography, topography, architecture or science; a
|
||||||
|
performance; a broadcast; a phonogram; a compilation of data to the
|
||||||
|
extent it is protected as a copyrightable work; or a work performed by
|
||||||
|
a variety or circus performer to the extent it is not otherwise
|
||||||
|
considered a literary or artistic work.
|
||||||
|
i. "You" means an individual or entity exercising rights under this
|
||||||
|
License who has not previously violated the terms of this License with
|
||||||
|
respect to the Work, or who has received express permission from the
|
||||||
|
Licensor to exercise rights under this License despite a previous
|
||||||
|
violation.
|
||||||
|
j. "Publicly Perform" means to perform public recitations of the Work and
|
||||||
|
to communicate to the public those public recitations, by any means or
|
||||||
|
process, including by wire or wireless means or public digital
|
||||||
|
performances; to make available to the public Works in such a way that
|
||||||
|
members of the public may access these Works from a place and at a
|
||||||
|
place individually chosen by them; to perform the Work to the public
|
||||||
|
by any means or process and the communication to the public of the
|
||||||
|
performances of the Work, including by public digital performance; to
|
||||||
|
broadcast and rebroadcast the Work by any means including signs,
|
||||||
|
sounds or images.
|
||||||
|
k. "Reproduce" means to make copies of the Work by any means including
|
||||||
|
without limitation by sound or visual recordings and the right of
|
||||||
|
fixation and reproducing fixations of the Work, including storage of a
|
||||||
|
protected performance or phonogram in digital form or other electronic
|
||||||
|
medium.
|
||||||
|
|
||||||
|
2. Fair Dealing Rights. Nothing in this License is intended to reduce,
|
||||||
|
limit, or restrict any uses free from copyright or rights arising from
|
||||||
|
limitations or exceptions that are provided for in connection with the
|
||||||
|
copyright protection under copyright law or other applicable laws.
|
||||||
|
|
||||||
|
3. License Grant. Subject to the terms and conditions of this License,
|
||||||
|
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
|
||||||
|
perpetual (for the duration of the applicable copyright) license to
|
||||||
|
exercise the rights in the Work as stated below:
|
||||||
|
|
||||||
|
a. to Reproduce the Work, to incorporate the Work into one or more
|
||||||
|
Collections, and to Reproduce the Work as incorporated in the
|
||||||
|
Collections;
|
||||||
|
b. to create and Reproduce Adaptations provided that any such Adaptation,
|
||||||
|
including any translation in any medium, takes reasonable steps to
|
||||||
|
clearly label, demarcate or otherwise identify that changes were made
|
||||||
|
to the original Work. For example, a translation could be marked "The
|
||||||
|
original work was translated from English to Spanish," or a
|
||||||
|
modification could indicate "The original work has been modified.";
|
||||||
|
c. to Distribute and Publicly Perform the Work including as incorporated
|
||||||
|
in Collections; and,
|
||||||
|
d. to Distribute and Publicly Perform Adaptations.
|
||||||
|
e. For the avoidance of doubt:
|
||||||
|
|
||||||
|
i. Non-waivable Compulsory License Schemes. In those jurisdictions in
|
||||||
|
which the right to collect royalties through any statutory or
|
||||||
|
compulsory licensing scheme cannot be waived, the Licensor
|
||||||
|
reserves the exclusive right to collect such royalties for any
|
||||||
|
exercise by You of the rights granted under this License;
|
||||||
|
ii. Waivable Compulsory License Schemes. In those jurisdictions in
|
||||||
|
which the right to collect royalties through any statutory or
|
||||||
|
compulsory licensing scheme can be waived, the Licensor waives the
|
||||||
|
exclusive right to collect such royalties for any exercise by You
|
||||||
|
of the rights granted under this License; and,
|
||||||
|
iii. Voluntary License Schemes. The Licensor waives the right to
|
||||||
|
collect royalties, whether individually or, in the event that the
|
||||||
|
Licensor is a member of a collecting society that administers
|
||||||
|
voluntary licensing schemes, via that society, from any exercise
|
||||||
|
by You of the rights granted under this License.
|
||||||
|
|
||||||
|
The above rights may be exercised in all media and formats whether now
|
||||||
|
known or hereafter devised. The above rights include the right to make
|
||||||
|
such modifications as are technically necessary to exercise the rights in
|
||||||
|
other media and formats. Subject to Section 8(f), all rights not expressly
|
||||||
|
granted by Licensor are hereby reserved.
|
||||||
|
|
||||||
|
4. Restrictions. The license granted in Section 3 above is expressly made
|
||||||
|
subject to and limited by the following restrictions:
|
||||||
|
|
||||||
|
a. You may Distribute or Publicly Perform the Work only under the terms
|
||||||
|
of this License. You must include a copy of, or the Uniform Resource
|
||||||
|
Identifier (URI) for, this License with every copy of the Work You
|
||||||
|
Distribute or Publicly Perform. You may not offer or impose any terms
|
||||||
|
on the Work that restrict the terms of this License or the ability of
|
||||||
|
the recipient of the Work to exercise the rights granted to that
|
||||||
|
recipient under the terms of the License. You may not sublicense the
|
||||||
|
Work. You must keep intact all notices that refer to this License and
|
||||||
|
to the disclaimer of warranties with every copy of the Work You
|
||||||
|
Distribute or Publicly Perform. When You Distribute or Publicly
|
||||||
|
Perform the Work, You may not impose any effective technological
|
||||||
|
measures on the Work that restrict the ability of a recipient of the
|
||||||
|
Work from You to exercise the rights granted to that recipient under
|
||||||
|
the terms of the License. This Section 4(a) applies to the Work as
|
||||||
|
incorporated in a Collection, but this does not require the Collection
|
||||||
|
apart from the Work itself to be made subject to the terms of this
|
||||||
|
License. If You create a Collection, upon notice from any Licensor You
|
||||||
|
must, to the extent practicable, remove from the Collection any credit
|
||||||
|
as required by Section 4(c), as requested. If You create an
|
||||||
|
Adaptation, upon notice from any Licensor You must, to the extent
|
||||||
|
practicable, remove from the Adaptation any credit as required by
|
||||||
|
Section 4(c), as requested.
|
||||||
|
b. You may Distribute or Publicly Perform an Adaptation only under the
|
||||||
|
terms of: (i) this License; (ii) a later version of this License with
|
||||||
|
the same License Elements as this License; (iii) a Creative Commons
|
||||||
|
jurisdiction license (either this or a later license version) that
|
||||||
|
contains the same License Elements as this License (e.g.,
|
||||||
|
Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible
|
||||||
|
License. If you license the Adaptation under one of the licenses
|
||||||
|
mentioned in (iv), you must comply with the terms of that license. If
|
||||||
|
you license the Adaptation under the terms of any of the licenses
|
||||||
|
mentioned in (i), (ii) or (iii) (the "Applicable License"), you must
|
||||||
|
comply with the terms of the Applicable License generally and the
|
||||||
|
following provisions: (I) You must include a copy of, or the URI for,
|
||||||
|
the Applicable License with every copy of each Adaptation You
|
||||||
|
Distribute or Publicly Perform; (II) You may not offer or impose any
|
||||||
|
terms on the Adaptation that restrict the terms of the Applicable
|
||||||
|
License or the ability of the recipient of the Adaptation to exercise
|
||||||
|
the rights granted to that recipient under the terms of the Applicable
|
||||||
|
License; (III) You must keep intact all notices that refer to the
|
||||||
|
Applicable License and to the disclaimer of warranties with every copy
|
||||||
|
of the Work as included in the Adaptation You Distribute or Publicly
|
||||||
|
Perform; (IV) when You Distribute or Publicly Perform the Adaptation,
|
||||||
|
You may not impose any effective technological measures on the
|
||||||
|
Adaptation that restrict the ability of a recipient of the Adaptation
|
||||||
|
from You to exercise the rights granted to that recipient under the
|
||||||
|
terms of the Applicable License. This Section 4(b) applies to the
|
||||||
|
Adaptation as incorporated in a Collection, but this does not require
|
||||||
|
the Collection apart from the Adaptation itself to be made subject to
|
||||||
|
the terms of the Applicable License.
|
||||||
|
c. If You Distribute, or Publicly Perform the Work or any Adaptations or
|
||||||
|
Collections, You must, unless a request has been made pursuant to
|
||||||
|
Section 4(a), keep intact all copyright notices for the Work and
|
||||||
|
provide, reasonable to the medium or means You are utilizing: (i) the
|
||||||
|
name of the Original Author (or pseudonym, if applicable) if supplied,
|
||||||
|
and/or if the Original Author and/or Licensor designate another party
|
||||||
|
or parties (e.g., a sponsor institute, publishing entity, journal) for
|
||||||
|
attribution ("Attribution Parties") in Licensor's copyright notice,
|
||||||
|
terms of service or by other reasonable means, the name of such party
|
||||||
|
or parties; (ii) the title of the Work if supplied; (iii) to the
|
||||||
|
extent reasonably practicable, the URI, if any, that Licensor
|
||||||
|
specifies to be associated with the Work, unless such URI does not
|
||||||
|
refer to the copyright notice or licensing information for the Work;
|
||||||
|
and (iv) , consistent with Ssection 3(b), in the case of an
|
||||||
|
Adaptation, a credit identifying the use of the Work in the Adaptation
|
||||||
|
(e.g., "French translation of the Work by Original Author," or
|
||||||
|
"Screenplay based on original Work by Original Author"). The credit
|
||||||
|
required by this Section 4(c) may be implemented in any reasonable
|
||||||
|
manner; provided, however, that in the case of a Adaptation or
|
||||||
|
Collection, at a minimum such credit will appear, if a credit for all
|
||||||
|
contributing authors of the Adaptation or Collection appears, then as
|
||||||
|
part of these credits and in a manner at least as prominent as the
|
||||||
|
credits for the other contributing authors. For the avoidance of
|
||||||
|
doubt, You may only use the credit required by this Section for the
|
||||||
|
purpose of attribution in the manner set out above and, by exercising
|
||||||
|
Your rights under this License, You may not implicitly or explicitly
|
||||||
|
assert or imply any connection with, sponsorship or endorsement by the
|
||||||
|
Original Author, Licensor and/or Attribution Parties, as appropriate,
|
||||||
|
of You or Your use of the Work, without the separate, express prior
|
||||||
|
written permission of the Original Author, Licensor and/or Attribution
|
||||||
|
Parties.
|
||||||
|
d. Except as otherwise agreed in writing by the Licensor or as may be
|
||||||
|
otherwise permitted by applicable law, if You Reproduce, Distribute or
|
||||||
|
Publicly Perform the Work either by itself or as part of any
|
||||||
|
Adaptations or Collections, You must not distort, mutilate, modify or
|
||||||
|
take other derogatory action in relation to the Work which would be
|
||||||
|
prejudicial to the Original Author's honor or reputation. Licensor
|
||||||
|
agrees that in those jurisdictions (e.g. Japan), in which any exercise
|
||||||
|
of the right granted in Section 3(b) of this License (the right to
|
||||||
|
make Adaptations) would be deemed to be a distortion, mutilation,
|
||||||
|
modification or other derogatory action prejudicial to the Original
|
||||||
|
Author's honor and reputation, the Licensor will waive or not assert,
|
||||||
|
as appropriate, this Section, to the fullest extent permitted by the
|
||||||
|
applicable national law, to enable You to reasonably exercise Your
|
||||||
|
right under Section 3(b) of this License (right to make Adaptations)
|
||||||
|
but not otherwise.
|
||||||
|
|
||||||
|
5. Representations, Warranties and Disclaimer
|
||||||
|
|
||||||
|
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR
|
||||||
|
OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
|
||||||
|
KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
|
||||||
|
INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
|
||||||
|
LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
|
||||||
|
WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
|
||||||
|
OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
|
||||||
|
|
||||||
|
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
|
||||||
|
LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
|
||||||
|
ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
|
||||||
|
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
|
||||||
|
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
7. Termination
|
||||||
|
|
||||||
|
a. This License and the rights granted hereunder will terminate
|
||||||
|
automatically upon any breach by You of the terms of this License.
|
||||||
|
Individuals or entities who have received Adaptations or Collections
|
||||||
|
from You under this License, however, will not have their licenses
|
||||||
|
terminated provided such individuals or entities remain in full
|
||||||
|
compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
|
||||||
|
survive any termination of this License.
|
||||||
|
b. Subject to the above terms and conditions, the license granted here is
|
||||||
|
perpetual (for the duration of the applicable copyright in the Work).
|
||||||
|
Notwithstanding the above, Licensor reserves the right to release the
|
||||||
|
Work under different license terms or to stop distributing the Work at
|
||||||
|
any time; provided, however that any such election will not serve to
|
||||||
|
withdraw this License (or any other license that has been, or is
|
||||||
|
required to be, granted under the terms of this License), and this
|
||||||
|
License will continue in full force and effect unless terminated as
|
||||||
|
stated above.
|
||||||
|
|
||||||
|
8. Miscellaneous
|
||||||
|
|
||||||
|
a. Each time You Distribute or Publicly Perform the Work or a Collection,
|
||||||
|
the Licensor offers to the recipient a license to the Work on the same
|
||||||
|
terms and conditions as the license granted to You under this License.
|
||||||
|
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
|
||||||
|
offers to the recipient a license to the original Work on the same
|
||||||
|
terms and conditions as the license granted to You under this License.
|
||||||
|
c. If any provision of this License is invalid or unenforceable under
|
||||||
|
applicable law, it shall not affect the validity or enforceability of
|
||||||
|
the remainder of the terms of this License, and without further action
|
||||||
|
by the parties to this agreement, such provision shall be reformed to
|
||||||
|
the minimum extent necessary to make such provision valid and
|
||||||
|
enforceable.
|
||||||
|
d. No term or provision of this License shall be deemed waived and no
|
||||||
|
breach consented to unless such waiver or consent shall be in writing
|
||||||
|
and signed by the party to be charged with such waiver or consent.
|
||||||
|
e. This License constitutes the entire agreement between the parties with
|
||||||
|
respect to the Work licensed here. There are no understandings,
|
||||||
|
agreements or representations with respect to the Work not specified
|
||||||
|
here. Licensor shall not be bound by any additional provisions that
|
||||||
|
may appear in any communication from You. This License may not be
|
||||||
|
modified without the mutual written agreement of the Licensor and You.
|
||||||
|
f. The rights granted under, and the subject matter referenced, in this
|
||||||
|
License were drafted utilizing the terminology of the Berne Convention
|
||||||
|
for the Protection of Literary and Artistic Works (as amended on
|
||||||
|
September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
|
||||||
|
Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
|
||||||
|
and the Universal Copyright Convention (as revised on July 24, 1971).
|
||||||
|
These rights and subject matter take effect in the relevant
|
||||||
|
jurisdiction in which the License terms are sought to be enforced
|
||||||
|
according to the corresponding provisions of the implementation of
|
||||||
|
those treaty provisions in the applicable national law. If the
|
||||||
|
standard suite of rights granted under applicable copyright law
|
||||||
|
includes additional rights not granted under this License, such
|
||||||
|
additional rights are deemed to be included in the License; this
|
||||||
|
License is not intended to restrict the license of any rights under
|
||||||
|
applicable law.
|
||||||
|
|
||||||
|
|
||||||
|
Creative Commons Notice
|
||||||
|
|
||||||
|
Creative Commons is not a party to this License, and makes no warranty
|
||||||
|
whatsoever in connection with the Work. Creative Commons will not be
|
||||||
|
liable to You or any party on any legal theory for any damages
|
||||||
|
whatsoever, including without limitation any general, special,
|
||||||
|
incidental or consequential damages arising in connection to this
|
||||||
|
license. Notwithstanding the foregoing two (2) sentences, if Creative
|
||||||
|
Commons has expressly identified itself as the Licensor hereunder, it
|
||||||
|
shall have all rights and obligations of Licensor.
|
||||||
|
|
||||||
|
Except for the limited purpose of indicating to the public that the
|
||||||
|
Work is licensed under the CCPL, Creative Commons does not authorize
|
||||||
|
the use by either party of the trademark "Creative Commons" or any
|
||||||
|
related trademark or logo of Creative Commons without the prior
|
||||||
|
written consent of Creative Commons. Any permitted use will be in
|
||||||
|
compliance with Creative Commons' then-current trademark usage
|
||||||
|
guidelines, as may be published on its website or otherwise made
|
||||||
|
available upon request from time to time. For the avoidance of doubt,
|
||||||
|
this trademark restriction does not form part of the License.
|
||||||
|
|
||||||
|
Creative Commons may be contacted at https://creativecommons.org/.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# 16x16 RPG characters `v3.0`
|
||||||
|
|
||||||
|
16x16px RPG character sprite sheet for up to down games.
|
||||||
|
|
||||||
|
This asset pack has been downloaded from https://route1rodent.itch.io/16x16-rpg-character-sprite-sheet
|
||||||
|
|
||||||
|
Adapted from opengameart.org 's "[NES-Style RPG Characters](https://opengameart.org/content/nes-style-rpg-characters)" and "[More NES-style RPG Characters](https://opengameart.org/content/more-nes-style-rpg-characters)", with additional content made by [@route1rodent](https://route1rodent.itch.io).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
16x16 RPG character sprite sheet (c) by @route1rodent
|
||||||
|
|
||||||
|
"16x16 RPG character sprite sheet" is licensed under a
|
||||||
|
Creative Commons Attribution-ShareAlike 3.0 Unported License (CC BY-SA 3.0).
|
||||||
|
|
||||||
|
You should have received a copy of the license along with this
|
||||||
|
work. If not, see http://creativecommons.org/licenses/by-sa/3.0/.
|
||||||
|
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
Maintained by @route1rodent:
|
||||||
|
|
||||||
|
- itch.io: https://route1rodent.itch.io
|
||||||
|
- Twitter: https://twitter.com/route1rodent
|
||||||
|
- Github: https://github.com/itsjavi
|
||||||
|
- Blog: https://blog.itsjavi.com
|
||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,208 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""pop_extract_font.py — SDLPoP-шрифты в один Sprinter .atl-ресурс.
|
||||||
|
|
||||||
|
Источник истины — оригинальные встроенные шрифты SDLPoP:
|
||||||
|
* hc_small_font_data[] из menu.c — пункты меню;
|
||||||
|
* hc_font_data[] из seg009.c — крупные сообщения/заголовки.
|
||||||
|
|
||||||
|
В исходнике глиф — IMAGE_DATA(height, width, flags) и 1-битные строки.
|
||||||
|
Здесь его силуэт переводится в getimage (0xFF = прозрачность) и пакуется в
|
||||||
|
FONT\\FONT.ATL. Это именно шрифтовой атлас, а не заранее нарисованные
|
||||||
|
строки: номер ленты = код ASCII, поэтому runtime сохраняет произвольный
|
||||||
|
текст и пропорциональную ширину оригинала.
|
||||||
|
|
||||||
|
Хвост страницы [0x3C00..0x3FFF] намеренно оставлен свободным: pop_menu.c
|
||||||
|
кладёт туда снимок игровой палитры на время паузы. Тогда 1 КБ не занимает
|
||||||
|
дефицитную W2.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
SRC_SMALL = ROOT / "SDLPoP" / "src" / "menu.c"
|
||||||
|
SRC_BIG = ROOT / "SDLPoP" / "src" / "seg009.c"
|
||||||
|
OUT_DIR = ROOT / "poc" / "res" / "font"
|
||||||
|
OUT_ATL = OUT_DIR / "font.atl"
|
||||||
|
OUT_H = ROOT / "roomtest" / "pop_font.h"
|
||||||
|
|
||||||
|
FIRST, LAST = 32, 126
|
||||||
|
FONT_COLOR = 0x3F
|
||||||
|
# ВТОРОЙ ЦВЕТ КРУПНОГО ШРИФТА. У SDLPoP шрифт — маска, и цвет задаётся
|
||||||
|
# параметром рисования (show_hof_text рисует текст дважды: тень цветом 0 со
|
||||||
|
# сдвигом (1,1) и сам текст цветом 0xB7). У нас цвет запечён в пиксели
|
||||||
|
# глифа, поэтому второй цвет — это второй набор глифов. Он нужен таблице
|
||||||
|
# рекордов: чёрная тень строк и тёмное имя на золотой полосе ввода.
|
||||||
|
# Набор крупный, потому что draw_text() рисует hc_font (seg009:1594).
|
||||||
|
FONT_DARK_COLOR = 0x3E
|
||||||
|
# Каталог SPA1 хранит число картинок одним байтом, а три полных набора по
|
||||||
|
# 95 глифов — это 285. Тёмному набору полный ASCII и не нужен: им пишется
|
||||||
|
# только таблица рекордов (имена A..Z, время вида 59:43 и курсор ввода
|
||||||
|
# '_'), поэтому он обрезан по '_'. 190 + 64 = 254 картинки — потолок.
|
||||||
|
DARK_FIRST, DARK_LAST = 32, 95
|
||||||
|
ATL_MAGIC = b"SPA1"
|
||||||
|
ATL_DIR_OFF = 0x68
|
||||||
|
SNAPSHOT_OFF = 0x3C00
|
||||||
|
PAGE_SIZE = 16384
|
||||||
|
|
||||||
|
|
||||||
|
def extract_array(path: Path, name: str) -> list[int]:
|
||||||
|
text = path.read_text()
|
||||||
|
match = re.search(rf"byte {name}\[\] = \{{(.*?)\n\}};", text, re.S)
|
||||||
|
assert match, f"{name} not found in {path}"
|
||||||
|
body = re.sub(r"//[^\n]*", "", match.group(1))
|
||||||
|
body = re.sub(r"/\*.*?\*/", "", body, flags=re.S)
|
||||||
|
|
||||||
|
def word(mm: re.Match) -> str:
|
||||||
|
value = int(mm.group(1), 0)
|
||||||
|
return f"{value & 0xFF}, {(value >> 8) & 0xFF}"
|
||||||
|
|
||||||
|
def bin4(mm: re.Match) -> str:
|
||||||
|
b7, b6, b5, b4 = (int(v, 0) if v != "_" else 0 for v in mm.groups())
|
||||||
|
return str((b4 << 4) | (b5 << 5) | (b6 << 6) | (b7 << 7))
|
||||||
|
|
||||||
|
def bin8(mm: re.Match) -> str:
|
||||||
|
bits = [int(v, 0) if v != "_" else 0 for v in mm.groups()]
|
||||||
|
return str(sum(bit << (7 - i) for i, bit in enumerate(bits)))
|
||||||
|
|
||||||
|
def image_data(mm: re.Match) -> str:
|
||||||
|
h, w, flags = (int(v, 0) for v in mm.groups())
|
||||||
|
return (f"{h & 0xFF}, {(h >> 8) & 0xFF}, "
|
||||||
|
f"{w & 0xFF}, {(w >> 8) & 0xFF}, "
|
||||||
|
f"{flags & 0xFF}, {(flags >> 8) & 0xFF}")
|
||||||
|
|
||||||
|
body = re.sub(r"\bWORD\s*\(\s*(\w+)\s*\)", word, body)
|
||||||
|
body = re.sub(r"\bIMAGE_DATA\s*\(\s*(\w+)\s*,\s*(\w+)\s*,\s*(\w+)\s*\)",
|
||||||
|
image_data, body)
|
||||||
|
body = re.sub(r"\bBINARY_4\s*\(\s*([_\dxXa-fA-F]+)\s*,\s*([_\dxXa-fA-F]+)\s*,\s*"
|
||||||
|
r"([_\dxXa-fA-F]+)\s*,\s*([_\dxXa-fA-F]+)\s*\)", bin4, body)
|
||||||
|
body = re.sub(r"\bBINARY_8\s*\(\s*([_\dxXa-fA-F]+)\s*,\s*([_\dxXa-fA-F]+)\s*,\s*"
|
||||||
|
r"([_\dxXa-fA-F]+)\s*,\s*([_\dxXa-fA-F]+)\s*,\s*([_\dxXa-fA-F]+)\s*,\s*"
|
||||||
|
r"([_\dxXa-fA-F]+)\s*,\s*([_\dxXa-fA-F]+)\s*,\s*([_\dxXa-fA-F]+)\s*\)", bin8, body)
|
||||||
|
body = re.sub(r"\b_\b", "0", body)
|
||||||
|
# Hex обязан идти первым: иначе regexp съедает в `0x20` только `0`.
|
||||||
|
return [int(v, 0) for v in re.findall(r"0[xX][0-9a-fA-F]+|\d+", body)]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_glyphs(vals: list[int]) -> tuple[dict, list[dict]]:
|
||||||
|
first, last = vals[0], vals[1]
|
||||||
|
font = {
|
||||||
|
"first": first,
|
||||||
|
"last": last,
|
||||||
|
"ascent": vals[2] | (vals[3] << 8),
|
||||||
|
"below": vals[4] | (vals[5] << 8),
|
||||||
|
"space_lines": vals[6] | (vals[7] << 8),
|
||||||
|
"space_chars": vals[8] | (vals[9] << 8),
|
||||||
|
}
|
||||||
|
pos = 10 + (last - first + 1) * 2 # offsets у SDLPoP вычисляются в runtime
|
||||||
|
glyphs = []
|
||||||
|
while pos < len(vals):
|
||||||
|
h = vals[pos] | (vals[pos + 1] << 8)
|
||||||
|
w = vals[pos + 2] | (vals[pos + 3] << 8)
|
||||||
|
pos += 6 # height, width, flags
|
||||||
|
stride = (w + 7) // 8
|
||||||
|
rows = []
|
||||||
|
for _ in range(h):
|
||||||
|
rows.append(vals[pos:pos + stride])
|
||||||
|
pos += stride
|
||||||
|
glyphs.append({"h": h, "w": w, "rows": rows})
|
||||||
|
assert len(glyphs) == last - first + 1, f"glyph count {len(glyphs)}"
|
||||||
|
return font, glyphs
|
||||||
|
|
||||||
|
|
||||||
|
def glyph_pixels(glyph: dict, color: int) -> bytes:
|
||||||
|
"""1 бит/пиксель SDLPoP -> 8bpp getimage: 1 = color."""
|
||||||
|
out = bytearray()
|
||||||
|
for row in glyph["rows"]:
|
||||||
|
for x in range(glyph["w"]):
|
||||||
|
byte = row[x // 8]
|
||||||
|
on = byte & (1 << (7 - (x & 7)))
|
||||||
|
out.append(color if on else 0xFF)
|
||||||
|
return bytes(out)
|
||||||
|
|
||||||
|
|
||||||
|
def pack_atlas(fonts: list[tuple[dict, list[dict], int]]) -> int:
|
||||||
|
entries = []
|
||||||
|
for font, glyphs, color, first, last in fonts:
|
||||||
|
assert font["first"] <= first <= last <= font["last"]
|
||||||
|
entries.extend((glyph, color) for glyph in
|
||||||
|
glyphs[first - font["first"]:last - font["first"] + 1])
|
||||||
|
assert len(entries) <= 255
|
||||||
|
|
||||||
|
data_off = ATL_DIR_OFF + len(entries) * 8
|
||||||
|
out = bytearray(data_off)
|
||||||
|
out[0:4] = ATL_MAGIC
|
||||||
|
out[4] = len(entries)
|
||||||
|
for index, (glyph, color) in enumerate(entries):
|
||||||
|
offset = len(out)
|
||||||
|
pixels = glyph_pixels(glyph, color)
|
||||||
|
out += glyph["w"].to_bytes(2, "little")
|
||||||
|
out += glyph["h"].to_bytes(2, "little")
|
||||||
|
out += pixels
|
||||||
|
directory = ATL_DIR_OFF + index * 8
|
||||||
|
out[directory:directory + 2] = offset.to_bytes(2, "little")
|
||||||
|
out[directory + 2] = glyph["w"]
|
||||||
|
out[directory + 3] = glyph["h"]
|
||||||
|
out[directory + 4] = 1
|
||||||
|
out[directory + 5] = 1
|
||||||
|
|
||||||
|
if len(out) > SNAPSHOT_OFF:
|
||||||
|
raise SystemExit(f"font atlas: {len(out)} Б, нет места для palette snapshot @ {SNAPSHOT_OFF:#x}")
|
||||||
|
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
OUT_ATL.write_bytes(out)
|
||||||
|
return len(out)
|
||||||
|
|
||||||
|
|
||||||
|
def emit_header(small: dict, big: dict, atlas_size: int) -> None:
|
||||||
|
small_n = LAST - FIRST + 1
|
||||||
|
text = f"""/*
|
||||||
|
* pop_font.h — контракт FONT\\FONT.ATL, СГЕНЕРИРОВАН.
|
||||||
|
* Источник: SDLPoP hc_small_font_data[] и hc_font_data[].
|
||||||
|
* Генератор: toolchain/pop_extract_font.py. Руками не править.
|
||||||
|
*/
|
||||||
|
#ifndef POP_FONT_H
|
||||||
|
#define POP_FONT_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define POP_FONT_COLOR 0x{FONT_COLOR:02X}
|
||||||
|
#define POP_FONT_DARK_COLOR 0x{FONT_DARK_COLOR:02X}
|
||||||
|
#define POP_FONT_FIRST {FIRST}
|
||||||
|
#define POP_FONT_LAST {LAST}
|
||||||
|
#define POP_FONT_SMALL_BASE 0
|
||||||
|
#define POP_FONT_SMALL_ASCENT {small['ascent']}
|
||||||
|
#define POP_FONT_SMALL_SPACE {small['space_chars']}
|
||||||
|
#define POP_FONT_BIG_BASE {small_n}
|
||||||
|
#define POP_FONT_BIG_ASCENT {big['ascent']}
|
||||||
|
#define POP_FONT_BIG_SPACE {big['space_chars']}
|
||||||
|
/* Тот же КРУПНЫЙ шрифт цветом POP_FONT_DARK_COLOR: тень и текст на светлом.
|
||||||
|
* Набор обрезан по '_' — им пишется только таблица рекордов. */
|
||||||
|
#define POP_FONT_DARK_BASE {2 * small_n}
|
||||||
|
#define POP_FONT_DARK_FIRST {DARK_FIRST}
|
||||||
|
#define POP_FONT_DARK_LAST {DARK_LAST}
|
||||||
|
#define POP_FONT_SNAPSHOT_OFF 0x{SNAPSHOT_OFF:04X}
|
||||||
|
#define POP_FONT_ATLAS_SIZE {atlas_size}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
"""
|
||||||
|
OUT_H.write_text(text)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parsed = []
|
||||||
|
for path, name in ((SRC_SMALL, "hc_small_font_data"), (SRC_BIG, "hc_font_data")):
|
||||||
|
font, glyphs = parse_glyphs(extract_array(path, name))
|
||||||
|
print(f"{name}: chars={font['first']}..{font['last']} ascent={font['ascent']} "
|
||||||
|
f"space={font['space_chars']}")
|
||||||
|
parsed.append((font, glyphs))
|
||||||
|
sets = [(parsed[0][0], parsed[0][1], FONT_COLOR, FIRST, LAST),
|
||||||
|
(parsed[1][0], parsed[1][1], FONT_COLOR, FIRST, LAST),
|
||||||
|
(parsed[1][0], parsed[1][1], FONT_DARK_COLOR, DARK_FIRST, DARK_LAST)]
|
||||||
|
atlas_size = pack_atlas(sets)
|
||||||
|
emit_header(parsed[0][0], parsed[1][0], atlas_size)
|
||||||
|
print(f"{OUT_ATL}: 2 x {LAST - FIRST + 1} + {DARK_LAST - DARK_FIRST + 1} glyphs, "
|
||||||
|
f"{atlas_size} Б; "
|
||||||
|
f"snapshot from 0x{SNAPSHOT_OFF:04X}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,349 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Упаковка story/PV intro SDLPoP в полосы SPA1 для Sprinter.
|
||||||
|
|
||||||
|
Исходники остаются единственным источником истины: TITLE/res41..45, res51 и
|
||||||
|
PV/res800/850/950/980. Runtime получает только готовые 320x200 композиции
|
||||||
|
и 256-цветные палитры. Это намеренно переносит смешивание палитров Princess,
|
||||||
|
Jaffar, спальни и кровати из ограниченного Z80 в build-time.
|
||||||
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
TITLE = ROOT / "SDLPoP" / "data" / "TITLE"
|
||||||
|
PV = ROOT / "SDLPoP" / "data" / "PV"
|
||||||
|
PRINCE = ROOT / "SDLPoP" / "data" / "PRINCE"
|
||||||
|
KID = ROOT / "SDLPoP" / "data" / "KID"
|
||||||
|
OUT = ROOT / "poc" / "res" / "pv"
|
||||||
|
|
||||||
|
ATL_MAGIC = b"SPA1"
|
||||||
|
ATL_DIR_OFF = 0x68
|
||||||
|
ATL_MIN_SIZE = 0x100
|
||||||
|
PAGE_SIZE = 16384
|
||||||
|
STRIP_H = 49
|
||||||
|
|
||||||
|
# ФОН ТЕКСТОВОЙ РАМКИ — ОТДЕЛЬНЫЙ ИНДЕКС. У оригинала это индекс 14 в
|
||||||
|
# строке палитры спрайтов title40, и load_title_images() красит его либо в
|
||||||
|
# #100060 (интро), либо в #800000 (финал, seg001:586). Раньше мы
|
||||||
|
# ремапили 14 в 9 — «тёмно-синий» палитры res51; подменить его нельзя,
|
||||||
|
# потому что тем же индексом нарисована сама титульная картинка (8265
|
||||||
|
# пикселей). Поэтому фон уезжает в свободный индекс, и финал красит
|
||||||
|
# только его (pop_pal_story_ending).
|
||||||
|
STORY_BG_INDEX = 16
|
||||||
|
STORY_BG_COLOR = (0x10, 0x00, 0x60)
|
||||||
|
# Цвет текста таблицы рекордов: 0xB7 оригинала = седьмой цвет палитры
|
||||||
|
# title (224,160,0). Глифы шрифта запечены индексом POP_FONT_COLOR, так
|
||||||
|
# что цвет им даёт именно эта запись; тёмный набор (POP_FONT_DARK_COLOR)
|
||||||
|
# остаётся чёрным — старшие записи story.pal и так чёрные.
|
||||||
|
STORY_FONT_INDEX = 0x3F
|
||||||
|
|
||||||
|
|
||||||
|
def transpose_cols(width: int, height: int, rowmajor: bytes) -> bytes:
|
||||||
|
"""Row-major -> column-major для бесплатного horizontal flip в BGI."""
|
||||||
|
out = bytearray(width * height)
|
||||||
|
for column in range(width):
|
||||||
|
base = column * height
|
||||||
|
for row in range(height):
|
||||||
|
out[base + row] = rowmajor[row * width + column]
|
||||||
|
return bytes(out)
|
||||||
|
|
||||||
|
|
||||||
|
def atlas_blob(pixels: bytes, width: int, height: int) -> bytes:
|
||||||
|
"""Один непрозрачный getimage в минимальном SPA1-атласе."""
|
||||||
|
data_off = ATL_DIR_OFF + 8
|
||||||
|
out = bytearray(data_off)
|
||||||
|
out[:4] = ATL_MAGIC
|
||||||
|
out[4] = 1
|
||||||
|
out[ATL_DIR_OFF:ATL_DIR_OFF + 2] = data_off.to_bytes(2, "little")
|
||||||
|
out[ATL_DIR_OFF + 2] = min(width, 255)
|
||||||
|
out[ATL_DIR_OFF + 3] = height
|
||||||
|
out[ATL_DIR_OFF + 4] = out[ATL_DIR_OFF + 5] = 1
|
||||||
|
out += width.to_bytes(2, "little") + height.to_bytes(2, "little")
|
||||||
|
out += pixels
|
||||||
|
if len(out) < ATL_MIN_SIZE:
|
||||||
|
out += bytes(ATL_MIN_SIZE - len(out))
|
||||||
|
if len(out) > PAGE_SIZE:
|
||||||
|
raise SystemExit(f"intro atlas {len(out)} B > {PAGE_SIZE} B")
|
||||||
|
return bytes(out)
|
||||||
|
|
||||||
|
|
||||||
|
def sprite_atlas(path: Path, entries: dict[int, tuple[int, int, bytes]]) -> None:
|
||||||
|
"""Атлас небольших прозрачных кадров, целиком живущий в одной EMM.
|
||||||
|
|
||||||
|
В отличие от atlas_blob() для полноэкранной полосы, здесь нужен каталог
|
||||||
|
нескольких картинок: runtime держит страницу открытой всю PV-сцену и
|
||||||
|
переключает только idx. Пустые индексы не требуются — все наборы ниже
|
||||||
|
нумеруются подряд с нуля.
|
||||||
|
"""
|
||||||
|
count = len(entries)
|
||||||
|
if not count or count > 255:
|
||||||
|
raise SystemExit(f"{path}: bad sprite atlas count {count}")
|
||||||
|
data_off = ATL_DIR_OFF + count * 8
|
||||||
|
directory = bytearray(count * 8)
|
||||||
|
blobs = bytearray()
|
||||||
|
for idx in range(count):
|
||||||
|
width, height, pixels = entries[idx]
|
||||||
|
offset = data_off + len(blobs)
|
||||||
|
blobs += width.to_bytes(2, "little") + height.to_bytes(2, "little")
|
||||||
|
blobs += pixels
|
||||||
|
d = idx * 8
|
||||||
|
directory[d:d + 2] = offset.to_bytes(2, "little")
|
||||||
|
directory[d + 2] = width
|
||||||
|
directory[d + 3] = height
|
||||||
|
directory[d + 4] = directory[d + 5] = 1
|
||||||
|
out = bytearray(data_off)
|
||||||
|
out[:4] = ATL_MAGIC
|
||||||
|
out[4] = count
|
||||||
|
out[ATL_DIR_OFF:ATL_DIR_OFF + len(directory)] = directory
|
||||||
|
out += blobs
|
||||||
|
if len(out) < ATL_MIN_SIZE:
|
||||||
|
out += bytes(ATL_MIN_SIZE - len(out))
|
||||||
|
if len(out) > PAGE_SIZE:
|
||||||
|
raise SystemExit(f"{path}: {len(out)} B > one EMM page")
|
||||||
|
path.write_bytes(out)
|
||||||
|
|
||||||
|
|
||||||
|
def sprite_frames(directory: Path, first: int, last: int,
|
||||||
|
color_base: int, column_major: bool = False
|
||||||
|
) -> dict[int, tuple[int, int, bytes]]:
|
||||||
|
"""PNG-кадры -> getimage. Ноль PNG становится прозрачным 0xFF BGI."""
|
||||||
|
result = {}
|
||||||
|
for index, number in enumerate(range(first, last + 1)):
|
||||||
|
image = png(directory, number)
|
||||||
|
pixels = bytes(0xFF if pixel == 0 else color_base + pixel
|
||||||
|
for pixel in image.tobytes())
|
||||||
|
if column_major:
|
||||||
|
pixels = transpose_cols(image.width, image.height, pixels)
|
||||||
|
result[index] = image.width, image.height, pixels
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def png(directory: Path, number: int) -> Image.Image:
|
||||||
|
image = Image.open(directory / f"res{number}.png")
|
||||||
|
if image.mode != "P":
|
||||||
|
raise SystemExit(f"res{number}.png: expected indexed PNG, got {image.mode}")
|
||||||
|
return image.copy()
|
||||||
|
|
||||||
|
|
||||||
|
def pal_file(path: Path) -> list[tuple[int, int, int]]:
|
||||||
|
"""16 цветов из .pal ресурса (4 байта заголовка, дальше RGB по 6 бит)."""
|
||||||
|
raw = path.read_bytes()
|
||||||
|
return [(raw[4 + i * 3] << 2, raw[5 + i * 3] << 2, raw[6 + i * 3] << 2)
|
||||||
|
for i in range(16)]
|
||||||
|
|
||||||
|
|
||||||
|
def palette(image: Image.Image) -> list[tuple[int, int, int]]:
|
||||||
|
raw = image.getpalette()
|
||||||
|
return [tuple(raw[index * 3:index * 3 + 3]) for index in range(16)]
|
||||||
|
|
||||||
|
|
||||||
|
def write_palette(path: Path, entries: list[tuple[int, int, int]],
|
||||||
|
extra=None) -> None:
|
||||||
|
"""B,G,R,0 — ровно формат gfx_pal_fload; незадействованное чёрное."""
|
||||||
|
out = bytearray()
|
||||||
|
entries = (entries + [(0, 0, 0)] * 256)[:256]
|
||||||
|
for index, color in (extra or {}).items():
|
||||||
|
entries[index] = color
|
||||||
|
for red, green, blue in entries:
|
||||||
|
out += bytes((blue, green, red, 0))
|
||||||
|
path.write_bytes(out)
|
||||||
|
|
||||||
|
|
||||||
|
def write_scene(prefix: str, number: int, canvas: Image.Image) -> None:
|
||||||
|
if canvas.size != (320, 200):
|
||||||
|
raise SystemExit(f"{prefix}{number}: expected 320x200, got {canvas.size}")
|
||||||
|
for part, top in enumerate(range(0, 200, STRIP_H)):
|
||||||
|
band = canvas.crop((0, top, 320, min(top + STRIP_H, 200)))
|
||||||
|
(OUT / f"{prefix}{number}_{part}.atl").write_bytes(
|
||||||
|
atlas_blob(band.tobytes(), band.width, band.height))
|
||||||
|
|
||||||
|
|
||||||
|
def story_scenes() -> list[Image.Image]:
|
||||||
|
"""show_title/end_sequence в единой с title палитре.
|
||||||
|
|
||||||
|
res41 кодирует тёмный фон индексом 14, а его палитра делает этот индекс
|
||||||
|
чёрным; show_title() SDLPoP подменяет его #100060 либо #800000. Тексты
|
||||||
|
res42..45 имеют индекс 1, но этот индекс у res41 тоже тёмный; ремап на
|
||||||
|
индекс title/res51 15 (белый) позволяет проявлять историю прямо поверх
|
||||||
|
последнего title-экрана без смены палитры. Фон уезжает в
|
||||||
|
STORY_BG_INDEX, чтобы финал мог покрасить его отдельно.
|
||||||
|
|
||||||
|
Последний экран — фон таблицы рекордов: та же рамка плюс логотип
|
||||||
|
PRINCE OF PERSIA на y=24 (HOF_POP оригинала — тот же спрайт res54, что
|
||||||
|
и в титрах, только выше).
|
||||||
|
"""
|
||||||
|
frame_raw = bytearray(png(TITLE, 41).tobytes())
|
||||||
|
for offset, pixel in enumerate(frame_raw):
|
||||||
|
if pixel == 14:
|
||||||
|
frame_raw[offset] = STORY_BG_INDEX
|
||||||
|
frame = Image.frombytes("P", (320, 200), bytes(frame_raw))
|
||||||
|
result = []
|
||||||
|
for text_number, ypos in ((42, 25), (43, 25), (45, 26), (44, 25)):
|
||||||
|
canvas = frame.copy()
|
||||||
|
raw = bytearray(canvas.tobytes())
|
||||||
|
text = png(TITLE, text_number)
|
||||||
|
for sy in range(text.height):
|
||||||
|
for sx in range(text.width):
|
||||||
|
if text.getpixel((sx, sy)):
|
||||||
|
raw[(ypos + sy) * 320 + 24 + sx] = 15
|
||||||
|
canvas.frombytes(bytes(raw))
|
||||||
|
result.append(canvas)
|
||||||
|
result.append(frame)
|
||||||
|
|
||||||
|
hof = frame.copy()
|
||||||
|
raw = bytearray(hof.tobytes())
|
||||||
|
logo = png(TITLE, 54)
|
||||||
|
for sy in range(logo.height):
|
||||||
|
for sx in range(logo.width):
|
||||||
|
pixel = logo.getpixel((sx, sy))
|
||||||
|
if pixel: # blitters_10h_transp: 0 сквозной
|
||||||
|
raw[(24 + sy) * 320 + 24 + sx] = pixel
|
||||||
|
hof.frombytes(bytes(raw))
|
||||||
|
result.append(hof)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def pv_palette_canvas() -> tuple[Image.Image, list[tuple[int, int, int]]]:
|
||||||
|
"""Спальня + bed. Номера 0..15=room, 16..31=Princess,
|
||||||
|
32..47=Jaffar, 48..63=bed. Благодаря этому у смешанных PNG не теряются
|
||||||
|
исходные цвета при одном gfx_pal_fload() на весь PV."""
|
||||||
|
room = png(PV, 951)
|
||||||
|
bed = png(PV, 981)
|
||||||
|
raw = bytearray(room.tobytes())
|
||||||
|
bed_raw = bed.tobytes()
|
||||||
|
for sy in range(bed.height):
|
||||||
|
for sx in range(bed.width):
|
||||||
|
pixel = bed_raw[sy * bed.width + sx]
|
||||||
|
if pixel:
|
||||||
|
raw[(142 + sy) * 320 + sx] = 48 + pixel
|
||||||
|
canvas = Image.frombytes("P", room.size, bytes(raw))
|
||||||
|
# 80..95 — пламя факелов. Это отдельный chtab PRINCE в оригинале; без
|
||||||
|
# отдельной строки цвета динамические кадры становились бы цветами стены.
|
||||||
|
# 112..127 (0x70) — палитра КИДА: на PV-экране финала и сцен 8/9 он и
|
||||||
|
# мышь рисуются СВОИМИ атласами (chtab_2), а их индексы запечены под
|
||||||
|
# игровые слоты 0x70. Без этой строки они выходили бы чёрными.
|
||||||
|
return (canvas, palette(room) + palette(png(PV, 801)) +
|
||||||
|
palette(png(PV, 851)) + palette(bed) + palette(png(PV, 901)) +
|
||||||
|
palette(png(PRINCE, 151)) + [(0, 0, 0)] * 16 +
|
||||||
|
pal_file(KID / "res400.pal"))
|
||||||
|
|
||||||
|
|
||||||
|
def sprite_paste(dst: Image.Image, sprite: Image.Image, x: int, floor: int,
|
||||||
|
color_base: int) -> None:
|
||||||
|
"""Положить спрайт ногами на floor, remap его палитру в отдельную строку."""
|
||||||
|
raw = bytearray(dst.tobytes())
|
||||||
|
src = sprite.tobytes()
|
||||||
|
y = floor - sprite.height
|
||||||
|
for sy in range(sprite.height):
|
||||||
|
dy = y + sy
|
||||||
|
if dy < 0 or dy >= 200:
|
||||||
|
continue
|
||||||
|
for sx in range(sprite.width):
|
||||||
|
dx = x + sx
|
||||||
|
pixel = src[sy * sprite.width + sx]
|
||||||
|
if pixel and 0 <= dx < 320:
|
||||||
|
raw[dy * 320 + dx] = color_base + pixel
|
||||||
|
dst.frombytes(bytes(raw))
|
||||||
|
|
||||||
|
|
||||||
|
def pv_scenes() -> tuple[list[Image.Image], list[tuple[int, int, int]]]:
|
||||||
|
base, colors = pv_palette_canvas()
|
||||||
|
|
||||||
|
# Позиции Char из init_princess()/init_vizier(): x=120/198, y=166.
|
||||||
|
# В оригинале точный offset задаёт frame table; статические композиции
|
||||||
|
# выравнивают персонажей по полу y=166, не хранят таблицы PV-анимации.
|
||||||
|
waiting = base.copy()
|
||||||
|
sprite_paste(waiting, png(PV, 801), 110, 166, 16)
|
||||||
|
|
||||||
|
jaffar = waiting.copy()
|
||||||
|
sprite_paste(jaffar, png(PV, 851), 188, 166, 32)
|
||||||
|
|
||||||
|
magic = base.copy()
|
||||||
|
sprite_paste(magic, png(PV, 805), 110, 166, 16)
|
||||||
|
sprite_paste(magic, png(PV, 870), 188, 166, 32)
|
||||||
|
# res953 — первый полный кадр песочных часов; он использует палитру room.
|
||||||
|
sprite_paste(magic, png(PV, 953), 18, 166, 0)
|
||||||
|
|
||||||
|
alone = base.copy()
|
||||||
|
sprite_paste(alone, png(PV, 816), 110, 166, 16)
|
||||||
|
sprite_paste(alone, png(PV, 959), 18, 166, 0)
|
||||||
|
|
||||||
|
# FG9: статические ключевые позы тех же scene 2/4/6/8/9/12. PV2
|
||||||
|
# использует второй набор Princess (res900), поэтому он лежит в строке
|
||||||
|
# палитры 64..79. Мышь в оригинале меняет только короткую позу рядом с
|
||||||
|
# Princess; здесь соответствующая стадия выделена отдельным кадром.
|
||||||
|
stand = base.copy()
|
||||||
|
sprite_paste(stand, png(PV, 801), 134, 166, 16)
|
||||||
|
|
||||||
|
lying = base.copy()
|
||||||
|
sprite_paste(lying, png(PV, 901), 78, 162, 64)
|
||||||
|
# Часы для cutscene_4 выбираются runtime по оставшимся минутам и
|
||||||
|
# анимируются вместе с песком; в статической основе их быть не должно.
|
||||||
|
|
||||||
|
mouse = base.copy()
|
||||||
|
sprite_paste(mouse, png(PV, 920), 120, 169, 64)
|
||||||
|
sprite_paste(mouse, png(PV, 959), 18, 166, 0)
|
||||||
|
|
||||||
|
short_time = base.copy()
|
||||||
|
sprite_paste(short_time, png(PV, 816), 146, 166, 16)
|
||||||
|
sprite_paste(short_time, png(PV, 953), 18, 166, 0)
|
||||||
|
|
||||||
|
return [waiting, jaffar, magic, alone, stand, lying, mouse, short_time], colors
|
||||||
|
|
||||||
|
|
||||||
|
def pv_animation_assets() -> None:
|
||||||
|
"""Ресурсы покадрового intro.
|
||||||
|
|
||||||
|
`b0_*` — единственный чистый фон. Он копируется accelerator-ом между
|
||||||
|
обеими экранными страницами; спрайты рисуются банком без shadow, так что
|
||||||
|
следующий кадр автоматически начинает с фона. Каждый actor-атлас <=16K,
|
||||||
|
поэтому нет чтения 320x200 с HDD в анимационном цикле.
|
||||||
|
"""
|
||||||
|
base, _ = pv_palette_canvas()
|
||||||
|
write_scene("b", 0, base)
|
||||||
|
# frame_tbl_cuts из SDLPoP адресует Princess как image 0..16 ресурса
|
||||||
|
# 800 (то есть PNG res801..817). Для Jaffar PV1 адресуется ресурс 850:
|
||||||
|
# image 0..37 = res851..888. Все 38 кадров не помещаются в одну EMM,
|
||||||
|
# поэтому сохраняем их тремя последовательными атласами. Runtime знает
|
||||||
|
# границы, но номер кадра остаётся исходным image-id 0..37.
|
||||||
|
sprite_atlas(OUT / "a0.atl", sprite_frames(PV, 801, 817, 16, True))
|
||||||
|
sprite_atlas(OUT / "j0.atl", sprite_frames(PV, 851, 863, 32, True))
|
||||||
|
sprite_atlas(OUT / "j1.atl", sprite_frames(PV, 864, 874, 32, True))
|
||||||
|
sprite_atlas(OUT / "j2.atl", sprite_frames(PV, 875, 888, 32, True))
|
||||||
|
# ВТОРОЙ набор Princess (PV2, res901..930 = image 0..29 ресурса 900).
|
||||||
|
# Он нужен сценам 8/9 (сидит и гладит мышь, встаёт, приседает) и финалу
|
||||||
|
# (поворачивается и обнимает Кида) — поз PV1 для этого не хватает.
|
||||||
|
# 26.7 КБ не влезают в одну EMM, поэтому два атласа; номер кадра при
|
||||||
|
# этом остаётся исходным image-id 0..29, границу знает runtime.
|
||||||
|
# Палитровая строка 64..79 — та же, что у PV2 в статических сценах.
|
||||||
|
sprite_atlas(OUT / "a1.atl", sprite_frames(PV, 901, 917, 64, True))
|
||||||
|
sprite_atlas(OUT / "a2.atl", sprite_frames(PV, 918, 930, 64, True))
|
||||||
|
# res953/res954 — состояния часов, res960..962 — три фазы песка.
|
||||||
|
sprite_atlas(OUT / "h0.atl", sprite_frames(PV, 952, 962, 0))
|
||||||
|
sprite_atlas(OUT / "t0.atl", sprite_frames(PRINCE, 151, 159, 80))
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
stories = story_scenes()
|
||||||
|
write_palette(OUT / "story.pal", palette(png(TITLE, 51)),
|
||||||
|
{STORY_BG_INDEX: STORY_BG_COLOR,
|
||||||
|
STORY_FONT_INDEX: (224, 160, 0)})
|
||||||
|
for number, scene in enumerate(stories):
|
||||||
|
write_scene("s", number, scene)
|
||||||
|
|
||||||
|
scenes, colors = pv_scenes()
|
||||||
|
write_palette(OUT / "pv.pal", colors)
|
||||||
|
for number, scene in enumerate(scenes):
|
||||||
|
write_scene("p", number, scene)
|
||||||
|
|
||||||
|
pv_animation_assets()
|
||||||
|
|
||||||
|
print(f"{OUT}: {len(stories)} story + 8 PV compositions + base x 5 strips, "
|
||||||
|
f"6 animated actor atlases, one strip/page <= {STRIP_H} rows")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -21,7 +21,7 @@ HERE = os.path.dirname(os.path.abspath(__file__))
|
|||||||
sys.path.insert(0, HERE)
|
sys.path.insert(0, HERE)
|
||||||
import pop_pack_bg as B # pack_atlas, blob, scale6to8, ENV_SHIFT
|
import pop_pack_bg as B # pack_atlas, blob, scale6to8, ENV_SHIFT
|
||||||
|
|
||||||
KID_DIR = "/Users/alex/Projects/DIY/Z80/Sprinter/C-Compiler/applications/PoP/SDLPoP/data/KID"
|
KID_DIR = os.path.join(HERE, "..", "SDLPoP", "data", "KID")
|
||||||
OUT_DIR = os.path.join(HERE, "..", "poc", "res", "kid")
|
OUT_DIR = os.path.join(HERE, "..", "poc", "res", "kid")
|
||||||
PAL_BASE = 0x70 # слоты палитры Sprinter под Kid
|
PAL_BASE = 0x70 # слоты палитры Sprinter под Kid
|
||||||
SWORD_PAL_BASE = 0x80 # chtab_0 (меч в руке) -> слоты 0x80..0x8F
|
SWORD_PAL_BASE = 0x80 # chtab_0 (меч в руке) -> слоты 0x80..0x8F
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""pop_pack_title.py — титульные композиции SDLPoP для Sprinter.
|
||||||
|
|
||||||
|
Исходник — PNG-ресурсы ``data/TITLE/res51..55`` из локального SDLPoP.
|
||||||
|
Runtime не должен держать 320x200 картинку в W2 и не разбирает PNG: каждая
|
||||||
|
готовая композиция разрезана на пять getimage-лент высотой не более 49
|
||||||
|
строк. Лента вместе с заголовком атласа занимает одну EMM-страницу; экран
|
||||||
|
рисуется лента за лентой, и страница сразу освобождается.
|
||||||
|
|
||||||
|
Композиции в точности повторяют ``show_title()`` до story/intro:
|
||||||
|
t0 — основной рисунок;
|
||||||
|
t1 — рисунок + PRESENTS;
|
||||||
|
t2 — рисунок + название игры;
|
||||||
|
t3 — рисунок + логотип PRINCE OF PERSIA и Jordan Mechner.
|
||||||
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
SRC = ROOT / "SDLPoP" / "data" / "TITLE"
|
||||||
|
OUT = ROOT / "poc" / "res" / "title"
|
||||||
|
|
||||||
|
ATL_MAGIC = b"SPA1"
|
||||||
|
ATL_DIR_OFF = 0x68
|
||||||
|
ATL_MIN_SIZE = 0x100
|
||||||
|
PAGE_SIZE = 16384
|
||||||
|
STRIP_H = 49
|
||||||
|
|
||||||
|
|
||||||
|
def atlas_blob(pixels: bytes, width: int, height: int) -> bytes:
|
||||||
|
"""Один непрозрачный getimage в минимальном SPA1-атласе."""
|
||||||
|
data_off = ATL_DIR_OFF + 8
|
||||||
|
out = bytearray(data_off)
|
||||||
|
out[:4] = ATL_MAGIC
|
||||||
|
out[4] = 1
|
||||||
|
out[ATL_DIR_OFF:ATL_DIR_OFF + 2] = data_off.to_bytes(2, "little")
|
||||||
|
# Каталог хранит preview-габарит в uint8, но atlas_image читает только
|
||||||
|
# offset; реальный getimage-width — u16 в блобе. Так уже пакуются
|
||||||
|
# широкие 320-пиксельные полосы фона.
|
||||||
|
out[ATL_DIR_OFF + 2] = min(width, 255)
|
||||||
|
out[ATL_DIR_OFF + 3] = height
|
||||||
|
out[ATL_DIR_OFF + 4] = out[ATL_DIR_OFF + 5] = 1
|
||||||
|
out += width.to_bytes(2, "little") + height.to_bytes(2, "little")
|
||||||
|
out += pixels
|
||||||
|
if len(out) < ATL_MIN_SIZE:
|
||||||
|
out += bytes(ATL_MIN_SIZE - len(out))
|
||||||
|
if len(out) > PAGE_SIZE:
|
||||||
|
raise SystemExit(f"title atlas {len(out)} B > {PAGE_SIZE} B")
|
||||||
|
return bytes(out)
|
||||||
|
|
||||||
|
|
||||||
|
def image(n: int) -> Image.Image:
|
||||||
|
im = Image.open(SRC / f"res{n}.png")
|
||||||
|
if im.mode != "P":
|
||||||
|
raise SystemExit(f"res{n}.png: expected indexed PNG, got {im.mode}")
|
||||||
|
return im.copy()
|
||||||
|
|
||||||
|
|
||||||
|
def assert_palette(base: Image.Image, overlay: Image.Image, n: int) -> None:
|
||||||
|
if base.getpalette()[:48] != overlay.getpalette()[:48]:
|
||||||
|
raise SystemExit(f"res{n}.png uses a palette different from res51.png")
|
||||||
|
|
||||||
|
|
||||||
|
def paste_opaque(dst: Image.Image, src: Image.Image, x: int, y: int, n: int) -> None:
|
||||||
|
assert_palette(dst, src, n)
|
||||||
|
dst.paste(src, (x, y))
|
||||||
|
|
||||||
|
|
||||||
|
def paste_transparent(dst: Image.Image, src: Image.Image, x: int, y: int, n: int) -> None:
|
||||||
|
assert_palette(dst, src, n)
|
||||||
|
# TITLE_POP — единственный из пяти элементов, который SDLPoP рисует
|
||||||
|
# blitters_10h_transp. В PNG тот же факт записан colour-key index 0.
|
||||||
|
# Image.point() у P-изображения возвращает снова P, а paste() принимает
|
||||||
|
# маску только L/1/RGBA. Строим L явно, не меняя индексы самой картинки.
|
||||||
|
mask = Image.frombytes("L", src.size,
|
||||||
|
bytes(0 if p == 0 else 255 for p in src.tobytes()))
|
||||||
|
dst.paste(src, (x, y), mask)
|
||||||
|
|
||||||
|
|
||||||
|
# Семнадцатая запись — фон текстовой рамки story (STORY_BG_INDEX в
|
||||||
|
# pop_pack_intro.py). Она нужна здесь потому, что «In the absence…»
|
||||||
|
# проявляется полосами ПОВЕРХ последнего кадра титров, то есть при ещё
|
||||||
|
# загруженной title.pal: без этой записи фон рамки был бы чёрным.
|
||||||
|
STORY_BG_INDEX = 16
|
||||||
|
STORY_BG_COLOR = (0x10, 0x00, 0x60)
|
||||||
|
|
||||||
|
|
||||||
|
def write_palette(base: Image.Image) -> None:
|
||||||
|
rgb = base.getpalette()[:48]
|
||||||
|
pal = bytearray()
|
||||||
|
for i in range(16):
|
||||||
|
r, g, b = rgb[i * 3:i * 3 + 3]
|
||||||
|
pal += bytes((b, g, r, 0)) # родной формат gfx_pal_fload
|
||||||
|
assert len(pal) == STORY_BG_INDEX * 4
|
||||||
|
r, g, b = STORY_BG_COLOR
|
||||||
|
pal += bytes((b, g, r, 0))
|
||||||
|
(OUT / "title.pal").write_bytes(pal)
|
||||||
|
|
||||||
|
|
||||||
|
def write_scene(scene: int, canvas: Image.Image) -> int:
|
||||||
|
count = 0
|
||||||
|
for part, top in enumerate(range(0, canvas.height, STRIP_H)):
|
||||||
|
band = canvas.crop((0, top, canvas.width,
|
||||||
|
min(top + STRIP_H, canvas.height)))
|
||||||
|
path = OUT / f"t{scene}_{part}.atl"
|
||||||
|
path.write_bytes(atlas_blob(band.tobytes(), band.width, band.height))
|
||||||
|
count += 1
|
||||||
|
return count
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
main_image = image(51)
|
||||||
|
if main_image.size != (320, 200):
|
||||||
|
raise SystemExit(f"res51.png: expected 320x200, got {main_image.size}")
|
||||||
|
|
||||||
|
presents = main_image.copy()
|
||||||
|
paste_opaque(presents, image(52), 96, 106, 52)
|
||||||
|
|
||||||
|
game = main_image.copy()
|
||||||
|
paste_opaque(game, image(53), 96, 122, 53)
|
||||||
|
|
||||||
|
logo = main_image.copy()
|
||||||
|
paste_transparent(logo, image(54), 24, 107, 54)
|
||||||
|
paste_opaque(logo, image(55), 48, 184, 55)
|
||||||
|
|
||||||
|
write_palette(main_image)
|
||||||
|
scenes = (main_image, presents, game, logo)
|
||||||
|
strips = [write_scene(i, scene) for i, scene in enumerate(scenes)]
|
||||||
|
assert strips == [5, 5, 5, 5]
|
||||||
|
print(f"{OUT}: {len(scenes)} title compositions x {strips[0]} strips, "
|
||||||
|
f"one strip <= {STRIP_H} rows / one EMM page")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -30,9 +30,10 @@ animation) for this first static render; they use curr_modifier/backtable.
|
|||||||
import os
|
import os
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
LEVEL_DIR = "/Users/alex/Projects/DIY/Z80/Sprinter/C-Compiler/applications/PoP/SDLPoP/data/LEVELS"
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
VPALACE_DIR = "/Users/alex/Projects/DIY/Z80/Sprinter/C-Compiler/applications/PoP/SDLPoP/data/VPALACE"
|
LEVEL_DIR = os.path.join(ROOT, "SDLPoP", "data", "LEVELS")
|
||||||
VDUNGEON_DIR = "/Users/alex/Projects/DIY/Z80/Sprinter/C-Compiler/applications/PoP/SDLPoP/data/VDUNGEON"
|
VPALACE_DIR = os.path.join(ROOT, "SDLPoP", "data", "VPALACE")
|
||||||
|
VDUNGEON_DIR = os.path.join(ROOT, "SDLPoP", "data", "VDUNGEON")
|
||||||
|
|
||||||
# For dungeon levels (tbl_level_type == 0) the environment sprites come from
|
# For dungeon levels (tbl_level_type == 0) the environment sprites come from
|
||||||
# VDUNGEON where present, otherwise fall back to the shared VPALACE set
|
# VDUNGEON where present, otherwise fall back to the shared VPALACE set
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ build/
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# ОС / редакторы / локальные настройки
|
# ОС / редакторы / локальные настройки
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
local.mk
|
||||||
|
.resource-stamps/
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
.DS_Store
|
.DS_Store
|
||||||
._*
|
._*
|
||||||
*~
|
*~
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Sprinter C Compiler contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -22,14 +22,20 @@
|
|||||||
# Основные цели:
|
# Основные цели:
|
||||||
# make собрать sprpop.exe и разложить build/
|
# make собрать sprpop.exe и разложить build/
|
||||||
# make hdd + образ build/hdd/sprpop.chd
|
# make hdd + образ build/hdd/sprpop.chd
|
||||||
# make mame-link однократно связать образ с MAME (см. ниже)
|
# make run-hdd запустить MAME с локальным образом игры
|
||||||
# make resources перегенерировать ресурсы из assets/orig/
|
# make resources перегенерировать ресурсы из assets/orig/
|
||||||
# make music-mp3 музыка из другого набора записей (flac|mp3|ogg|mt32)
|
# make music-mp3 музыка из другого набора записей (flac|mp3|ogg|mt32)
|
||||||
# make clean снести build/ (ассеты не трогает)
|
# make clean снести build/ (ассеты не трогает)
|
||||||
# make distclean clean + снести упакованные ассеты assets/packed/
|
# make distclean clean + снести упакованные ассеты assets/packed/
|
||||||
|
|
||||||
SPRINTER_ROOT ?= $(abspath $(CURDIR)/../..)
|
-include $(CURDIR)/local.mk
|
||||||
|
SPRINTER_ROOT ?= $(if $(wildcard $(CURDIR)/../../bin/sprinter-cc),$(abspath $(CURDIR)/../..),)
|
||||||
|
ifeq ($(strip $(SPRINTER_ROOT)),)
|
||||||
|
$(error Задайте SPRINTER_ROOT или создайте SprPoP/local.mk)
|
||||||
|
endif
|
||||||
PROJ_ROOT := $(SPRINTER_ROOT)
|
PROJ_ROOT := $(SPRINTER_ROOT)
|
||||||
|
export SPRINTER_ROOT MAME_HOME MAME_BIN MAME_ROMPATH MAME_DSS_IMAGE
|
||||||
|
export MAME_SYSTEM_HDD_IMAGE MAME_BIOS CHDMAN_BIN
|
||||||
EXAMPLE := sprpop
|
EXAMPLE := sprpop
|
||||||
SRC_DIR := src
|
SRC_DIR := src
|
||||||
BUILD_DIR := build
|
BUILD_DIR := build
|
||||||
@@ -259,8 +265,7 @@ HDD_PACK_ARGS := $(BUILD_DIR)/$(EXAMPLE).exe \
|
|||||||
# Всё дерево SprPoP лежит рядом с EXE, но не меняет дефолт глобального
|
# Всё дерево SprPoP лежит рядом с EXE, но не меняет дефолт глобального
|
||||||
# упаковщика: остальные приложения по-прежнему попадают в корень HDD.
|
# упаковщика: остальные приложения по-прежнему попадают в корень HDD.
|
||||||
HDD_DEST_DIR := games/sprpop
|
HDD_DEST_DIR := games/sprpop
|
||||||
# Образ живёт в build/, а не в mame/v306/IMG — приложение автономно. Связь с
|
# Образ живёт в build/; run-hdd передаёт его MAME напрямую как hard2.
|
||||||
# MAME — одна символьная ссылка, ставится однократно: make mame-link.
|
|
||||||
HDD_IMG := $(CURDIR)/$(BUILD_DIR)/hdd/$(EXAMPLE).chd
|
HDD_IMG := $(CURDIR)/$(BUILD_DIR)/hdd/$(EXAMPLE).chd
|
||||||
|
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(PROJ_ROOT)/app.mk
|
||||||
@@ -560,14 +565,6 @@ hdd: stage | $(BUILD_DIR)/hdd
|
|||||||
$(BUILD_DIR)/hdd:
|
$(BUILD_DIR)/hdd:
|
||||||
@mkdir -p $@
|
@mkdir -p $@
|
||||||
|
|
||||||
# Однократная связка с MAME: run_bridge.sh жёстко открывает IMG/test_hdd.chd,
|
|
||||||
# поэтому подсовываем ему символьную ссылку на наш образ. chdman переписывает
|
|
||||||
# сам файл, ссылка остаётся живой — но MAME после пересборки образа ОБЯЗАН
|
|
||||||
# полный рестарт (новый inode).
|
|
||||||
mame-link: hdd
|
|
||||||
ln -sf $(HDD_IMG) $(PROJ_ROOT)/mame/v306/IMG/test_hdd.chd
|
|
||||||
@echo "MAME: IMG/test_hdd.chd -> $(HDD_IMG)"
|
|
||||||
|
|
||||||
# Музыка из конкретного набора записей. Каждая цель пересобирает музыку и
|
# Музыка из конкретного набора записей. Каждая цель пересобирает музыку и
|
||||||
# сразу раскладывает её в build/, чтобы `make hdd` взял уже новую.
|
# сразу раскладывает её в build/, чтобы `make hdd` взял уже новую.
|
||||||
# make music-mp3 собрать музыку из mp3
|
# make music-mp3 собрать музыку из mp3
|
||||||
@@ -624,4 +621,4 @@ distclean: clean
|
|||||||
.PHONY: stage check-orig resources resources-rebuild resources-bg resources-kid \
|
.PHONY: stage check-orig resources resources-rebuild resources-bg resources-kid \
|
||||||
resources-actors resources-sound resources-music resources-font \
|
resources-actors resources-sound resources-music resources-font \
|
||||||
resources-title resources-pv resources-levels $(MUSIC_TARGETS) \
|
resources-title resources-pv resources-levels $(MUSIC_TARGETS) \
|
||||||
$(FETCH_TARGETS) test-tools relink distclean mame-link
|
$(FETCH_TARGETS) test-tools relink distclean run-hdd
|
||||||
|
|||||||
@@ -8,9 +8,10 @@
|
|||||||
сборка лежат внутри этой папки. Наружу нужен только компилятор:
|
сборка лежат внутри этой папки. Наружу нужен только компилятор:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
export SPRINTER_ROOT=/путь/к/C-Compiler # не нужен, если папка лежит внутри его дерева
|
export SPRINTER_ROOT=/путь/к/C-Compiler
|
||||||
make # build/sprpop.exe + ресурсы в build/
|
make # build/sprpop.exe + ресурсы в build/
|
||||||
make hdd # + образ build/hdd/sprpop.chd
|
make hdd # + образ build/hdd/sprpop.chd
|
||||||
|
make run-hdd MAME_HOME=/путь/к/MAME/runtime # запуск с локальным CHD
|
||||||
```
|
```
|
||||||
|
|
||||||
Чужих данных в репозитории нет — есть адреса, откуда их взять. Чтобы
|
Чужих данных в репозитории нет — есть адреса, откуда их взять. Чтобы
|
||||||
@@ -39,7 +40,7 @@ build/ выход: sprpop.exe, каталоги ресурсов, hdd/,
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `make` | собрать `build/sprpop.exe` и разложить ресурсы в `build/` |
|
| `make` | собрать `build/sprpop.exe` и разложить ресурсы в `build/` |
|
||||||
| `make hdd` | + образ жёсткого диска `build/hdd/sprpop.chd` |
|
| `make hdd` | + образ жёсткого диска `build/hdd/sprpop.chd` |
|
||||||
| `make mame-link` | однократно подставить образ в MAME (символьная ссылка на `IMG/test_hdd.chd`) |
|
| `make run-hdd` | передать локальный CHD в MAME как `-hard2` без изменения установки эмулятора |
|
||||||
| `make fetch` | скачать внешние исходные данные в `assets/orig/`: `fetch-sdlpop`, `fetch-music`, `fetch-check`, `fetch-list` |
|
| `make fetch` | скачать внешние исходные данные в `assets/orig/`: `fetch-sdlpop`, `fetch-music`, `fetch-check`, `fetch-list` |
|
||||||
| `make resources` | перегенерировать ресурсы из `assets/orig/` |
|
| `make resources` | перегенерировать ресурсы из `assets/orig/` |
|
||||||
| `make music-mp3` | музыка из другого набора записей: `music-flac` (умолчание), `music-mp3`, `music-ogg`, `music-mt32` |
|
| `make music-mp3` | музыка из другого набора записей: `music-flac` (умолчание), `music-mp3`, `music-ogg`, `music-mt32` |
|
||||||
|
|||||||
@@ -2,7 +2,11 @@
|
|||||||
# здесь — только сами наборы и список модулей, которые в них линкуются.
|
# здесь — только сами наборы и список модулей, которые в них линкуются.
|
||||||
# Наружу тесты знают тот же единственный путь, что и само приложение, —
|
# Наружу тесты знают тот же единственный путь, что и само приложение, —
|
||||||
# корень тулчейна (см. ../../Makefile). Всё остальное считается от него.
|
# корень тулчейна (см. ../../Makefile). Всё остальное считается от него.
|
||||||
SPRINTER_ROOT ?= $(abspath $(CURDIR)/../../../..)
|
-include $(CURDIR)/../../local.mk
|
||||||
|
SPRINTER_ROOT ?= $(if $(wildcard $(CURDIR)/../../../../bin/sprinter-cc),$(abspath $(CURDIR)/../../../..),)
|
||||||
|
ifeq ($(strip $(SPRINTER_ROOT)),)
|
||||||
|
$(error Задайте SPRINTER_ROOT для host-тестов SprPoP)
|
||||||
|
endif
|
||||||
TESTKIT := $(SPRINTER_ROOT)/testkit
|
TESTKIT := $(SPRINTER_ROOT)/testkit
|
||||||
APP_DIR := $(abspath $(CURDIR)/../..)
|
APP_DIR := $(abspath $(CURDIR)/../..)
|
||||||
ENGINE_DIR := $(APP_DIR)/src
|
ENGINE_DIR := $(APP_DIR)/src
|
||||||
|
|||||||
@@ -6,3 +6,18 @@
|
|||||||
# Клон исторических исходников VC служит локальным справочным материалом и
|
# Клон исторических исходников VC служит локальным справочным материалом и
|
||||||
# сохраняет собственный .git; во внешний репозиторий его как gitlink не кладём.
|
# сохраняет собственный .git; во внешний репозиторий его как gitlink не кладём.
|
||||||
/VolkovCommander/
|
/VolkovCommander/
|
||||||
|
|
||||||
|
# Собственный выход Commander и локальные пути к SDK/MAME.
|
||||||
|
local.mk
|
||||||
|
build/
|
||||||
|
artifacts/*-local/
|
||||||
|
.sprinter-cc-*/
|
||||||
|
.resource-stamps/
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
*.dSYM/
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.DS_Store
|
||||||
|
._*
|
||||||
|
.vscode/
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
3.12
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Sprinter C Compiler contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
# Sprinter Commander — двухпанельный файловый менеджер.
|
# Sprinter Commander — двухпанельный файловый менеджер.
|
||||||
|
|
||||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
|
||||||
EXAMPLE := sprcmd
|
EXAMPLE := sprcmd
|
||||||
SRC_DIR := src
|
SRC_DIR := src
|
||||||
BUILD_DIR := build
|
BUILD_DIR := build
|
||||||
MEMORY := big
|
MEMORY := big
|
||||||
ALLOCS ?= 3000
|
ALLOCS ?= 3000
|
||||||
HDD_IMG := $(CURDIR)/build/hdd/sprcmd.chd
|
HDD_IMG := $(CURDIR)/build/hdd/sprcmd.chd
|
||||||
MDVIEW2_DIR := $(PROJ_ROOT)/examples/mdview2
|
MDVIEW2_DIR := $(CURDIR)/tests/fixtures/mdview2
|
||||||
MDVIEW2_SRCS := $(wildcard $(MDVIEW2_DIR)/*.c)
|
MDVIEW2_SRCS := $(wildcard $(MDVIEW2_DIR)/*.c)
|
||||||
MDVIEW2_HEADERS := $(wildcard $(MDVIEW2_DIR)/*.h)
|
MDVIEW2_HEADERS := $(wildcard $(MDVIEW2_DIR)/*.h)
|
||||||
MDVIEW2_TEST_EXE := $(CURDIR)/build/external/mdview2.exe
|
MDVIEW2_TEST_EXE := $(CURDIR)/build/external/mdview2.exe
|
||||||
@@ -38,7 +37,7 @@ EXTRA_SRCS := \
|
|||||||
src/sc_theme.c \
|
src/sc_theme.c \
|
||||||
src/sc_video_system.c
|
src/sc_video_system.c
|
||||||
|
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/sdk.mk
|
||||||
|
|
||||||
HEADERS := $(wildcard include/*.h)
|
HEADERS := $(wildcard include/*.h)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Общая привязка Commander и его target-проб к Sprinter-CC.
|
||||||
|
VOLKOV_ROOT := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
|
||||||
|
-include $(VOLKOV_ROOT)/local.mk
|
||||||
|
SPRINTER_ROOT ?= $(if $(wildcard $(VOLKOV_ROOT)/../../bin/sprinter-cc),$(abspath $(VOLKOV_ROOT)/../..),)
|
||||||
|
ifeq ($(strip $(SPRINTER_ROOT)),)
|
||||||
|
$(error Задайте SPRINTER_ROOT или создайте Volkov/local.mk)
|
||||||
|
endif
|
||||||
|
PROJ_ROOT := $(SPRINTER_ROOT)
|
||||||
|
export SPRINTER_ROOT MAME_HOME MAME_BIN MAME_ROMPATH MAME_DSS_IMAGE
|
||||||
|
export MAME_SYSTEM_HDD_IMAGE MAME_BIOS CHDMAN_BIN
|
||||||
|
include $(PROJ_ROOT)/app.mk
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Sprinter C Compiler contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Vendored third-party components are under their own licenses:
|
||||||
|
|
||||||
|
third_party/sdcc/ — SDCC 4.5 under GPL v2 with linking exception.
|
||||||
|
See third_party/sdcc/COPYING.txt.
|
||||||
|
third_party/solid-c/ — Original Sprinter Solid C, used only as a reference
|
||||||
|
for compatibility. Licence not specified upstream;
|
||||||
|
treat as reference material, do not redistribute
|
||||||
|
original binaries.
|
||||||
|
mame/v306/ — MAME (https://www.mamedev.org), GPL v2+.
|
||||||
|
mame/v306/IMG/*.img *.chd *.iso — Sprinter ROM / DSS / sample disk images
|
||||||
|
from Peters Plus. Redistribution policy: see
|
||||||
|
Peters Plus documentation.
|
||||||
|
|
||||||
|
Documentation in docs/converted/, docs/reference/, docs/samples/, and
|
||||||
|
docs/memory management/ contains material originally published by Peters Plus
|
||||||
|
team (Иван Мак, Дмитрий Паринов and others). Used here for cross-compilation
|
||||||
|
reference; original copyrights apply.
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# MDVIEW2 fixture для теста запуска дочернего приложения
|
||||||
|
|
||||||
|
Снимок исходников `examples/mdview2` из Sprinter-SDCC commit `5323b16`
|
||||||
|
(2026-09-15). Лицензия MIT находится в `LICENSE`. Fixture специально
|
||||||
|
зафиксирован: тест Commander должен проверять запуск полноэкранного viewer,
|
||||||
|
PageDown/raw-режим и восстановление окружения после возврата, независимо от
|
||||||
|
последующих изменений проекта Examples. Обновлять снимок только вместе с
|
||||||
|
повторной проверкой сценария `mame_p5_viewer.lua` и эталонных кадров.
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
# MDView v1.0 (b3) — просмотрщик Markdown для Sprinter
|
||||||
|
|
||||||
|
**MDView** — программа просмотра документов Markdown для компьютера
|
||||||
|
**Sprinter-2000** (Z80, ОС ESTEX). Файл загружается в расширенную память (EMM)
|
||||||
|
и один раз «прогоняется» через парсер: готовые к показу строки (пары
|
||||||
|
символ+атрибут) складываются в **рендер-кэш** в EMM, после чего прокрутка в
|
||||||
|
любую сторону — это просто копирование готовых строк на экран, без повторного
|
||||||
|
парсинга. Даже на файлах в сотни килобайт листание остаётся мгновенным.
|
||||||
|
|
||||||
|
Текстовый режим 80×32, цветное оформление элементов разметки, три режима
|
||||||
|
просмотра (MD / RAW / HEX) и четыре кодировки с автоопределением.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
* **Markdown-рендеринг** с цветовым оформлением:
|
||||||
|
* заголовки `#`…`######` (H5/H6 отображаются как H4);
|
||||||
|
* **жирный** (`**текст**`), *курсив* (`*текст*`), подчёркнутый (`_текст_`),
|
||||||
|
~~зачёркнутый~~ (`~~текст~~`), `встроенный код` (`` `текст` ``);
|
||||||
|
* экранирование `\*`, `\_`, `` \` `` и любой ASCII-пунктуации;
|
||||||
|
* чекбоксы `[x]` / `[ ]` в списках;
|
||||||
|
* ненумерованные (`-`, `*`, `+`) и нумерованные (`1.`, `1)`) списки
|
||||||
|
с базовой вложенностью по отступам;
|
||||||
|
* цитаты `>` (склейка многострочных, маркер │ на переносах);
|
||||||
|
* fenced-блоки кода ` ``` ` (без переносов, горизонтальный скролл);
|
||||||
|
* таблицы `| … | … |` — рисуются псевдографической рамкой, ширины колонок
|
||||||
|
вычисляются по содержимому (до 16 колонок);
|
||||||
|
* горизонтальные разделители `---` / `***` / `___`;
|
||||||
|
* жёсткие переносы (два пробела или `\` в конце строки);
|
||||||
|
* мягкая склейка абзацев с переносом по словам под ширину экрана.
|
||||||
|
* **Кодировки: CP866, CP1251, KOI8-R, UTF-8.**
|
||||||
|
* автоопределение при открытии (BOM → UTF-8; валидность multibyte-структуры;
|
||||||
|
частотный анализ ходовых русских букв для 8-битных);
|
||||||
|
* переключение по кругу клавишей **F8** в любой момент;
|
||||||
|
* 8-битные кодировки отличаются только перекодировкой глифов на отрисовке —
|
||||||
|
переключение мгновенно;
|
||||||
|
* UTF-8 декодируется в CP866 в **отдельный набор** (файл + индекс + кэш);
|
||||||
|
второй набор готовится **в фоне**, пока вы читаете документ, — обычно
|
||||||
|
к первому нажатию F8 он уже построен и переключение мгновенно,
|
||||||
|
с сохранением позиции. Если фон не успел, F8 докручивает начатую
|
||||||
|
сборку (со спиннером), а не начинает её заново.
|
||||||
|
* **Три режима просмотра:**
|
||||||
|
* **MD** — форматированный Markdown (по умолчанию);
|
||||||
|
* **RAW** (**F2**) — исходный текст без разметки: перенос строк кратно 80
|
||||||
|
(**F3** — режим панорамы с горизонтальным скроллом);
|
||||||
|
* **HEX** (**F4**) — дамп *оригинального* файла:
|
||||||
|
`0x012340 │ 16 байт hex │ 16 печатных символов`. Печатная колонка
|
||||||
|
интерпретируется текущей кодировкой; для UTF-8 глиф ставится на позиции
|
||||||
|
лид-байта, continuation-байты показываются точкой.
|
||||||
|
* **Единая позиция** при любых переключениях: MD ↔ RAW ↔ HEX и смена
|
||||||
|
кодировки сохраняют текущее место в документе (между наборами разного
|
||||||
|
размера — пропорционально, с точностью до строки).
|
||||||
|
* **Прогрессивная загрузка**: первый экран показывается сразу, индексация
|
||||||
|
продолжается в фоне; по готовой части документа уже можно листать,
|
||||||
|
**Esc**/**F10** прерывают загрузку.
|
||||||
|
* **Фоновая работа незаметна**: второй набор кодировки строится только в
|
||||||
|
паузах между клавишами (после нажатия выдерживается пауза), поэтому
|
||||||
|
скролл — в том числе с автоповтором — не теряет плавности.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Запуск
|
||||||
|
|
||||||
|
```
|
||||||
|
MDVIEW2.EXE <файл.md>
|
||||||
|
```
|
||||||
|
|
||||||
|
Без аргумента открывается `README.MD` из текущего каталога.
|
||||||
|
|
||||||
|
## Клавиши
|
||||||
|
|
||||||
|
| Клавиша | Действие |
|
||||||
|
|--------------|-------------------------------------------------------------|
|
||||||
|
| ↑ / ↓ | прокрутка на одну строку |
|
||||||
|
| PgUp / PgDn | прокрутка на экран (30 строк) |
|
||||||
|
| Home / End | в начало / в конец документа |
|
||||||
|
| ← / → | горизонтальный сдвиг: код/таблицы в MD, панорама в RAW |
|
||||||
|
| F1 | справка |
|
||||||
|
| F2 | RAW-режим ↔ MD |
|
||||||
|
| F3 | в RAW: перенос строк ↔ панорама |
|
||||||
|
| F4 | HEX-режим ↔ прежний вид |
|
||||||
|
| F8 | кодировка: CP866 → CP1251 → KOI8-R → UTF-8 → … |
|
||||||
|
| Esc / F10 | выход (во время загрузки — прервать её) |
|
||||||
|
|
||||||
|
Статус-бар (верхняя строка): имя файла, кодировка, диапазон видимых строк
|
||||||
|
и процент прокрутки. Нижняя строка — меню доступных F-клавиш.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ограничения
|
||||||
|
|
||||||
|
| Параметр | Значение |
|
||||||
|
|---------------------------------|-------------------------------------------|
|
||||||
|
| Размер файла | до 256 КБ (больший — обрезается с предупреждением) |
|
||||||
|
| Логических строк (после переносов) | до 18 432 |
|
||||||
|
| Длина строки в рендер-кэше | 255 ячеек (RAW/HEX ограничения не имеют) |
|
||||||
|
| Колонок в таблице | до 16 |
|
||||||
|
| Шаг табуляции | 4 |
|
||||||
|
|
||||||
|
При исчерпании любого лимита документ завершается строкой-сообщением
|
||||||
|
с указанием причины обрыва; всё, что вошло, доступно для просмотра.
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
* Sprinter-2000 с ОС ESTEX;
|
||||||
|
* расширенная память (EMM): в худшем случае (файл 256 КБ + оба набора
|
||||||
|
кодировок) — до ~150 страниц по 16 КБ (~2,4 МБ). Для типичных файлов
|
||||||
|
в десятки килобайт достаточно нескольких десятков страниц.
|
||||||
|
|
||||||
|
Код, данные, стек и куча программы занимают окна W1+W2 (32 КБ, режим
|
||||||
|
памяти `small`); окно W3 используется только для доступа к EMM-страницам.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Сборка
|
||||||
|
|
||||||
|
Требуется тулчейн этого репозитория (обёртка `sprinter-cc` над SDCC 4.5).
|
||||||
|
Из каталога `examples/mdview2`:
|
||||||
|
|
||||||
|
```
|
||||||
|
make # собрать mdview2.exe
|
||||||
|
make floppy # собрать и упаковать дискету для MAME (mc.img)
|
||||||
|
make run # floppy + автоматический прогон в MAME (скриншоты)
|
||||||
|
```
|
||||||
|
|
||||||
|
На дискету кладутся: `MDVIEW2.EXE` и три документа, каждый в своей кодировке
|
||||||
|
(заодно покрывают все пути автодетекта): `README.MD` — этот файл, как есть
|
||||||
|
(UTF-8); `DEMO.MD` — демонстрация всех элементов разметки (CP1251);
|
||||||
|
`CHANGES.MD` — история версий (CP866).
|
||||||
|
|
||||||
|
## Структура исходников
|
||||||
|
|
||||||
|
| Файл | Назначение |
|
||||||
|
|------------------|--------------------------------------------------------------|
|
||||||
|
| `mdview2.c` | ядро: EMM-аллокации, рендер-кэш, наборы кодировок, загрузка файла, главный цикл |
|
||||||
|
| `mdview2_index.c`| парсер/индексатор Markdown — единственный проход по файлу |
|
||||||
|
| `mdview2_md.c` | MD-вид: отрисовка из кэша, прокрутка |
|
||||||
|
| `mdview2_raw.c` | RAW-вид (F2/F3) |
|
||||||
|
| `mdview2_hex.c` | HEX-вид (F4) |
|
||||||
|
| `mdview2_enc.c` | кодировки: детект, ремап-таблицы, конвертер UTF-8 → CP866 |
|
||||||
|
| `mdview2_table.c`| отрисовка таблиц |
|
||||||
|
| `mdview2_status.c`| статус-бар, меню, спиннер |
|
||||||
|
| `mdview2_help.c` | справка (F1) |
|
||||||
|
| `mdview2_conf.h` | конфигурация: `WITH_RAW` / `WITH_HEX` (модули отключаемы) |
|
||||||
|
| `mdview2.h` | общие константы, атрибуты, межмодульный API |
|
||||||
|
|
||||||
|
Подробности архитектуры — в `docs/mdview2-plan.md`.
|
||||||
|
|
||||||
|
## Лицензия и авторы
|
||||||
|
|
||||||
|
© 2026 Петров А.Г. Часть проекта Sprinter C Compiler
|
||||||
|
(см. LICENSE в корне репозитория).
|
||||||
+1338
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,304 @@
|
|||||||
|
/*
|
||||||
|
* mdview2.h — общие определения и интерфейс между модулями mdview2.
|
||||||
|
*
|
||||||
|
* Монолит mdview2.c расщеплён на ядро + модули: index (парсер), md/raw (виды),
|
||||||
|
* enc (кодировки), table, help. Здесь — разделяемые константы экрана/атрибутов,
|
||||||
|
* словари флагов/стилей, тип cache_rec_t, extern-объявления разделяемого
|
||||||
|
* состояния ядра и API каждого модуля (сгруппированы по секциям ниже).
|
||||||
|
*/
|
||||||
|
#ifndef MDVIEW2_H
|
||||||
|
#define MDVIEW2_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <palette.h> /* COLOR(), COLOR_* */
|
||||||
|
#include "mdview2_conf.h" /* WITH_RAW / WITH_HEX — выбор опциональных модулей */
|
||||||
|
|
||||||
|
/* ---- Геометрия экрана -------------------------------------------- */
|
||||||
|
#define SCREEN_W 80
|
||||||
|
#define SCREEN_H 32
|
||||||
|
#define VIEW_TOP_ROW 1
|
||||||
|
#define VIEW_H 30 /* видимая область: строки 1..30 включительно */
|
||||||
|
#define MENU_ROW 31
|
||||||
|
#define TAB_STOP 4
|
||||||
|
#define HPAN_STEP 8u /* шаг горизонтального сдвига (←/→) */
|
||||||
|
|
||||||
|
/* ---- Геометрия статус-бара (строка 0): фиксированные поля и разделители ---- */
|
||||||
|
#define SPINNER_COL 8 /* слот спиннера загрузки */
|
||||||
|
#define DIV1_X 45 /* разделитель │ перед числами диапазона строк */
|
||||||
|
#define DIV2_X 71 /* разделитель │ перед процентом */
|
||||||
|
/* Числа/метка пишутся с DIVn_X+2; область между разделителями — [DIV1_X+1 .. DIV2_X-1]. */
|
||||||
|
|
||||||
|
/* ---- Параметры файла и памяти ------------------------------------ */
|
||||||
|
#define PAGE_BITS 14u
|
||||||
|
#define PAGE_SIZE (1u << PAGE_BITS) /* размер EMM-страницы: 16 КБ */
|
||||||
|
#define PAGE_MASK ((uint16_t)(PAGE_SIZE - 1u))
|
||||||
|
#define MAX_PAGES 16 /* 16 страниц × 16 КБ = 256 КБ */
|
||||||
|
#define MAX_FILE ((uint32_t)MAX_PAGES * PAGE_SIZE)
|
||||||
|
#define FILE_BUF ((char *)0xC000) /* окно W3, куда мапится текущая EMM-страница */
|
||||||
|
|
||||||
|
/* ---- Палитра атрибутов ------------------------------------------- */
|
||||||
|
#define ATTR_RESET COLOR(COLOR_LIGHTGRAY, COLOR_BLACK)
|
||||||
|
#define ATTR_TEXT COLOR(COLOR_LIGHTGRAY, COLOR_BLUE)
|
||||||
|
#define ATTR_TEXT_TITLE1 COLOR(COLOR_YELLOW, COLOR_BLUE)
|
||||||
|
#define ATTR_TEXT_TITLE2 COLOR(COLOR_LIGHTBLUE, COLOR_BLUE)
|
||||||
|
#define ATTR_TEXT_TITLE3 COLOR(COLOR_LIGHTGREEN, COLOR_BLUE)
|
||||||
|
#define ATTR_TEXT_TITLE4 COLOR(COLOR_LIGHTGREEN, COLOR_BLUE)
|
||||||
|
#define ATTR_TEXT_BOLD COLOR(COLOR_LIGHTRED, COLOR_BLUE)
|
||||||
|
#define ATTR_TEXT_ITALIC COLOR(COLOR_LIGHTGREEN, COLOR_BLUE)
|
||||||
|
#define ATTR_TEXT_UNDERSORE COLOR(COLOR_LIGHTMAGENTA, COLOR_BLUE)
|
||||||
|
#define ATTR_TEXT_CODE COLOR(COLOR_WHITE, COLOR_BLUE)
|
||||||
|
#define ATTR_TEXT_STRIKE COLOR(COLOR_DARKGRAY, COLOR_BLUE)
|
||||||
|
#define ATTR_LIST_MARKER COLOR(COLOR_LIGHTCYAN, COLOR_BLUE)
|
||||||
|
#define ATTR_QUOTE_MARKER COLOR(COLOR_CYAN, COLOR_BLUE)
|
||||||
|
#define ATTR_HR COLOR(COLOR_CYAN, COLOR_BLUE)
|
||||||
|
#define ATTR_BOX COLOR(COLOR_CYAN, COLOR_BLUE)
|
||||||
|
#define ATTR_TRUNC COLOR(COLOR_YELLOW, COLOR_BLUE)
|
||||||
|
#define ATTR_BAR COLOR(COLOR_BLACK, COLOR_LIGHTCYAN)
|
||||||
|
#define ATTR_BAR_SPINNER COLOR(COLOR_WHITE, COLOR_LIGHTCYAN)
|
||||||
|
#define ATTR_MENU_T COLOR(COLOR_BLACK, COLOR_LIGHTCYAN)
|
||||||
|
#define ATTR_MENU_K COLOR(COLOR_YELLOW, COLOR_BLACK)
|
||||||
|
#define ATTR_WARN COLOR(COLOR_YELLOW, COLOR_RED) /* строка-обрыв (исчерпан кэш/лимит) */
|
||||||
|
|
||||||
|
/* ---- Атрибуты диалога справки ------------------------------------ */
|
||||||
|
#define ATTR_HELP_BG COLOR(COLOR_LIGHTGRAY, COLOR_BLACK)
|
||||||
|
#define ATTR_HELP_BDR COLOR(COLOR_WHITE, COLOR_BLACK)
|
||||||
|
#define ATTR_HELP_TIT COLOR(COLOR_YELLOW, COLOR_BLACK)
|
||||||
|
#define ATTR_HELP_HDR COLOR(COLOR_WHITE, COLOR_BLACK)
|
||||||
|
|
||||||
|
/* ---- Режимы просмотра -------------------------------------------- */
|
||||||
|
#define VIEW_MD 0 /* markdown с форматированием (по умолчанию) */
|
||||||
|
#define VIEW_RAW_WRAP 1 /* RAW: длинные строки переносятся кратно 80 */
|
||||||
|
#define VIEW_RAW_HSCROLL 2 /* RAW: длинные строки в одну, гориз. скролл */
|
||||||
|
#define VIEW_HEX 3 /* HEX-дамп оригинального файла */
|
||||||
|
#define VIEW_IS_RAW(v) ((v) == VIEW_RAW_WRAP || (v) == VIEW_RAW_HSCROLL)
|
||||||
|
|
||||||
|
/* ---- Кодировки --------------------------------------------------- */
|
||||||
|
/* 8-битные (CP866/CP1251/KOI8R) различаются только ремапом глифов [128-255]
|
||||||
|
* на отрисовке; UTF-8 — отдельный декодированный набор. */
|
||||||
|
#define ENC_CP866 0
|
||||||
|
#define ENC_CP1251 1
|
||||||
|
#define ENC_KOI8R 2
|
||||||
|
#define ENC_UTF8 3
|
||||||
|
#define ENC_UNSUPPORTED -1 /* UTF16 / UTF32 */
|
||||||
|
#define CONV_MARGIN 4096u /* на сколько байт держать UTF-конвертацию впереди индексатора */
|
||||||
|
|
||||||
|
/* ---- Флаги сегмента индекса (IF_*) — общие для индексатора/кэша/вью ---- */
|
||||||
|
#define IF_CONT 0x01u /* сегмент является продолжением перенесённой строки */
|
||||||
|
#define IF_NOWRAP 0x02u /* строка не переносится (кодовый блок / HR / таблица) */
|
||||||
|
#define IF_BLANK 0x04u /* визуально пустая строка */
|
||||||
|
#define IF_CODE 0x08u /* тело fenced code-блока (verbatim-режим) */
|
||||||
|
#define IF_HSCROLL 0x10u /* блок горизонтально скроллируется ЦЕЛИКОМ (код/таблица),
|
||||||
|
* включая строки короче 80 — двигаются как единый блок.
|
||||||
|
* НЕ ставится на HR и границы fence (им нечего прятать). */
|
||||||
|
#define IF_TRUNC_MSG 0x20u /* строка-обрыв: рисуется ВЖИВУЮ фикс. текстом (без контент-кэша);
|
||||||
|
* cache_rec.reserved несёт код причины (TRUNC_*). */
|
||||||
|
|
||||||
|
/* ---- Причина обрыва индексации (cache_rec.reserved при IF_TRUNC_MSG) ---- */
|
||||||
|
#define TRUNC_CONTENT 1 /* исчерпан контент-кэш (MAX_CACHE_CONTENT_PAGES) */
|
||||||
|
#define TRUNC_LINES 2 /* исчерпана ёмкость индекса (max_lines) */
|
||||||
|
#define TRUNC_FILE 3 /* файл больше MAX_FILE — прочитаны первые 256 КБ */
|
||||||
|
|
||||||
|
/* ---- Словарь сегментов/стилей (общий для индексатора и таблиц) ---- */
|
||||||
|
/* Тип сегмента-продолжения, передаваемый в emit_seg()/inline_scan(). */
|
||||||
|
#define CK_PLAIN 0
|
||||||
|
#define CK_QUOTE 1
|
||||||
|
#define CK_LIST 2
|
||||||
|
#define CK_OTHER 3
|
||||||
|
|
||||||
|
/* Начальный inline-стиль сегмента (INIT_STYLE_*). */
|
||||||
|
#define INIT_STYLE_PLAIN 0x0
|
||||||
|
#define INIT_STYLE_BOLD 0x1
|
||||||
|
#define INIT_STYLE_ITALIC 0x2
|
||||||
|
#define INIT_STYLE_UNDER 0x3
|
||||||
|
#define INIT_STYLE_CODE 0x4
|
||||||
|
#define INIT_STYLE_STRIKE 0x5
|
||||||
|
|
||||||
|
/* ---- Таблицы: CP866 light box-drawing (общий для индексатора и модуля) ---- */
|
||||||
|
#define TBL_MAX_COLS 16
|
||||||
|
#define TBL_ATTR ATTR_BOX
|
||||||
|
#define TBL_H 0xC4 /* ─ */
|
||||||
|
#define TBL_V 0xB3 /* │ */
|
||||||
|
#define TBL_TL 0xDA /* ┌ */
|
||||||
|
#define TBL_TM 0xC2 /* ┬ */
|
||||||
|
#define TBL_TR 0xBF /* ┐ */
|
||||||
|
#define TBL_ML 0xC3 /* ├ */
|
||||||
|
#define TBL_MM 0xC5 /* ┼ */
|
||||||
|
#define TBL_MR 0xB4 /* ┤ */
|
||||||
|
#define TBL_BL 0xC0 /* └ */
|
||||||
|
#define TBL_BM 0xC1 /* ┴ */
|
||||||
|
#define TBL_BR 0xD9 /* ┘ */
|
||||||
|
|
||||||
|
/* ================================================================== *
|
||||||
|
* Разделяемые символы mdview2.c, используемые модулем RAW.
|
||||||
|
* ================================================================== */
|
||||||
|
extern uint8_t g_view; /* активный режим просмотра (VIEW_*) */
|
||||||
|
extern uint32_t file_size; /* размер активного буфера документа */
|
||||||
|
extern const uint8_t *g_remap; /* таблица ремапа [128-255] (0 = нет) */
|
||||||
|
extern uint8_t g_scratch_phys; /* scratch EMM-страница для win_rest */
|
||||||
|
|
||||||
|
char fb(uint32_t p); /* байт активного буфера (W3-маппинг) */
|
||||||
|
void win_rest(uint8_t row, uint8_t col, uint8_t h, uint8_t w, uint8_t page, uint16_t off);
|
||||||
|
void fill_row(uint8_t y, uint8_t attr);
|
||||||
|
uint8_t pct16(uint16_t num, uint16_t den); /* num*100/den (0..100), 16-бит, без __divulong */
|
||||||
|
uint32_t seg_off(uint16_t idx); /* offset исходника для логической строки idx */
|
||||||
|
uint16_t line_at_off(uint32_t off); /* обратно: последняя строка с seg_off <= off */
|
||||||
|
|
||||||
|
/* ================================================================== *
|
||||||
|
* API модуля RAW (mdview2_raw.c).
|
||||||
|
* ================================================================== */
|
||||||
|
void raw_seed_from(uint16_t md_top_line); /* вход в RAW: позиция по top_line */
|
||||||
|
uint32_t raw_pos(void); /* текущий байт-offset верха экрана RAW */
|
||||||
|
void raw_reanchor(uint32_t off); /* поставить RAW на строку с байтом off */
|
||||||
|
void raw_draw(void); /* перерисовать область документа в RAW */
|
||||||
|
uint8_t raw_pct(void); /* % прокрутки по байтам (для render_raw_status_numbers) */
|
||||||
|
uint8_t raw_key(uint8_t scan); /* навигация RAW; 1 = обработано */
|
||||||
|
void raw_screen_init(void); /* очистка экрана с нужным аттрибутом */
|
||||||
|
void raw_renorm(void); /* выровнять позицию при смене под-режима (F3) */
|
||||||
|
|
||||||
|
/* ================================================================== *
|
||||||
|
* API модуля HEX (mdview2_hex.c) — дамп ОРИГИНАЛЬНОГО файла (F4).
|
||||||
|
* Позиция hex_pos/hex_reanchor — в байтах ОРИГИНАЛА (orig_file_*);
|
||||||
|
* конвертацию в/из активного буфера делает ядро (map_off).
|
||||||
|
* ================================================================== */
|
||||||
|
uint32_t hex_pos(void); /* offset верхнего ряда (кратен 16) */
|
||||||
|
void hex_reanchor(uint32_t orig_off); /* поставить на ряд с байтом orig_off */
|
||||||
|
void hex_draw(void); /* перерисовать область документа */
|
||||||
|
uint8_t hex_key(uint8_t scan); /* навигация HEX; 1 = обработано */
|
||||||
|
void hex_screen_init(void); /* очистка экрана */
|
||||||
|
|
||||||
|
/* ================================================================== *
|
||||||
|
* Запись директории рендер-кэша (одна на видимую строку, РОВНО 8 байт —
|
||||||
|
* cache_dir_get/put адресуют сдвигом idx<<3). Контент — len пар (char,attr).
|
||||||
|
* ================================================================== */
|
||||||
|
typedef struct cache_rec_s {
|
||||||
|
uint8_t page; /* EMM-страница рендер-кэша (физический номер) */
|
||||||
|
uint16_t off; /* смещение в странице, 0..16383 (байты, не ячейки) */
|
||||||
|
uint8_t len; /* длина контента в ЯЧЕЙКАХ (char,attr пар), 0..255; 0 = пустая/HR */
|
||||||
|
uint8_t flags; /* копия IF_NOWRAP/IF_CODE/IF_BLANK на момент рендера */
|
||||||
|
uint8_t reserved; /* не используется */
|
||||||
|
uint8_t pad[2]; /* явный резерв, добивка до 8 байт */
|
||||||
|
} cache_rec_t;
|
||||||
|
|
||||||
|
void cache_dir_get(uint16_t idx, cache_rec_t *r); /* читатель директории кэша (ядро) */
|
||||||
|
void put_str_attr(uint8_t x, uint8_t y, const char *s, uint8_t attr); /* печать строки с атрибутом */
|
||||||
|
|
||||||
|
/* ================================================================== *
|
||||||
|
* Состояние навигации/представления (определено в mdview2.c).
|
||||||
|
* ================================================================== */
|
||||||
|
extern uint16_t n_lines; /* всего строк в индексе текущего набора */
|
||||||
|
extern uint16_t top_line; /* верхняя видимая строка */
|
||||||
|
extern uint8_t viewport_x; /* горизонтальный сдвиг (nowrap-строки) */
|
||||||
|
extern char filename[]; /* имя файла для статус-бара */
|
||||||
|
extern uint8_t g_loading; /* 1 во время index_lines() */
|
||||||
|
extern uint8_t g_ready; /* 1 когда первичный документ построен (F2 RAW) */
|
||||||
|
extern uint8_t g_f8_enabled; /* можно ли сейчас переключать кодировку */
|
||||||
|
|
||||||
|
/* ================================================================== *
|
||||||
|
* Статус-бар (строка 0), меню (строка 31), спиннер — mdview2_status.c.
|
||||||
|
* Атомарные части: prerender (фикс. хром, один раз) / encoding (поле кодировки)
|
||||||
|
* / numbers (диапазон строк + %) / menu / spinner — обновляются по отдельности.
|
||||||
|
* ================================================================== */
|
||||||
|
void prerender_status(void); /* фикс. часть: фон, разделители, MDVIEW, имя файла (1 раз) */
|
||||||
|
void status_encoding(void); /* только поле кодировки (col 37) */
|
||||||
|
void render_md_status_numbers(void); /* числа MD: диапазон строк + % */
|
||||||
|
void render_raw_status(void); /* RAW mode: метка режима */
|
||||||
|
void render_hex_status(void); /* HEX mode: метка режима */
|
||||||
|
void render_full_status(void); /* encoding + numbers (MD) */
|
||||||
|
void render_menu(void); /* строка меню (по смене режима/готовности) */
|
||||||
|
uint16_t drawable_lines(void); /* число строк, готовых к показу */
|
||||||
|
void spinner_tick(void); /* кадр спиннера загрузки */
|
||||||
|
void spinner_show(uint8_t on); /* вкл/выкл спиннер */
|
||||||
|
void render_percent_progress(uint8_t pct); /* показать процентный прогресс просмотра */
|
||||||
|
|
||||||
|
/* ================================================================== *
|
||||||
|
* Отрисовка области MD-документа и прокрутка (mdview2_md.c).
|
||||||
|
* ================================================================== */
|
||||||
|
void draw_viewport_from_cache(void); /* перерисовать область документа из кэша */
|
||||||
|
void clamp_top(void); /* привести top_line к диапазону */
|
||||||
|
void md_scroll_up(uint16_t n);
|
||||||
|
void md_scroll_down(uint16_t n);
|
||||||
|
void md_scroll_horizon(int8_t delta); /* горизонтальный сдвиг nowrap-блоков */
|
||||||
|
uint8_t md_key(uint8_t scan); /* навигация MD (после загрузки); 1 = обработано */
|
||||||
|
|
||||||
|
/* ================================================================== *
|
||||||
|
* API модуля справки (mdview2_help.c).
|
||||||
|
* ================================================================== */
|
||||||
|
void show_help(void); /* модальный диалог F1 */
|
||||||
|
|
||||||
|
/* ================================================================== *
|
||||||
|
* Парсер/индексатор (mdview2_index.c) и разделяемое с ним состояние.
|
||||||
|
* Хранилище индекса (index_blk/pages/phys, n_lines/max_lines) — в ядре
|
||||||
|
* (аллокация/doc-slot), индексатор пишет в него; cache_*-писатели и
|
||||||
|
* progress_tick ядро экспортирует индексатору (spinner_tick — из status).
|
||||||
|
* ================================================================== */
|
||||||
|
#define INDEX_RECS_PER_PAGE 2048u /* 16384 / 8; запись всегда в одной странице */
|
||||||
|
#define MAX_CACHE_LINE_LEN 255u /* кап длины контента строки в ячейках (буфер g_cells) */
|
||||||
|
|
||||||
|
extern uint16_t max_lines; /* ёмкость индекса: index_pages * 2048 */
|
||||||
|
extern uint8_t index_blk, index_pages; /* дескриптор EMM-блока индекса */
|
||||||
|
extern uint8_t index_phys[]; /* физ. страницы индекса */
|
||||||
|
extern uint8_t index_truncated; /* индекс упёрся в ёмкость */
|
||||||
|
extern uint8_t g_abort; /* F10/Esc во время загрузки → прервать индексацию */
|
||||||
|
extern uint8_t g_file_clamped; /* файл был обрезан до MAX_FILE при загрузке (>256 КБ) */
|
||||||
|
|
||||||
|
uint8_t cache_reserve(uint16_t nbytes, uint8_t *out_page, uint16_t *out_off);
|
||||||
|
void cache_commit(uint8_t page, uint16_t off, const void *buf, uint16_t len);
|
||||||
|
void cache_dir_put(uint16_t idx, const cache_rec_t *r);
|
||||||
|
void progress_tick(void); /* кооперативный шаг loading-loop (рисует/листает) */
|
||||||
|
|
||||||
|
/* Резюмируемая индексация (для фоновой сборки второго набора): begin сбрасывает
|
||||||
|
* проход, step выполняет до budget итераций (блоков исходника) и возвращает 1,
|
||||||
|
* когда индекс финализирован. Межшаговое состояние — статики модуля index;
|
||||||
|
* оно не входит в docset_t (см. bg_step ядра). */
|
||||||
|
void index_begin(void);
|
||||||
|
uint8_t index_step(uint8_t budget);
|
||||||
|
extern uint8_t g_bg_building; /* 1 = фоновый шаг индексации: не трогать экран (спиннер) */
|
||||||
|
|
||||||
|
/* ================================================================== *
|
||||||
|
* Буфер ячеек рендера (mdview2.c) — общий с модулем таблиц.
|
||||||
|
* Таблицы эмитят свои строки теми же примитивами, что и индексатор.
|
||||||
|
* ================================================================== */
|
||||||
|
extern uint8_t g_ncells; /* ячеек в текущем сегменте */
|
||||||
|
void gc_put(char ch, uint8_t attr); /* добавить ячейку в g_cells */
|
||||||
|
void gc_fill(char ch, uint8_t attr, uint8_t n); /* n одинаковых ячеек */
|
||||||
|
uint8_t inline_scan(uint32_t q, uint32_t q_end, uint8_t col,
|
||||||
|
uint8_t ckind, uint8_t line_style, uint8_t base_attr,
|
||||||
|
uint8_t nowrap);
|
||||||
|
|
||||||
|
/* ================================================================== *
|
||||||
|
* API модуля таблиц (mdview2_table.c) — вызывается из index_lines().
|
||||||
|
* ================================================================== */
|
||||||
|
uint32_t row_end(uint32_t p); /* offset завершающего '\n' (или file_size) */
|
||||||
|
uint32_t table_first_cell(uint32_t row_start); /* контент первой ячейки строки */
|
||||||
|
uint8_t table_next_cell(uint32_t *pp, uint32_t lineend, uint32_t *cs, uint32_t *ce);
|
||||||
|
uint8_t table_is_sep_row(uint32_t row_start, uint32_t lineend);
|
||||||
|
void table_border(const uint8_t *widths, uint8_t ncols, char left, char mid, char right);
|
||||||
|
void table_data_row(uint32_t row_start, uint32_t lineend, const uint8_t *widths, uint8_t ncols);
|
||||||
|
|
||||||
|
/* ================================================================== *
|
||||||
|
* Кодировки и UTF-8 конвертация (mdview2_enc.c).
|
||||||
|
* Состояние кодека владеется модулем; build_doc()/index_lines() ядра
|
||||||
|
* оркестрируют его (привязка UTF-буфера к живому file_*, сборка наборов).
|
||||||
|
* ================================================================== */
|
||||||
|
extern uint8_t g_encoding; /* активная кодировка (ENC_*) */
|
||||||
|
extern uint8_t g_utf_building; /* 1 пока UTF-конвертация не дошла до конца исходника */
|
||||||
|
extern uint8_t utf_avail; /* 1 = страницы UTF-8 выделены */
|
||||||
|
extern uint8_t utf_blk, utf_pages; /* дескриптор EMM-буфера UTF-набора */
|
||||||
|
extern uint8_t utf_phys[]; /* физ. страницы UTF-буфера */
|
||||||
|
extern uint32_t utf_size; /* размер сконвертированного UTF-потока */
|
||||||
|
|
||||||
|
/* Ядро-owned состояние, читаемое кодеком (определено в mdview2.c). */
|
||||||
|
extern uint8_t cur_page; /* текущая страница файла в W3 (инвалидация маппинга) */
|
||||||
|
extern uint8_t orig_file_phys[]; /* физ. страницы снимка исходного файла */
|
||||||
|
extern uint8_t orig_file_pages;
|
||||||
|
extern uint32_t orig_file_size;
|
||||||
|
|
||||||
|
void set_encoding(uint8_t enc); /* активная кодировка + таблица ремапа */
|
||||||
|
const char *enc_name(uint8_t enc); /* короткое имя для статус-бара */
|
||||||
|
uint8_t detect_encoding(void); /* автоопределение по сэмплу байт */
|
||||||
|
void win_rest_remap(uint8_t row, uint8_t w, uint8_t page, uint16_t off); /* вывод среза с ремапом */
|
||||||
|
void utf_convert_more(uint32_t target); /* инкрементальная конвертация до target байт */
|
||||||
|
uint8_t utf_alloc(void); /* выделить EMM-страницы под UTF-набор; 0 = нет EMM */
|
||||||
|
uint8_t utf_cp_glyph(uint32_t cp); /* кодпойнт → один CP866-глиф ('?' без аналога) */
|
||||||
|
|
||||||
|
#endif /* MDVIEW2_H */
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* mdview2_conf.h — конфигурация сборки mdview2: какие опциональные модули
|
||||||
|
* просмотра включать. 1 = включить (код входит в бинарь), 0 = исключить
|
||||||
|
* полностью (нулевой расход кода/памяти).
|
||||||
|
*
|
||||||
|
* Модули обёрнуты в #if WITH_xxx целиком, поэтому файлы можно держать в
|
||||||
|
* сборке всегда (при 0 они компилируются в пустой объектник) — менять
|
||||||
|
* нужно только эти define.
|
||||||
|
*/
|
||||||
|
#ifndef MDVIEW2_CONF_H
|
||||||
|
#define MDVIEW2_CONF_H
|
||||||
|
|
||||||
|
#define WITH_RAW 1 /* RAW-просмотр исходника (F2): mdview2_raw.c */
|
||||||
|
#define WITH_HEX 1 /* HEX-дамп оригинального файла (F4): mdview2_hex.c */
|
||||||
|
|
||||||
|
#endif /* MDVIEW2_CONF_H */
|
||||||
@@ -0,0 +1,319 @@
|
|||||||
|
/*
|
||||||
|
* mdview2_enc.c — кодировки и конвертация UTF-8 → CP866.
|
||||||
|
*
|
||||||
|
* Две связанные подсистемы:
|
||||||
|
* 1. 8-битные кодировки (CP866/CP1251/KOI8-R) — различаются только ремапом
|
||||||
|
* старших байтов [128-255] на ОТРИСОВКЕ (g_remap, win_rest_remap).
|
||||||
|
* Автоопределение — detect_encoding по частоте ходовых букв.
|
||||||
|
* 2. UTF-8 — отдельный набор: исходник инкрементально декодируется в
|
||||||
|
* utf_phys[] как чистый CP866 (utf_convert_more), после чего весь
|
||||||
|
* конвейер индексации/кэша работает по нему без ремапа.
|
||||||
|
*
|
||||||
|
* Владеет состоянием кодека (conv-буфер, позиция конвертации, UTF-страницы);
|
||||||
|
* описание разделяемых символов и API — в mdview2.h. Оркестрацией (привязка
|
||||||
|
* UTF-буфера к живому file_*, сборка наборов) занимается build_doc() ядра.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <conio.h> /* COLOR()/COLOR_* для ATTR_* в attr_is_content */
|
||||||
|
#include <sprinter.h> /* sprinter_page_w3 */
|
||||||
|
#include <sprinter_mem.h> /* bank_read/bank_write/mem_alloc_pages/mem_get_page */
|
||||||
|
#include "mdview2.h"
|
||||||
|
|
||||||
|
/* ---- Состояние кодировки/UTF (владелец — этот модуль) ------------- */
|
||||||
|
uint8_t g_encoding = ENC_CP866; /* активная кодировка */
|
||||||
|
|
||||||
|
uint8_t utf_blk;
|
||||||
|
uint8_t utf_pages;
|
||||||
|
uint8_t utf_phys[MAX_PAGES];
|
||||||
|
uint32_t utf_size;
|
||||||
|
uint8_t utf_avail; /* 1 = страницы UTF-8 выделены */
|
||||||
|
uint8_t g_utf_building; /* 1 пока конвертация не дошла до конца исходника */
|
||||||
|
|
||||||
|
/* Приватное состояние конвертера (наружу не торчит — сброс через
|
||||||
|
* utf_conv_reset(), чтение оригинала через cv_read). */
|
||||||
|
static uint32_t utf_src; /* позиция чтения ОРИГИНАЛА (utf-8) */
|
||||||
|
static uint8_t cv_page = 0xFF; /* orig-страница в W3 для cv_read (своя от cur_page) */
|
||||||
|
|
||||||
|
/* Таблицы ремапа СТАРШИХ байтов [128-255] в CP866. Индексируются (ch-0x80):
|
||||||
|
* младшие 128 (ASCII) — identity, в ремапе не участвуют (win_rest_remap
|
||||||
|
* трогает только ch>=0x80), поэтому в таблицах их нет. Кириллица + ходовая
|
||||||
|
* пунктуация; неизвестное → '?' (0x3F). Сгенерированы Python codecs. */
|
||||||
|
static const uint8_t cp1251_to_866[128] = {
|
||||||
|
/* 80 */ 0x3F, 0x3F, 0x27, 0x3F, 0x22, 0x2E, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3C, 0x3F, 0x3F, 0x3F, 0x3F,
|
||||||
|
/* 90 */ 0x3F, 0x27, 0x27, 0x22, 0x22, 0x07, 0x2D, 0x2D, 0x3F, 0x3F, 0x3F, 0x3E, 0x3F, 0x3F, 0x3F, 0x3F,
|
||||||
|
/* A0 */ 0xFF, 0xF6, 0xF7, 0x3F, 0xFD, 0x3F, 0x3F, 0x3F, 0xF0, 0x63, 0xF2, 0x3C, 0x3F, 0x3F, 0x72, 0xF4,
|
||||||
|
/* B0 */ 0xF8, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0xFA, 0xF1, 0xFC, 0xF3, 0x3E, 0x3F, 0x3F, 0x3F, 0xF5,
|
||||||
|
/* C0 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
||||||
|
/* D0 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
|
||||||
|
/* E0 */ 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
|
||||||
|
/* F0 */ 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
|
||||||
|
};
|
||||||
|
static const uint8_t koi8r_to_866[128] = {
|
||||||
|
/* 80 */ 0xC4, 0xB3, 0xDA, 0xBF, 0xC0, 0xD9, 0xC3, 0xB4, 0xC2, 0xC1, 0xC5, 0xDF, 0xDC, 0xDB, 0xDD, 0xDE,
|
||||||
|
/* 90 */ 0xB0, 0xB1, 0xB2, 0x3F, 0xFE, 0xF9, 0xFB, 0x3F, 0x3F, 0x3F, 0xFF, 0x3F, 0xF8, 0x3F, 0xFA, 0x3F,
|
||||||
|
/* A0 */ 0xCD, 0xBA, 0xD5, 0xF1, 0xD6, 0xC9, 0xB8, 0xB7, 0xBB, 0xD4, 0xD3, 0xC8, 0xBE, 0xBD, 0xBC, 0xC6,
|
||||||
|
/* B0 */ 0xC7, 0xCC, 0xB5, 0xF0, 0xB6, 0xB9, 0xD1, 0xD2, 0xCB, 0xCF, 0xD0, 0xCA, 0xD8, 0xD7, 0xCE, 0x63,
|
||||||
|
/* C0 */ 0xEE, 0xA0, 0xA1, 0xE6, 0xA4, 0xA5, 0xE4, 0xA3, 0xE5, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE,
|
||||||
|
/* D0 */ 0xAF, 0xEF, 0xE0, 0xE1, 0xE2, 0xE3, 0xA6, 0xA2, 0xEC, 0xEB, 0xA7, 0xE8, 0xED, 0xE9, 0xE7, 0xEA,
|
||||||
|
/* E0 */ 0x9E, 0x80, 0x81, 0x96, 0x84, 0x85, 0x94, 0x83, 0x95, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
|
||||||
|
/* F0 */ 0x8F, 0x9F, 0x90, 0x91, 0x92, 0x93, 0x86, 0x82, 0x9C, 0x9B, 0x87, 0x98, 0x9D, 0x99, 0x97, 0x9A,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Устанавливает активную кодировку и таблицу ремапа (для 8-битных). */
|
||||||
|
void set_encoding(uint8_t enc)
|
||||||
|
{
|
||||||
|
g_encoding = enc;
|
||||||
|
g_remap = (enc == ENC_CP1251) ? cp1251_to_866 :
|
||||||
|
(enc == ENC_KOI8R) ? koi8r_to_866 : (const uint8_t *)0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Самые ходовые строчные русские буквы (о е а и н т с р в л) — их байты
|
||||||
|
* различают 8-битные кодировки по частоте. */
|
||||||
|
// static const uint8_t common866 [10] = {0xAE,0xA5,0xA0,0xA8,0xAD,0xE2,0xE1,0xE0,0xA2,0xAB};
|
||||||
|
// static const uint8_t common1251[10] = {0xEE,0xE5,0xE0,0xE8,0xED,0xF2,0xF1,0xF0,0xE2,0xEB};
|
||||||
|
// static const uint8_t commonkoi [10] = {0xCF,0xC5,0xC1,0xC9,0xCE,0xD4,0xD3,0xD2,0xD7,0xCC};
|
||||||
|
// static uint8_t in_set10(const uint8_t *s, uint8_t b)
|
||||||
|
// {
|
||||||
|
// for (uint8_t i = 0; i < 10; i++) if (s[i] == b) return 1;
|
||||||
|
// return 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
/* Проверяем только пять самых популярных символов (о е а и н) */
|
||||||
|
static const uint8_t common866 [5] = {0xAE,0xA5,0xA0,0xA8,0xAD};
|
||||||
|
static const uint8_t common1251[5] = {0xEE,0xE5,0xE0,0xE8,0xED};
|
||||||
|
static const uint8_t commonkoi [5] = {0xCF,0xC5,0xC1,0xC9,0xCE};
|
||||||
|
static uint8_t in_set10(const uint8_t *s, uint8_t b)
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < 5; i++) if (s[i] == b) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Автоопределение кодировки дешёвым сканом байтов (до построения индекса).
|
||||||
|
* BOM → UTF8; иначе валидность UTF-8 (структура multibyte); иначе 8-бит по
|
||||||
|
* частоте ходовых букв; фолбэк CP866. */
|
||||||
|
uint8_t detect_encoding(void)
|
||||||
|
{
|
||||||
|
if (file_size >= 3 && (uint8_t)fb(0) == 0xEF && (uint8_t)fb(1) == 0xBB && (uint8_t)fb(2) == 0xBF)
|
||||||
|
return ENC_UTF8;
|
||||||
|
|
||||||
|
uint32_t n = file_size;
|
||||||
|
if (n > 1024u) n = 1024u; /* сэмпл: первый 1 КБ — детекции хватает */
|
||||||
|
uint8_t utf_ok = 1, has_mb = 0, has_high = 0, cont = 0;
|
||||||
|
uint16_t s866 = 0, s1251 = 0, skoi = 0;
|
||||||
|
|
||||||
|
for (uint32_t p = 0; p < n; p++) {
|
||||||
|
uint8_t b = (uint8_t)fb(p);
|
||||||
|
if (b < 0x80) { if (cont) { utf_ok = 0; cont = 0; } continue; }
|
||||||
|
has_high = 1;
|
||||||
|
if (in_set10(common866, b)) s866++;
|
||||||
|
if (in_set10(common1251, b)) s1251++;
|
||||||
|
if (in_set10(commonkoi, b)) skoi++;
|
||||||
|
if (cont) {
|
||||||
|
if ((b & 0xC0) == 0x80) cont--;
|
||||||
|
else { utf_ok = 0; cont = 0; }
|
||||||
|
}
|
||||||
|
else if (b >= 0xC2 && b <= 0xDF) { cont = 1; has_mb = 1; }
|
||||||
|
else if (b >= 0xE0 && b <= 0xEF) { cont = 2; has_mb = 1; }
|
||||||
|
else if (b >= 0xF0 && b <= 0xF4) { cont = 3; has_mb = 1; }
|
||||||
|
else utf_ok = 0; /* битый лид/одиночный континюэйшн */
|
||||||
|
}
|
||||||
|
/* Незавершённая multibyte-последовательность на КОНЦЕ — нарушение только
|
||||||
|
* если это настоящий EOF; на границе сэмпла (n<file_size) это просто
|
||||||
|
* обрезка, не считаем за ошибку. */
|
||||||
|
if (cont && n == file_size) utf_ok = 0;
|
||||||
|
|
||||||
|
if (!has_high) return ENC_CP866; /* чистый ASCII */
|
||||||
|
if (utf_ok && has_mb) return ENC_UTF8;
|
||||||
|
if (s1251 >= s866 && s1251 >= skoi) return ENC_CP1251;
|
||||||
|
if (skoi >= s866) return ENC_KOI8R;
|
||||||
|
return ENC_CP866;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *enc_name(uint8_t enc)
|
||||||
|
{
|
||||||
|
return (enc == ENC_CP1251) ? "CP1251" :
|
||||||
|
(enc == ENC_KOI8R) ? "KOI8-R" :
|
||||||
|
(enc == ENC_UTF8) ? "UTF-8 " : "CP866 ";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Контентный глиф (ремапим при смене кодировки) vs структурный (рамка/HR/
|
||||||
|
* маркеры — уже CP866, не трогаем). Различаем по attr. */
|
||||||
|
static uint8_t attr_is_content(uint8_t a)
|
||||||
|
{
|
||||||
|
return (uint8_t)(a != ATTR_BOX && a != ATTR_HR &&
|
||||||
|
a != ATTR_LIST_MARKER && a != ATTR_QUOTE_MARKER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Вывод среза строки кэша с ремапом контентных глифов [128-255] через
|
||||||
|
* g_remap (CP1251/KOI8). Без активной таблицы — прямой win_rest. */
|
||||||
|
void win_rest_remap(uint8_t row, uint8_t w, uint8_t page, uint16_t off)
|
||||||
|
{
|
||||||
|
if (!g_remap || w == 0) {
|
||||||
|
win_rest(row, 0, 1, w, page, off);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t buf[SCREEN_W * 2];
|
||||||
|
bank_read(page, off, buf, (uint16_t)w * 2u);
|
||||||
|
for (uint8_t i = 0; i < w; i++) {
|
||||||
|
uint8_t ch = buf[(uint16_t)i * 2u];
|
||||||
|
if (ch >= 0x80 && attr_is_content(buf[(uint16_t)i * 2u + 1u]))
|
||||||
|
buf[(uint16_t)i * 2u] = g_remap[ch - 0x80]; /* таблицы хранят только старшие 128 */
|
||||||
|
}
|
||||||
|
bank_write(g_scratch_phys, 0, buf, (uint16_t)w * 2u);
|
||||||
|
win_rest(row, 0, 1, w, g_scratch_phys, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================= UTF-8 → CP866 (Фаза 2) ======================== */
|
||||||
|
|
||||||
|
/* Кириллица U+0400..U+045F → CP866. Русский набор + Ё/ё и часть украинских
|
||||||
|
* (Є є Ї ї Ў ў), которые есть в CP866; пропуски → '?' (0x3F). */
|
||||||
|
static const uint8_t utf_cyr_to_866[96] = {
|
||||||
|
/* 0400 */ 0x3F, 0xF0, 0x3F, 0x3F, 0xF2, 0x3F, 0x3F, 0xF4, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0xF6, 0x3F,
|
||||||
|
/* 0410 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
||||||
|
/* 0420 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
|
||||||
|
/* 0430 */ 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
|
||||||
|
/* 0440 */ 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
|
||||||
|
/* 0450 */ 0x3F, 0xF1, 0x3F, 0x3F, 0xF3, 0x3F, 0x3F, 0xF5, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0xF7, 0x3F,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Поток вывода конвертера: пишем в utf_phys чанками по странице, буфер в
|
||||||
|
* near-памяти, флаш через один bank_write (не побайтовый swap W3). */
|
||||||
|
static uint8_t conv_buf[256];
|
||||||
|
static uint16_t conv_n; /* байт в буфере, ещё не сброшено */
|
||||||
|
static uint8_t conv_page; /* индекс текущей страницы в utf_phys */
|
||||||
|
static uint16_t conv_off; /* уже сброшенное смещение в этой странице */
|
||||||
|
|
||||||
|
static void conv_flush(void)
|
||||||
|
{
|
||||||
|
if (conv_n) {
|
||||||
|
bank_write(utf_phys[conv_page], conv_off, conv_buf, conv_n);
|
||||||
|
conv_off = (uint16_t)(conv_off + conv_n);
|
||||||
|
conv_n = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Добавляет один CP866-байт в выходной поток. Запись никогда не пересекает
|
||||||
|
* границу страницы (256 делит 16384 нацело). */
|
||||||
|
static void conv_put(uint8_t b)
|
||||||
|
{
|
||||||
|
if ((uint16_t)(conv_off + conv_n) == PAGE_SIZE) { /* страница заполнена */
|
||||||
|
conv_flush();
|
||||||
|
conv_page++;
|
||||||
|
conv_off = 0;
|
||||||
|
}
|
||||||
|
conv_buf[conv_n++] = b;
|
||||||
|
if (conv_n == (uint16_t)sizeof(conv_buf)) conv_flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Подстановки одиночных не-кириллических символов UTF-8 → один CP866-байт.
|
||||||
|
* Таблица вместо switch (экономия кода); линейный поиск дёшев — вызывается
|
||||||
|
* лишь при конвертации на редких символьных кодпойнтах. Чтобы добавить новый
|
||||||
|
* символ — достаточно дописать одну строку {кодпойнт, байт-CP866}.
|
||||||
|
* (Символы с НЕ-1:1 заменой, напр. … -> "...", обрабатываются отдельно ниже.) */
|
||||||
|
typedef struct { uint16_t utf8; uint8_t cp866; } utf_sym_t;
|
||||||
|
static const utf_sym_t utf_sym[] = {
|
||||||
|
{0x00A0, 0x20}, {0x00AB, 0x3C}, {0x00BB, 0x3E}, /* nbsp « » → space < > */
|
||||||
|
{0x00B0, 0xF8}, {0x00B7, 0xFA}, /* ° · */
|
||||||
|
{0x2013, 0x2D}, {0x2014, 0x2D}, /* – — → '-' */
|
||||||
|
{0x2018, 0x27}, {0x2019, 0x27}, /* ‘ ’ → '\'' */
|
||||||
|
{0x201C, 0x22}, {0x201D, 0x22}, /* “ ” → '"' */
|
||||||
|
{0x2022, 0xF9}, {0x2116, 0xFC}, /* • → ∙ № */
|
||||||
|
{0x2190, 0x1B}, {0x2191, 0x18}, {0x2192, 0x1A}, {0x2193, 0x19}, /* ← ↑ → ↓ */
|
||||||
|
{0x2500, 0xC4}, {0x2502, 0xB3}, {0x250C, 0xDA}, {0x2510, 0xBF}, /* ─ │ ┌ ┐ */
|
||||||
|
{0x2514, 0xC0}, {0x2518, 0xD9}, {0x251C, 0xC3}, {0x2524, 0xB4}, /* └ ┘ ├ ┤ */
|
||||||
|
{0x252C, 0xC2}, {0x2534, 0xC1}, {0x253C, 0xC5}, /* ┬ ┴ ┼ */
|
||||||
|
{0x2580, 0xDF}, {0x2584, 0xDC}, {0x2588, 0xDB}, /* ▀ ▄ █ */
|
||||||
|
{0x2591, 0xB0}, {0x2592, 0xB1}, {0x2593, 0xB2}, {0x25A0, 0xFE}, /* ░ ▒ ▓ ■ */
|
||||||
|
{0x2713, 0xFB}, {0x2714, 0xFB}, /* ✓ ✔ → галка */
|
||||||
|
{0x2715, 0x78}, {0x2717, 0x78}, {0x2718, 0x78}, /* ✕ ✗ ✘ → 'x' */
|
||||||
|
};
|
||||||
|
#define UTF_SYM_N (uint8_t)(sizeof(utf_sym) / sizeof(utf_sym[0]))
|
||||||
|
|
||||||
|
/* Кодпойнт → ОДИН CP866-глиф: кириллица по таблице, ходовые символы по
|
||||||
|
* utf_sym[], прочее → '?'. Общая для конвертера и HEX-printable (F4). */
|
||||||
|
uint8_t utf_cp_glyph(uint32_t cp)
|
||||||
|
{
|
||||||
|
if (cp < 0x80) return (uint8_t)cp;
|
||||||
|
if (cp >= 0x0400 && cp <= 0x045F) return utf_cyr_to_866[cp - 0x0400];
|
||||||
|
for (uint8_t i = 0; i < UTF_SYM_N; i++)
|
||||||
|
if (utf_sym[i].utf8 == (uint16_t)cp) return utf_sym[i].cp866;
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Кодпойнт → CP866-глиф(ы) в поток конвертера. Спецслучаи с НЕ-1:1 заменой
|
||||||
|
* (… → "...", BOM → ничего) здесь; остальное через utf_cp_glyph(). */
|
||||||
|
static void conv_emit_cp(uint32_t cp)
|
||||||
|
{
|
||||||
|
if (cp == 0x2026) { conv_put('.'); conv_put('.'); conv_put('.'); return; } /* … → "..." */
|
||||||
|
if (cp == 0xFEFF) return; /* BOM/ZWNBSP — выкинуть */
|
||||||
|
conv_put(utf_cp_glyph(cp));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Последовательное чтение байта ОРИГИНАЛА (utf-8) конвертером. Оригинал лежит
|
||||||
|
* в orig_file_phys[]; мапим его страницу в W3 напрямую, со своей кэш-переменной
|
||||||
|
* cv_page (отдельной от cur_page индексатора, т.к. оба используют W3 и
|
||||||
|
* чередуются). На границе с fb()-чтением utf-буфера кэши взаимно сбрасываются. */
|
||||||
|
static uint8_t cv_read(uint32_t s)
|
||||||
|
{
|
||||||
|
uint8_t pg = (uint8_t)(s >> PAGE_BITS);
|
||||||
|
if (pg != cv_page) { sprinter_page_w3(orig_file_phys[pg]); cv_page = pg; }
|
||||||
|
return *((volatile uint8_t *)(0xC000u + (uint16_t)(s & PAGE_MASK)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Возобновляемая конвертация: дописывает utf_phys из оригинала, пока
|
||||||
|
* сконвертированных (flushed) байт меньше target и не достигнут конец
|
||||||
|
* исходника. По выходу file_size = доступная (сконвертированная) часть; при
|
||||||
|
* достижении конца фиксирует utf_size и снимает g_utf_building. */
|
||||||
|
void utf_convert_more(uint32_t target)
|
||||||
|
{
|
||||||
|
cv_page = 0xFF; /* W3 был на utf (fb индексатора) — пере-смаппим orig */
|
||||||
|
uint32_t n = orig_file_size;
|
||||||
|
while (utf_src < n &&
|
||||||
|
(uint32_t)((uint32_t)conv_page * PAGE_SIZE + conv_off) < target) {
|
||||||
|
uint8_t b = (uint8_t)cv_read(utf_src++);
|
||||||
|
if (b < 0x80) {
|
||||||
|
conv_emit_cp(b);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t cp;
|
||||||
|
uint8_t need;
|
||||||
|
if ((b & 0xE0) == 0xC0) { cp = (uint32_t)(b & 0x1F); need = 1; }
|
||||||
|
else if ((b & 0xF0) == 0xE0) { cp = (uint32_t)(b & 0x0F); need = 2; }
|
||||||
|
else if ((b & 0xF8) == 0xF0) { cp = (uint32_t)(b & 0x07); need = 3; }
|
||||||
|
else { conv_put('?'); continue; }
|
||||||
|
|
||||||
|
uint8_t ok = 1;
|
||||||
|
for (uint8_t k = 0; k < need; k++) {
|
||||||
|
if (utf_src >= n) { ok = 0; break; }
|
||||||
|
uint8_t cb = (uint8_t)cv_read(utf_src);
|
||||||
|
if ((cb & 0xC0) != 0x80) { ok = 0; break; }
|
||||||
|
cp = (cp << 6) | (uint32_t)(cb & 0x3F);
|
||||||
|
utf_src++;
|
||||||
|
}
|
||||||
|
if (!ok || need == 3) { conv_put('?'); continue; }
|
||||||
|
conv_emit_cp(cp);
|
||||||
|
}
|
||||||
|
conv_flush();
|
||||||
|
file_size = (uint32_t)conv_page * PAGE_SIZE + conv_off; /* доступно индексатору */
|
||||||
|
if (utf_src >= n) { utf_size = file_size; g_utf_building = 0; }
|
||||||
|
cur_page = 0xFF; /* W3 был на orig — fb индексатора пере-смаппит utf */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Выделяет страницы под UTF-8 набор (конвертированный ≤ оригинала по размеру)
|
||||||
|
* и сбрасывает приватное состояние конвертера в начало (conv-буфер, utf_src,
|
||||||
|
* g_utf_building). Сама конвертация — инкрементальная, через utf_convert_more().
|
||||||
|
* Возврат: 1 — успех, 0 — нет EMM. */
|
||||||
|
uint8_t utf_alloc(void)
|
||||||
|
{
|
||||||
|
utf_pages = orig_file_pages;
|
||||||
|
utf_blk = mem_alloc_pages(utf_pages);
|
||||||
|
if (utf_blk == 0) return 0;
|
||||||
|
for (uint8_t i = 0; i < utf_pages; i++)
|
||||||
|
utf_phys[i] = mem_get_page(utf_blk, i);
|
||||||
|
conv_n = 0; conv_page = 0; conv_off = 0; /* converter reset (вызывается один раз) */
|
||||||
|
utf_src = 0;
|
||||||
|
g_utf_building = 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* mdview2_help.c — диалог справки (F1).
|
||||||
|
*
|
||||||
|
* Модальное окно поверх документа: рамка с заголовком, список горячих
|
||||||
|
* клавиш и поддерживаемых markdown-элементов. Блокирует до нажатия любой
|
||||||
|
* клавиши, после чего восстанавливает статус-бар, область документа и меню.
|
||||||
|
*
|
||||||
|
* Зависимости от ядра (mdview2.c): render_full_status / draw_viewport_from_cache
|
||||||
|
* / render_menu для восстановления экрана. Текст справки — в CP866.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <conio.h>
|
||||||
|
#include <bios/text.h>
|
||||||
|
#include "mdview2.h"
|
||||||
|
|
||||||
|
/* Геометрия диалога справки (в символьных координатах 80×32). */
|
||||||
|
#define HELP_X 8u /* левая граница рамки */
|
||||||
|
#define HELP_Y 3u /* верхняя граница рамки */
|
||||||
|
#define HELP_W 64u /* ширина рамки (включая │) */
|
||||||
|
#define HELP_H 26u /* высота рамки (включая ─) */
|
||||||
|
|
||||||
|
/* Заполняет одну внутреннюю строку диалога (r=0 — первая строка за рамкой).
|
||||||
|
* Строка s в кодировке CP866; остаток до края дополняется пробелами. */
|
||||||
|
static void help_line(uint8_t r, const char *s, uint8_t attr)
|
||||||
|
{
|
||||||
|
uint8_t x = HELP_X + 1u;
|
||||||
|
uint8_t y = HELP_Y + 1u + r;
|
||||||
|
uint8_t len = (uint8_t)strlen(s);
|
||||||
|
|
||||||
|
if (len > HELP_W - 2u)
|
||||||
|
len = HELP_W - 2u;
|
||||||
|
|
||||||
|
bios_set_place(y, x);
|
||||||
|
if (len > 0)
|
||||||
|
bios_writeattr(s, len, attr);
|
||||||
|
/* place уже продвинут bios_writeattr на len колонок (verified). */
|
||||||
|
if (len < HELP_W - 2u) {
|
||||||
|
bios_fillcharattr(' ', ATTR_HELP_BG, (uint8_t)(HELP_W - 2u - len));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void show_help(void)
|
||||||
|
{
|
||||||
|
/* " Помощь " в CP866 (8 байт) */
|
||||||
|
static const char title[] = " \x8F\xAE\xAC\xAE\xE9\xEC ";
|
||||||
|
uint8_t tlen = 8u;
|
||||||
|
uint8_t lft = (uint8_t)((HELP_W - 2u - tlen) / 2u); /* = 27 */
|
||||||
|
uint8_t rgt = (uint8_t)(HELP_W - 2u - tlen - lft);
|
||||||
|
/* Верхняя граница рамки с заголовком по центру.
|
||||||
|
* place продвигается каждым BIOS-вызовом (verified), поэтому
|
||||||
|
* достаточно одной установки места на всю строку рамки. */
|
||||||
|
wrchar(HELP_X, HELP_Y, 0xDA, ATTR_HELP_BDR); /* ┌ */
|
||||||
|
bios_set_place(HELP_Y, (uint8_t)(HELP_X + 1u));
|
||||||
|
if (lft > 0) bios_fillcharattr(0xC4, ATTR_HELP_BDR, lft);
|
||||||
|
if (tlen > 0) bios_writeattr(title, tlen, ATTR_HELP_TIT);
|
||||||
|
if (rgt > 0) bios_fillcharattr(0xC4, ATTR_HELP_BDR, rgt);
|
||||||
|
wrchar(HELP_X + HELP_W - 1u, HELP_Y, 0xBF, ATTR_HELP_BDR); /* ┐ */
|
||||||
|
|
||||||
|
/* Боковые границы (левый и правый │ для каждой строки тела) */
|
||||||
|
for (uint8_t r = 1u; r < HELP_H - 1u; r++) {
|
||||||
|
wrchar(HELP_X, HELP_Y + r, 0xB3, ATTR_HELP_BDR); /* │ */
|
||||||
|
wrchar(HELP_X + HELP_W - 1u, HELP_Y + r, 0xB3, ATTR_HELP_BDR); /* │ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Нижняя граница рамки */
|
||||||
|
wrchar(HELP_X, HELP_Y + HELP_H - 1u, 0xC0, ATTR_HELP_BDR); /* └ */
|
||||||
|
bios_set_place((uint8_t)(HELP_Y + HELP_H - 1u), (uint8_t)(HELP_X + 1u));
|
||||||
|
bios_fillcharattr(0xC4, ATTR_HELP_BDR, (uint8_t)(HELP_W - 2u));
|
||||||
|
wrchar(HELP_X + HELP_W - 1u, HELP_Y + HELP_H - 1u, 0xD9, ATTR_HELP_BDR); /* ┘ */
|
||||||
|
|
||||||
|
/* Содержимое (20 внутренних строк) */
|
||||||
|
uint8_t r = 0;
|
||||||
|
help_line(r++, "", ATTR_HELP_BG);
|
||||||
|
help_line(r++, " MDView v1.0 (b3) -- Markdown Viewer for Sprinter", ATTR_HELP_HDR);
|
||||||
|
help_line(r++, " (c) 2026 \x8F\xA5\xE2\xE0\xAE\xA2 \x80\x2E\x83\x2E",
|
||||||
|
ATTR_HELP_BG);
|
||||||
|
help_line(r++, "", ATTR_HELP_BG);
|
||||||
|
help_line(r++, " Navigation:", ATTR_HELP_HDR);
|
||||||
|
help_line(r++, " \x18 \x19 Scroll one line up / down", ATTR_HELP_BG);
|
||||||
|
help_line(r++, " PgUp PgDn Scroll one page up / down", ATTR_HELP_BG);
|
||||||
|
help_line(r++, " Home End Jump to beginning / end of document", ATTR_HELP_BG);
|
||||||
|
help_line(r++, " \x1B \x1A Horizontal pan (code blocks/tables/unwrap)", ATTR_HELP_BG);
|
||||||
|
help_line(r++, " Esc F10 Exit", ATTR_HELP_BG);
|
||||||
|
help_line(r++, "", ATTR_HELP_BG);
|
||||||
|
help_line(r++, " Markdown elements:", ATTR_HELP_HDR);
|
||||||
|
help_line(r++, " # ## ### Headings H1-H6, **bold**, *italic*,", ATTR_HELP_BG);
|
||||||
|
help_line(r++, " `code`, ``` code ```, ~~strike~~, > quote", ATTR_HELP_BG);
|
||||||
|
help_line(r++, " - * + 1. 2. Ordered list, |----|----| Tables", ATTR_HELP_BG);
|
||||||
|
help_line(r++, "", ATTR_HELP_BG);
|
||||||
|
help_line(r++, " Encoding and View modes:", ATTR_HELP_HDR);
|
||||||
|
help_line(r++, " F2 RAW Mode", ATTR_HELP_BG);
|
||||||
|
help_line(r++, " F3 Wrap/Unwrap Mode for RAW View", ATTR_HELP_BG);
|
||||||
|
help_line(r++, " F4 HEX Mode (dump of original file)", ATTR_HELP_BG);
|
||||||
|
help_line(r++, " F8 Cycle CP866 / CP1251 / KOI8-R / UTF-8", ATTR_HELP_BG);
|
||||||
|
help_line(r++, " Auto-detected on open; F8 to override", ATTR_HELP_BG);
|
||||||
|
help_line(r++, "", ATTR_HELP_BG);
|
||||||
|
help_line(r++, " File size: up to 256 KB (EMM). Lines: up to 18432.", ATTR_HELP_BG);
|
||||||
|
|
||||||
|
(void)getkey();
|
||||||
|
|
||||||
|
render_full_status();
|
||||||
|
draw_viewport_from_cache();
|
||||||
|
render_menu();
|
||||||
|
}
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
/*
|
||||||
|
* mdview2_hex.c — HEX-дамп ОРИГИНАЛЬНОГО файла (F4).
|
||||||
|
*
|
||||||
|
* Показывает исходные байты файла (orig_file_phys), а не активный буфер:
|
||||||
|
* hex-дамп декодированного UTF-набора был бы искажением. Printable-колонка
|
||||||
|
* интерпретируется ТЕКУЩЕЙ кодировкой: CP866 как есть, CP1251/KOI8 через
|
||||||
|
* g_remap, UTF-8 — глиф на позиции лид-байта (continuation-байты → '.').
|
||||||
|
*
|
||||||
|
* Формат ряда (79 колонок, один атрибут ATTR_TEXT — как RAW):
|
||||||
|
* 0x012340 │ XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX │ 16 символов
|
||||||
|
*
|
||||||
|
* Ряд выровнен на 16 байт и никогда не пересекает границу EMM-страницы
|
||||||
|
* (16 делит 16384 нацело) — один bank_read на ряд, fb()/W3-маппинг не нужны.
|
||||||
|
* Позиция (hex_top) живёт в байтах ОРИГИНАЛА; конвертацию в/из байт-позиции
|
||||||
|
* активного буфера делает ядро (view_pos/view_reanchor через map_off).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <conio.h>
|
||||||
|
#include <bios/text.h>
|
||||||
|
#include <sprinter_mem.h> /* bank_read (чтение оригинала по страницам EMM) */
|
||||||
|
#include "mdview2.h"
|
||||||
|
|
||||||
|
#if WITH_HEX /* весь модуль опционален (mdview2_conf.h); при 0 пустой объектник */
|
||||||
|
|
||||||
|
#define HEX_BPR 16u /* байт на ряд */
|
||||||
|
#define HEX_SPAN ((uint32_t)(VIEW_H - 1) * HEX_BPR)
|
||||||
|
|
||||||
|
/* Геометрия ряда (колонки). */
|
||||||
|
#define HX_OFF 1u /* "0x012340" */
|
||||||
|
#define HX_SEP1 10u /* │ */
|
||||||
|
#define HX_DUMP 12u /* пары hex-цифр */
|
||||||
|
#define HX_SEP2 61u /* │ */
|
||||||
|
#define HX_PRINT 63u /* printable */
|
||||||
|
|
||||||
|
static uint32_t hex_top; /* offset верхнего ряда (кратен 16) */
|
||||||
|
|
||||||
|
uint32_t hex_pos(void) { return hex_top; }
|
||||||
|
|
||||||
|
/* Максимальный hex_top: последний ряд файла на нижней строке экрана. */
|
||||||
|
static uint32_t hex_max_top(void)
|
||||||
|
{
|
||||||
|
uint32_t last = (orig_file_size == 0) ? 0
|
||||||
|
: ((orig_file_size - 1u) & ~(uint32_t)(HEX_BPR - 1u));
|
||||||
|
return (last > HEX_SPAN) ? (last - HEX_SPAN) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hex_reanchor(uint32_t orig_off)
|
||||||
|
{
|
||||||
|
uint32_t mt = hex_max_top();
|
||||||
|
orig_off &= ~(uint32_t)(HEX_BPR - 1u);
|
||||||
|
hex_top = (orig_off > mt) ? mt : orig_off;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hex_screen_init(void)
|
||||||
|
{
|
||||||
|
bios_clearwin(VIEW_TOP_ROW, 0, VIEW_H, SCREEN_W, ATTR_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- отрисовка ----------------------------------------------------- */
|
||||||
|
|
||||||
|
static uint8_t hexdig(uint8_t v)
|
||||||
|
{
|
||||||
|
v &= 15u;
|
||||||
|
return (uint8_t)((v < 10u) ? ('0' + v) : ('A' - 10 + v));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Один байт оригинала (для UTF-lookahead за границей ряда — редкий путь). */
|
||||||
|
static uint8_t fbo(uint32_t off)
|
||||||
|
{
|
||||||
|
uint8_t b;
|
||||||
|
bank_read(orig_file_phys[off >> PAGE_BITS], (uint16_t)(off & PAGE_MASK), &b, 1);
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Printable-глиф для n байт ряда b[] (off — offset b[0] в файле) → out[]. */
|
||||||
|
static void hex_print_cells(const uint8_t *b, uint32_t off, uint8_t n, uint8_t *out)
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < n; i++) {
|
||||||
|
uint8_t c = b[i];
|
||||||
|
uint8_t g;
|
||||||
|
if (c < 0x20u) {
|
||||||
|
g = '.';
|
||||||
|
} else if (c < 0x80u || g_encoding != ENC_UTF8) {
|
||||||
|
g = (uint8_t)((c >= 0x80u && g_remap) ? g_remap[c - 0x80u] : c);
|
||||||
|
} else if ((c & 0xC0u) == 0x80u) {
|
||||||
|
g = '.'; /* continuation-байт */
|
||||||
|
} else {
|
||||||
|
/* Лид-байт: собрать кодпойнт (хвост может выйти за ряд → fbo).
|
||||||
|
* 4-байтовые (за пределами CP866) и битые последовательности → '.'. */
|
||||||
|
uint32_t cp;
|
||||||
|
uint8_t need, ok = 1;
|
||||||
|
if ((c & 0xE0u) == 0xC0u) { cp = (uint32_t)(c & 0x1Fu); need = 1; }
|
||||||
|
else if ((c & 0xF0u) == 0xE0u) { cp = (uint32_t)(c & 0x0Fu); need = 2; }
|
||||||
|
else { out[i] = '.'; continue; }
|
||||||
|
for (uint8_t k = 1; k <= need; k++) {
|
||||||
|
uint32_t p = off + i + k;
|
||||||
|
if (p >= orig_file_size) { ok = 0; break; }
|
||||||
|
uint8_t cb = (uint8_t)((i + k < n) ? b[i + k] : fbo(p));
|
||||||
|
if ((cb & 0xC0u) != 0x80u) { ok = 0; break; }
|
||||||
|
cp = (cp << 6) | (uint32_t)(cb & 0x3Fu);
|
||||||
|
}
|
||||||
|
g = ok ? utf_cp_glyph(cp) : '.';
|
||||||
|
if (g < 0x20u) g = '.'; /* overlong мог дать управляющий */
|
||||||
|
}
|
||||||
|
out[i] = g;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t hex_line[SCREEN_W]; /* собранный ряд (только символы, единый attr) */
|
||||||
|
|
||||||
|
static void hex_draw_row(uint32_t off, uint8_t row)
|
||||||
|
{
|
||||||
|
uint8_t buf[HEX_BPR];
|
||||||
|
uint8_t n = 0;
|
||||||
|
|
||||||
|
memset(hex_line, ' ', SCREEN_W);
|
||||||
|
if (off < orig_file_size) {
|
||||||
|
uint32_t rem = orig_file_size - off;
|
||||||
|
n = (rem >= HEX_BPR) ? (uint8_t)HEX_BPR : (uint8_t)rem;
|
||||||
|
bank_read(orig_file_phys[off >> PAGE_BITS], (uint16_t)(off & PAGE_MASK), buf, n);
|
||||||
|
}
|
||||||
|
if (n) { /* ряды за EOF — пустые */
|
||||||
|
hex_line[HX_OFF] = '0';
|
||||||
|
hex_line[HX_OFF + 1] = 'x';
|
||||||
|
hex_line[HX_OFF + 2] = hexdig((uint8_t)(off >> 20));
|
||||||
|
hex_line[HX_OFF + 3] = hexdig((uint8_t)(off >> 16));
|
||||||
|
hex_line[HX_OFF + 4] = hexdig((uint8_t)(off >> 12));
|
||||||
|
hex_line[HX_OFF + 5] = hexdig((uint8_t)(off >> 8));
|
||||||
|
hex_line[HX_OFF + 6] = hexdig((uint8_t)(off >> 4));
|
||||||
|
hex_line[HX_OFF + 7] = hexdig((uint8_t)off);
|
||||||
|
hex_line[HX_SEP1] = TBL_V;
|
||||||
|
for (uint8_t i = 0; i < n; i++) {
|
||||||
|
uint8_t x = (uint8_t)(HX_DUMP + i * 3u + ((i >= 8u) ? 1u : 0u));
|
||||||
|
hex_line[x] = hexdig((uint8_t)(buf[i] >> 4));
|
||||||
|
hex_line[x + 1] = hexdig(buf[i]);
|
||||||
|
}
|
||||||
|
hex_line[HX_SEP2] = TBL_V;
|
||||||
|
hex_print_cells(buf, off, n, hex_line + HX_PRINT);
|
||||||
|
}
|
||||||
|
bios_set_place(row, 0);
|
||||||
|
bios_write_until((const char *)hex_line, SCREEN_W, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hex_draw(void)
|
||||||
|
{
|
||||||
|
uint32_t off = hex_top;
|
||||||
|
for (uint8_t r = 0; r < VIEW_H; r++) {
|
||||||
|
hex_draw_row(off, (uint8_t)(VIEW_TOP_ROW + r));
|
||||||
|
off += HEX_BPR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- статус-бар: % по байтам (как calc_raw_pct) --------------------- */
|
||||||
|
|
||||||
|
static uint8_t calc_hex_pct(void)
|
||||||
|
{
|
||||||
|
return pct16((uint16_t)(hex_top >> 8), (uint16_t)(orig_file_size >> 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- навигация (вызывается из главного цикла при g_view == VIEW_HEX) */
|
||||||
|
|
||||||
|
uint8_t hex_key(uint8_t scan)
|
||||||
|
{
|
||||||
|
uint32_t mt = hex_max_top();
|
||||||
|
switch (scan) {
|
||||||
|
/* Одна строка — аппаратный скролл окна + подрисовка одного ряда
|
||||||
|
* (как в MD/RAW), без перерисовки всего экрана. */
|
||||||
|
case KEY_DOWN:
|
||||||
|
if (hex_top < mt) {
|
||||||
|
hex_top += HEX_BPR;
|
||||||
|
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 1, 0); /* содержимое вверх */
|
||||||
|
hex_draw_row(hex_top + HEX_SPAN, (uint8_t)(VIEW_TOP_ROW + VIEW_H - 1));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KEY_UP:
|
||||||
|
if (hex_top) {
|
||||||
|
hex_top -= HEX_BPR;
|
||||||
|
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 2, 0); /* содержимое вниз */
|
||||||
|
hex_draw_row(hex_top, VIEW_TOP_ROW);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KEY_PGDN: {
|
||||||
|
uint32_t nt = hex_top + (uint32_t)VIEW_H * HEX_BPR;
|
||||||
|
if (nt > mt) nt = mt;
|
||||||
|
if (nt != hex_top) { hex_top = nt; hex_draw(); }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case KEY_PGUP: {
|
||||||
|
uint32_t d = (uint32_t)VIEW_H * HEX_BPR;
|
||||||
|
uint32_t nt = (hex_top >= d) ? hex_top - d : 0;
|
||||||
|
if (nt != hex_top) { hex_top = nt; hex_draw(); }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case KEY_HOME:
|
||||||
|
if (hex_top) { hex_top = 0; hex_draw(); }
|
||||||
|
break;
|
||||||
|
case KEY_END:
|
||||||
|
if (hex_top != mt) { hex_top = mt; hex_draw(); }
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return 0; /* не наша клавиша */
|
||||||
|
}
|
||||||
|
render_percent_progress(calc_hex_pct());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* WITH_HEX */
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,227 @@
|
|||||||
|
/*
|
||||||
|
* mdview2_md.c — представление MD-документа (peer к mdview2_raw.c): отрисовка
|
||||||
|
* области из рендер-кэша + прокрутка (верт./гориз.) + навигация md_key().
|
||||||
|
* Статус-бар, меню и спиннер — в mdview2_status.c (общие для MD/RAW).
|
||||||
|
*
|
||||||
|
* Работает поверх готового рендер-кэша (cache_dir_get → cache_rec_t) и
|
||||||
|
* состояния навигации (top_line/viewport_x/n_lines, см. mdview2.h). Никакого
|
||||||
|
* парсинга/обращения к исходному файлу: всё рисуется из (char,attr)-ячеек кэша
|
||||||
|
* через win_rest_remap (ремап 8-битных кодировок — в mdview2_enc.c).
|
||||||
|
*
|
||||||
|
* Пара видов: mdview2_md.c (MD, из кэша) ↔ mdview2_raw.c (RAW, из fb()).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <conio.h> /* wrchar/scroll/COLOR */
|
||||||
|
#include <bios/text.h> /* bios_set_place/bios_fillcharattr */
|
||||||
|
#include "mdview2.h"
|
||||||
|
|
||||||
|
/* Фаза 4-5 — cache-only draw path: рисует одну строку ИЗ КЭША, без
|
||||||
|
* единого обращения к fb()/исходному файлу. Горизонтальный скролл для
|
||||||
|
* nowrap-строк (Фаза 5) — это просто смещение начала среза на vx*2
|
||||||
|
* байт ВНУТРИ ТОГО ЖЕ кэш-буфера (cache_reserve гарантирует, что вся
|
||||||
|
* строка лежит в одной странице, так что off+vx*2 не пересекает
|
||||||
|
* границу страницы); не nowrap-строки игнорируют viewport_x, как и
|
||||||
|
* раньше в живом render_line() (там это называлось effective_vx). */
|
||||||
|
static void draw_line_from_cache(uint16_t line_idx, uint8_t row)
|
||||||
|
{
|
||||||
|
cache_rec_t rec;
|
||||||
|
cache_dir_get(line_idx, &rec);
|
||||||
|
|
||||||
|
/* Строка-обрыв (исчерпан контент-кэш / лимит строк): фикс. текст вживую,
|
||||||
|
* без обращения к контент-кэшу (см. index_lines финализацию). */
|
||||||
|
if (rec.flags & IF_TRUNC_MSG) {
|
||||||
|
const char *msg;
|
||||||
|
switch (rec.reserved) {
|
||||||
|
case TRUNC_LINES: msg = "*** Line limit reached - document truncated ***"; break;
|
||||||
|
case TRUNC_FILE: msg = "*** File too large - truncated at 256 KB ***"; break;
|
||||||
|
default: msg = "*** Content cache exhausted - document truncated ***"; break;
|
||||||
|
}
|
||||||
|
bios_set_place(row, 0);
|
||||||
|
bios_fillcharattr(' ', ATTR_WARN, SCREEN_W);
|
||||||
|
put_str_attr(2, row, msg, ATTR_WARN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bios_set_place(row, 0);
|
||||||
|
bios_fillcharattr(' ', ATTR_TEXT, SCREEN_W);
|
||||||
|
|
||||||
|
/* Горизонтальный сдвиг применяется ко ВСЕМ строкам hscroll-блока (код,
|
||||||
|
* таблицы) — по ТИПУ, а не по длине: блок двигается целиком, включая
|
||||||
|
* строки короче 80 (короткая строка, ушедшая за левый край, просто
|
||||||
|
* показывает маркер '<' на пустом месте). HR и границы fence не имеют
|
||||||
|
* IF_HSCROLL и не сдвигаются. */
|
||||||
|
uint8_t vx = (rec.flags & IF_HSCROLL) ? viewport_x : 0;
|
||||||
|
|
||||||
|
if (rec.len > vx) {
|
||||||
|
uint16_t remaining = (uint16_t)(rec.len - vx);
|
||||||
|
uint8_t w = (remaining > SCREEN_W) ? SCREEN_W : (uint8_t)remaining;
|
||||||
|
uint16_t off = (uint16_t)(rec.off + (uint16_t)vx * 2u);
|
||||||
|
win_rest_remap(row, w, rec.page, off);
|
||||||
|
if (remaining > SCREEN_W) {
|
||||||
|
wrchar(SCREEN_W - 1, row, '>', ATTR_TRUNC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (vx > 0) {
|
||||||
|
wrchar(0, row, '<', ATTR_TRUNC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Перерисовывает все VIEW_H строк окна из кэша — основной путь
|
||||||
|
* перерисовки видимой области после загрузки файла (Фаза 3-4). */
|
||||||
|
void draw_viewport_from_cache(void)
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < VIEW_H; i++) {
|
||||||
|
draw_line_from_cache((uint16_t)(top_line + i), (uint8_t)(VIEW_TOP_ROW + i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ==================================================================
|
||||||
|
* Прокрутка
|
||||||
|
* ================================================================== */
|
||||||
|
|
||||||
|
/* Ограничивает top_line допустимым диапазоном с учётом высоты окна. */
|
||||||
|
void clamp_top(void)
|
||||||
|
{
|
||||||
|
if (n_lines <= VIEW_H) {
|
||||||
|
top_line = 0;
|
||||||
|
} else if (top_line > n_lines - VIEW_H) {
|
||||||
|
top_line = (uint16_t)(n_lines - VIEW_H);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Прокрутка вверх на n строк с частичной перерисовкой при n == 1. */
|
||||||
|
void md_scroll_up(uint16_t n)
|
||||||
|
{
|
||||||
|
uint16_t new_top_line = (top_line >= n) ? (uint16_t)(top_line - n) : 0;
|
||||||
|
if( new_top_line != top_line) {
|
||||||
|
top_line = new_top_line;
|
||||||
|
if (n == 1) {
|
||||||
|
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 2, 0);
|
||||||
|
draw_line_from_cache((uint16_t)(top_line), (uint8_t)(VIEW_TOP_ROW));
|
||||||
|
} else {
|
||||||
|
draw_viewport_from_cache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Прокрутка вниз на n строк с частичной перерисовкой при n == 1. */
|
||||||
|
void md_scroll_down(uint16_t n)
|
||||||
|
{
|
||||||
|
uint16_t new_top_line = (top_line + n < n_lines - VIEW_H) ?
|
||||||
|
(uint16_t)(top_line + n) : n_lines - VIEW_H;
|
||||||
|
if( new_top_line != top_line) {
|
||||||
|
top_line = new_top_line;
|
||||||
|
if (n == 1) {
|
||||||
|
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 1, 0);
|
||||||
|
draw_line_from_cache((uint16_t)(top_line + VIEW_H - 1), (uint8_t)(VIEW_TOP_ROW + VIEW_H - 1));
|
||||||
|
} else {
|
||||||
|
clamp_top();
|
||||||
|
draw_viewport_from_cache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Горизонтальный сдвиг (только если в окне есть nowrap-строки).
|
||||||
|
* Максимум сдвига ограничен самой широкой nowrap-строкой на экране.
|
||||||
|
* Фаза 5: ширина берётся прямо из директории кэша (rec.len — уже
|
||||||
|
* посчитанная реальная длина в ячейках после рендера), без единого
|
||||||
|
* обращения к fb()/seg_off()/исходному файлу. */
|
||||||
|
void md_scroll_horizon(int8_t delta)
|
||||||
|
{
|
||||||
|
uint16_t maxw = 0;
|
||||||
|
for (uint8_t i = 0; i < VIEW_H; i++) {
|
||||||
|
uint16_t li = (uint16_t)(top_line + i);
|
||||||
|
if (li >= n_lines) break;
|
||||||
|
cache_rec_t rec;
|
||||||
|
cache_dir_get(li, &rec);
|
||||||
|
if (!(rec.flags & IF_HSCROLL)) continue; /* только код/таблицы */
|
||||||
|
if (rec.len > maxw) maxw = rec.len; /* самая широкая строка блока */
|
||||||
|
}
|
||||||
|
if (maxw == 0) return; /* нет строк шире экрана */
|
||||||
|
|
||||||
|
/* Максимальный сдвиг = ширина за пределами экрана, в границах uint8. */
|
||||||
|
uint16_t over = (maxw > SCREEN_W) ? (uint16_t)(maxw - SCREEN_W) : 0;
|
||||||
|
if (over > 248u) over = 248u;
|
||||||
|
uint8_t max_vx = (uint8_t)over;
|
||||||
|
|
||||||
|
int16_t nx = (int16_t)viewport_x + delta;
|
||||||
|
if (nx < 0) nx = 0;
|
||||||
|
if (nx > (int16_t)max_vx) nx = max_vx;
|
||||||
|
|
||||||
|
/* ПОРЯДОК ВАЖЕН: записываем viewport_x ДО сравнения, а сравниваем уже
|
||||||
|
* сохранённую копию old_vx с new_vx. Иначе SDCC генерирует для
|
||||||
|
* `if (new_vx != viewport_x) viewport_x = new_vx;` такой код:
|
||||||
|
* ld a, new_vx ; sub a,(viewport_x) ; jr Z,.. ; ld (viewport_x),a
|
||||||
|
* — т.е. для записи переиспользует регистр A, испорченный вычитанием в
|
||||||
|
* сравнении, и кладёт в viewport_x не new_vx, а (new_vx - old_vx).
|
||||||
|
* (asm-дамп 2026-06-24 подтвердил; даёт 10-8=2, 10-2=8 — ровно
|
||||||
|
* наблюдавшийся "прыжок" скролла.) Записав viewport_x первой, мы убираем
|
||||||
|
* портящий sub с пути записи; сравнение ниже только решает, перерисовывать
|
||||||
|
* ли экран. */
|
||||||
|
uint8_t new_vx = (uint8_t)nx;
|
||||||
|
uint8_t old_vx = viewport_x;
|
||||||
|
viewport_x = new_vx;
|
||||||
|
if (new_vx != old_vx)
|
||||||
|
draw_viewport_from_cache();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Процент прокрутки (0..100) относительно доступного диапазона. */
|
||||||
|
static uint8_t calc_md_pct(void)
|
||||||
|
{
|
||||||
|
uint16_t total = drawable_lines();
|
||||||
|
if (total <= VIEW_H) return 100;
|
||||||
|
return pct16(top_line, (uint16_t)(total - VIEW_H));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- навигация MD после загрузки (peer к raw_key) ---------------- */
|
||||||
|
|
||||||
|
/* Обрабатывает навигационную клавишу в MD-режиме (вызывается из главного
|
||||||
|
* цикла). Сама перерисовывает область/статус. Возврат: 1 — обработано,
|
||||||
|
* 0 — не наша клавиша (F1/F8/F10 разбирает main). */
|
||||||
|
uint8_t md_key(uint8_t scan)
|
||||||
|
{
|
||||||
|
switch (scan) {
|
||||||
|
case KEY_UP:
|
||||||
|
md_scroll_up(1);
|
||||||
|
break;
|
||||||
|
case KEY_DOWN:
|
||||||
|
md_scroll_down(1);
|
||||||
|
break;
|
||||||
|
case KEY_LEFT:
|
||||||
|
md_scroll_horizon(-(int8_t)HPAN_STEP);
|
||||||
|
break;
|
||||||
|
case KEY_RIGHT:
|
||||||
|
md_scroll_horizon(+(int8_t)HPAN_STEP);
|
||||||
|
break;
|
||||||
|
case KEY_PGUP:
|
||||||
|
md_scroll_up(VIEW_H);
|
||||||
|
break;
|
||||||
|
case KEY_PGDN:
|
||||||
|
md_scroll_down(VIEW_H);
|
||||||
|
break;
|
||||||
|
case KEY_HOME:
|
||||||
|
if(top_line != 0 || viewport_x != 0 ) {
|
||||||
|
top_line = 0;
|
||||||
|
viewport_x = 0;
|
||||||
|
draw_viewport_from_cache();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KEY_END:
|
||||||
|
uint16_t new_top_line = (n_lines > VIEW_H) ? (uint16_t)(n_lines - VIEW_H) : 0;
|
||||||
|
if(top_line != new_top_line || viewport_x != 0 ) {
|
||||||
|
top_line = new_top_line;
|
||||||
|
viewport_x = 0;
|
||||||
|
draw_viewport_from_cache();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0; /* не наша клавиша */
|
||||||
|
}
|
||||||
|
|
||||||
|
render_percent_progress(calc_md_pct());
|
||||||
|
render_md_status_numbers();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
/*
|
||||||
|
* mdview2_raw.c — RAW-просмотр исходного текста (без markdown-форматирования).
|
||||||
|
*
|
||||||
|
* Работает по АКТИВНОМУ буферу документа через fb() (8-бит = исходные байты,
|
||||||
|
* UTF-8 = декодированный в CP866 буфер), ремап ≥0x80 (CP1251/KOI8) — на
|
||||||
|
* отрисовке. Индекс/кэш markdown не используются: позиция — байт-offset,
|
||||||
|
* 1 байт = 1 ячейка, \t показываем пробелом. Два режима:
|
||||||
|
* VIEW_RAW_WRAP — длинные строки переносятся кратно 80 (рвём слова);
|
||||||
|
* VIEW_RAW_HSCROLL — строка в один ряд, горизонтальный скролл.
|
||||||
|
*
|
||||||
|
* Память: 0 доп. EMM; near-буфер строки + переиспользование g_scratch_phys.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <conio.h>
|
||||||
|
#include <bios/text.h>
|
||||||
|
#include "mdview2.h"
|
||||||
|
|
||||||
|
#if WITH_RAW /* весь модуль — опционален (mdview_conf.h); при 0 пустой объектник */
|
||||||
|
|
||||||
|
static uint32_t raw_top; /* байт-offset верхней строки экрана */
|
||||||
|
static uint16_t raw_hpan; /* гориз. сдвиг (только HSCROLL) */
|
||||||
|
|
||||||
|
/* ---- примитивы навигации по физическим строкам / переносам -------- */
|
||||||
|
|
||||||
|
static uint32_t raw_line_start(uint32_t off)
|
||||||
|
{
|
||||||
|
while (off && fb(off - 1) != '\n') off--;
|
||||||
|
return off;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* WRAP: начало следующего ряда от off (\n завершает строку; иначе +80). */
|
||||||
|
static uint32_t raw_next_wrap(uint32_t off)
|
||||||
|
{
|
||||||
|
uint32_t e = off, lim = off + SCREEN_W;
|
||||||
|
while (e < file_size) {
|
||||||
|
if (fb(e) == '\n') return e + 1;
|
||||||
|
e++;
|
||||||
|
if (e >= lim) { /* прошли 80 без \n */
|
||||||
|
if (e < file_size && fb(e) == '\n') return e + 1; /* ровно 80 + \n */
|
||||||
|
return e; /* длинная строка → продолжение */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e; /* EOF */
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t raw_prev_wrap(uint32_t off)
|
||||||
|
{
|
||||||
|
if (off == 0) return 0;
|
||||||
|
uint32_t pls = raw_line_start(off - 1); /* off-1 — это \n пред. строки */
|
||||||
|
while (pls + SCREEN_W < (off - 1)) pls += SCREEN_W;
|
||||||
|
return pls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HSCROLL: начало следующей/предыдущей физической строки. */
|
||||||
|
static uint32_t raw_next_line(uint32_t off)
|
||||||
|
{
|
||||||
|
while (off < file_size && fb(off) != '\n') off++;
|
||||||
|
return (off < file_size) ? off + 1 : off;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t raw_prev_line(uint32_t off)
|
||||||
|
{
|
||||||
|
if (off == 0) return 0;
|
||||||
|
return raw_line_start(off - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- отрисовка ряда (общий near-буфер → g_scratch_phys → win_rest) - */
|
||||||
|
|
||||||
|
static uint8_t raw_buf[SCREEN_W + 1]; /* только символы (атрибут единый, задаётся при выводе) */
|
||||||
|
|
||||||
|
/* В RAW атрибут единый (ATTR_TEXT): буфер — только 80 символов, без пар
|
||||||
|
* (char,attr). Кладём один байт-глиф (\t→пробел, ремап ≥0x80). */
|
||||||
|
static void raw_cell(uint8_t i, uint8_t ch)
|
||||||
|
{
|
||||||
|
if (ch >= 0x80 && g_remap) ch = g_remap[ch - 0x80];
|
||||||
|
raw_buf[i] = ch;
|
||||||
|
}
|
||||||
|
/* Вывод готового 80-символьного буфера в строку экрана одним BIOS-вызовом
|
||||||
|
* (char-буфер + единый attr — без WINREST/scratch-страницы). */
|
||||||
|
static void raw_flush(uint8_t row)
|
||||||
|
{
|
||||||
|
bios_set_place(row, 0);
|
||||||
|
// bios_writeattr((const char *)raw_buf, SCREEN_W, ATTR_TEXT);
|
||||||
|
// bios_writeattr_until((const char *)raw_buf, SCREEN_W, ATTR_TEXT, 0);
|
||||||
|
bios_write_until((const char *)raw_buf, SCREEN_W, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t raw_draw_wrap_row(uint32_t off, uint8_t row)
|
||||||
|
{
|
||||||
|
uint8_t i = 0;
|
||||||
|
uint32_t e = off, lim = off + SCREEN_W;
|
||||||
|
while (i < SCREEN_W && e < file_size) {
|
||||||
|
char c = fb(e);
|
||||||
|
if (c == '\n') {
|
||||||
|
raw_cell(i, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
raw_cell(i, (uint8_t)(c == '\t' ? ' ' : c));
|
||||||
|
i++; e++;
|
||||||
|
}
|
||||||
|
raw_flush(row);
|
||||||
|
return raw_next_wrap(off);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t raw_draw_hscroll_row(uint32_t off, uint8_t row)
|
||||||
|
{
|
||||||
|
uint32_t e = off;
|
||||||
|
while (e < file_size && fb(e) != '\n') e++; /* e = конец строки */
|
||||||
|
uint16_t len = e - off;
|
||||||
|
|
||||||
|
uint8_t i = 0;
|
||||||
|
uint32_t p = off + raw_hpan;
|
||||||
|
while (i < SCREEN_W && p < e) {
|
||||||
|
char c = fb(p);
|
||||||
|
raw_cell(i, (uint8_t)(c == '\t' ? ' ' : c));
|
||||||
|
i++; p++;
|
||||||
|
}
|
||||||
|
// raw_buf[i] = 0;
|
||||||
|
|
||||||
|
if (raw_hpan > 0)
|
||||||
|
raw_buf[0] = '<';
|
||||||
|
if (len > (uint16_t)raw_hpan + SCREEN_W)
|
||||||
|
raw_buf[SCREEN_W - 1] = '>';
|
||||||
|
if (i < SCREEN_W)
|
||||||
|
raw_buf[i] = 0;
|
||||||
|
raw_flush(row);
|
||||||
|
return (e < file_size) ? e + 1 : e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Рисует один ряд от off (по режиму) и возвращает offset следующего ряда. */
|
||||||
|
static uint32_t raw_draw_one(uint32_t off, uint8_t row)
|
||||||
|
{
|
||||||
|
if (off >= file_size) { fill_row(row, ATTR_TEXT); return off; }
|
||||||
|
return (g_view == VIEW_RAW_WRAP) ? raw_draw_wrap_row(off, row)
|
||||||
|
: raw_draw_hscroll_row(off, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
void raw_draw(void)
|
||||||
|
{
|
||||||
|
uint32_t off = raw_top;
|
||||||
|
for (uint8_t r = 0; r < VIEW_H; r++)
|
||||||
|
off = raw_draw_one(off, (uint8_t)(VIEW_TOP_ROW + r));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Прокрутка на одну строку: аппаратный scroll окна документа + отрисовка
|
||||||
|
* ОДНОЙ новой строки (как в MD-режиме). Смещение нижнего ряда вычисляется
|
||||||
|
* проходом VIEW_H рядов от raw_top — без отдельного инкрементального счётчика,
|
||||||
|
* который рассинхронизировался, когда контент кончается в середине экрана
|
||||||
|
* (короче окна после wrap→unwrap у конца файла). */
|
||||||
|
static uint32_t raw_next(uint32_t off)
|
||||||
|
{
|
||||||
|
return (g_view == VIEW_RAW_WRAP) ? raw_next_wrap(off) : raw_next_line(off);
|
||||||
|
}
|
||||||
|
static uint32_t raw_prev(uint32_t off)
|
||||||
|
{
|
||||||
|
return (g_view == VIEW_RAW_WRAP) ? raw_prev_wrap(off) : raw_prev_line(off);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- статус-бар: % по байтам (отрисовка — render_raw_status_numbers
|
||||||
|
* в mdview2_status.c, читает позицию через этот raw_pct) ---- */
|
||||||
|
|
||||||
|
/* Процент по байтам без 32-битного деления: масштабируем offset'ы (>>8 →
|
||||||
|
* ≤1024), дальше 16-битный pct16 (без __divulong). raw_top приватен модулю,
|
||||||
|
* поэтому % считаем здесь и отдаём готовым числом в статус-модуль. */
|
||||||
|
uint8_t calc_raw_pct(void)
|
||||||
|
{
|
||||||
|
uint16_t a = (uint16_t)(raw_top >> 8);
|
||||||
|
uint16_t b = (uint16_t)(file_size >> 8);
|
||||||
|
return pct16(a, b); /* den==0 (файл < 1 КБ) → pct16 вернёт 0 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- посев позиции / пересев при смене активного буфера ----------- */
|
||||||
|
|
||||||
|
uint32_t raw_pos(void)
|
||||||
|
{
|
||||||
|
return raw_top;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Поставить RAW на начало строки, содержащей байт off (позиционирование
|
||||||
|
* при F2 MD→RAW и F8-переносе позиции между наборами). */
|
||||||
|
void raw_reanchor(uint32_t off)
|
||||||
|
{
|
||||||
|
if (off > file_size) off = file_size;
|
||||||
|
raw_top = raw_line_start(off);
|
||||||
|
raw_hpan = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void raw_seed_from(uint16_t md_top_line)
|
||||||
|
{
|
||||||
|
raw_reanchor(seg_off(md_top_line));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Сброс в начало (клавиша Home). */
|
||||||
|
static void raw_home(void)
|
||||||
|
{
|
||||||
|
raw_top = 0;
|
||||||
|
raw_hpan = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void raw_end(void)
|
||||||
|
{
|
||||||
|
raw_top = file_size; /* отмотать VIEW_H рядов назад */
|
||||||
|
for (uint8_t k = 0; k < VIEW_H; k++)
|
||||||
|
raw_top = raw_prev(raw_top);
|
||||||
|
raw_hpan = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void raw_screen_init(void) {
|
||||||
|
bios_clearwin(1, 0, 30, 80, ATTR_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Нормализация позиции при смене под-режима RAW (F3 Wrap/Unwrap): выровнять
|
||||||
|
* raw_top на начало физической строки и снять горизонтальный сдвиг. */
|
||||||
|
void raw_renorm(void)
|
||||||
|
{
|
||||||
|
raw_top = raw_line_start(raw_top);
|
||||||
|
raw_hpan = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void raw_scroll_down(uint8_t delta) {
|
||||||
|
uint32_t off = raw_top;
|
||||||
|
for (uint8_t r = 0; r < VIEW_H; r++) {
|
||||||
|
if (off >= file_size)
|
||||||
|
return; /* экран не заполнен контентом */
|
||||||
|
off = raw_next(off);
|
||||||
|
}
|
||||||
|
if (off >= file_size)
|
||||||
|
return; /* контент ровно по экран, ниже пусто */
|
||||||
|
|
||||||
|
if(delta ==1) {
|
||||||
|
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 1, 0); /* содержимое вверх */
|
||||||
|
off = raw_draw_one(off, (uint8_t)(VIEW_TOP_ROW + VIEW_H - 1)); /* новый нижний ряд */
|
||||||
|
raw_top = raw_next(raw_top);
|
||||||
|
} else {
|
||||||
|
for (uint8_t k = 0; k < delta; k++) {
|
||||||
|
uint32_t n2 = raw_next(raw_top);
|
||||||
|
if (n2 >= file_size)
|
||||||
|
break;
|
||||||
|
raw_top = n2;
|
||||||
|
}
|
||||||
|
raw_draw(); /* PgUp/PgDn/Home/End — полный экран */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void raw_scroll_up(uint8_t delta) {
|
||||||
|
if (raw_top == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (delta == 1) {
|
||||||
|
raw_top = raw_prev(raw_top);
|
||||||
|
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 2, 0); /* содержимое вниз */
|
||||||
|
(void)raw_draw_one(raw_top, VIEW_TOP_ROW);
|
||||||
|
} else {
|
||||||
|
for (uint8_t k = 0; k < delta; k++)
|
||||||
|
raw_top = raw_prev(raw_top);
|
||||||
|
raw_draw(); /* PgUp/PgDn/Home/End — полный экран */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- навигация (вызывается из главного цикла при g_view != VIEW_MD) - */
|
||||||
|
|
||||||
|
uint8_t raw_key(uint8_t scan)
|
||||||
|
{
|
||||||
|
switch (scan) {
|
||||||
|
case KEY_DOWN:
|
||||||
|
raw_scroll_down(1);
|
||||||
|
break;
|
||||||
|
case KEY_UP:
|
||||||
|
raw_scroll_up(1);
|
||||||
|
break;
|
||||||
|
case KEY_PGDN:
|
||||||
|
raw_scroll_down(VIEW_H);
|
||||||
|
break;
|
||||||
|
case KEY_PGUP:
|
||||||
|
raw_scroll_up(VIEW_H);
|
||||||
|
break;
|
||||||
|
case KEY_HOME:
|
||||||
|
raw_home();
|
||||||
|
raw_draw();
|
||||||
|
break;
|
||||||
|
case KEY_END:
|
||||||
|
raw_end();
|
||||||
|
raw_draw();
|
||||||
|
break;
|
||||||
|
case KEY_LEFT:
|
||||||
|
if (g_view == VIEW_RAW_HSCROLL && raw_hpan) {
|
||||||
|
raw_hpan = (raw_hpan >= HPAN_STEP) ? (uint16_t)(raw_hpan - HPAN_STEP) : 0;
|
||||||
|
raw_draw();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KEY_RIGHT:
|
||||||
|
if (g_view == VIEW_RAW_HSCROLL && raw_hpan < 248u) {
|
||||||
|
raw_hpan = (uint16_t)(raw_hpan + HPAN_STEP);
|
||||||
|
raw_draw();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return 0; /* не наша клавиша */
|
||||||
|
}
|
||||||
|
|
||||||
|
// render_raw_status_numbers();
|
||||||
|
render_percent_progress(calc_raw_pct());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* WITH_RAW */
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
/*
|
||||||
|
* mdview2_status.c — статус-бар (строка 0), нижнее меню (строка 31) и спиннер.
|
||||||
|
*
|
||||||
|
* Хром бара АТОМИЗИРОВАН — обновляется по частям, без перерисовки всего бара:
|
||||||
|
* prerender_status() — фон + разделители │ + "MDVIEW" + имя файла (1 раз);
|
||||||
|
* status_encoding() — только поле кодировки (col 37), по F8;
|
||||||
|
* render_md_status_numbers() — числа MD: диапазон строк + % (при скролле);
|
||||||
|
* render_raw_status_numbers()— числа RAW: метка режима + % по байтам;
|
||||||
|
* render_full_status() — encoding + numbers (MD), при смене набора/режима;
|
||||||
|
* render_menu() — строка меню, по смене режима/готовности;
|
||||||
|
* spinner_tick/show — индикатор занятости во время загрузки.
|
||||||
|
* Оба *_status_numbers() самоочищаются по геометрии разделителей (DIV1_X/DIV2_X
|
||||||
|
* в mdview2.h) — фикс. хром не перерисовывается. RAW-позицию даёт raw_pct()
|
||||||
|
* из mdview2_raw.c (raw_top там приватен).
|
||||||
|
*
|
||||||
|
* Раскладка строки 0:
|
||||||
|
* 0 1..6 8 10.. 37..43 45 46..70 71 72..79
|
||||||
|
* . MDVIEW spinner filename encod. │ L a-b / total │ pct%
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h> /* dec8/dec16, cputs */
|
||||||
|
#include <conio.h> /* textattr/gotoxy/wrchar/COLOR */
|
||||||
|
#include <limits.h>
|
||||||
|
#include <bios/text.h> /* bios_fillcharattr/bios_write_until */
|
||||||
|
#include "mdview2.h"
|
||||||
|
|
||||||
|
/* ---- Спиннер (слот SPINNER_COL в баре) --------------------------- */
|
||||||
|
static const char spinner_chars[4] = { '|', '/', '-', '\\' };
|
||||||
|
static uint8_t spinner_phase = 0;
|
||||||
|
static uint8_t spinner_active = 0;
|
||||||
|
|
||||||
|
/* Продвигает спиннер на один кадр (если включён). */
|
||||||
|
void spinner_tick(void)
|
||||||
|
{
|
||||||
|
if (!spinner_active)
|
||||||
|
return;
|
||||||
|
wrchar(SPINNER_COL, 0, spinner_chars[spinner_phase & 3], ATTR_BAR_SPINNER);
|
||||||
|
spinner_phase++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Вкл/выкл спиннер; при выключении гасит его позицию. */
|
||||||
|
void spinner_show(uint8_t on)
|
||||||
|
{
|
||||||
|
spinner_active = on;
|
||||||
|
if (!on) wrchar(SPINNER_COL, 0, ' ', ATTR_BAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Вычисления для чисел статус-бара ----------------------------- */
|
||||||
|
|
||||||
|
/* Число строк, готовых к показу. Во время загрузки последняя эмитированная
|
||||||
|
* строка ещё в g_cells (не зафлашена), поэтому доступны [0..n_lines-2]. */
|
||||||
|
uint16_t drawable_lines(void)
|
||||||
|
{
|
||||||
|
if (g_loading)
|
||||||
|
return (n_lines >= 1) ? (uint16_t)(n_lines - 1) : 0;
|
||||||
|
return n_lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---- Атомарные части статус-бара ---------------------------------- */
|
||||||
|
|
||||||
|
/* Фиксированный хром бара — рисуется ОДИН раз при старте: фон, два
|
||||||
|
* разделителя │, метка "MDVIEW" и имя файла (они не меняются за сессию). */
|
||||||
|
void prerender_status(void)
|
||||||
|
{
|
||||||
|
fill_row(0, ATTR_BAR);
|
||||||
|
wrchar(DIV1_X, 0, 0xB3, ATTR_BAR); /* │ */
|
||||||
|
wrchar(DIV2_X, 0, 0xB3, ATTR_BAR); /* │ */
|
||||||
|
put_str_attr(1, 0, "MDVIEW", ATTR_BAR);
|
||||||
|
put_str_attr(10, 0, filename, ATTR_BAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Поле текущей кодировки (col 37, 7 ячеек). Меняется только по F8. */
|
||||||
|
void status_encoding(void)
|
||||||
|
{
|
||||||
|
textattr(ATTR_BAR);
|
||||||
|
gotoxy(DIV1_X - 10, 0);
|
||||||
|
bios_write_until(enc_name(g_encoding), 8, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t local_total = UINT_MAX;
|
||||||
|
static uint16_t local_last = UINT_MAX;
|
||||||
|
static uint8_t local_loading = UCHAR_MAX;
|
||||||
|
static uint8_t local_pct = UCHAR_MAX;
|
||||||
|
|
||||||
|
/* Числовая часть (MD): "L a-b / total" между разделителями + "pct%" справа.
|
||||||
|
* Перезаписывает ТОЛЬКО области между/после разделителей, сами │ и фикс.
|
||||||
|
* хром не трогает. */
|
||||||
|
void render_md_status_numbers(void)
|
||||||
|
{
|
||||||
|
uint16_t total = drawable_lines();
|
||||||
|
uint16_t last = top_line + VIEW_H;
|
||||||
|
if (last > total)
|
||||||
|
last = total;
|
||||||
|
|
||||||
|
if(local_total != total || local_last != last || local_loading != g_loading) {
|
||||||
|
local_total = total; local_last = last; local_loading = g_loading;
|
||||||
|
textattr(ATTR_BAR);
|
||||||
|
gotoxy(DIV1_X + 2, 0);
|
||||||
|
bios_fillcharattr(' ', ATTR_BAR, DIV2_X - DIV1_X - 2); /* очистить [DIV1_X+1 .. DIV2_X-1] */
|
||||||
|
gotoxy(DIV1_X + 2, 0);
|
||||||
|
cputs("L ");
|
||||||
|
dec16(top_line + 1);
|
||||||
|
cputs("-");
|
||||||
|
dec16(last);
|
||||||
|
cputs(" / ");
|
||||||
|
dec16(total);
|
||||||
|
if (g_loading) cputs("..."); /* ещё грузится */
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Полное обновление переменной части (MD): кодировка + числа. */
|
||||||
|
void render_full_status(void)
|
||||||
|
{
|
||||||
|
local_loading = UCHAR_MAX;
|
||||||
|
status_encoding();
|
||||||
|
render_md_status_numbers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_percent_progress(uint8_t pct) {
|
||||||
|
if (local_pct != pct) {
|
||||||
|
local_pct = pct;
|
||||||
|
gotoxy(DIV2_X + 2, 0);
|
||||||
|
bios_fillcharattr(' ', ATTR_BAR, SCREEN_W - DIV2_X - 2); /* очистить [DIV2_X+1 .. конец] */
|
||||||
|
gotoxy(DIV2_X + 2, 0);
|
||||||
|
dec8(pct);
|
||||||
|
cputs("%");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WITH_RAW
|
||||||
|
/* RAW-вариант числовой части бара (peer к render_md_status_numbers): метка
|
||||||
|
* режима между разделителями + процент справа. Сам очищает свои области (та же
|
||||||
|
* геометрия DIVn_X), атомарен — не требует общего fill всего бара. Позиция
|
||||||
|
* берётся готовым числом из raw_pct() (raw_top приватен mdview2_raw.c). */
|
||||||
|
void render_raw_status(void)
|
||||||
|
{
|
||||||
|
// uint8_t pct = raw_pct();
|
||||||
|
textattr(ATTR_BAR);
|
||||||
|
gotoxy(DIV1_X + 1, 0);
|
||||||
|
bios_writeattr_until(g_view == VIEW_RAW_WRAP ? " RAW wrap" : " RAW pan",
|
||||||
|
DIV2_X - DIV1_X - 1, ATTR_BAR, 0); /* метка + пад до DIV2_X-1 */
|
||||||
|
}
|
||||||
|
#endif /* WITH_RAW */
|
||||||
|
|
||||||
|
#if WITH_HEX
|
||||||
|
/* HEX-вариант числовой части бара (peer к render_raw_status). */
|
||||||
|
void render_hex_status(void)
|
||||||
|
{
|
||||||
|
textattr(ATTR_BAR);
|
||||||
|
gotoxy(DIV1_X + 1, 0);
|
||||||
|
bios_writeattr_until(" HEX", DIV2_X - DIV1_X - 1, ATTR_BAR, 0);
|
||||||
|
}
|
||||||
|
#endif /* WITH_HEX */
|
||||||
|
|
||||||
|
/* ---- Нижнее меню (строка 31) -------------------------------------- */
|
||||||
|
|
||||||
|
void render_menu(void)
|
||||||
|
{
|
||||||
|
/* Блок i = колонка i*8 (8 шириной): 2 поз. НОМЕР клавиши без 'F' (стиль
|
||||||
|
* ATTR_MENU_K, ведущий «чёрный» пробел кроме F10) + 6 поз. ТЕКСТ-функция
|
||||||
|
* (ATTR_MENU_T) сразу за номером. Номера рисуем для ВСЕХ 10 клавиш; текст —
|
||||||
|
* только у задействованных и доступных сейчас. */
|
||||||
|
char num[3];
|
||||||
|
|
||||||
|
fill_row(MENU_ROW, ATTR_MENU_T);
|
||||||
|
|
||||||
|
num[2] = 0;
|
||||||
|
for (uint8_t i = 0; i < 9; i++) { /* F1..F9: ' 1'..' 9' */
|
||||||
|
num[0] = ' '; num[1] = (char)('1' + i);
|
||||||
|
put_str_attr((uint8_t)(i * 8), MENU_ROW, num, ATTR_MENU_K);
|
||||||
|
}
|
||||||
|
put_str_attr(9 * 8, MENU_ROW, "10", ATTR_MENU_K); /* F10 */
|
||||||
|
|
||||||
|
put_str_attr(0 * 8 + 2, MENU_ROW, "Help", ATTR_MENU_T);
|
||||||
|
#if WITH_RAW
|
||||||
|
if (g_ready) /* RAW доступен после постройки документа */
|
||||||
|
put_str_attr(1 * 8 + 2, MENU_ROW, (g_view == VIEW_MD) ? "RAW" : "MD", ATTR_MENU_T);
|
||||||
|
if (VIEW_IS_RAW(g_view)) /* Wrap/Unwrap — только в RAW */
|
||||||
|
put_str_attr(2 * 8 + 2, MENU_ROW,
|
||||||
|
(g_view == VIEW_RAW_WRAP) ? "UnWrap" : "Wrap", ATTR_MENU_T);
|
||||||
|
#endif
|
||||||
|
#if WITH_HEX
|
||||||
|
if (g_ready) /* HEX доступен после постройки документа */
|
||||||
|
put_str_attr(3 * 8 + 2, MENU_ROW, (g_view == VIEW_HEX) ? "Back" : "Hex", ATTR_MENU_T);
|
||||||
|
#endif
|
||||||
|
if (g_f8_enabled) /* смена кодировки сейчас возможна */
|
||||||
|
put_str_attr(7 * 8 + 2, MENU_ROW, "CodePg", ATTR_MENU_T);
|
||||||
|
put_str_attr(9 * 8 + 2, MENU_ROW, "Exit", ATTR_MENU_T);
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* mdview2_table.c — выровненная отрисовка markdown-таблиц.
|
||||||
|
*
|
||||||
|
* Разбор |-разделённых ячеек и эмиссия рамок/строк в общий буфер ячеек
|
||||||
|
* рендера (gc_put/gc_fill, см. mdview2.h). Вызывается из index_lines()
|
||||||
|
* ядра при обнаружении таблицы. Геометрия рамок (TBL_n), словарь стилей
|
||||||
|
* (CK_n, INIT_STYLE_n) и cell-buffer API объявлены в mdview2.h.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <conio.h> /* COLOR() / COLOR_* для атрибутов TBL_ATTR/ATTR_TEXT */
|
||||||
|
#include "mdview2.h"
|
||||||
|
|
||||||
|
uint32_t row_end(uint32_t p)
|
||||||
|
{
|
||||||
|
while (p < file_size && fb(p) != '\n') p++;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Начало контента первой ячейки строки (после ведущих пробелов и '|'). */
|
||||||
|
uint32_t table_first_cell(uint32_t row_start)
|
||||||
|
{
|
||||||
|
uint32_t p = row_start;
|
||||||
|
while (p < file_size && fb(p) == ' ') p++;
|
||||||
|
if (p < file_size && fb(p) == '|') p++;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Следующая ячейка: [*cs,*ce) — обрезанный диапазон; *pp продвигается за '|'.
|
||||||
|
* 0 — ячеек больше нет (включая хвостовую пустоту после последнего '|'). */
|
||||||
|
uint8_t table_next_cell(uint32_t *pp, uint32_t lineend, uint32_t *cs, uint32_t *ce)
|
||||||
|
{
|
||||||
|
uint32_t p = *pp;
|
||||||
|
if (p >= lineend) return 0;
|
||||||
|
uint32_t a = p;
|
||||||
|
while (p < lineend && fb(p) != '|') p++;
|
||||||
|
uint32_t b = p;
|
||||||
|
uint8_t had_pipe = (uint8_t)(p < lineend && fb(p) == '|');
|
||||||
|
if (had_pipe) p++;
|
||||||
|
*pp = p;
|
||||||
|
while (a < b && fb(a) == ' ') a++;
|
||||||
|
while (b > a && fb(b - 1) == ' ') b--;
|
||||||
|
if (!had_pipe && a == b) return 0;
|
||||||
|
*cs = a; *ce = b;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Строка-разделитель (|---|:-:|): каждая ячейка непустая и только '-'/':'. */
|
||||||
|
uint8_t table_is_sep_row(uint32_t row_start, uint32_t lineend)
|
||||||
|
{
|
||||||
|
uint32_t p = table_first_cell(row_start);
|
||||||
|
uint32_t cs, ce;
|
||||||
|
uint8_t n = 0;
|
||||||
|
while (table_next_cell(&p, lineend, &cs, &ce)) {
|
||||||
|
if (cs >= ce) return 0;
|
||||||
|
for (uint32_t q = cs; q < ce; q++) { char c = fb(q); if (c != '-' && c != ':') return 0; }
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
return (uint8_t)(n > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Горизонтальная рамка в g_cells: left + (H×(w+2) + mid|right) по колонкам. */
|
||||||
|
void table_border(const uint8_t *widths, uint8_t ncols, char left, char mid, char right)
|
||||||
|
{
|
||||||
|
gc_put(left, TBL_ATTR);
|
||||||
|
for (uint8_t c = 0; c < ncols; c++) {
|
||||||
|
gc_fill(TBL_H, TBL_ATTR, (uint8_t)(widths[c] + 2));
|
||||||
|
gc_put((c == (uint8_t)(ncols - 1)) ? right : mid, TBL_ATTR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Строка данных в g_cells: │ <ячейка, добитая до widths[c]> │ … */
|
||||||
|
void table_data_row(uint32_t row_start, uint32_t lineend, const uint8_t *widths, uint8_t ncols)
|
||||||
|
{
|
||||||
|
uint32_t p = table_first_cell(row_start);
|
||||||
|
uint32_t cs, ce;
|
||||||
|
gc_put(TBL_V, TBL_ATTR);
|
||||||
|
for (uint8_t c = 0; c < ncols; c++) {
|
||||||
|
uint8_t got = table_next_cell(&p, lineend, &cs, &ce);
|
||||||
|
gc_put(' ', TBL_ATTR);
|
||||||
|
uint8_t rendered = 0;
|
||||||
|
if (got && ce > cs) {
|
||||||
|
uint8_t before = g_ncells;
|
||||||
|
(void)inline_scan(cs, ce, 0, CK_OTHER, INIT_STYLE_PLAIN, ATTR_TEXT, 1);
|
||||||
|
rendered = (uint8_t)(g_ncells - before);
|
||||||
|
}
|
||||||
|
if (rendered < widths[c]) gc_fill(' ', ATTR_TEXT, (uint8_t)(widths[c] - rendered));
|
||||||
|
gc_put(' ', TBL_ATTR);
|
||||||
|
gc_put(TBL_V, TBL_ATTR);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт HDD с target-пробой и UI-fixture F6 local rename.
|
# Создаёт HDD с target-пробой и UI-fixture F6 local rename.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
IMAGE=${1:-$PROJECT_DIR/build/hdd/p10_rename.chd}
|
IMAGE=${1:-$PROJECT_DIR/build/hdd/p10_rename.chd}
|
||||||
@@ -16,6 +17,6 @@ cleanup()
|
|||||||
trap cleanup EXIT HUP INT TERM
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
python3 "$PROJECT_DIR/tests/p10_rename_fixture.py" create "$TEMP_DIR"
|
python3 "$PROJECT_DIR/tests/p10_rename_fixture.py" create "$TEMP_DIR"
|
||||||
sh "$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
sh "$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$COMMANDER" "$PROBE" "$TEMP_DIR/RENTEST"
|
"$COMMANDER" "$PROBE" "$TEMP_DIR/RENTEST"
|
||||||
printf 'P10 rename HDD ready: %s\n' "$IMAGE"
|
printf 'P10 rename HDD ready: %s\n' "$IMAGE"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт HDD с target-пробой и UI-fixture F8 delete.
|
# Создаёт HDD с target-пробой и UI-fixture F8 delete.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
IMAGE=${1:-$PROJECT_DIR/build/hdd/p11_delete.chd}
|
IMAGE=${1:-$PROJECT_DIR/build/hdd/p11_delete.chd}
|
||||||
@@ -16,6 +17,6 @@ cleanup()
|
|||||||
trap cleanup EXIT HUP INT TERM
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
python3 "$PROJECT_DIR/tests/p11_delete_fixture.py" create "$TEMP_DIR"
|
python3 "$PROJECT_DIR/tests/p11_delete_fixture.py" create "$TEMP_DIR"
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$COMMANDER" "$PROBE" "$TEMP_DIR/DELTEST"
|
"$COMMANDER" "$PROBE" "$TEMP_DIR/DELTEST"
|
||||||
printf 'P11 delete HDD ready: %s\n' "$IMAGE"
|
printf 'P11 delete HDD ready: %s\n' "$IMAGE"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт HDD с двумя панелями для группового F5.
|
# Создаёт HDD с двумя панелями для группового F5.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
IMAGE=${1:-$PROJECT_DIR/build/hdd/p12_group_copy.chd}
|
IMAGE=${1:-$PROJECT_DIR/build/hdd/p12_group_copy.chd}
|
||||||
@@ -15,6 +16,6 @@ cleanup()
|
|||||||
trap cleanup EXIT HUP INT TERM
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
python3 "$PROJECT_DIR/tests/p12_group_copy_fixture.py" create "$TEMP_DIR"
|
python3 "$PROJECT_DIR/tests/p12_group_copy_fixture.py" create "$TEMP_DIR"
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$COMMANDER" "$TEMP_DIR/GRSRC" "$TEMP_DIR/GRDST"
|
"$COMMANDER" "$TEMP_DIR/GRSRC" "$TEMP_DIR/GRDST"
|
||||||
printf 'P12 group-copy HDD ready: %s\n' "$IMAGE"
|
printf 'P12 group-copy HDD ready: %s\n' "$IMAGE"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт HDD-fixture группового F8.
|
# Создаёт HDD-fixture группового F8.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
IMAGE=${1:-$PROJECT_DIR/build/hdd/p13_group_delete.chd}
|
IMAGE=${1:-$PROJECT_DIR/build/hdd/p13_group_delete.chd}
|
||||||
@@ -15,6 +16,6 @@ cleanup()
|
|||||||
trap cleanup EXIT HUP INT TERM
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
python3 "$PROJECT_DIR/tests/p13_group_delete_fixture.py" create "$TEMP_DIR"
|
python3 "$PROJECT_DIR/tests/p13_group_delete_fixture.py" create "$TEMP_DIR"
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$COMMANDER" "$TEMP_DIR/GDEL"
|
"$COMMANDER" "$TEMP_DIR/GDEL"
|
||||||
printf 'P13 group-delete HDD ready: %s\n' "$IMAGE"
|
printf 'P13 group-delete HDD ready: %s\n' "$IMAGE"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт HDD-fixture стандартных цветов имён.
|
# Создаёт HDD-fixture стандартных цветов имён.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
IMAGE=${1:-$PROJECT_DIR/build/hdd/p14_default_colors.chd}
|
IMAGE=${1:-$PROJECT_DIR/build/hdd/p14_default_colors.chd}
|
||||||
@@ -15,6 +16,6 @@ cleanup()
|
|||||||
trap cleanup EXIT HUP INT TERM
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
python3 "$PROJECT_DIR/tests/p14_default_colors_fixture.py" create "$TEMP_DIR"
|
python3 "$PROJECT_DIR/tests/p14_default_colors_fixture.py" create "$TEMP_DIR"
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$COMMANDER" "$TEMP_DIR/COLORS"
|
"$COMMANDER" "$TEMP_DIR/COLORS"
|
||||||
printf 'P14 default-colours HDD ready: %s\n' "$IMAGE"
|
printf 'P14 default-colours HDD ready: %s\n' "$IMAGE"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт HDD-fixture отмены рекурсивного F5.
|
# Создаёт HDD-fixture отмены рекурсивного F5.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
IMAGE=${1:-$PROJECT_DIR/build/hdd/p15_tree_cancel.chd}
|
IMAGE=${1:-$PROJECT_DIR/build/hdd/p15_tree_cancel.chd}
|
||||||
@@ -15,6 +16,6 @@ cleanup()
|
|||||||
trap cleanup EXIT HUP INT TERM
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
python3 "$PROJECT_DIR/tests/p15_tree_cancel_fixture.py" create "$TEMP_DIR"
|
python3 "$PROJECT_DIR/tests/p15_tree_cancel_fixture.py" create "$TEMP_DIR"
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$COMMANDER" "$TEMP_DIR/CSRC" "$TEMP_DIR/CDST"
|
"$COMMANDER" "$TEMP_DIR/CSRC" "$TEMP_DIR/CDST"
|
||||||
printf 'P15 tree-cancel HDD ready: %s\n' "$IMAGE"
|
printf 'P15 tree-cancel HDD ready: %s\n' "$IMAGE"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт HDD-fixture рекурсивного F5 одного каталога.
|
# Создаёт HDD-fixture рекурсивного F5 одного каталога.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
IMAGE=${1:-$PROJECT_DIR/build/hdd/p15_tree_copy.chd}
|
IMAGE=${1:-$PROJECT_DIR/build/hdd/p15_tree_copy.chd}
|
||||||
@@ -15,6 +16,6 @@ cleanup()
|
|||||||
trap cleanup EXIT HUP INT TERM
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
python3 "$PROJECT_DIR/tests/p15_tree_copy_fixture.py" create "$TEMP_DIR"
|
python3 "$PROJECT_DIR/tests/p15_tree_copy_fixture.py" create "$TEMP_DIR"
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$COMMANDER" "$TEMP_DIR/SRC" "$TEMP_DIR/DST"
|
"$COMMANDER" "$TEMP_DIR/SRC" "$TEMP_DIR/DST"
|
||||||
printf 'P15 tree-copy HDD ready: %s\n' "$IMAGE"
|
printf 'P15 tree-copy HDD ready: %s\n' "$IMAGE"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт HDD-fixture target-inside-source guard.
|
# Создаёт HDD-fixture target-inside-source guard.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
IMAGE=${1:-$PROJECT_DIR/build/hdd/p15_tree_self.chd}
|
IMAGE=${1:-$PROJECT_DIR/build/hdd/p15_tree_self.chd}
|
||||||
@@ -15,6 +16,6 @@ cleanup()
|
|||||||
trap cleanup EXIT HUP INT TERM
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
python3 "$PROJECT_DIR/tests/p15_tree_self_fixture.py" create "$TEMP_DIR"
|
python3 "$PROJECT_DIR/tests/p15_tree_self_fixture.py" create "$TEMP_DIR"
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$COMMANDER" "$TEMP_DIR/SRC"
|
"$COMMANDER" "$TEMP_DIR/SRC"
|
||||||
printf 'P15 tree-self HDD ready: %s\n' "$IMAGE"
|
printf 'P15 tree-self HDD ready: %s\n' "$IMAGE"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт HDD-fixture group F5 с двумя directory roots.
|
# Создаёт HDD-fixture group F5 с двумя directory roots.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
IMAGE=${1:-$PROJECT_DIR/build/hdd/p16_group_tree_copy.chd}
|
IMAGE=${1:-$PROJECT_DIR/build/hdd/p16_group_tree_copy.chd}
|
||||||
@@ -15,6 +16,6 @@ cleanup()
|
|||||||
trap cleanup EXIT HUP INT TERM
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
python3 "$PROJECT_DIR/tests/p16_group_tree_copy_fixture.py" create "$TEMP_DIR"
|
python3 "$PROJECT_DIR/tests/p16_group_tree_copy_fixture.py" create "$TEMP_DIR"
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$COMMANDER" "$TEMP_DIR/GSRC" "$TEMP_DIR/GDST"
|
"$COMMANDER" "$TEMP_DIR/GSRC" "$TEMP_DIR/GDST"
|
||||||
printf 'P16 group-tree HDD ready: %s\n' "$IMAGE"
|
printf 'P16 group-tree HDD ready: %s\n' "$IMAGE"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт HDD-fixture runtime-cancel рекурсивного группового F8.
|
# Создаёт HDD-fixture runtime-cancel рекурсивного группового F8.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
IMAGE=${1:-$PROJECT_DIR/build/hdd/p17_tree_delete_cancel.chd}
|
IMAGE=${1:-$PROJECT_DIR/build/hdd/p17_tree_delete_cancel.chd}
|
||||||
@@ -15,6 +16,6 @@ cleanup()
|
|||||||
trap cleanup EXIT HUP INT TERM
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
python3 "$PROJECT_DIR/tests/p17_tree_delete_cancel_fixture.py" create "$TEMP_DIR"
|
python3 "$PROJECT_DIR/tests/p17_tree_delete_cancel_fixture.py" create "$TEMP_DIR"
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$COMMANDER" "$TEMP_DIR/CDEL"
|
"$COMMANDER" "$TEMP_DIR/CDEL"
|
||||||
printf 'P17 tree-delete cancel HDD ready: %s\n' "$IMAGE"
|
printf 'P17 tree-delete cancel HDD ready: %s\n' "$IMAGE"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт HDD-fixture рекурсивного группового F8.
|
# Создаёт HDD-fixture рекурсивного группового F8.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
IMAGE=${1:-$PROJECT_DIR/build/hdd/p17_tree_delete.chd}
|
IMAGE=${1:-$PROJECT_DIR/build/hdd/p17_tree_delete.chd}
|
||||||
@@ -15,6 +16,6 @@ cleanup()
|
|||||||
trap cleanup EXIT HUP INT TERM
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
python3 "$PROJECT_DIR/tests/p17_tree_delete_fixture.py" create "$TEMP_DIR"
|
python3 "$PROJECT_DIR/tests/p17_tree_delete_fixture.py" create "$TEMP_DIR"
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$COMMANDER" "$TEMP_DIR/RDEL"
|
"$COMMANDER" "$TEMP_DIR/RDEL"
|
||||||
printf 'P17 tree-delete HDD ready: %s\n' "$IMAGE"
|
printf 'P17 tree-delete HDD ready: %s\n' "$IMAGE"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт HDD с точными FAT date/time и R/H/S/A у source-файла.
|
# Создаёт HDD с точными FAT date/time и R/H/S/A у source-файла.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
IMAGE=${1:-$PROJECT_DIR/build/hdd/p18_metadata.chd}
|
IMAGE=${1:-$PROJECT_DIR/build/hdd/p18_metadata.chd}
|
||||||
@@ -18,7 +19,7 @@ cleanup()
|
|||||||
trap cleanup EXIT HUP INT TERM
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
python3 "$PROJECT_DIR/tests/p18_metadata_fixture.py" create "$TEMP_DIR"
|
python3 "$PROJECT_DIR/tests/p18_metadata_fixture.py" create "$TEMP_DIR"
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$BASE_CHD" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$BASE_CHD" \
|
||||||
"$COMMANDER" "$TEMP_DIR/MSRC" "$TEMP_DIR/MDST"
|
"$COMMANDER" "$TEMP_DIR/MSRC" "$TEMP_DIR/MDST"
|
||||||
|
|
||||||
chdman extractraw -i "$BASE_CHD" -o "$RAW" -f >/dev/null
|
chdman extractraw -i "$BASE_CHD" -o "$RAW" -f >/dev/null
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт HDD для target-пробы транзакционного overwrite.
|
# Создаёт HDD для target-пробы транзакционного overwrite.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
IMAGE=${1:-$PROJECT_DIR/build/hdd/p19_overwrite.chd}
|
IMAGE=${1:-$PROJECT_DIR/build/hdd/p19_overwrite.chd}
|
||||||
@@ -18,7 +19,7 @@ cleanup()
|
|||||||
trap cleanup EXIT HUP INT TERM
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
python3 "$PROJECT_DIR/tests/p19_overwrite_fixture.py" create "$TEMP_DIR"
|
python3 "$PROJECT_DIR/tests/p19_overwrite_fixture.py" create "$TEMP_DIR"
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$BASE_CHD" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$BASE_CHD" \
|
||||||
"$PROBE" "$TEMP_DIR/SOURCE" "$TEMP_DIR/TARGET"
|
"$PROBE" "$TEMP_DIR/SOURCE" "$TEMP_DIR/TARGET"
|
||||||
chdman extractraw -i "$BASE_CHD" -o "$RAW" -f >/dev/null
|
chdman extractraw -i "$BASE_CHD" -o "$RAW" -f >/dev/null
|
||||||
printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC"
|
printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт HDD полного UI-сценария conflict-policy P19.
|
# Создаёт HDD полного UI-сценария conflict-policy P19.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
IMAGE=${1:-$PROJECT_DIR/build/hdd/p19_policy.chd}
|
IMAGE=${1:-$PROJECT_DIR/build/hdd/p19_policy.chd}
|
||||||
@@ -18,7 +19,7 @@ cleanup()
|
|||||||
trap cleanup EXIT HUP INT TERM
|
trap cleanup EXIT HUP INT TERM
|
||||||
|
|
||||||
python3 "$PROJECT_DIR/tests/p19_policy_fixture.py" create "$TEMP_DIR"
|
python3 "$PROJECT_DIR/tests/p19_policy_fixture.py" create "$TEMP_DIR"
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$BASE_CHD" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$BASE_CHD" \
|
||||||
"$COMMANDER" "$TEMP_DIR/P19SRC" "$TEMP_DIR/P19DST"
|
"$COMMANDER" "$TEMP_DIR/P19SRC" "$TEMP_DIR/P19DST"
|
||||||
chdman extractraw -i "$BASE_CHD" -o "$RAW" -f >/dev/null
|
chdman extractraw -i "$BASE_CHD" -o "$RAW" -f >/dev/null
|
||||||
printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC"
|
printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC"
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Создаёт стандартную FAT12-дискету для P3 stress-теста.
|
# Создаёт стандартную FAT12-дискету для P3 stress-теста.
|
||||||
# Аргументы: [образ [исполняемый файл]]; по умолчанию — mc.img и Commander.
|
# Аргументы: [образ [исполняемый файл]]; по умолчанию — локальный образ.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
DEFAULT_IMAGE="$PROJECT_DIR/../../mame/v306/IMG/mc.img"
|
DEFAULT_IMAGE="$PROJECT_DIR/build/media/p3_stress.img"
|
||||||
IMAGE=${1:-$DEFAULT_IMAGE}
|
IMAGE=${1:-$DEFAULT_IMAGE}
|
||||||
EXECUTABLE=${2:-$PROJECT_DIR/build/sprcmd.exe}
|
EXECUTABLE=${2:-$PROJECT_DIR/build/sprcmd.exe}
|
||||||
TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-p3.XXXXXX")
|
TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-p3.XXXXXX")
|
||||||
|
mkdir -p "$(dirname -- "$IMAGE")"
|
||||||
|
|
||||||
cleanup()
|
cleanup()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
# Аргументы: [образ [исполняемый файл]]; по умолчанию — локальный build/HDD.
|
# Аргументы: [образ [исполняемый файл]]; по умолчанию — локальный build/HDD.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p3_stress.chd"
|
DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p3_stress.chd"
|
||||||
@@ -44,7 +45,7 @@ done
|
|||||||
cp "$PROJECT_DIR/tests/p3_data/P3ROOT.TXT" "$FIXTURE_ROOT/P3ROOT.TXT"
|
cp "$PROJECT_DIR/tests/p3_data/P3ROOT.TXT" "$FIXTURE_ROOT/P3ROOT.TXT"
|
||||||
cp "$PROJECT_DIR/tests/p3_data/ZETA.BIN" "$FIXTURE_ROOT/ZETA.BIN"
|
cp "$PROJECT_DIR/tests/p3_data/ZETA.BIN" "$FIXTURE_ROOT/ZETA.BIN"
|
||||||
|
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$EXECUTABLE" \
|
"$EXECUTABLE" \
|
||||||
"$FIXTURE_ROOT/EMPTY" "$FIXTURE_ROOT/TESTDIR" \
|
"$FIXTURE_ROOT/EMPTY" "$FIXTURE_ROOT/TESTDIR" \
|
||||||
"$FIXTURE_ROOT/MANY" "$FIXTURE_ROOT/BIGDIR" \
|
"$FIXTURE_ROOT/MANY" "$FIXTURE_ROOT/BIGDIR" \
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
# Аргументы: [образ [исполняемый файл]].
|
# Аргументы: [образ [исполняемый файл]].
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p4_copy.chd"
|
DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p4_copy.chd"
|
||||||
@@ -20,7 +21,7 @@ trap cleanup EXIT HUP INT TERM
|
|||||||
mkdir -p "$(dirname -- "$IMAGE")"
|
mkdir -p "$(dirname -- "$IMAGE")"
|
||||||
python3 "$PROJECT_DIR/tests/p4_fixture.py" create "$FIXTURE_ROOT"
|
python3 "$PROJECT_DIR/tests/p4_fixture.py" create "$FIXTURE_ROOT"
|
||||||
|
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$EXECUTABLE" "$FIXTURE_ROOT/COPYFROM" "$FIXTURE_ROOT/COPYTO"
|
"$EXECUTABLE" "$FIXTURE_ROOT/COPYFROM" "$FIXTURE_ROOT/COPYTO"
|
||||||
|
|
||||||
echo "P4 copy HDD ready: $IMAGE (D:, executable: SPRCMD.EXE)"
|
echo "P4 copy HDD ready: $IMAGE (D:, executable: SPRCMD.EXE)"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт отдельный HDD для EXEC/restore-теста P4.2.
|
# Создаёт отдельный HDD для EXEC/restore-теста P4.2.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p4_exec.chd"
|
DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p4_exec.chd"
|
||||||
@@ -25,7 +26,7 @@ cp "$PROJECT_DIR/tests/p3_data/P3ROOT.TXT" \
|
|||||||
cp "$PROJECT_DIR/tests/p3_data/INNER.TXT" \
|
cp "$PROJECT_DIR/tests/p3_data/INNER.TXT" \
|
||||||
"$FIXTURE_ROOT/EXECLEFT/NOTE.TXT"
|
"$FIXTURE_ROOT/EXECLEFT/NOTE.TXT"
|
||||||
|
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$COMMANDER" "$FIXTURE_ROOT/EXECLEFT" "$FIXTURE_ROOT/EXECRGHT"
|
"$COMMANDER" "$FIXTURE_ROOT/EXECLEFT" "$FIXTURE_ROOT/EXECRGHT"
|
||||||
|
|
||||||
echo "P4 EXEC HDD ready: $IMAGE (D:, child: EXECLEFT/P4CHILD.EXE)"
|
echo "P4 EXEC HDD ready: $IMAGE (D:, child: EXECLEFT/P4CHILD.EXE)"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт HDD с отдельной матрицей отказов банкового copy-job.
|
# Создаёт HDD с отдельной матрицей отказов банкового copy-job.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p5_copy_faults.chd"
|
DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p5_copy_faults.chd"
|
||||||
@@ -19,7 +20,7 @@ trap cleanup EXIT HUP INT TERM
|
|||||||
mkdir -p "$(dirname -- "$IMAGE")"
|
mkdir -p "$(dirname -- "$IMAGE")"
|
||||||
python3 "$PROJECT_DIR/tests/p5_copy_faults_fixture.py" create "$FIXTURE_ROOT"
|
python3 "$PROJECT_DIR/tests/p5_copy_faults_fixture.py" create "$FIXTURE_ROOT"
|
||||||
|
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$EXECUTABLE" "$FIXTURE_ROOT/SOURCE" "$FIXTURE_ROOT/TARGET"
|
"$EXECUTABLE" "$FIXTURE_ROOT/SOURCE" "$FIXTURE_ROOT/TARGET"
|
||||||
|
|
||||||
echo "P5 copy-fault HDD ready: $IMAGE"
|
echo "P5 copy-fault HDD ready: $IMAGE"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт почти полный HDD для реальной ошибки записи ENOSPC.
|
# Создаёт почти полный HDD для реальной ошибки записи ENOSPC.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p5_enospc.chd"
|
DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p5_enospc.chd"
|
||||||
@@ -20,7 +21,7 @@ mkdir -p "$(dirname -- "$IMAGE")"
|
|||||||
python3 "$PROJECT_DIR/tests/p5_enospc_fixture.py" create "$FIXTURE_ROOT"
|
python3 "$PROJECT_DIR/tests/p5_enospc_fixture.py" create "$FIXTURE_ROOT"
|
||||||
python3 "$PROJECT_DIR/tests/p5_enospc_fixture.py" verify-initial "$FIXTURE_ROOT"
|
python3 "$PROJECT_DIR/tests/p5_enospc_fixture.py" verify-initial "$FIXTURE_ROOT"
|
||||||
|
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$EXECUTABLE" "$FIXTURE_ROOT/SOURCE" "$FIXTURE_ROOT/TARGET" \
|
"$EXECUTABLE" "$FIXTURE_ROOT/SOURCE" "$FIXTURE_ROOT/TARGET" \
|
||||||
"$FIXTURE_ROOT/FILLER.BIN"
|
"$FIXTURE_ROOT/FILLER.BIN"
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт HDD для P5: 20 копирований, 100 refresh и cleanup-циклы.
|
# Создаёт HDD для P5: 20 копирований, 100 refresh и cleanup-циклы.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p5_stability.chd"
|
DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p5_stability.chd"
|
||||||
@@ -20,7 +21,7 @@ mkdir -p "$(dirname -- "$IMAGE")"
|
|||||||
python3 "$PROJECT_DIR/tests/p5_fixture.py" create "$FIXTURE_ROOT"
|
python3 "$PROJECT_DIR/tests/p5_fixture.py" create "$FIXTURE_ROOT"
|
||||||
python3 "$PROJECT_DIR/tests/p5_fixture.py" verify-initial "$FIXTURE_ROOT"
|
python3 "$PROJECT_DIR/tests/p5_fixture.py" verify-initial "$FIXTURE_ROOT"
|
||||||
|
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$EXECUTABLE" "$FIXTURE_ROOT/SOURCE" "$FIXTURE_ROOT/TARGET"
|
"$EXECUTABLE" "$FIXTURE_ROOT/SOURCE" "$FIXTURE_ROOT/TARGET"
|
||||||
|
|
||||||
echo "P5 stability HDD ready: $IMAGE"
|
echo "P5 stability HDD ready: $IMAGE"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт HDD для физического unload/load во время F5.
|
# Создаёт HDD для физического unload/load во время F5.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p5_media.chd"
|
DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p5_media.chd"
|
||||||
@@ -18,7 +19,7 @@ trap cleanup EXIT HUP INT TERM
|
|||||||
|
|
||||||
mkdir -p "$(dirname -- "$IMAGE")"
|
mkdir -p "$(dirname -- "$IMAGE")"
|
||||||
python3 "$PROJECT_DIR/tests/p5_media_fixture.py" create "$FIXTURE_ROOT"
|
python3 "$PROJECT_DIR/tests/p5_media_fixture.py" create "$FIXTURE_ROOT"
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$EXECUTABLE" "$FIXTURE_ROOT/MEDIA"
|
"$EXECUTABLE" "$FIXTURE_ROOT/MEDIA"
|
||||||
|
|
||||||
echo "P5 media-change HDD ready: $IMAGE"
|
echo "P5 media-change HDD ready: $IMAGE"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Создаёт каталоги с итоговыми panel.count ровно 27 и 28 (включая `..`).
|
# Создаёт каталоги с итоговыми panel.count ровно 27 и 28 (включая `..`).
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p5_panel_edges.chd"
|
DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p5_panel_edges.chd"
|
||||||
@@ -32,7 +33,7 @@ while [ "$i" -lt 27 ]; do
|
|||||||
i=$((i + 1))
|
i=$((i + 1))
|
||||||
done
|
done
|
||||||
|
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$EXECUTABLE" "$FIXTURE_ROOT/N27" "$FIXTURE_ROOT/N28"
|
"$EXECUTABLE" "$FIXTURE_ROOT/N27" "$FIXTURE_ROOT/N28"
|
||||||
|
|
||||||
echo "P5 panel-edge HDD ready: N27=26+parent, N28=27+parent"
|
echo "P5 panel-edge HDD ready: N27=26+parent, N28=27+parent"
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
|||||||
DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p5_viewer.chd"
|
DEFAULT_IMAGE="$PROJECT_DIR/build/hdd/p5_viewer.chd"
|
||||||
IMAGE=${1:-$DEFAULT_IMAGE}
|
IMAGE=${1:-$DEFAULT_IMAGE}
|
||||||
COMMANDER=${2:-$PROJECT_DIR/build/sprcmd.exe}
|
COMMANDER=${2:-$PROJECT_DIR/build/sprcmd.exe}
|
||||||
VIEWER=${3:-$PROJECT_DIR/../../examples/mdview2/mdview2.exe}
|
VIEWER=${3:-$PROJECT_DIR/build/external/mdview2.exe}
|
||||||
README=${4:-$PROJECT_DIR/../../examples/mdview2/README.md}
|
README=${4:-$PROJECT_DIR/tests/fixtures/mdview2/README.md}
|
||||||
TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-viewer.XXXXXX")
|
TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/sprcmd-viewer.XXXXXX")
|
||||||
FIXTURE_ROOT="$TEMP_DIR/VIEWDATA"
|
FIXTURE_ROOT="$TEMP_DIR/VIEWDATA"
|
||||||
|
|
||||||
@@ -22,7 +22,8 @@ mkdir -p "$(dirname -- "$IMAGE")" "$FIXTURE_ROOT/VIEWER"
|
|||||||
cp "$VIEWER" "$FIXTURE_ROOT/VIEWER/MDVIEW2.EXE"
|
cp "$VIEWER" "$FIXTURE_ROOT/VIEWER/MDVIEW2.EXE"
|
||||||
cp "$README" "$FIXTURE_ROOT/VIEWER/README.MD"
|
cp "$README" "$FIXTURE_ROOT/VIEWER/README.MD"
|
||||||
|
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$COMMANDER" "$FIXTURE_ROOT/VIEWER"
|
"$COMMANDER" "$FIXTURE_ROOT/VIEWER"
|
||||||
|
|
||||||
echo "P5 viewer HDD ready: VIEWER/MDVIEW2.EXE + README.MD"
|
echo "P5 viewer HDD ready: VIEWER/MDVIEW2.EXE + README.MD"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Собирает HDD с target-регрессией и двумя каталогами UI-сортировки.
|
# Собирает HDD с target-регрессией и двумя каталогами UI-сортировки.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
IMAGE=${1:-$PROJECT_DIR/build/hdd/p6_sort.chd}
|
IMAGE=${1:-$PROJECT_DIR/build/hdd/p6_sort.chd}
|
||||||
@@ -19,7 +20,7 @@ trap cleanup EXIT HUP INT TERM
|
|||||||
mkdir -p "$(dirname -- "$IMAGE")"
|
mkdir -p "$(dirname -- "$IMAGE")"
|
||||||
python3 "$PROJECT_DIR/tests/p6_sort_fixture.py" create "$FIXTURE_ROOT"
|
python3 "$PROJECT_DIR/tests/p6_sort_fixture.py" create "$FIXTURE_ROOT"
|
||||||
|
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$EXECUTABLE" "$PROBE" "$FIXTURE_ROOT/SORTL" "$FIXTURE_ROOT/SORTR"
|
"$EXECUTABLE" "$PROBE" "$FIXTURE_ROOT/SORTL" "$FIXTURE_ROOT/SORTR"
|
||||||
|
|
||||||
echo "P6 sort HDD ready: $IMAGE"
|
echo "P6 sort HDD ready: $IMAGE"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Собирает HDD target/UI-регрессии выбора записей.
|
# Собирает HDD target/UI-регрессии выбора записей.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
IMAGE=${1:-$PROJECT_DIR/build/hdd/p7_select.chd}
|
IMAGE=${1:-$PROJECT_DIR/build/hdd/p7_select.chd}
|
||||||
@@ -18,6 +19,6 @@ trap cleanup EXIT HUP INT TERM
|
|||||||
|
|
||||||
mkdir -p "$(dirname -- "$IMAGE")"
|
mkdir -p "$(dirname -- "$IMAGE")"
|
||||||
python3 "$PROJECT_DIR/tests/p7_select_fixture.py" create "$FIXTURE_ROOT"
|
python3 "$PROJECT_DIR/tests/p7_select_fixture.py" create "$FIXTURE_ROOT"
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$EXECUTABLE" "$PROBE" "$FIXTURE_ROOT/SELECT"
|
"$EXECUTABLE" "$PROBE" "$FIXTURE_ROOT/SELECT"
|
||||||
echo "P7 selection HDD ready: $IMAGE"
|
echo "P7 selection HDD ready: $IMAGE"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# Собирает HDD target/UI-регрессии F7 mkdir.
|
# Собирает HDD target/UI-регрессии F7 mkdir.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для упаковки HDD}"
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
IMAGE=${1:-$PROJECT_DIR/build/hdd/p9_mkdir.chd}
|
IMAGE=${1:-$PROJECT_DIR/build/hdd/p9_mkdir.chd}
|
||||||
@@ -18,6 +19,6 @@ trap cleanup EXIT HUP INT TERM
|
|||||||
|
|
||||||
mkdir -p "$(dirname -- "$IMAGE")"
|
mkdir -p "$(dirname -- "$IMAGE")"
|
||||||
python3 "$PROJECT_DIR/tests/p9_mkdir_fixture.py" create "$FIXTURE_ROOT"
|
python3 "$PROJECT_DIR/tests/p9_mkdir_fixture.py" create "$FIXTURE_ROOT"
|
||||||
"$PROJECT_DIR/../../toolchain/make_hdd.sh" "$IMAGE" \
|
"$SPRINTER_ROOT/toolchain/make_hdd.sh" "$IMAGE" \
|
||||||
"$EXECUTABLE" "$PROBE" "$FIXTURE_ROOT/MKTEST"
|
"$EXECUTABLE" "$PROBE" "$FIXTURE_ROOT/MKTEST"
|
||||||
echo "P9 mkdir HDD ready: $IMAGE"
|
echo "P9 mkdir HDD ready: $IMAGE"
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
# Пробы намеренно собираются в small: это изолированный диагностический
|
# Пробы намеренно собираются в small: это изолированный диагностический
|
||||||
# инструмент, а не topology будущего Commander. Сборка big проверяется в P2.
|
# инструмент, а не topology будущего Commander. Сборка big проверяется в P2.
|
||||||
|
|
||||||
PROJ_ROOT := $(abspath $(CURDIR)/../../../..)
|
|
||||||
EXAMPLE := p1probe
|
EXAMPLE := p1probe
|
||||||
MEMORY := small
|
MEMORY := small
|
||||||
ALLOCS ?= 3000
|
ALLOCS ?= 3000
|
||||||
@@ -16,7 +15,7 @@ EXTRA_FLAGS := --safe --max-allocs $(ALLOCS)
|
|||||||
EXTRA_SRCS := p1_sys.c p1_emm.c p1_dir.c p1_screen.c p1_keyboard.c p1_exec_test.c
|
EXTRA_SRCS := p1_sys.c p1_emm.c p1_dir.c p1_screen.c p1_keyboard.c p1_exec_test.c
|
||||||
EXTRA_DATA := p1child.exe
|
EXTRA_DATA := p1child.exe
|
||||||
|
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/../../sdk.mk
|
||||||
|
|
||||||
# EXEC-тест ожидает дочерний EXE рядом с основной пробой.
|
# EXEC-тест ожидает дочерний EXE рядом с основной пробой.
|
||||||
$(EXE): Makefile p1child.exe
|
$(EXE): Makefile p1child.exe
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
# P2.2 — отдельная проба системных BIOS/ESTEX-вызовов текстовой палитры.
|
# P2.2 — отдельная проба системных BIOS/ESTEX-вызовов текстовой палитры.
|
||||||
|
|
||||||
PROJ_ROOT := $(abspath $(CURDIR)/../../../..)
|
|
||||||
EXAMPLE := p2palette
|
EXAMPLE := p2palette
|
||||||
MEMORY := big
|
MEMORY := big
|
||||||
ALLOCS ?= 3000
|
ALLOCS ?= 3000
|
||||||
EXTRA_FLAGS := --safe --max-allocs $(ALLOCS)
|
EXTRA_FLAGS := --safe --max-allocs $(ALLOCS)
|
||||||
|
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/../../sdk.mk
|
||||||
|
|
||||||
$(EXE): Makefile
|
$(EXE): Makefile
|
||||||
|
|||||||
@@ -3,13 +3,12 @@
|
|||||||
# Тест использует системные ESTEX-функции WINREST/RDCHAR/SCROLL, не
|
# Тест использует системные ESTEX-функции WINREST/RDCHAR/SCROLL, не
|
||||||
# открывает BIOS-окна и не обращается к текстовой VRAM напрямую.
|
# открывает BIOS-окна и не обращается к текстовой VRAM напрямую.
|
||||||
|
|
||||||
PROJ_ROOT := $(abspath $(CURDIR)/../../../..)
|
|
||||||
EXAMPLE := p2screen
|
EXAMPLE := p2screen
|
||||||
MEMORY := big
|
MEMORY := big
|
||||||
ALLOCS ?= 3000
|
ALLOCS ?= 3000
|
||||||
EXTRA_FLAGS := --safe --max-allocs $(ALLOCS)
|
EXTRA_FLAGS := --safe --max-allocs $(ALLOCS)
|
||||||
EXTRA_SRCS := p2_platform.c
|
EXTRA_SRCS := p2_platform.c
|
||||||
|
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/../../sdk.mk
|
||||||
|
|
||||||
$(EXE): Makefile p2_screen.h sc_glyphs.h
|
$(EXE): Makefile p2_screen.h sc_glyphs.h
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
# Диагностическая проба предельного каталога P3.
|
# Диагностическая проба предельного каталога P3.
|
||||||
|
|
||||||
PROJ_ROOT := $(abspath $(CURDIR)/../../../..)
|
|
||||||
EXAMPLE := p3dir
|
EXAMPLE := p3dir
|
||||||
MEMORY := small
|
MEMORY := small
|
||||||
ALLOCS ?= 3000
|
ALLOCS ?= 3000
|
||||||
EXTRA_FLAGS := --safe --max-allocs $(ALLOCS)
|
EXTRA_FLAGS := --safe --max-allocs $(ALLOCS)
|
||||||
|
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/../../sdk.mk
|
||||||
|
|
||||||
$(EXE): Makefile
|
$(EXE): Makefile
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
# Матрица влияния W3/EMM на длинный F_FIRST/F_NEXT.
|
# Матрица влияния W3/EMM на длинный F_FIRST/F_NEXT.
|
||||||
|
|
||||||
PROJ_ROOT := $(abspath $(CURDIR)/../../../..)
|
|
||||||
EXAMPLE := p3map
|
EXAMPLE := p3map
|
||||||
MEMORY := big
|
MEMORY := big
|
||||||
ALLOCS ?= 3000
|
ALLOCS ?= 3000
|
||||||
EXTRA_FLAGS := --safe --max-allocs $(ALLOCS) --bank 1=bank1_enum.c
|
EXTRA_FLAGS := --safe --max-allocs $(ALLOCS) --bank 1=bank1_enum.c
|
||||||
|
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/../../sdk.mk
|
||||||
|
|
||||||
$(EXE): Makefile bank1_enum.c
|
$(EXE): Makefile bank1_enum.c
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# Полный scan/store/sort большого каталога теми же модулями, что Commander.
|
# Полный scan/store/sort большого каталога теми же модулями, что Commander.
|
||||||
|
|
||||||
PROJ_ROOT := $(abspath $(CURDIR)/../../../..)
|
|
||||||
EXAMPLE := p3scan
|
EXAMPLE := p3scan
|
||||||
MEMORY := big
|
MEMORY := big
|
||||||
ALLOCS ?= 3000
|
ALLOCS ?= 3000
|
||||||
@@ -9,6 +8,6 @@ EXTRA_FLAGS := --safe --max-allocs $(ALLOCS) -I ../../include \
|
|||||||
--bank 1=../../src/sc_sort.c
|
--bank 1=../../src/sc_sort.c
|
||||||
EXTRA_SRCS := ../../src/sc_panel.c ../../src/sc_store.c
|
EXTRA_SRCS := ../../src/sc_panel.c ../../src/sc_store.c
|
||||||
|
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/../../sdk.mk
|
||||||
|
|
||||||
$(EXE): Makefile ../../src/sc_source_fs.c ../../src/sc_sort.c
|
$(EXE): Makefile ../../src/sc_source_fs.c ../../src/sc_sort.c
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
# Синтетическая проверка границы одной EMM-страницы панели: 640/641.
|
# Синтетическая проверка границы одной EMM-страницы панели: 640/641.
|
||||||
|
|
||||||
PROJ_ROOT := $(abspath $(CURDIR)/../../../..)
|
|
||||||
EXAMPLE := p3store
|
EXAMPLE := p3store
|
||||||
MEMORY := big
|
MEMORY := big
|
||||||
ALLOCS ?= 3000
|
ALLOCS ?= 3000
|
||||||
EXTRA_FLAGS := --safe --max-allocs $(ALLOCS) -I ../../include
|
EXTRA_FLAGS := --safe --max-allocs $(ALLOCS) -I ../../include
|
||||||
EXTRA_SRCS := ../../src/sc_store.c
|
EXTRA_SRCS := ../../src/sc_store.c
|
||||||
|
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/../../sdk.mk
|
||||||
|
|
||||||
$(EXE): Makefile ../../src/sc_store.c ../../include/sc_entry.h \
|
$(EXE): Makefile ../../src/sc_store.c ../../include/sc_entry.h \
|
||||||
../../include/sc_panel.h ../../include/sc_store.h
|
../../include/sc_panel.h ../../include/sc_store.h
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
# P4 — прямой файловый обмен через EMM-страницу в W3.
|
# P4 — прямой файловый обмен через EMM-страницу в W3.
|
||||||
|
|
||||||
PROJ_ROOT := $(abspath $(CURDIR)/../../../..)
|
|
||||||
EXAMPLE := p4bio
|
EXAMPLE := p4bio
|
||||||
MEMORY := big
|
MEMORY := big
|
||||||
ALLOCS ?= 3000
|
ALLOCS ?= 3000
|
||||||
EXTRA_FLAGS := --safe --max-allocs $(ALLOCS)
|
EXTRA_FLAGS := --safe --max-allocs $(ALLOCS)
|
||||||
|
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/../../sdk.mk
|
||||||
|
|
||||||
$(EXE): Makefile
|
$(EXE): Makefile
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
# Дочерняя программа для интеграционного EXEC-теста Commander.
|
# Дочерняя программа для интеграционного EXEC-теста Commander.
|
||||||
|
|
||||||
PROJ_ROOT := $(abspath $(CURDIR)/../../../..)
|
|
||||||
EXAMPLE := p4child
|
EXAMPLE := p4child
|
||||||
MEMORY := tiny
|
MEMORY := tiny
|
||||||
ALLOCS ?= 3000
|
ALLOCS ?= 3000
|
||||||
EXTRA_FLAGS := --safe --max-allocs $(ALLOCS)
|
EXTRA_FLAGS := --safe --max-allocs $(ALLOCS)
|
||||||
|
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/../../sdk.mk
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Запускает MAME с локальным тестовым CHD как hard2 (D:) и заданным Lua.
|
# Запускает MAME с локальным тестовым CHD как hard2 (D:) и заданным Lua.
|
||||||
# Скрипт не пересобирает образ и не чистит каталог кадров.
|
# Состояние эмулятора изолировано; каталог кадров задаёт вызывающий.
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
@@ -10,36 +10,19 @@ if [ "$#" -lt 3 ] || [ "$#" -gt 4 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
PROJECT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
MAME_DIR="$PROJECT_DIR/../../mame/v306"
|
if [ -z "${SPRINTER_ROOT:-}" ] && [ -f "$PROJECT_DIR/../../bin/sprinter-cc" ]; then
|
||||||
|
SPRINTER_ROOT="$PROJECT_DIR/../.."
|
||||||
|
fi
|
||||||
|
if [ -z "${MAME_HOME:-}" ] && [ -f "$PROJECT_DIR/../../mame/v306/mame.arm" ]; then
|
||||||
|
MAME_HOME="$PROJECT_DIR/../../mame/v306"
|
||||||
|
fi
|
||||||
|
: "${SPRINTER_ROOT:?задайте SPRINTER_ROOT для запуска теста}"
|
||||||
|
export MAME_HOME
|
||||||
IMAGE=$(CDPATH= cd -- "$(dirname -- "$1")" && pwd)/$(basename -- "$1")
|
IMAGE=$(CDPATH= cd -- "$(dirname -- "$1")" && pwd)/$(basename -- "$1")
|
||||||
LUA=$(CDPATH= cd -- "$(dirname -- "$2")" && pwd)/$(basename -- "$2")
|
LUA=$(CDPATH= cd -- "$(dirname -- "$2")" && pwd)/$(basename -- "$2")
|
||||||
SNAPSHOT_DIR=$(mkdir -p "$3" && CDPATH= cd -- "$3" && pwd)
|
SNAPSHOT_DIR=$(mkdir -p "$3" && CDPATH= cd -- "$3" && pwd)
|
||||||
TIMEOUT=${4:-75}
|
TIMEOUT=${4:-75}
|
||||||
|
|
||||||
if pgrep -fl 'mame[^ ]* sprinter( |$)' | grep . >/dev/null 2>&1; then
|
exec python3 "$SPRINTER_ROOT/toolchain/run_sprinter_mame.py" \
|
||||||
echo "run_mame_hdd.sh: уже запущен MAME sprinter" >&2
|
--hdd "$IMAGE" --script "$LUA" --snapshot-dir "$SNAPSHOT_DIR" \
|
||||||
exit 1
|
--timeout "$TIMEOUT" --video opengl
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$MAME_DIR"
|
|
||||||
# В macOS GNU timeout может отсутствовать; Python обеспечивает тот же лимит.
|
|
||||||
if command -v timeout >/dev/null 2>&1; then
|
|
||||||
set -- timeout "$TIMEOUT"
|
|
||||||
else
|
|
||||||
set -- python3 -c '
|
|
||||||
import subprocess, sys
|
|
||||||
try:
|
|
||||||
sys.exit(subprocess.run(sys.argv[2:], timeout=float(sys.argv[1])).returncode)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
print("run_mame_hdd.sh: истёк лимит времени", file=sys.stderr)
|
|
||||||
sys.exit(124)
|
|
||||||
' "$TIMEOUT"
|
|
||||||
fi
|
|
||||||
exec "$@" ./mame.arm sprinter \
|
|
||||||
-skip_gameinfo -video opengl -window -nofilter \
|
|
||||||
-beta:wd179x:0 35hd -beta:wd179x:1 35hd \
|
|
||||||
-flop1 ./IMG/mc.img -flop2 ./IMG/dss171u.img \
|
|
||||||
-isa0 zxbus_adapter -isa0:zxbus_adapter:card neogs \
|
|
||||||
-hard1 ./IMG/sp_hdd_sys.chd -hard2 "$IMAGE" \
|
|
||||||
-ata2:0 cdrom -cdrom ./IMG/SprinterCD.iso -bios v3.06 \
|
|
||||||
-snapshot_directory "$SNAPSHOT_DIR" -autoboot_script "$LUA"
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Разделение Sprinter-CC, MAME и приложений
|
# Разделение Sprinter-CC, MAME и приложений
|
||||||
|
|
||||||
Статус: план, без переноса файлов. Дата: 2026-09-15.
|
Статус: реализация. Контракт SDK и локальных образов готов; выделение Git
|
||||||
|
репозиториев и физический перенос выполняются. Дата: 2026-09-15.
|
||||||
|
|
||||||
## Цель и границы
|
## Цель и границы
|
||||||
|
|
||||||
@@ -25,8 +26,9 @@ MAME приложению не нужны.
|
|||||||
└── VSCode-Sprinter/ # Git-репозиторий расширения VS Code
|
└── VSCode-Sprinter/ # Git-репозиторий расширения VS Code
|
||||||
```
|
```
|
||||||
|
|
||||||
Имена `C-Compiler`, `Volkov` и `PoP-Archive` здесь рабочие; адреса Git remote
|
Имена `C-Compiler`, `Volkov` и `PoP-Archive` пока локальные; адреса Git remote
|
||||||
новых репозиториев и окончательные имена фиксируются до миграции. Папка
|
новых репозиториев будут добавлены после создания доступных для записи URL.
|
||||||
|
Отсутствие remote не мешает сохранить независимую локальную историю. Папка
|
||||||
`Applications/` не становится общим репозиторием: каждый продукт имеет свою
|
`Applications/` не становится общим репозиторием: каждый продукт имеет свою
|
||||||
историю, релизы, игнорируемые ресурсы и тесты. Физическое расположение рядом
|
историю, релизы, игнорируемые ресурсы и тесты. Физическое расположение рядом
|
||||||
удобно, но не входит в контракт сборки.
|
удобно, но не входит в контракт сборки.
|
||||||
@@ -167,7 +169,7 @@ MAME документируются вместе; extension не копируе
|
|||||||
| Корневые `Makefile` и `app.mk` собирают examples/MAME и пишут общую floppy/HDD под `mame/v306` | В SDK оставить `make`/`size-check`/собственные тесты; упаковщик принимает выходной путь и MAME-профиль. Примеры собираются в Examples, MAME — в MAME. Удаление корневых целей без замены лишило бы пользователя привычного run: документировать новые команды и при необходимости оставить переходные targets с явной подсказкой. |
|
| Корневые `Makefile` и `app.mk` собирают examples/MAME и пишут общую floppy/HDD под `mame/v306` | В SDK оставить `make`/`size-check`/собственные тесты; упаковщик принимает выходной путь и MAME-профиль. Примеры собираются в Examples, MAME — в MAME. Удаление корневых целей без замены лишило бы пользователя привычного run: документировать новые команды и при необходимости оставить переходные targets с явной подсказкой. |
|
||||||
| `make host-tests` SDK запускает `applications/SprPoP/tests/host` | Тесты игры вызываются из SprPoP по `SPRINTER_ROOT`; SDK тестирует только SDK. При этом общий `testkit` остаётся SDK. |
|
| `make host-tests` SDK запускает `applications/SprPoP/tests/host` | Тесты игры вызываются из SprPoP по `SPRINTER_ROOT`; SDK тестирует только SDK. При этом общий `testkit` остаётся SDK. |
|
||||||
| SprPoP `mame-link` меняет `MAME_HOME/IMG/test_hdd.chd` | `make run` передаёт `build/hdd/sprpop.chd` как `-hard2`; состояние установки MAME не меняется. |
|
| SprPoP `mame-link` меняет `MAME_HOME/IMG/test_hdd.chd` | `make run` передаёт `build/hdd/sprpop.chd` как `-hard2`; состояние установки MAME не меняется. |
|
||||||
| Volkov `hdd-p5-viewer` собирает `examples/mdview2` | Перенести внутрь Volkov небольшой viewer fixture либо зафиксированный тестовый EXE/исходник с лицензией. Предпочтение — локальный fixture, потому что тест проверяет запуск дочерней программы, а не функциональность mdview2. Проверить, какие свойства реального viewer обязательны, перед заменой. |
|
| Volkov `hdd-p5-viewer` собирает `examples/mdview2` | Сохранить зафиксированный исходный viewer fixture внутри Volkov с лицензией и исходным revision: сценарий проверяет fullscreen/raw/PgDn/EMM и восстановление Commander после реального EXEC; упрощённая заглушка не покрыла бы эти свойства. Локальный fixture уже добавлен. |
|
||||||
| Volkov `tests/run_mame_hdd.sh` и другие сценарии считают MAME `../../mame/v306` | Принять общие переменные MAME и локальный образ, не менять сценарные Lua-файлы без необходимости. |
|
| Volkov `tests/run_mame_hdd.sh` и другие сценарии считают MAME `../../mame/v306` | Принять общие переменные MAME и локальный образ, не менять сценарные Lua-файлы без необходимости. |
|
||||||
| PoP `roomtest`, `bgtest`, `poc` ищут собственные `toolchain/` и `SDLPoP` через корень SDK | Ввести `POP_ROOT`/`APP_ROOT` внутри PoP и локальные пути к референсам; общие SDK-инструменты оставить по `SPRINTER_ROOT`. Не переносить чужие источники и оригинальные игровые данные в Git. |
|
| PoP `roomtest`, `bgtest`, `poc` ищут собственные `toolchain/` и `SDLPoP` через корень SDK | Ввести `POP_ROOT`/`APP_ROOT` внутри PoP и локальные пути к референсам; общие SDK-инструменты оставить по `SPRINTER_ROOT`. Не переносить чужие источники и оригинальные игровые данные в Git. |
|
||||||
| Examples Makefiles и `rpgwalk/conv_sprites.py` рассчитаны на `../..` и тулкитский `third_party/` | Подключать SDK по `SPRINTER_ROOT`, resource fixture держать рядом с примером либо получать по зафиксированному рецепту. |
|
| Examples Makefiles и `rpgwalk/conv_sprites.py` рассчитаны на `../..` и тулкитский `third_party/` | Подключать SDK по `SPRINTER_ROOT`, resource fixture держать рядом с примером либо получать по зафиксированному рецепту. |
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
local.mk
|
||||||
|
build/
|
||||||
|
.sprinter-cc-*/
|
||||||
|
.resource-stamps/
|
||||||
|
.disk_tmp/
|
||||||
|
*.exe
|
||||||
|
*.asm
|
||||||
|
*.lst
|
||||||
|
*.lk
|
||||||
|
*.ihx
|
||||||
|
*.noi
|
||||||
|
*.sym
|
||||||
|
*.map
|
||||||
|
*.rel
|
||||||
|
*.cdb
|
||||||
|
*.mem
|
||||||
|
*.rst
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.DS_Store
|
||||||
|
._*
|
||||||
|
.vscode/
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3.12
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Sprinter C Compiler contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Примеры Sprinter-CC: каждый образ и сборка принадлежат своему примеру.
|
||||||
|
EXAMPLES := balls mdview mdview2 rpgwalk scroll space
|
||||||
|
.PHONY: all clean $(EXAMPLES)
|
||||||
|
|
||||||
|
all: $(EXAMPLES)
|
||||||
|
|
||||||
|
$(EXAMPLES):
|
||||||
|
$(MAKE) -C $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@for project in $(EXAMPLES); do $(MAKE) -C $$project clean || exit 1; done
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Примеры Sprinter-CC
|
||||||
|
|
||||||
|
Один репозиторий демонстраций SDK: `balls`, `mdview`, `mdview2`,
|
||||||
|
`rpgwalk`, `scroll`, `space`. Каждая программа собирается отдельно, а её
|
||||||
|
дискета находится в `build/media/` внутри каталога примера.
|
||||||
|
|
||||||
|
Для сборки укажите путь к установленному SDK:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export SPRINTER_ROOT=/путь/к/C-Compiler
|
||||||
|
make # все примеры
|
||||||
|
make -C mdview2 # один пример
|
||||||
|
make -C mdview2 floppy
|
||||||
|
```
|
||||||
|
|
||||||
|
Для запуска дополнительно укажите `MAME_HOME` — каталог подготовленной
|
||||||
|
среды MAME с `mame.arm`, `roms/` и `IMG/`. `MAME_BIN`, `MAME_ROMPATH`,
|
||||||
|
`MAME_DSS_IMAGE`, `MAME_SYSTEM_HDD_IMAGE`, `MAME_BIOS` переопределяют
|
||||||
|
отдельные пути/вариант. Персональные значения можно хранить в игнорируемом
|
||||||
|
`local.mk`; после перемещения репозитория соседство с SDK не требуется.
|
||||||
|
Исходник RPG-спрайта хранится в `third_party/16x16-RPG-characters` вместе
|
||||||
|
с лицензией и нужен только `rpgwalk`.
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
|
||||||
EXAMPLE := balls
|
EXAMPLE := balls
|
||||||
EXTRA_FLAGS ?= --gfx 256
|
EXTRA_FLAGS ?= --gfx 256
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/../sdk.mk
|
||||||
|
|||||||
@@ -3,11 +3,10 @@
|
|||||||
# small memory mode: code in W1, data/stack/heap in W2 (32 KB total).
|
# small memory mode: code in W1, data/stack/heap in W2 (32 KB total).
|
||||||
# W3 stays free for the file buffer (EMM-mapped).
|
# W3 stays free for the file buffer (EMM-mapped).
|
||||||
|
|
||||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
|
||||||
EXAMPLE := mdview
|
EXAMPLE := mdview
|
||||||
MEMORY := small
|
MEMORY := small
|
||||||
|
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/../sdk.mk
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Образ дискеты: только mdview.exe + README.MD (перекодированный
|
# Образ дискеты: только mdview.exe + README.MD (перекодированный
|
||||||
@@ -33,10 +32,10 @@ $(README_DISK): README.MD | $(DISK_TMP)
|
|||||||
iconv -c -f UTF-8 -t CP866 README.MD > $@ || true
|
iconv -c -f UTF-8 -t CP866 README.MD > $@ || true
|
||||||
|
|
||||||
floppy: $(EXAMPLE).exe $(README_DISK)
|
floppy: $(EXAMPLE).exe $(README_DISK)
|
||||||
python3 $(MAKE_DISK) $(FLOPPY_IMG) $(EXAMPLE).exe $(README_DISK)
|
$(PYTHON) $(MAKE_DISK) "$(FLOPPY_IMG)" $(EXAMPLE).exe $(README_DISK)
|
||||||
@echo
|
@echo
|
||||||
@echo "Floppy ready: $(FLOPPY_IMG)"
|
@echo "Floppy ready: $(FLOPPY_IMG)"
|
||||||
@echo "Run: cd $(MAME_DIR) && ./run_mame.sh"
|
@echo "Run: make run MAME_HOME=/путь/к/MAME/runtime"
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf .sprinter-cc-* $(EXAMPLE).exe $(DISK_TMP)
|
rm -rf .sprinter-cc-* $(EXAMPLE).exe $(DISK_TMP)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
# small memory mode: code in W1, data/stack/heap in W2 (32 KB total).
|
# small memory mode: code in W1, data/stack/heap in W2 (32 KB total).
|
||||||
# W3 stays free for the file buffer (EMM-mapped).
|
# W3 stays free for the file buffer (EMM-mapped).
|
||||||
|
|
||||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
|
||||||
EXAMPLE := mdview2
|
EXAMPLE := mdview2
|
||||||
EXTRA_SRCS := mdview2_raw.c mdview2_hex.c mdview2_help.c mdview2_table.c mdview2_enc.c mdview2_md.c mdview2_status.c mdview2_index.c
|
EXTRA_SRCS := mdview2_raw.c mdview2_hex.c mdview2_help.c mdview2_table.c mdview2_enc.c mdview2_md.c mdview2_status.c mdview2_index.c
|
||||||
MEMORY := small
|
MEMORY := small
|
||||||
@@ -14,7 +13,7 @@ MEMORY := small
|
|||||||
# кода на всём приложении (замер 2026-07-04) ценой более долгой компиляции.
|
# кода на всём приложении (замер 2026-07-04) ценой более долгой компиляции.
|
||||||
EXTRA_FLAGS := --max-allocs 100000
|
EXTRA_FLAGS := --max-allocs 100000
|
||||||
|
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/../sdk.mk
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Образ дискеты: mdview2.exe + документация. Каждый документ — в СВОЕЙ
|
# Образ дискеты: mdview2.exe + документация. Каждый документ — в СВОЕЙ
|
||||||
@@ -46,10 +45,10 @@ $(DEMO_DISK): DEMO.MD | $(DISK_TMP)
|
|||||||
iconv -c -f UTF-8 -t CP1251 DEMO.MD > $@ || true
|
iconv -c -f UTF-8 -t CP1251 DEMO.MD > $@ || true
|
||||||
|
|
||||||
floppy: $(EXAMPLE).exe $(DEMO_DISK) $(CHLOG_DISK)
|
floppy: $(EXAMPLE).exe $(DEMO_DISK) $(CHLOG_DISK)
|
||||||
python3 $(MAKE_DISK) $(FLOPPY_IMG) $(EXAMPLE).exe README.md $(DEMO_DISK) $(CHLOG_DISK)
|
$(PYTHON) $(MAKE_DISK) "$(FLOPPY_IMG)" $(EXAMPLE).exe README.md $(DEMO_DISK) $(CHLOG_DISK)
|
||||||
@echo
|
@echo
|
||||||
@echo "Floppy ready: $(FLOPPY_IMG)"
|
@echo "Floppy ready: $(FLOPPY_IMG)"
|
||||||
@echo "Run: cd $(MAME_DIR) && ./run_mame.sh"
|
@echo "Run: make run MAME_HOME=/путь/к/MAME/runtime"
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf .sprinter-cc-* $(EXAMPLE).exe $(DISK_TMP)
|
rm -rf .sprinter-cc-* $(EXAMPLE).exe $(DISK_TMP)
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
|
||||||
EXAMPLE := rpgwalk
|
EXAMPLE := rpgwalk
|
||||||
EXTRA_FLAGS ?= --gfx 256 --memory tiny
|
EXTRA_FLAGS ?= --gfx 256 --memory tiny
|
||||||
# rpgprof.exe в EXTRA_DATA — `make floppy` кладёт на дискету ОБА exe.
|
# rpgprof.exe в EXTRA_DATA — `make floppy` кладёт на дискету ОБА exe.
|
||||||
EXTRA_DATA := bard1.atl bard2.atl bard.pal rpgprof.exe
|
EXTRA_DATA := bard1.atl bard2.atl bard.pal rpgprof.exe
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/../sdk.mk
|
||||||
|
|
||||||
# Профилировочная копия демо (бордер-полосы + маркеры для wpiset) —
|
# Профилировочная копия демо (бордер-полосы + маркеры для wpiset) —
|
||||||
# собирается вместе с rpgwalk; инструкция в шапке rpgprof.c.
|
# собирается вместе с rpgwalk; инструкция в шапке rpgprof.c.
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"""
|
"""
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
SRC = ("../../third_party/16x16-RPG-characters/sprites/"
|
SRC = ("../third_party/16x16-RPG-characters/sprites/"
|
||||||
"old-style/02-bard.png")
|
"old-style/02-bard.png")
|
||||||
BASE = 16 # цвета PNG кладём после EGA-шестнадцати
|
BASE = 16 # цвета PNG кладём после EGA-шестнадцати
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
|
||||||
EXAMPLE := scroll
|
EXAMPLE := scroll
|
||||||
EXTRA_FLAGS ?= --gfx 256
|
EXTRA_FLAGS ?= --gfx 256
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/../sdk.mk
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Общая привязка примеров к установленному Sprinter-CC.
|
||||||
|
-include $(CURDIR)/../local.mk
|
||||||
|
SPRINTER_ROOT ?= $(if $(wildcard $(CURDIR)/../../bin/sprinter-cc),$(abspath $(CURDIR)/../..),)
|
||||||
|
ifeq ($(strip $(SPRINTER_ROOT)),)
|
||||||
|
$(error Задайте SPRINTER_ROOT или создайте Examples/local.mk)
|
||||||
|
endif
|
||||||
|
PROJ_ROOT := $(SPRINTER_ROOT)
|
||||||
|
include $(PROJ_ROOT)/app.mk
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
|
||||||
EXAMPLE := space
|
EXAMPLE := space
|
||||||
EXTRA_FLAGS ?= --gfx 256 --memory tiny
|
EXTRA_FLAGS ?= --gfx 256 --memory tiny
|
||||||
EXTRA_DATA := space.atl
|
EXTRA_DATA := space.atl
|
||||||
include $(PROJ_ROOT)/app.mk
|
include $(CURDIR)/../sdk.mk
|
||||||
|
|
||||||
# Атлас: три ленты 16×16×4 (астероид/взрыв/маяк) → одна EMM-страница.
|
# Атлас: три ленты 16×16×4 (астероид/взрыв/маяк) → одна EMM-страница.
|
||||||
space.atl: gen_sprites.py $(PROJ_ROOT)/toolchain/mkatlas.py
|
space.atl: gen_sprites.py $(PROJ_ROOT)/toolchain/mkatlas.py
|
||||||
|
|||||||
+359
@@ -0,0 +1,359 @@
|
|||||||
|
Creative Commons Legal Code
|
||||||
|
|
||||||
|
Attribution-ShareAlike 3.0 Unported
|
||||||
|
|
||||||
|
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||||
|
LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
|
||||||
|
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||||
|
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||||
|
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
|
||||||
|
DAMAGES RESULTING FROM ITS USE.
|
||||||
|
|
||||||
|
License
|
||||||
|
|
||||||
|
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
|
||||||
|
COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
|
||||||
|
COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
|
||||||
|
AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
|
||||||
|
|
||||||
|
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
|
||||||
|
TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
|
||||||
|
BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
|
||||||
|
CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
|
||||||
|
CONDITIONS.
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
|
||||||
|
a. "Adaptation" means a work based upon the Work, or upon the Work and
|
||||||
|
other pre-existing works, such as a translation, adaptation,
|
||||||
|
derivative work, arrangement of music or other alterations of a
|
||||||
|
literary or artistic work, or phonogram or performance and includes
|
||||||
|
cinematographic adaptations or any other form in which the Work may be
|
||||||
|
recast, transformed, or adapted including in any form recognizably
|
||||||
|
derived from the original, except that a work that constitutes a
|
||||||
|
Collection will not be considered an Adaptation for the purpose of
|
||||||
|
this License. For the avoidance of doubt, where the Work is a musical
|
||||||
|
work, performance or phonogram, the synchronization of the Work in
|
||||||
|
timed-relation with a moving image ("synching") will be considered an
|
||||||
|
Adaptation for the purpose of this License.
|
||||||
|
b. "Collection" means a collection of literary or artistic works, such as
|
||||||
|
encyclopedias and anthologies, or performances, phonograms or
|
||||||
|
broadcasts, or other works or subject matter other than works listed
|
||||||
|
in Section 1(f) below, which, by reason of the selection and
|
||||||
|
arrangement of their contents, constitute intellectual creations, in
|
||||||
|
which the Work is included in its entirety in unmodified form along
|
||||||
|
with one or more other contributions, each constituting separate and
|
||||||
|
independent works in themselves, which together are assembled into a
|
||||||
|
collective whole. A work that constitutes a Collection will not be
|
||||||
|
considered an Adaptation (as defined below) for the purposes of this
|
||||||
|
License.
|
||||||
|
c. "Creative Commons Compatible License" means a license that is listed
|
||||||
|
at https://creativecommons.org/compatiblelicenses that has been
|
||||||
|
approved by Creative Commons as being essentially equivalent to this
|
||||||
|
License, including, at a minimum, because that license: (i) contains
|
||||||
|
terms that have the same purpose, meaning and effect as the License
|
||||||
|
Elements of this License; and, (ii) explicitly permits the relicensing
|
||||||
|
of adaptations of works made available under that license under this
|
||||||
|
License or a Creative Commons jurisdiction license with the same
|
||||||
|
License Elements as this License.
|
||||||
|
d. "Distribute" means to make available to the public the original and
|
||||||
|
copies of the Work or Adaptation, as appropriate, through sale or
|
||||||
|
other transfer of ownership.
|
||||||
|
e. "License Elements" means the following high-level license attributes
|
||||||
|
as selected by Licensor and indicated in the title of this License:
|
||||||
|
Attribution, ShareAlike.
|
||||||
|
f. "Licensor" means the individual, individuals, entity or entities that
|
||||||
|
offer(s) the Work under the terms of this License.
|
||||||
|
g. "Original Author" means, in the case of a literary or artistic work,
|
||||||
|
the individual, individuals, entity or entities who created the Work
|
||||||
|
or if no individual or entity can be identified, the publisher; and in
|
||||||
|
addition (i) in the case of a performance the actors, singers,
|
||||||
|
musicians, dancers, and other persons who act, sing, deliver, declaim,
|
||||||
|
play in, interpret or otherwise perform literary or artistic works or
|
||||||
|
expressions of folklore; (ii) in the case of a phonogram the producer
|
||||||
|
being the person or legal entity who first fixes the sounds of a
|
||||||
|
performance or other sounds; and, (iii) in the case of broadcasts, the
|
||||||
|
organization that transmits the broadcast.
|
||||||
|
h. "Work" means the literary and/or artistic work offered under the terms
|
||||||
|
of this License including without limitation any production in the
|
||||||
|
literary, scientific and artistic domain, whatever may be the mode or
|
||||||
|
form of its expression including digital form, such as a book,
|
||||||
|
pamphlet and other writing; a lecture, address, sermon or other work
|
||||||
|
of the same nature; a dramatic or dramatico-musical work; a
|
||||||
|
choreographic work or entertainment in dumb show; a musical
|
||||||
|
composition with or without words; a cinematographic work to which are
|
||||||
|
assimilated works expressed by a process analogous to cinematography;
|
||||||
|
a work of drawing, painting, architecture, sculpture, engraving or
|
||||||
|
lithography; a photographic work to which are assimilated works
|
||||||
|
expressed by a process analogous to photography; a work of applied
|
||||||
|
art; an illustration, map, plan, sketch or three-dimensional work
|
||||||
|
relative to geography, topography, architecture or science; a
|
||||||
|
performance; a broadcast; a phonogram; a compilation of data to the
|
||||||
|
extent it is protected as a copyrightable work; or a work performed by
|
||||||
|
a variety or circus performer to the extent it is not otherwise
|
||||||
|
considered a literary or artistic work.
|
||||||
|
i. "You" means an individual or entity exercising rights under this
|
||||||
|
License who has not previously violated the terms of this License with
|
||||||
|
respect to the Work, or who has received express permission from the
|
||||||
|
Licensor to exercise rights under this License despite a previous
|
||||||
|
violation.
|
||||||
|
j. "Publicly Perform" means to perform public recitations of the Work and
|
||||||
|
to communicate to the public those public recitations, by any means or
|
||||||
|
process, including by wire or wireless means or public digital
|
||||||
|
performances; to make available to the public Works in such a way that
|
||||||
|
members of the public may access these Works from a place and at a
|
||||||
|
place individually chosen by them; to perform the Work to the public
|
||||||
|
by any means or process and the communication to the public of the
|
||||||
|
performances of the Work, including by public digital performance; to
|
||||||
|
broadcast and rebroadcast the Work by any means including signs,
|
||||||
|
sounds or images.
|
||||||
|
k. "Reproduce" means to make copies of the Work by any means including
|
||||||
|
without limitation by sound or visual recordings and the right of
|
||||||
|
fixation and reproducing fixations of the Work, including storage of a
|
||||||
|
protected performance or phonogram in digital form or other electronic
|
||||||
|
medium.
|
||||||
|
|
||||||
|
2. Fair Dealing Rights. Nothing in this License is intended to reduce,
|
||||||
|
limit, or restrict any uses free from copyright or rights arising from
|
||||||
|
limitations or exceptions that are provided for in connection with the
|
||||||
|
copyright protection under copyright law or other applicable laws.
|
||||||
|
|
||||||
|
3. License Grant. Subject to the terms and conditions of this License,
|
||||||
|
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
|
||||||
|
perpetual (for the duration of the applicable copyright) license to
|
||||||
|
exercise the rights in the Work as stated below:
|
||||||
|
|
||||||
|
a. to Reproduce the Work, to incorporate the Work into one or more
|
||||||
|
Collections, and to Reproduce the Work as incorporated in the
|
||||||
|
Collections;
|
||||||
|
b. to create and Reproduce Adaptations provided that any such Adaptation,
|
||||||
|
including any translation in any medium, takes reasonable steps to
|
||||||
|
clearly label, demarcate or otherwise identify that changes were made
|
||||||
|
to the original Work. For example, a translation could be marked "The
|
||||||
|
original work was translated from English to Spanish," or a
|
||||||
|
modification could indicate "The original work has been modified.";
|
||||||
|
c. to Distribute and Publicly Perform the Work including as incorporated
|
||||||
|
in Collections; and,
|
||||||
|
d. to Distribute and Publicly Perform Adaptations.
|
||||||
|
e. For the avoidance of doubt:
|
||||||
|
|
||||||
|
i. Non-waivable Compulsory License Schemes. In those jurisdictions in
|
||||||
|
which the right to collect royalties through any statutory or
|
||||||
|
compulsory licensing scheme cannot be waived, the Licensor
|
||||||
|
reserves the exclusive right to collect such royalties for any
|
||||||
|
exercise by You of the rights granted under this License;
|
||||||
|
ii. Waivable Compulsory License Schemes. In those jurisdictions in
|
||||||
|
which the right to collect royalties through any statutory or
|
||||||
|
compulsory licensing scheme can be waived, the Licensor waives the
|
||||||
|
exclusive right to collect such royalties for any exercise by You
|
||||||
|
of the rights granted under this License; and,
|
||||||
|
iii. Voluntary License Schemes. The Licensor waives the right to
|
||||||
|
collect royalties, whether individually or, in the event that the
|
||||||
|
Licensor is a member of a collecting society that administers
|
||||||
|
voluntary licensing schemes, via that society, from any exercise
|
||||||
|
by You of the rights granted under this License.
|
||||||
|
|
||||||
|
The above rights may be exercised in all media and formats whether now
|
||||||
|
known or hereafter devised. The above rights include the right to make
|
||||||
|
such modifications as are technically necessary to exercise the rights in
|
||||||
|
other media and formats. Subject to Section 8(f), all rights not expressly
|
||||||
|
granted by Licensor are hereby reserved.
|
||||||
|
|
||||||
|
4. Restrictions. The license granted in Section 3 above is expressly made
|
||||||
|
subject to and limited by the following restrictions:
|
||||||
|
|
||||||
|
a. You may Distribute or Publicly Perform the Work only under the terms
|
||||||
|
of this License. You must include a copy of, or the Uniform Resource
|
||||||
|
Identifier (URI) for, this License with every copy of the Work You
|
||||||
|
Distribute or Publicly Perform. You may not offer or impose any terms
|
||||||
|
on the Work that restrict the terms of this License or the ability of
|
||||||
|
the recipient of the Work to exercise the rights granted to that
|
||||||
|
recipient under the terms of the License. You may not sublicense the
|
||||||
|
Work. You must keep intact all notices that refer to this License and
|
||||||
|
to the disclaimer of warranties with every copy of the Work You
|
||||||
|
Distribute or Publicly Perform. When You Distribute or Publicly
|
||||||
|
Perform the Work, You may not impose any effective technological
|
||||||
|
measures on the Work that restrict the ability of a recipient of the
|
||||||
|
Work from You to exercise the rights granted to that recipient under
|
||||||
|
the terms of the License. This Section 4(a) applies to the Work as
|
||||||
|
incorporated in a Collection, but this does not require the Collection
|
||||||
|
apart from the Work itself to be made subject to the terms of this
|
||||||
|
License. If You create a Collection, upon notice from any Licensor You
|
||||||
|
must, to the extent practicable, remove from the Collection any credit
|
||||||
|
as required by Section 4(c), as requested. If You create an
|
||||||
|
Adaptation, upon notice from any Licensor You must, to the extent
|
||||||
|
practicable, remove from the Adaptation any credit as required by
|
||||||
|
Section 4(c), as requested.
|
||||||
|
b. You may Distribute or Publicly Perform an Adaptation only under the
|
||||||
|
terms of: (i) this License; (ii) a later version of this License with
|
||||||
|
the same License Elements as this License; (iii) a Creative Commons
|
||||||
|
jurisdiction license (either this or a later license version) that
|
||||||
|
contains the same License Elements as this License (e.g.,
|
||||||
|
Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible
|
||||||
|
License. If you license the Adaptation under one of the licenses
|
||||||
|
mentioned in (iv), you must comply with the terms of that license. If
|
||||||
|
you license the Adaptation under the terms of any of the licenses
|
||||||
|
mentioned in (i), (ii) or (iii) (the "Applicable License"), you must
|
||||||
|
comply with the terms of the Applicable License generally and the
|
||||||
|
following provisions: (I) You must include a copy of, or the URI for,
|
||||||
|
the Applicable License with every copy of each Adaptation You
|
||||||
|
Distribute or Publicly Perform; (II) You may not offer or impose any
|
||||||
|
terms on the Adaptation that restrict the terms of the Applicable
|
||||||
|
License or the ability of the recipient of the Adaptation to exercise
|
||||||
|
the rights granted to that recipient under the terms of the Applicable
|
||||||
|
License; (III) You must keep intact all notices that refer to the
|
||||||
|
Applicable License and to the disclaimer of warranties with every copy
|
||||||
|
of the Work as included in the Adaptation You Distribute or Publicly
|
||||||
|
Perform; (IV) when You Distribute or Publicly Perform the Adaptation,
|
||||||
|
You may not impose any effective technological measures on the
|
||||||
|
Adaptation that restrict the ability of a recipient of the Adaptation
|
||||||
|
from You to exercise the rights granted to that recipient under the
|
||||||
|
terms of the Applicable License. This Section 4(b) applies to the
|
||||||
|
Adaptation as incorporated in a Collection, but this does not require
|
||||||
|
the Collection apart from the Adaptation itself to be made subject to
|
||||||
|
the terms of the Applicable License.
|
||||||
|
c. If You Distribute, or Publicly Perform the Work or any Adaptations or
|
||||||
|
Collections, You must, unless a request has been made pursuant to
|
||||||
|
Section 4(a), keep intact all copyright notices for the Work and
|
||||||
|
provide, reasonable to the medium or means You are utilizing: (i) the
|
||||||
|
name of the Original Author (or pseudonym, if applicable) if supplied,
|
||||||
|
and/or if the Original Author and/or Licensor designate another party
|
||||||
|
or parties (e.g., a sponsor institute, publishing entity, journal) for
|
||||||
|
attribution ("Attribution Parties") in Licensor's copyright notice,
|
||||||
|
terms of service or by other reasonable means, the name of such party
|
||||||
|
or parties; (ii) the title of the Work if supplied; (iii) to the
|
||||||
|
extent reasonably practicable, the URI, if any, that Licensor
|
||||||
|
specifies to be associated with the Work, unless such URI does not
|
||||||
|
refer to the copyright notice or licensing information for the Work;
|
||||||
|
and (iv) , consistent with Ssection 3(b), in the case of an
|
||||||
|
Adaptation, a credit identifying the use of the Work in the Adaptation
|
||||||
|
(e.g., "French translation of the Work by Original Author," or
|
||||||
|
"Screenplay based on original Work by Original Author"). The credit
|
||||||
|
required by this Section 4(c) may be implemented in any reasonable
|
||||||
|
manner; provided, however, that in the case of a Adaptation or
|
||||||
|
Collection, at a minimum such credit will appear, if a credit for all
|
||||||
|
contributing authors of the Adaptation or Collection appears, then as
|
||||||
|
part of these credits and in a manner at least as prominent as the
|
||||||
|
credits for the other contributing authors. For the avoidance of
|
||||||
|
doubt, You may only use the credit required by this Section for the
|
||||||
|
purpose of attribution in the manner set out above and, by exercising
|
||||||
|
Your rights under this License, You may not implicitly or explicitly
|
||||||
|
assert or imply any connection with, sponsorship or endorsement by the
|
||||||
|
Original Author, Licensor and/or Attribution Parties, as appropriate,
|
||||||
|
of You or Your use of the Work, without the separate, express prior
|
||||||
|
written permission of the Original Author, Licensor and/or Attribution
|
||||||
|
Parties.
|
||||||
|
d. Except as otherwise agreed in writing by the Licensor or as may be
|
||||||
|
otherwise permitted by applicable law, if You Reproduce, Distribute or
|
||||||
|
Publicly Perform the Work either by itself or as part of any
|
||||||
|
Adaptations or Collections, You must not distort, mutilate, modify or
|
||||||
|
take other derogatory action in relation to the Work which would be
|
||||||
|
prejudicial to the Original Author's honor or reputation. Licensor
|
||||||
|
agrees that in those jurisdictions (e.g. Japan), in which any exercise
|
||||||
|
of the right granted in Section 3(b) of this License (the right to
|
||||||
|
make Adaptations) would be deemed to be a distortion, mutilation,
|
||||||
|
modification or other derogatory action prejudicial to the Original
|
||||||
|
Author's honor and reputation, the Licensor will waive or not assert,
|
||||||
|
as appropriate, this Section, to the fullest extent permitted by the
|
||||||
|
applicable national law, to enable You to reasonably exercise Your
|
||||||
|
right under Section 3(b) of this License (right to make Adaptations)
|
||||||
|
but not otherwise.
|
||||||
|
|
||||||
|
5. Representations, Warranties and Disclaimer
|
||||||
|
|
||||||
|
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR
|
||||||
|
OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
|
||||||
|
KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
|
||||||
|
INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
|
||||||
|
LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
|
||||||
|
WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
|
||||||
|
OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
|
||||||
|
|
||||||
|
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
|
||||||
|
LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
|
||||||
|
ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
|
||||||
|
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
|
||||||
|
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
7. Termination
|
||||||
|
|
||||||
|
a. This License and the rights granted hereunder will terminate
|
||||||
|
automatically upon any breach by You of the terms of this License.
|
||||||
|
Individuals or entities who have received Adaptations or Collections
|
||||||
|
from You under this License, however, will not have their licenses
|
||||||
|
terminated provided such individuals or entities remain in full
|
||||||
|
compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
|
||||||
|
survive any termination of this License.
|
||||||
|
b. Subject to the above terms and conditions, the license granted here is
|
||||||
|
perpetual (for the duration of the applicable copyright in the Work).
|
||||||
|
Notwithstanding the above, Licensor reserves the right to release the
|
||||||
|
Work under different license terms or to stop distributing the Work at
|
||||||
|
any time; provided, however that any such election will not serve to
|
||||||
|
withdraw this License (or any other license that has been, or is
|
||||||
|
required to be, granted under the terms of this License), and this
|
||||||
|
License will continue in full force and effect unless terminated as
|
||||||
|
stated above.
|
||||||
|
|
||||||
|
8. Miscellaneous
|
||||||
|
|
||||||
|
a. Each time You Distribute or Publicly Perform the Work or a Collection,
|
||||||
|
the Licensor offers to the recipient a license to the Work on the same
|
||||||
|
terms and conditions as the license granted to You under this License.
|
||||||
|
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
|
||||||
|
offers to the recipient a license to the original Work on the same
|
||||||
|
terms and conditions as the license granted to You under this License.
|
||||||
|
c. If any provision of this License is invalid or unenforceable under
|
||||||
|
applicable law, it shall not affect the validity or enforceability of
|
||||||
|
the remainder of the terms of this License, and without further action
|
||||||
|
by the parties to this agreement, such provision shall be reformed to
|
||||||
|
the minimum extent necessary to make such provision valid and
|
||||||
|
enforceable.
|
||||||
|
d. No term or provision of this License shall be deemed waived and no
|
||||||
|
breach consented to unless such waiver or consent shall be in writing
|
||||||
|
and signed by the party to be charged with such waiver or consent.
|
||||||
|
e. This License constitutes the entire agreement between the parties with
|
||||||
|
respect to the Work licensed here. There are no understandings,
|
||||||
|
agreements or representations with respect to the Work not specified
|
||||||
|
here. Licensor shall not be bound by any additional provisions that
|
||||||
|
may appear in any communication from You. This License may not be
|
||||||
|
modified without the mutual written agreement of the Licensor and You.
|
||||||
|
f. The rights granted under, and the subject matter referenced, in this
|
||||||
|
License were drafted utilizing the terminology of the Berne Convention
|
||||||
|
for the Protection of Literary and Artistic Works (as amended on
|
||||||
|
September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
|
||||||
|
Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
|
||||||
|
and the Universal Copyright Convention (as revised on July 24, 1971).
|
||||||
|
These rights and subject matter take effect in the relevant
|
||||||
|
jurisdiction in which the License terms are sought to be enforced
|
||||||
|
according to the corresponding provisions of the implementation of
|
||||||
|
those treaty provisions in the applicable national law. If the
|
||||||
|
standard suite of rights granted under applicable copyright law
|
||||||
|
includes additional rights not granted under this License, such
|
||||||
|
additional rights are deemed to be included in the License; this
|
||||||
|
License is not intended to restrict the license of any rights under
|
||||||
|
applicable law.
|
||||||
|
|
||||||
|
|
||||||
|
Creative Commons Notice
|
||||||
|
|
||||||
|
Creative Commons is not a party to this License, and makes no warranty
|
||||||
|
whatsoever in connection with the Work. Creative Commons will not be
|
||||||
|
liable to You or any party on any legal theory for any damages
|
||||||
|
whatsoever, including without limitation any general, special,
|
||||||
|
incidental or consequential damages arising in connection to this
|
||||||
|
license. Notwithstanding the foregoing two (2) sentences, if Creative
|
||||||
|
Commons has expressly identified itself as the Licensor hereunder, it
|
||||||
|
shall have all rights and obligations of Licensor.
|
||||||
|
|
||||||
|
Except for the limited purpose of indicating to the public that the
|
||||||
|
Work is licensed under the CCPL, Creative Commons does not authorize
|
||||||
|
the use by either party of the trademark "Creative Commons" or any
|
||||||
|
related trademark or logo of Creative Commons without the prior
|
||||||
|
written consent of Creative Commons. Any permitted use will be in
|
||||||
|
compliance with Creative Commons' then-current trademark usage
|
||||||
|
guidelines, as may be published on its website or otherwise made
|
||||||
|
available upon request from time to time. For the avoidance of doubt,
|
||||||
|
this trademark restriction does not form part of the License.
|
||||||
|
|
||||||
|
Creative Commons may be contacted at https://creativecommons.org/.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# 16x16 RPG characters `v3.0`
|
||||||
|
|
||||||
|
16x16px RPG character sprite sheet for up to down games.
|
||||||
|
|
||||||
|
This asset pack has been downloaded from https://route1rodent.itch.io/16x16-rpg-character-sprite-sheet
|
||||||
|
|
||||||
|
Adapted from opengameart.org 's "[NES-Style RPG Characters](https://opengameart.org/content/nes-style-rpg-characters)" and "[More NES-style RPG Characters](https://opengameart.org/content/more-nes-style-rpg-characters)", with additional content made by [@route1rodent](https://route1rodent.itch.io).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
16x16 RPG character sprite sheet (c) by @route1rodent
|
||||||
|
|
||||||
|
"16x16 RPG character sprite sheet" is licensed under a
|
||||||
|
Creative Commons Attribution-ShareAlike 3.0 Unported License (CC BY-SA 3.0).
|
||||||
|
|
||||||
|
You should have received a copy of the license along with this
|
||||||
|
work. If not, see http://creativecommons.org/licenses/by-sa/3.0/.
|
||||||
|
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
Maintained by @route1rodent:
|
||||||
|
|
||||||
|
- itch.io: https://route1rodent.itch.io
|
||||||
|
- Twitter: https://twitter.com/route1rodent
|
||||||
|
- Github: https://github.com/itsjavi
|
||||||
|
- Blog: https://blog.itsjavi.com
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user