/* * pop_qsave.c — файловая транзакция и схема игрового снимка. * Живёт в холодном банке: в обычном кадре исполняется только проверка * резидентного флага запроса, вся тяжёлая работа бывает по F6/F9. */ #include #include #include /* rename */ #include #include #include "pop_qsave.h" #include "pop_status.h" #include "pop_kid.h" #include "pop_guard.h" #include "pop_map.h" #include "pop_level.h" #include "pop_trob.h" #include "pop_bg.h" #include "pop_state.h" #include "pop_tile.h" #include "pop_sfx.h" #include "pop_pace.h" #include "pop_timer.h" #include "roomtest_cold.h" #define QS_VERSION 4 #define QS_SAVE 1 #define QS_LOAD 2 #define QS_LOAD_BACKUP 3 #define QS_LIMIT 4096 #define QS_BASE 0x100 extern uint8_t pop_qsave_request; static void qs_char(pop_qs_io_t *io, pop_char_t *c) { pop_qs_u8(io, &c->frame); pop_qs_u8(io, &c->x); pop_qs_u8(io, &c->y); pop_qs_i8(io, &c->direction); pop_qs_i8(io, &c->curr_col); pop_qs_i8(io, &c->curr_row); pop_qs_u8(io, &c->action); pop_qs_i8(io, &c->fall_x); pop_qs_i8(io, &c->fall_y); pop_qs_u8(io, &c->room); pop_qs_u8(io, &c->repeat); pop_qs_u8(io, &c->charid); pop_qs_u8(io, &c->sword); pop_qs_i8(io, &c->alive); pop_qs_u16(io, &c->curr_seq); } static void qs_payload(pop_qs_io_t *io) { uint8_t speed=pop_speed_mode, immortal=pop_immortal, sound=pop_snd_want; /* Пользовательские параметры входят в снимок, а не остаются свойством * текущего запуска. Читаем сначала во временные байты: повреждённые * значения не должны частично применить остальное игровое состояние. */ pop_qs_u8(io, &speed); pop_qs_u8(io, &immortal); pop_qs_u8(io, &sound); if (io->reading && io->apply) { if (speed >= POP_SPEED_MODES || immortal > 2 || sound > 1) { io->failed=1; return; } pop_speed_mode=speed; pop_immortal=immortal; pop_snd_want=sound; } /* Глобальный timer сохраняется до состояния уровня: QuickLoad на том же * уровне вернёт ровно тот логический кадр, без «бесплатной» минуты. */ pop_timer_qsave(io); qs_char(io, &Kid); qs_char(io, &Guard); pop_qs_u8(io, &cur_room); pop_qs_u8(io, &kid_room); pop_qs_u8(io, &pop_checkpoint); pop_qs_u8(io, &pop_next_level); pop_qs_i8(io, &knock); pop_qs_u8(io, &hitp_curr); pop_qs_u8(io, &hitp_max); pop_qs_u8(io, &hitp_beg_lev); pop_qs_i8(io, &hitp_delta); pop_qs_u8(io, &guardhp_curr); pop_qs_u8(io, &guardhp_max); pop_qs_i8(io, &guardhp_delta); pop_qs_u8(io, &guard_skill); pop_qs_i8(io, &guard_refrac); pop_qs_i8(io, &justblocked); pop_qs_i8(io, &kid_sword_strike); pop_qs_i8(io, &offguard); pop_qs_i8(io, &holding_sword); pop_qs_u8(io, &pop_guard_hurt); pop_qs_i8(io, &can_guard_see_kid); pop_qs_i8(io, &is_guard_notice); pop_qs_i8(io, &pop_united_shadow); pop_qs_u8(io, &pop_shadow_init); pop_qs_i8(io, &pop_guard_notice_timer); pop_qs_u16(io, &pop_fight_seed.lo); pop_qs_u16(io, &pop_fight_seed.hi); pop_qs_u8(io, &pop_kid_dead); pop_qs_u8(io, &pop_kid_hurt); pop_qs_u8(io, &pop_have_sword); pop_qs_u8(io, &pop_item_taken); pop_qs_u8(io, &pop_flash_time); pop_qs_u8(io, &pop_flash_color); pop_qs_u8(io, &pop_feather); pop_qs_u8(io, &pop_upside); pop_qs_u8(io, &pop_upside_want); pop_qs_u8(io, &pop_upside_dirty); pop_qs_u8(io, &pop_leave_dir); pop_qs_u8(io, &pop_leave_timer); pop_qs_u8(io, &pop_loose_fell); pop_qs_u8(io, &pop_ceil_fell); pop_qs_u8(io, &pop_debris_at); pop_qs_u8(io, &pop_fell_out); pop_qs_bytes(io, pop_loose_modif, sizeof(pop_loose_modif)); pop_qs_bytes(io, pop_ceil_modif, sizeof(pop_ceil_modif)); pop_qs_u8(io, &pop_loose_landed); pop_qs_u8(io, &pop_loose_exit); pop_qs_u8(io, &pop_loose_exit_room); pop_qs_u8(io, &pop_droppedout); pop_qs_u16(io, &pop_leveldoor_open); pop_qs_i8(io, &pop_jumped_mirror); pop_qs_u8(io, &pop_seamless); pop_qs_u8(io, &pop_nav_hold); pop_level_qsave(io); pop_trob_qsave(io); pop_loose_mob_qsave(io); pop_map_qsave(io); pop_guard_ai_qsave(io); } static void qs_init(pop_qs_io_t *io, uint8_t page, uint16_t limit, uint8_t reading, uint8_t apply) { io->page=page; io->pos=QS_BASE; io->limit=(uint16_t)(QS_BASE+limit); io->checksum=0; io->count=0; io->reading=reading; io->apply=apply; io->failed=0; } static uint8_t qs_header_read(pop_qs_io_t *io, uint8_t *level) { uint8_t h[7]; if (!pop_qs_raw_get(io,h,sizeof(h))) return 0; if (h[0]!='P' || h[1]!='O' || h[2]!='P' || h[3]!='Q' || h[4] != QS_VERSION || h[5] < POP_LEVEL_FIRST || h[5] > POP_LEVEL_LAST || h[6] != 0) return 0; *level = h[5]; return 1; } static uint8_t qs_validate_page(uint8_t page, uint16_t size, uint8_t *level, uint16_t *payload_size) { pop_qs_io_t io; uint8_t tail[4]; uint16_t n, sum; if (size < 11) return 0; qs_init(&io,page,size,1,0); if (!qs_header_read(&io,level)) return 0; io.pos=(uint16_t)(QS_BASE+size-4); if (!pop_qs_raw_get(&io,tail,4)) return 0; n = (uint16_t)(tail[0] | ((uint16_t)tail[1] << 8)); sum = (uint16_t)(tail[2] | ((uint16_t)tail[3] << 8)); if (n != (uint16_t)(size-11) || sum != pop_qs_checksum_page(page,QS_BASE+7,n)) return 0; *payload_size=n; return 1; } static uint8_t qs_apply(uint8_t page, uint16_t size, uint16_t payload_size, uint8_t *level) { pop_qs_io_t io; qs_init(&io,page,size,1,1); if (!qs_header_read(&io,level)) return 0; qs_payload(&io); return (uint8_t)(!io.failed && io.count == payload_size && io.pos == (uint16_t)(QS_BASE+7+payload_size)); } static int8_t qs_save(uint8_t page) { pop_qs_io_t io; uint8_t tail[4]; const uint8_t h[7]={'P','O','P','Q',QS_VERSION,pop_current_level,0}; uint16_t size; qs_init(&io,page,QS_LIMIT,0,0); pop_qs_raw_put(&io,h,sizeof(h)); qs_payload(&io); tail[0]=(uint8_t)io.count; tail[1]=(uint8_t)(io.count >> 8); io.checksum=pop_qs_checksum_page(page,QS_BASE+7,io.count); 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; } unlink("POP.BAK"); (void)rename("POP.SAV", "POP.BAK"); if (rename("POP.NEW", "POP.SAV") != 0) { (void)rename("POP.BAK", "POP.SAV"); return -1; } return 1; } 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); if (size <= 0 || !qs_validate_page(page,(uint16_t)size,&level, &payload_size)) return -1; if (level != pop_current_level) { pop_next_level = level; if (pop_level_switch() != 0 || pop_current_level != level) return -1; } if (!qs_apply(page,(uint16_t)size,payload_size,&level)) return -1; Char = Kid; Opp = Guard; pop_qsave_restore_room(cur_room); return 1; } /* Проверка слотов для меню. Это именно probe: не вызывает pop_level_switch, * не применяет payload и не трогает звук. Отдельная EMM-страница делает * проверку независимой от живой страницы уровня. */ uint8_t pop_qsave_probe(void) __banked { uint8_t blk, page, level, slots = 0; uint16_t payload_size; int size; blk = mem_alloc_pages(1); if (!blk) return 0; page = mem_get_page(blk, 0); gfx_w0_page_prepare(page); 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); if (size > 0 && qs_validate_page(page, (uint16_t)size, &level, &payload_size)) slots |= POP_QS_SLOT_BACKUP; mem_free_block(blk); return slots; } int8_t pop_qsave_process(void) __banked { uint8_t request = pop_qsave_request; uint8_t blk,page; int8_t rc=-1; pop_qsave_request = 0; if (!request) return 0; /* Лейбл — ДО дисковой работы: она длится доли секунды, игра на это * время замирает, и строка обязана объяснить фриз, а не подводить под * ним итог. Не удалось — ниже перепишем на NO QUICKSAVE/QUICKLOAD. */ pop_status_show_now((uint8_t)(request == QS_SAVE ? POP_MSG_QUICKSAVE : POP_MSG_QUICKLOAD)); blk=mem_alloc_pages(1); if (!blk) return -1; page=mem_get_page(blk,0); gfx_w0_page_prepare(page); pop_sfx_pause(); if (request == QS_SAVE) rc = qs_save(page); else if (request == QS_LOAD_BACKUP) rc = qs_load_one("POP.BAK", page); else { rc = qs_load_one("POP.SAV",page); if (rc < 0) rc = qs_load_one("POP.BAK",page); } pop_sfx_start(); mem_free_block(blk); /* Успех ничего не меняет: нужная строка висит с начала операции. * Отказ переписывает её на NO QUICKSAVE/NO QUICKLOAD — обычной * заявкой, кадр её подхватит (порт seg000:497/514). */ if (rc <= 0) pop_status_msg = (uint8_t)(request == QS_SAVE ? POP_MSG_NO_QUICKSAVE : POP_MSG_NO_QUICKLOAD); return rc; }