diff --git a/app.mk b/app.mk index b616158..bf0fe77 100644 --- a/app.mk +++ b/app.mk @@ -40,7 +40,7 @@ FLOPPY_IMG := $(MAME_DIR)/IMG/mc.img # образ у себя и связывает его с MAME символьной ссылкой. HDD_IMG ?= $(MAME_DIR)/IMG/test_hdd.chd MAKE_DISK := $(MAME_DIR)/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 # Optional knobs — see top of file. diff --git a/applications/SprPoP/Makefile b/applications/SprPoP/Makefile index 46d0f70..86ccacc 100644 --- a/applications/SprPoP/Makefile +++ b/applications/SprPoP/Makefile @@ -34,6 +34,8 @@ EXAMPLE := sprpop SRC_DIR := src BUILD_DIR := build +MAKE_HDD := tools/make_hdd.sh + # huge: small-раскладка (CODE в W1, DATA в W2) + банки кода в W3. MEMORY ?= huge # --max-allocs-per-node: во сколько вариантов размещения регистров упирается @@ -91,7 +93,7 @@ BANK9_SRCS := pop_menu.c pop_status.c pop_music.c pop_app.c pop_timer.c \ # стоит 51 мс, так что трамплин банк→банк на их фоне не существует. # Отдельный банк для этого не нужен: в десятом лежал один pop_pal.c на # 485 байт, то есть 3% страницы. -BANK10_SRCS := pop_pal.c pop_config.c pop_hof.c +BANK10_SRCS := pop_pal.c pop_config.c pop_hof.c pop_path_bank.c BANK11_SRCS := pop_cutscene.c pop_intro.c BANK_NUMS := 1 2 3 4 5 6 7 8 9 10 11 BANKED_SRCS := $(foreach n,$(BANK_NUMS),$(BANK$(n)_SRCS)) diff --git a/applications/SprPoP/src/_pop_file.h b/applications/SprPoP/src/_pop_file.h new file mode 100644 index 0000000..6c6485e --- /dev/null +++ b/applications/SprPoP/src/_pop_file.h @@ -0,0 +1,33 @@ +/* + * _pop_file.h — внутренний файловый слой SprPoP для старых версий DSS. + * + * DSS до исправления относительных путей оставляет рабочий каталог внутри + * последнего подкаталога. POP_PATH_CALL восстанавливает абсолютный каталог + * приложения до и после операции. Это макрос намеренно: код исполняется в + * банке вызывающего, где доступен переданный указатель, и не расходует W1. + */ +#ifndef _POP_FILE_H +#define _POP_FILE_H + +#include +#include + +/* BANK10: получить каталог EXE через APPINFO B=1 и сделать его текущим. */ +int8_t pop_path_init(void) __banked; +int8_t pop_chdir_home(void) __banked; + +/* expression вычисляется только после успешного chdir. errno файловой + * операции сохраняется через завершающее восстановление каталога. */ +#define POP_PATH_CALL(result, expression) do { \ + if (pop_chdir_home() != 0) { \ + (result) = -1; \ + } else { \ + int _pop_path_errno; \ + (result) = (expression); \ + _pop_path_errno = errno; \ + (void)pop_chdir_home(); \ + errno = _pop_path_errno; \ + } \ +} while (0) + +#endif diff --git a/applications/SprPoP/src/pop_arc.c b/applications/SprPoP/src/pop_arc.c index 12ad845..1d40f06 100644 --- a/applications/SprPoP/src/pop_arc.c +++ b/applications/SprPoP/src/pop_arc.c @@ -12,6 +12,7 @@ #include #include #include "pop_arc.h" +#include "_pop_file.h" /* Пути 8.3, как требует DSS. Держим их ЗДЕСЬ: строка обязана лежать в том * же банке, что и код, который её читает (см. pop_arc.h). */ @@ -42,7 +43,7 @@ int8_t pop_arc_open(pop_arc_t *a, const char *path, a->fd = -1; a->count = 0; - fd = open(path, O_RDONLY); + POP_PATH_CALL(fd, open(path, O_RDONLY)); if (fd < 0) return -1; if (read(fd, head, sizeof(head)) != (int)sizeof(head) || memcmp(head, "PBA1", 4) != 0 || diff --git a/applications/SprPoP/src/pop_config.c b/applications/SprPoP/src/pop_config.c index 8f23a1c..4c7dd56 100644 --- a/applications/SprPoP/src/pop_config.c +++ b/applications/SprPoP/src/pop_config.c @@ -29,6 +29,7 @@ #include "pop_cheat.h" #include "pop_sfx.h" #include "_pop_config.h" +#include "_pop_file.h" #define CFG_BASE 0x100 @@ -151,7 +152,7 @@ static uint8_t cfg_read_file(uint8_t *buf, uint16_t *size) int fd, got; long n; - fd = open("POP.CFG", O_RDONLY); + POP_PATH_CALL(fd, open("POP.CFG", O_RDONLY)); if (fd < 0) return 0; n = lseek(fd, 0, SEEK_END); if (n < POP_CFG_MIN_SIZE || n > POP_CFG_MAX_SIZE || @@ -172,7 +173,7 @@ int8_t pop_cfg_load(void) __banked if (!cfg_read_file(buf, &size) || !pop_cfg_decode(buf, size)) return 0; if (buf[9] != POP_PROFILE_VANILLA) - printf("POP.CFG: ENHANCED profile -> VANILLA\n"); + puts("POP.CFG: ENHANCED profile -> VANILLA\n"); return 1; } @@ -192,7 +193,7 @@ int8_t pop_cfg_save(void) __banked gfx_w0_unmap(); /* Не unlink: bank_save_file сам делает O_TRUNC и не создаёт лишнего * окна, когда POP.CFG вообще отсутствует после ошибки unlink. */ - rc = bank_save_file(page, CFG_BASE, "POP.CFG", sizeof(buf)); + POP_PATH_CALL(rc, bank_save_file(page, CFG_BASE, "POP.CFG", sizeof(buf))); mem_free_block(blk); return (rc == sizeof(buf)) ? 1 : 0; } diff --git a/applications/SprPoP/src/pop_hof.c b/applications/SprPoP/src/pop_hof.c index e1142d4..a67613f 100644 --- a/applications/SprPoP/src/pop_hof.c +++ b/applications/SprPoP/src/pop_hof.c @@ -31,6 +31,7 @@ #include "pop_title.h" /* титульная картинка после ввода имени */ #include "pop_music.h" /* подкачка потоковой темы победы */ #include "pop_hof.h" +#include "_pop_file.h" /* MAX_HOF_COUNT оригинала — шесть строк. */ #define HOF_COUNT 6 @@ -162,7 +163,7 @@ static void hof_load(void) if (!blk) return; page = mem_get_page(blk, 0); gfx_w0_page_prepare(page); - got = bank_load_file(page, HOF_BASE, "POP.HOF", HOF_SIZE); + POP_PATH_CALL(got, bank_load_file(page, HOF_BASE, "POP.HOF", HOF_SIZE)); if (got == HOF_SIZE) { gfx_w0_map(page); for (i = 0; i < HOF_SIZE; i++) @@ -187,7 +188,11 @@ static void hof_save(void) gfx_w0_unmap(); /* Неудача записи не меняет только что показанную таблицу: следующее * открытие разумно начнёт с прежнего файла или с пустого списка. */ - bank_save_file(page, HOF_BASE, "POP.HOF", HOF_SIZE); + { + int saved; + POP_PATH_CALL(saved, + bank_save_file(page, HOF_BASE, "POP.HOF", HOF_SIZE)); + } mem_free_block(blk); } diff --git a/applications/SprPoP/src/pop_kboot.c b/applications/SprPoP/src/pop_kboot.c index 9405989..b51fbaf 100644 --- a/applications/SprPoP/src/pop_kboot.c +++ b/applications/SprPoP/src/pop_kboot.c @@ -18,6 +18,7 @@ #include "pop_guard.h" /* pop_guard_vflip_load — зеркала соперника */ #include "pop_arc.h" #include "kid_arc.h" +#include "_pop_file.h" /* Серии в архиве обязаны лежать подряд — так их кладёт список в Makefile, * и только поэтому индекс считается сложением. Если порядок собьётся, @@ -75,7 +76,8 @@ int pop_kid_data_load(const char *path) __banked blk=mem_alloc_pages(1); if (!blk) return -1; page=mem_get_page(blk,0); - n=bank_load_file(page,KD_DATA_OFF,path,16384-KD_DATA_OFF); + POP_PATH_CALL(n, bank_load_file(page,KD_DATA_OFF,path, + 16384-KD_DATA_OFF)); if (n < (int)(KID_BIN_SEQTBL_OFF + KID_SEQTBL_LEN)) { mem_free_block(blk); return -1; } diff --git a/applications/SprPoP/src/pop_level.h b/applications/SprPoP/src/pop_level.h index 5c359c9..1031c0e 100644 --- a/applications/SprPoP/src/pop_level.h +++ b/applications/SprPoP/src/pop_level.h @@ -45,7 +45,7 @@ extern uint8_t pop_current_level; void pop_level_qsave(pop_qs_io_t *io); /* Загрузить уровень НОМЕРОМ: освободить страницу прошлого, взять - * `LEVELS\res20NN.bin` (fallback `a:\res20NN.bin`) и выставить + * `LEVELS\res20NN.bin` относительно каталога EXE и выставить * pop_current_level. 0 — OK, -1 — файла нет / не влез (тогда * pop_current_level и страница НЕ тронуты — играем дальше на старом). * Порт связки load_lev_spr + load_level (seg000:1098/1169). */ diff --git a/applications/SprPoP/src/pop_level_cold.c b/applications/SprPoP/src/pop_level_cold.c index 5ba3b67..0ecfb4a 100644 --- a/applications/SprPoP/src/pop_level_cold.c +++ b/applications/SprPoP/src/pop_level_cold.c @@ -17,6 +17,7 @@ #include "pop_level.h" #include "_pop_level.h" #include "pop_geom.h" /* pop_x_bump / FIRST_ONSCREEN_COLUMN — pos_guards */ +#include "_pop_file.h" /* Низкоуровневое чтение через W3 выполняет резидентный bank_load_file(); * остальная одноразовая подготовка уровня безопасно остаётся в банке 8. */ @@ -58,7 +59,8 @@ static int level_read_file(const char *path) blk=mem_alloc_pages(1); if (!blk) return -1; page=mem_get_page(blk,0); - n=bank_load_file(page,LVL_DATA_OFF,path,16384-LVL_DATA_OFF); + POP_PATH_CALL(n, bank_load_file(page,LVL_DATA_OFF,path, + 16384-LVL_DATA_OFF)); if (n < (int)MIN_SIZE) { mem_free_block(blk); return -1; } gfx_w0_page_prepare(page); gfx_w0_map(page); @@ -80,18 +82,17 @@ int pop_level_load_num(uint8_t n) __banked /* Имена патчим на месте, а не через sprintf: printf-семейство тянет в * резидент W1 форматтер, которого здесь больше негде применить. */ static char pri[] = "LEVELS\\res2000.bin"; /* цифры — [12],[13] */ - static char alt[] = "a:\\res2000.bin"; /* цифры — [8],[9] */ uint8_t saved_blk = pop_lvl_blk, saved_page = pop_lvl_page, saved_ok = pop_lvl_ok; if (n > POP_LEVEL_LAST) return -1; - pri[12] = alt[8] = (char)('0' + n / 10); - pri[13] = alt[9] = (char)('0' + n % 10); + pri[12] = (char)('0' + n / 10); + pri[13] = (char)('0' + n % 10); /* Старую страницу отпускаем ТОЛЬКО после успешной загрузки новой: * level_read_file сам аллоцирует страницу, и если файла нет — остаёмся * на текущем уровне вместо падения в пустой уровень. */ pop_lvl_ok = 0; /* чтобы level_load_path не мешался */ - if (level_read_file(pri) != 0 && level_read_file(alt) != 0) { + if (level_read_file(pri) != 0) { pop_lvl_blk = saved_blk; pop_lvl_page = saved_page; pop_lvl_ok = saved_ok; return -1; } diff --git a/applications/SprPoP/src/pop_music.c b/applications/SprPoP/src/pop_music.c index eb8747c..40c08f4 100644 --- a/applications/SprPoP/src/pop_music.c +++ b/applications/SprPoP/src/pop_music.c @@ -17,6 +17,7 @@ #include "pop_music_tbl.h" #include "pop_sfx.h" #include "pop_app.h" /* POP_APP_DEMO — в демке музыки нет */ +#include "_pop_file.h" uint8_t pop_mus_page[POP_MUS_PAGES]; @@ -141,7 +142,7 @@ int8_t pop_music_load_begin(uint8_t id) __banked /* Трек лежит ОДНИМ файлом, и fd остаётся открытым до последней * страницы: `open` стоит дороже самого чтения (51,4 мс против 32,6 мс * на 16 КБ), и платить его на каждую страницу — чистые потери. */ - ld_fd = open(name, O_RDONLY); + POP_PATH_CALL(ld_fd, open(name, O_RDONLY)); if (ld_fd < 0) return -1; slot_blk[n] = mem_alloc_pages(pages); if (!slot_blk[n]) { close(ld_fd); return -1; } @@ -288,7 +289,7 @@ int8_t pop_music_ring_start(uint8_t id) __banked pop_music_free(); /* обычные слоты кольцу не нужны */ pop_music_ring_stop(); mus_name(name, id); - ring_fd = open(name, O_RDONLY); + POP_PATH_CALL(ring_fd, open(name, O_RDONLY)); if (ring_fd < 0) return -1; ring_fd_open = 1; ring_blk = mem_alloc_pages(RING_PAGES); diff --git a/applications/SprPoP/src/pop_pal.c b/applications/SprPoP/src/pop_pal.c index bd13d55..8f7ec8d 100644 --- a/applications/SprPoP/src/pop_pal.c +++ b/applications/SprPoP/src/pop_pal.c @@ -14,10 +14,12 @@ #include "pop_guard.h" #include "pop_pal.h" #include "pop_ui.h" /* POP_COL_OUTSIDE */ +#include "_pop_file.h" -static void pal_file_load(const char *path, const char *fallback) +static void pal_file_load(const char *path) { - if (gfx_pal_fload(0, path) < 0) gfx_pal_fload(0, fallback); + int rc; + POP_PATH_CALL(rc, gfx_pal_fload(0, path)); gfx_pal_sync(); pop_ui_palette_snapshot(); } @@ -84,7 +86,7 @@ void pop_pal_menu_dim(uint8_t fade) __banked void pop_pal_game_load(void) __banked { - pal_file_load("KID\\kid.pal", "a:\\kid.pal"); + pal_file_load("KID\\kid.pal"); /* kid.pal содержит все 256 записей и намеренно затирает эти три * динамических диапазона (тайлсет, Тень, набор соперника). * Восстановить их надо ДО snapshot. */ @@ -110,7 +112,7 @@ void pop_pal_level_load(uint8_t full) __banked void pop_pal_title_load(void) __banked { - pal_file_load("TITLE\\title.pal", "a:\\title.pal"); + pal_file_load("TITLE\\title.pal"); } /* ЕДИНСТВЕННОЕ ОТЛИЧИЕ ФИНАЛА ОТ ИНТРО — цвет фона текстовой рамки. @@ -120,7 +122,7 @@ void pop_pal_title_load(void) __banked * файла и до нового снимка — как тайлсет/Тень в pop_pal_game_load. */ void pop_pal_story_load(uint8_t ending) __banked { - pal_file_load("PV\\story.pal", "a:\\story.pal"); + pal_file_load("PV\\story.pal"); if (!ending) return; gfx_pal_set(0, POP_PAL_STORY_BG, 0x80, 0, 0); gfx_pal_set(1, POP_PAL_STORY_BG, 0x80, 0, 0); @@ -129,5 +131,5 @@ void pop_pal_story_load(uint8_t ending) __banked void pop_pal_pv_load(void) __banked { - pal_file_load("PV\\pv.pal", "a:\\pv.pal"); + pal_file_load("PV\\pv.pal"); } diff --git a/applications/SprPoP/src/pop_path_bank.c b/applications/SprPoP/src/pop_path_bank.c new file mode 100644 index 0000000..b49fc19 --- /dev/null +++ b/applications/SprPoP/src/pop_path_bank.c @@ -0,0 +1,52 @@ +/* + * pop_path_bank.c — каталог приложения и восстановление cwd, BANK10. + * + * APPINFO B=1 возвращает абсолютный каталог запущенного EXE вместе с диском. + * Получаем его один раз при старте. Буфер объявлен const намеренно: у + * Sprinter банковые страницы — RAM, а --constseg кладёт объект в BANK10, + * не расходуя дефицитную резидентную _DATA в W2. Записывает его только + * ESTEX во время pop_path_init(). + * + * ABI: публичные функции __banked; их вызывает POP_PATH_CALL из банка + * файлового клиента. После возврата трамплин восстанавливает прежнюю W3, + * поэтому переданный самой файловой операции указатель снова доступен. + */ +#include +#include + +#include "_pop_file.h" + +static const char pop_app_dir[256] = { 0 }; + +/* ESTEX APPINFO ($47), B=1: HL=буфер, CF/A=ошибка. */ +static int appinfo_dir(const char *buf) __naked +{ + (void)buf; + __asm + push ix + ld b, #1 + ld c, #0x47 + rst #0x10 + pop ix + jr c, _paid_err + ld de, #0 + ret + _paid_err: + call __errno_set + ld de, #-1 + ret + __endasm; +} + +int8_t pop_path_init(void) __banked +{ + const char *dir = pop_app_dir; + + if (appinfo_dir(dir) != 0 || !dir[0]) return -1; + return (int8_t)chdir(dir); +} + +int8_t pop_chdir_home(void) __banked +{ + return (int8_t)chdir((const char *)pop_app_dir); +} diff --git a/applications/SprPoP/src/pop_qsave.c b/applications/SprPoP/src/pop_qsave.c index 776d009..72ef0e9 100644 --- a/applications/SprPoP/src/pop_qsave.c +++ b/applications/SprPoP/src/pop_qsave.c @@ -22,6 +22,7 @@ #include "pop_pace.h" #include "pop_timer.h" #include "sprpop_cold.h" +#include "_pop_file.h" #define QS_VERSION 4 #define QS_SAVE 1 @@ -154,6 +155,7 @@ static int8_t qs_save(uint8_t page) uint8_t tail[4]; const uint8_t h[7]={'P','O','P','Q',QS_VERSION,pop_current_level,0}; uint16_t size; + int rc; qs_init(&io,page,QS_LIMIT,0,0); pop_qs_raw_put(&io,h,sizeof(h)); qs_payload(&io); @@ -162,14 +164,18 @@ static int8_t qs_save(uint8_t page) tail[2]=(uint8_t)io.checksum; tail[3]=(uint8_t)(io.checksum >> 8); pop_qs_raw_put(&io,tail,4); size=(uint16_t)(io.pos-QS_BASE); if (io.failed) return -1; - unlink("POP.NEW"); - if (bank_save_file(page,QS_BASE,"POP.NEW",size) != size) { - unlink("POP.NEW"); return -1; + POP_PATH_CALL(rc, unlink("POP.NEW")); + POP_PATH_CALL(rc, bank_save_file(page,QS_BASE,"POP.NEW",size)); + if (rc != size) { + POP_PATH_CALL(rc, unlink("POP.NEW")); + return -1; } - unlink("POP.BAK"); - (void)rename("POP.SAV", "POP.BAK"); - if (rename("POP.NEW", "POP.SAV") != 0) { - (void)rename("POP.BAK", "POP.SAV"); return -1; + POP_PATH_CALL(rc, unlink("POP.BAK")); + POP_PATH_CALL(rc, rename("POP.SAV", "POP.BAK")); + POP_PATH_CALL(rc, rename("POP.NEW", "POP.SAV")); + if (rc != 0) { + POP_PATH_CALL(rc, rename("POP.BAK", "POP.SAV")); + return -1; } return 1; } @@ -178,7 +184,8 @@ static int8_t qs_load_one(const char *name, uint8_t page) { uint8_t level; uint16_t payload_size; - int size=bank_load_file(page,QS_BASE,name,QS_LIMIT); + int size; + POP_PATH_CALL(size, bank_load_file(page,QS_BASE,name,QS_LIMIT)); if (size <= 0 || !qs_validate_page(page,(uint16_t)size,&level, &payload_size)) return -1; if (level != pop_current_level) { @@ -205,11 +212,11 @@ uint8_t pop_qsave_probe(void) __banked page = mem_get_page(blk, 0); gfx_w0_page_prepare(page); - size = bank_load_file(page, QS_BASE, "POP.SAV", QS_LIMIT); + POP_PATH_CALL(size, bank_load_file(page, QS_BASE, "POP.SAV", QS_LIMIT)); if (size > 0 && qs_validate_page(page, (uint16_t)size, &level, &payload_size)) slots |= POP_QS_SLOT_SAVE; - size = bank_load_file(page, QS_BASE, "POP.BAK", QS_LIMIT); + POP_PATH_CALL(size, bank_load_file(page, QS_BASE, "POP.BAK", QS_LIMIT)); if (size > 0 && qs_validate_page(page, (uint16_t)size, &level, &payload_size)) slots |= POP_QS_SLOT_BACKUP; diff --git a/applications/SprPoP/src/pop_room.c b/applications/SprPoP/src/pop_room.c index e5dbf69..b9a14d6 100644 --- a/applications/SprPoP/src/pop_room.c +++ b/applications/SprPoP/src/pop_room.c @@ -31,6 +31,7 @@ #include "pop_kid.h" /* Kid.curr_col/curr_row — порядок оверлеев */ #include "pop_cdraw.h" /* pop_cd — габарит кадра Кида (y-порядок плиты) */ #include "pop_map.h" /* pop_upside — переворот (зелье инверсии) */ +#include "_pop_file.h" #include #include @@ -83,7 +84,8 @@ static const char *bg_path(const char *name) static int bg_load_tile_pal(const char *name) { uint8_t buf[64]; - int fd = open(bg_path(name), O_RDONLY); + int fd; + POP_PATH_CALL(fd, open(bg_path(name), O_RDONLY)); if (fd < 0) return -1; if (read(fd, buf, 64) != 64) { close(fd); return -1; } gfx_pal_load(0, POP_PAL_ENV, 16, buf); diff --git a/applications/SprPoP/src/pop_ui.c b/applications/SprPoP/src/pop_ui.c index ca4354a..bba7902 100644 --- a/applications/SprPoP/src/pop_ui.c +++ b/applications/SprPoP/src/pop_ui.c @@ -17,6 +17,7 @@ #include "pop_ui.h" #include "pop_status.h" #include "pop_music.h" /* pop_music_service — подкачка кольца в долгих фейдах */ +#include "_pop_file.h" typedef struct { uint8_t base; @@ -54,8 +55,10 @@ static uint8_t text_index(const pop_font_desc_t *font, uint8_t ch) int pop_ui_init(void) __banked { + int rc; if (font_ready) return 0; - if (atlas_load(&font_at, "FONT\\font.atl") != 0) return -1; + POP_PATH_CALL(rc, atlas_load(&font_at, "FONT\\font.atl")); + if (rc != 0) return -1; font_ready = 1; return 0; } @@ -242,8 +245,10 @@ void pop_ui_palette_black(void) __banked uint8_t *black = pop_pal_buf; /* BIOS принимает максимум 64 записи за вызов; одним буфером покрываем - * все четыре четверти палитры, не занимая постоянную память. */ - for (i = 0; i < sizeof(black); i++) black[i] = 0; + * все четыре четверти палитры, не занимая постоянную память. + * sizeof(black) здесь нельзя: black — 16-битный указатель, и тогда + * обнулятся лишь первые два байта, а цвет заливки останется случайным. */ + for (i = 0; i < sizeof(pop_pal_buf); i++) black[i] = 0; for (chunk = 0; chunk < 4; chunk++) { uint8_t start = (uint8_t)(chunk << 6); gfx_pal_load(0, start, 64, black); @@ -468,8 +473,10 @@ int pop_screen_blit_atlas_strips(const char * const *parts, gfx_set_bank(GFX_BANK_NORMAL); for (part = 0; part < count; part++) { atlas_t at; + int rc; - if (atlas_load(&at, parts[part]) != 0) return -1; + POP_PATH_CALL(rc, atlas_load(&at, parts[part])); + if (rc != 0) return -1; strip_blit(&at, x, &y); } return 0; diff --git a/applications/SprPoP/src/pop_vflip.c b/applications/SprPoP/src/pop_vflip.c index 4b8e138..df45a23 100644 --- a/applications/SprPoP/src/pop_vflip.c +++ b/applications/SprPoP/src/pop_vflip.c @@ -40,15 +40,6 @@ int pop_vflip_take(const atlas_t *orig, const atlas_t *mirror) return 0; } -int pop_vflip_add(const atlas_t *orig, const char *path) -{ - atlas_t m; - - if (!orig || vf_n >= VF_MAX) return -1; - if (atlas_load(&m, path) != 0) return -1; - return pop_vflip_take(orig, &m); -} - uint8_t pop_vflip_page(uint8_t src) { uint8_t i; diff --git a/applications/SprPoP/src/pop_vflip.h b/applications/SprPoP/src/pop_vflip.h index 81fefec..f031fbd 100644 --- a/applications/SprPoP/src/pop_vflip.h +++ b/applications/SprPoP/src/pop_vflip.h @@ -26,11 +26,7 @@ #include #include -/* Загрузить зеркальный файл path и связать его с атласом orig. - * 0 — OK, -1 — файла нет / не хватило EMM / таблица полна. */ -int pop_vflip_add(const atlas_t *orig, const char *path); - -/* То же, но зеркало УЖЕ загружено (например, прочитано из архива): таблица +/* Зеркало УЖЕ загружено из архива: таблица * забирает его себе, и дальше он освобождается вместе с остальными. */ int pop_vflip_take(const atlas_t *orig, const atlas_t *mirror); diff --git a/applications/SprPoP/src/sprpop.c b/applications/SprPoP/src/sprpop.c index 0c9ab0a..60e1d37 100644 --- a/applications/SprPoP/src/sprpop.c +++ b/applications/SprPoP/src/sprpop.c @@ -41,6 +41,10 @@ #include "pop_pal.h" /* lifecycle палитры title/intro/level */ #include "pop_hof.h" /* persistent Hall of Fame — bank 9 */ #include "pop_ui.h" /* чёрные обе страницы перед сменой палитры */ +#include "_pop_file.h" /* cwd приложения для старых DSS */ + +#include +#include __sfr __at (0xFE) io_border; @@ -260,6 +264,11 @@ int main(void) uint8_t xdec_prev = 0, xinc_prev = 0, upsd_prev = 0; uint8_t failed = 0; + /* До первой файловой операции запоминаем абсолютный каталог EXE. + * Без этого старый DSS теряет последний компонент cwd после OPEN пути + * с подкаталогом и следующий файл ищет уровнем выше. */ + if (pop_path_init() != 0) return 1; + /* Загрузка ресурсов, палитра, первый экран — В БАНКЕ (sprpop_cold.c): * отрабатывает ровно один раз за игру, а кода там на полкилобайта. */ pop_app_reset(); diff --git a/applications/SprPoP/src/sprpop_cold.c b/applications/SprPoP/src/sprpop_cold.c index 1b6bd08..67f70c7 100644 --- a/applications/SprPoP/src/sprpop_cold.c +++ b/applications/SprPoP/src/sprpop_cold.c @@ -1151,7 +1151,7 @@ int pop_boot(void) __banked /* pop_cheats/speed применит pop_settings_apply после pop_sfx_init. */ pop_guard_reset(); if (pop_kid_data_load("KID\\kid.ani") != 0) { /* кадры+seqtbl в EMM-странице */ - printf("kid.ani load failed\n"); + puts("kid.ani load failed\n"); return -1; } pop_guard_load(); /* атласы стража (GUARD\\g0..g4) */ diff --git a/applications/SprPoP/tools/make_hdd.sh b/applications/SprPoP/tools/make_hdd.sh new file mode 100755 index 0000000..d4957ad --- /dev/null +++ b/applications/SprPoP/tools/make_hdd.sh @@ -0,0 +1,92 @@ +#!/bin/bash +# make_hdd.sh OUT.chd FILE1 [FILE2 ...] +# +# Собирает 32 МБ RAW-образ жёсткого диска (MBR + один раздел FAT16) с +# указанными файлами в корне и конвертит его в CHD для MAME (монтируется +# как -hard2). Быстрая альтернатива floppy (mc.img): MAME эмулирует FDD +# с реальной черепашьей скоростью, HDD — в разы быстрее. +# +# Требует: mtools (mpartition/mformat/mcopy) + chdman (из MAME). +# Sprinter DSS штатно монтирует стандартный MBR+FAT16 (подтверждено). +# +# Использование: +# tools/make_hdd.sh mame/v306/IMG/test_hdd.chd prog.exe data1 data2 ... +# tools/make_hdd.sh out.chd prog.exe KID:kid0.atl GUARD:g0.atl ... +# — аргумент вида КАТАЛОГ:файл кладёт файл в подкаталог образа +# (каталог создаётся); без префикса файл идёт в корень. +set -e + +if [ $# -lt 2 ]; then + echo "usage: $0 OUT.chd FILE1 [FILE2 ...]" >&2 + exit 1 +fi + +OUT_CHD="$1"; shift + +# Геометрия: 128*16*32*512 = 32 МБ. BPS=512. +CYLS=128; HEADS=16; SECS=32 +SECTORS=$((CYLS * HEADS * SECS)) # 65536 + +# macOS mktemp требует X'ы в КОНЦЕ шаблона. +RAW=$(mktemp /tmp/sphdd.raw.XXXXXX) +MTC=$(mktemp /tmp/sphdd.mtc.XXXXXX) +trap 'rm -f "$RAW" "$MTC"' EXIT + +dd if=/dev/zero of="$RAW" bs=512 count=$SECTORS status=none + +# mtools-диск z: = раздел 1 нашего RAW-образа. +printf 'drive z: file="%s" partition=1\n' "$RAW" > "$MTC" +export MTOOLSRC="$MTC" MTOOLS_SKIP_CHECK=1 + +# mtools собран с enable-raw-term и, когда ему нужно что-то спросить, +# открывает УПРАВЛЯЮЩИЙ ТЕРМИНАЛ напрямую (/dev/tty) — в обход stdin. +# Поэтому ни `< /dev/null`, ни перенаправление stdio не спасают: из +# терминала сборка молча вставала навсегда на `mmd z:/BG` (подтверждено +# lsof: fd 0 = /dev/null, fd 4 = /dev/tty, процесс спит). Лечится только +# отсутствием управляющего терминала — запускаем mtools в новой сессии, +# тогда открыть /dev/tty нечего и mtools идёт неинтерактивным путём. +# +# setsid(1) в macOS нет, поэтому делаем то же самое питоном. +# Каналов, откуда mtools может ждать ответ, ДВА, и закрывать надо оба: +# /dev/tty — убирается новой сессией (os.setsid); +# stdin — убирается перенаправлением из /dev/null. +# Закрыть только один недостаточно: с одним лишь setsid mmd переключался на +# stdin и вис уже на нём (lsof: fd 0 = /dev/ttys002). +mt() { + python3 -c 'import os,sys; os.setsid(); os.execvp(sys.argv[1], sys.argv[1:])' \ + "$@" < /dev/null +} + +# MBR + раздел 1 на весь диск (тип FAT16), затем формат FAT16. +echo "--- разметка и формат образа ---" +mt mpartition -I -c -t $CYLS -h $HEADS -s $SECS z: +mt mformat z: + +echo "--- копирование файлов ---" + +# Аргумент может быть либо файлом (кладём в корень), либо парой +# КАТАЛОГ:файл — тогда каталог создаётся на образе и файл кладётся туда. +# Имена на Sprinter/DSS — 8.3, каталоги тоже (KID, GUARD, BG, LEVELS...). +mt mmd "z:/games" >/dev/null 2>&1 || true +mt mmd "z:/games/sprpop" >/dev/null 2>&1 || true + +for f in "$@"; do + case "$f" in + *:*) dir="${f%%:*}"; src="${f#*:}" + # Каталог может уже быть — это не ошибка; stderr НЕ прячем, иначе + # непрошедший вопрос mtools снова окажется невидимым. + mt mmd "z:/games/sprpop/$dir" >/dev/null 2>&1 || true + echo "COPY $src to z:/games/sprpop/$dir/" + mt mcopy -o "$src" "z:/games/sprpop/$dir/" ;; + *) mt mcopy -o "$f" "z:/games/sprpop/" ;; + esac +done + +echo "--- содержимое образа ---" +mt mdir -/ z: + +# RAW -> CHD (без сжатия = быстро; та же геометрия). +echo "--- конвертация RAW -> CHD ---" +chdman createhd -i "$RAW" -o "$OUT_CHD" -c none \ + -chs $CYLS,$HEADS,$SECS -ss 512 -f +echo "готово: $OUT_CHD"