/* * pop_level.c — загрузка уровня PoP из сырого res200N.bin (Фаза L1). * См. pop_level.h. Порт формата blueprnt DAT 1.0 (Table 6 POP-DAT). */ #include #include #include #include #include /* gfx_w0_map / gfx_w0_unmap */ #include "pop_level.h" /* ISR-стаб W0-страницы (тот же приём, что atlas_load): при прерывании с * замапленной в W0 страницей CPU прыгает на 0x0038 → там обязан быть JP на * _gfx_w0_isr (восстановит окно). Символ внутренний для libbgi, но * стабильный — линкер тянет его из bgi256.lib (графика уже подключена). */ extern void _gfx_w0_isr(void); #define LVL_DATA_OFF 0x100u /* данные с 0x100; 0x00..0xFF — ISR-стаб */ /* Смещения блоков blueprnt относительно начала данных (Table 6). */ #define BP_FG 0u #define BP_BG 720u #define BP_LINKLOC 1440u /* doorlink1 (LINKLOC), 256 Б */ #define BP_LINKMAP 1696u /* doorlink2 (LINKMAP), 256 Б */ #define BP_LINKS 1952u #define BP_START 2112u #define ROOM_TILES 30u #define MIN_SIZE (BP_START + 3u) /* минимум, чтобы прочитать старт */ static uint8_t lvl_blk, lvl_page, lvl_ok; /* Копии таблиц дверных связей в W2 (LINKMAP мутабельна — таймеры кнопок). */ static uint8_t dl1[256], dl2[256]; int pop_level_load(const char *path) { uint8_t *pg = (uint8_t *)0xC000; /* страница мапится в W3 на время чтения */ uint8_t saved_w3, blk; uint16_t stub; int fd, n; fd = open(path, O_RDONLY); if (fd < 0) return -1; blk = mem_alloc_pages(1); if (!blk) { close(fd); return -1; } lvl_blk = blk; lvl_page = mem_get_page(blk, 0); saved_w3 = _io_page_w3; sprinter_page_w3(lvl_page); /* Файл целиком в offset 0x100 (ESTEX READ пишет в W3 — как atlas_load). */ n = read(fd, pg + LVL_DATA_OFF, 16384 - LVL_DATA_OFF); if (n >= (int)MIN_SIZE) { /* пропатчить ISR-стаб */ uint16_t i; stub = (uint16_t)&_gfx_w0_isr; pg[0x38] = 0xC3; /* JP _gfx_w0_isr */ pg[0x39] = (uint8_t)(stub & 0xFF); pg[0x3A] = (uint8_t)(stub >> 8); pg[0x66] = 0xED; pg[0x67] = 0x45; /* RETN (NMI-хвост) */ /* Скопировать таблицы дверных связей в W2 (страница ещё в W3). */ for (i = 0; i < 256; i++) { dl1[i] = pg[LVL_DATA_OFF + BP_LINKLOC + i]; dl2[i] = pg[LVL_DATA_OFF + BP_LINKMAP + i]; } } sprinter_page_w3(saved_w3); close(fd); if (n < (int)MIN_SIZE) { mem_free_block(blk); return -1; } lvl_ok = 1; return 0; } void pop_level_free(void) { if (lvl_ok) { mem_free_block(lvl_blk); lvl_ok = 0; } } /* Указатель на fg-тайлы комнаты r (1..24) в W0-пространстве (страница уже * замаплена вызывающим). */ static const uint8_t *room_fg_ptr(uint8_t r) { return (const uint8_t *)(LVL_DATA_OFF + BP_FG) + (uint16_t)(r - 1) * ROOM_TILES; } static const uint8_t *room_bg_ptr(uint8_t r) { return (const uint8_t *)(LVL_DATA_OFF + BP_BG) + (uint16_t)(r - 1) * ROOM_TILES; } int pop_room_load(uint8_t room, uint8_t *fg, uint8_t *bg, uint8_t *lcol_fg, uint8_t *lcol_bg, uint8_t *rcol_fg, uint8_t *rcol_bg, uint8_t *below_fg) { const uint8_t *rfg, *rbg, *lnk; uint8_t left, right, down, i; if (!lvl_ok || room < 1 || room > 24) return -1; gfx_w0_map(lvl_page); rfg = room_fg_ptr(room); rbg = room_bg_ptr(room); lnk = (const uint8_t *)(LVL_DATA_OFF + BP_LINKS) + (uint16_t)(room - 1) * 4; for (i = 0; i < ROOM_TILES; i++) { fg[i] = (uint8_t)(rfg[i] & 0x1F); /* тип тайла (верх. биты — модиф., пока не нужны) */ bg[i] = rbg[i]; /* backtable как есть */ } left = lnk[0]; /* L */ right = lnk[1]; /* R */ down = lnk[3]; /* D */ /* leftcol: правый столбец (col9) комнаты СЛЕВА, строки 0..2 (кромка col0), * а в [3..5] — ПРЕДпоследний (col8). Вторая колонка нужна коллизии: Kid, * стоящий В шве (curr_col = −1), смотрит вперёд на колонку −2, и без неё * там оказывалась мнимая стена (get_tile → TILE_WALL), которая выпихивала * его сквозь решётку. Оригинал резолвит любую колонку через * find_room_of_tile (seg006:005D). */ if (left >= 1 && left <= 24) { const uint8_t *lf = room_fg_ptr(left); const uint8_t *lb = room_bg_ptr(left); for (i = 0; i < 3; i++) { lcol_fg[i] = (uint8_t)(lf[i * 10 + 9] & 0x1F); lcol_fg[3 + i] = (uint8_t)(lf[i * 10 + 8] & 0x1F); lcol_bg[i] = lb[i * 10 + 9]; } } else { for (i = 0; i < 3; i++) { lcol_fg[i] = 20; lcol_fg[3 + i] = 20; lcol_bg[i] = 0; } } /* rightcol: левый столбец (col0) комнаты СПРАВА, строки 0..2 (кромка col9: * коллизия на правом шве + переход вправо) */ if (right >= 1 && right <= 24) { const uint8_t *rf = room_fg_ptr(right); const uint8_t *rb = room_bg_ptr(right); for (i = 0; i < 3; i++) { rcol_fg[i] = (uint8_t)(rf[i * 10 + 0] & 0x1F); rcol_fg[3 + i] = (uint8_t)(rf[i * 10 + 1] & 0x1F); /* колонка 11 (см. выше) */ rcol_bg[i] = rb[i * 10 + 0]; } } else { for (i = 0; i < 3; i++) { rcol_fg[i] = 20; rcol_fg[3 + i] = 20; rcol_bg[i] = 0; } } /* belowrow: верхний ряд (row0) комнаты снизу */ if (down >= 1 && down <= 24) { const uint8_t *df = room_fg_ptr(down); for (i = 0; i < 10; i++) below_fg[i] = (uint8_t)(df[i] & 0x1F); } else { for (i = 0; i < 10; i++) below_fg[i] = 20; /* нет комнаты снизу = стена */ } gfx_w0_unmap(); return 0; } int8_t pop_room_col_landing(uint8_t room, uint8_t col) { const uint8_t *rf; int8_t row, res = -1; if (!lvl_ok || room < 1 || room > 24 || col > 9) return -1; gfx_w0_map(lvl_page); rf = room_fg_ptr(room); for (row = 0; row < 3; row++) { /* кусок пролетает пустые ряды */ uint8_t t = (uint8_t)(rf[row * 10 + col] & 0x1F); if (t == 0) continue; /* empty — дальше вниз */ if (t == 1) res = row; /* floor — приземление → debris */ break; /* первый не-пустой тайл — стоп */ } gfx_w0_unmap(); return res; } uint8_t pop_doorlink1(uint8_t i) { return dl1[i]; } uint8_t pop_doorlink2(uint8_t i) { return dl2[i]; } void pop_doorlink2_set(uint8_t i, uint8_t v) { dl2[i] = v; } uint8_t pop_level_tile(uint8_t room, uint8_t tilepos) { uint8_t v; if (!lvl_ok || room < 1 || room > 24 || tilepos >= ROOM_TILES) return 0; gfx_w0_map(lvl_page); v = (uint8_t)(room_fg_ptr(room)[tilepos] & 0x1F); gfx_w0_unmap(); return v; } /* Батч-доступ: смаппить страницу уровня в W0 ОДИН раз, прочитать много * тайлов через pop_level_tile_raw, размаппить один раз. Экономит N свапов * окна 0 в горячих циклах (pop_process_trobs звал pop_level_tile на каждый * trob каждый кадр). Между begin/end НЕЛЬЗЯ рисовать/звать код, которому * нужно окно 0 в исходном состоянии — только сырые чтения уровня. */ void pop_level_access_begin(void) { if (lvl_ok) gfx_w0_map(lvl_page); } void pop_level_access_end(void) { if (lvl_ok) gfx_w0_unmap(); } uint8_t pop_level_tile_raw(uint8_t room, uint8_t tilepos) { if (!lvl_ok || room < 1 || room > 24 || tilepos >= ROOM_TILES) return 0; return (uint8_t)(room_fg_ptr(room)[tilepos] & 0x1F); } void pop_level_room_bg(uint8_t room, uint8_t *out30) { const uint8_t *rbg; uint8_t i; if (!out30) return; if (!lvl_ok || room < 1 || room > 24) { for (i = 0; i < ROOM_TILES; i++) out30[i] = 0; return; } gfx_w0_map(lvl_page); rbg = room_bg_ptr(room); for (i = 0; i < ROOM_TILES; i++) out30[i] = rbg[i]; gfx_w0_unmap(); } uint8_t pop_room_link(uint8_t room, uint8_t side) { uint8_t v; if (!lvl_ok || room < 1 || room > 24 || side > 3) return 0; gfx_w0_map(lvl_page); v = ((const uint8_t *)(LVL_DATA_OFF + BP_LINKS))[(uint16_t)(room - 1) * 4 + side]; gfx_w0_unmap(); return v; } static uint8_t start_byte(uint16_t off) { uint8_t v; if (!lvl_ok) return 0; gfx_w0_map(lvl_page); v = ((const uint8_t *)(LVL_DATA_OFF))[off]; gfx_w0_unmap(); return v; } uint8_t pop_level_start_room(void) { return start_byte(BP_START + 0); } uint8_t pop_level_start_pos(void) { return start_byte(BP_START + 1); } int8_t pop_level_start_dir(void) { return (int8_t)start_byte(BP_START + 2); }