f4b4852d51
roomtest: - QuickSave/QuickLoad (F6/F9): снапшот 'POPQ' v3 в POP.SAV/POP.BAK на HDD, транзакционная запись (POP.NEW -> rename, откат при ошибке), XOR-контрольная сумма payload'а; сериализация всех игровых переменных через W0-примитивы pop_qs_*; pop_qsave_process() на границе кадра вне Char-окон - pop_qsave_restore_room(): полная перезагрузка комнаты после загрузки (карта/края/швы, сброс bake-кэша, перерисовка обеих страниц, инвалидация кэшей спрайтов и HP) - сериализаторы в pop_map/pop_loose_mob/pop_trob/pop_guard_ai (+ восстановление инвариантов: mobs_live, trob_drawn, redraw) - immortal-чит 2 уровня: уровень 2 поглощает только малый урон Kid'а libc/libbgi: - bank_load_file()/bank_save_file() — резидентное файловое I/O в банк, без правила W3 (путь читается до переключения страницы) - gfx_w0_page_prepare(page) — подготовка W0-окна (IRQ/NMI-стабы) одной функцией; atlas_load.c и roomtest переведены на новые примитивы; ручные ISR-стабы удалены sprinter-cc / сборка: - --bank N=FILE.c: автогенерация n_banks (_n_banks_auto.c), ручные const n_banks удалены из тестов - roomtest/app.mk: ресурсы через stamp-файлы (.resource-stamps/) — один запуск упаковщика на группу вместо N под -B; HDD_PACK_ARGS
208 lines
7.6 KiB
C
208 lines
7.6 KiB
C
/*
|
|
* pop_qsave.c — файловая транзакция и схема игрового снимка.
|
|
* Живёт в холодном банке: в обычном кадре исполняется только проверка
|
|
* резидентного флага запроса, вся тяжёлая работа бывает по F6/F9.
|
|
*/
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h> /* rename */
|
|
#include <sprinter_mem.h>
|
|
#include <sprite.h>
|
|
#include "pop_qsave.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 "roomtest_cold.h"
|
|
|
|
#define QS_VERSION 3
|
|
#define QS_SAVE 1
|
|
#define QS_LOAD 2
|
|
#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;
|
|
}
|
|
|
|
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] < 1 || h[5] > 15 || 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;
|
|
}
|
|
|
|
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;
|
|
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 {
|
|
rc = qs_load_one("POP.SAV",page);
|
|
if (rc < 0) rc = qs_load_one("POP.BAK",page);
|
|
}
|
|
pop_sfx_start();
|
|
mem_free_block(blk);
|
|
return rc;
|
|
}
|