From c3a0dfbf425c8a03402524a12b5c7b20392f81b1 Mon Sep 17 00:00:00 2001 From: Nathaniel Walizer Date: Mon, 24 Mar 2025 02:15:11 -0700 Subject: [PATCH] Add save/load state support - Works with CHR RAM, still needs thorough testing --- Makefile | 1 + src/f6502.c | 10 ++- src/f6502.h | 13 +++- src/input.h | 2 + src/linux/port.c | 69 ++++++++++++++---- src/map/map001.c | 5 +- src/map/map002.c | 3 +- src/map/map003.c | 3 +- src/map/map004.c | 16 +++-- src/mapper.c | 24 +++++++ src/mapper.h | 5 ++ src/memory.c | 10 +++ src/memory.h | 22 ++++-- src/nes.c | 15 +++- src/nes.h | 8 +++ src/port.h | 12 ++++ src/ppu.c | 39 +++++++++++ src/ppu.h | 17 +++-- src/save.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++ src/save.h | 11 +++ src/serdes.c | 53 ++++++++++++++ src/serdes.h | 29 ++++++++ 22 files changed, 510 insertions(+), 35 deletions(-) create mode 100644 src/memory.c create mode 100644 src/save.c create mode 100644 src/save.h create mode 100644 src/serdes.c create mode 100644 src/serdes.h diff --git a/Makefile b/Makefile index aa7b8e9..7ca0cf5 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,7 @@ MAPDIR = $(SRCDIR)/map NESE_SRC_SRCS = f6502.c f6502_opcodes.c NESE_SRC_SRCS += nese.c nes.c cart.c mapper.c NESE_SRC_SRCS += ppu.c apu.c +NESE_SRC_SRCS += memory.c serdes.c save.c NESE_SRC_SRCS += $(OS)/port.c NESE_MAP_SRCS = $(notdir $(wildcard $(MAPDIR)/*.c)) diff --git a/src/f6502.c b/src/f6502.c index 2f0dfcc..2c839c9 100644 --- a/src/f6502.c +++ b/src/f6502.c @@ -186,7 +186,6 @@ static inline int f6502_write(nes_Memory* mem, mem->ppu.t &= ~(ppu_Control_Nametable_Mask << 10); mem->ppu.t |= ((uint16_t)val & ppu_Control_Nametable_Mask) << 10; mem->ppu.addr_inc = (val & ppu_Control_VRAM_Inc) ? 32 : 1; - // TODO: Trigger NMI if VBlank status is set? break; case ppu_reg_mask: @@ -1725,3 +1724,12 @@ int f6502_step(f6502_Core* core, int clocks) { core->clocks += clocks_elapsed; return clocks_elapsed; } + + +/* Ser/Des */ + +const Serdes_Item f6502_serdes[] = { + {offsetof(f6502_Core, registers), serdes_copy, serdes_copy, serdes_copy_size, (void*)(sizeof(f6502_Registers) + sizeof(f6502_Interrupt))}, + {offsetof(f6502_Core, memory), deserialize, serialize, serdes_size, nes_memory_serdes}, + {0} +}; diff --git a/src/f6502.h b/src/f6502.h index 803e1c7..b3a27e2 100644 --- a/src/f6502.h +++ b/src/f6502.h @@ -1,6 +1,9 @@ #ifndef F6502_H_ #define F6502_H_ +#include "serdes.h" + + #include #include @@ -41,10 +44,15 @@ typedef enum { } f6502_Interrupt; struct f6502_Core { - uint64_t clocks; + // Static f6502_Registers registers; f6502_Interrupt interrupts; + + // Specific nes_Memory memory; + + // Don't care + uint64_t clocks; }; typedef struct f6502_Core f6502_Core; @@ -55,4 +63,7 @@ void f6502_set_NMI(f6502_Core*, bool active); void f6502_set_IRQ(f6502_Core*, bool active); +extern const Serdes_Item f6502_serdes[]; + + #endif // F6502_H_ diff --git a/src/input.h b/src/input.h index 520b851..b9eea84 100644 --- a/src/input.h +++ b/src/input.h @@ -1,6 +1,8 @@ #ifndef NESE_INPUT_H_ #define NESE_INPUT_H_ +#include + #define nes_controller_bus_mask (0b11111000) diff --git a/src/linux/port.c b/src/linux/port.c index c2a48f9..d663980 100644 --- a/src/linux/port.c +++ b/src/linux/port.c @@ -2,14 +2,18 @@ #include #include -#include #include +#include +#include + + #include #include "cart.h" #include "nese.h" #include "port.h" +#include "save.h" #define DEBUG "Port" #include "log.h" @@ -20,20 +24,28 @@ * Memory mapping specifically needs to be ported for each OS */ -static void* nese_map_file(FILE* file, int size) { - void* addr = mmap(NULL, size, PROT_READ, MAP_SHARED, - fileno(file), 0); - if (MAP_FAILED == addr || NULL == addr) { - fprintf(stderr, "mmap failed: %d\n", (int)errno); - addr = NULL; +void* nese_map_file(FILE* file, int size, Filemap_Mode map_mode) { + int prot = ( Filemap_Mode_Write == map_mode ? + PROT_WRITE : PROT_READ); + int flags = ( Filemap_Mode_Write == map_mode ? + MAP_SHARED : MAP_PRIVATE); + void* mem = mmap(NULL, size, prot, flags, fileno(file), 0); + if ((void*)-1 == mem) { + fprintf(stderr, "Failed to map file: %d\n", errno); + mem = NULL; } - return addr; + return mem; } -static int nese_unmap_file(void* addr, int size) { - return munmap(addr, size); +void nese_unmap_file(void* mem, int size) { + munmap(mem, size); } +int nese_mkdir(const char* dir) { + return mkdir(dir, 0777); +} + + static int nese_file_size(FILE* file) { int size = -1; if (0 == fseek(file, 0, SEEK_END)) { @@ -89,13 +101,26 @@ typedef struct { /* Input */ +typedef enum { + Action_Error = -1, + Action_OK, + Action_Quit, + Action_Reset, + Action_Load, + Action_Save, + Action_Menu, + Action_Cancel, +} nese_Action; + int nese_update_input(void* plat_data, nes_Input* input) { // Gamepad states are already updated in nese_frame_ready() return 0; } -static const int sdl_alt_start_key = SDLK_RETURN; +#define sdl_save_key (SDLK_F1) +#define sdl_load_key (SDLK_F2) +#define sdl_alt_start_key (SDLK_RETURN) static const int sdl_keycodes[nes_controller_num_buttons] = { SDLK_a, @@ -140,6 +165,17 @@ static int process_events(nes* sys) { } else { input->gamepads[0].buttons &= ~mask; } + + } else if (SDL_KEYDOWN == event.type) { + switch (event.key.keysym.sym) { + case sdl_save_key: + status = Action_Save; + break; + + case sdl_load_key: + status = Action_Load; + break; + } } // TODO: Menu or other hotkeys } @@ -277,7 +313,13 @@ int nese_frame_ready(void* plat_data) { status = process_events(plat->sys); - // TODO: Perform menu actions + if (Action_Save == status) { + status = save_state(plat->sys, plat->cart.filename); + } else if (Action_Load == status) { + status = load_state(plat->sys, plat->cart.filename); + } + + // TODO: Perform more actions if (0 == status) { plat->t_target += FRAME_TIME_NS; @@ -397,7 +439,8 @@ static int load_cart(const char* filename, platform_data* plat) { status = -1; } else { filesize = nese_file_size(file); - cart_data = nese_map_file(file, filesize); + cart_data = nese_map_file(file, filesize, + Filemap_Mode_Read); if (NULL == cart_data) { fprintf(stderr, "Failed to map %s\n", filename); status = -1; diff --git a/src/map/map001.c b/src/map/map001.c index b5254f3..aa40ac6 100644 --- a/src/map/map001.c +++ b/src/map/map001.c @@ -88,7 +88,10 @@ static void map001_reset(nes_Mapper* map, nes_Memory* mem) { static int map001_init(nes_Mapper* map, const ines_Header* hdr, nes_Memory* mem) { - map001_reset(map, mem); + map001_data* data = (map001_data*)map->data; + mmc1_update_prg(data, mem); + mmc1_update_chr(data, mem); + mmc1_update_vram(data, mem); return 0; } diff --git a/src/map/map002.c b/src/map/map002.c index 0890b0c..4b96b66 100644 --- a/src/map/map002.c +++ b/src/map/map002.c @@ -23,7 +23,8 @@ static int map002_init(nes_Mapper* map, const ines_Header* hdr, int last_bank = (mem->n_rom_banks / 2) - 1; mem->rom_bank[2] = prg_rom_page(mem, (last_bank * 2) + 0); mem->rom_bank[3] = prg_rom_page(mem, (last_bank * 2) + 1); - map002_reset(map, mem); + uxrom_set_bank((map002_data*)map->data, mem, + ((map002_data*)map->data)->bank); return 0; } diff --git a/src/map/map003.c b/src/map/map003.c index 6ffcb4d..43b897f 100644 --- a/src/map/map003.c +++ b/src/map/map003.c @@ -26,7 +26,8 @@ static void map003_reset(nes_Mapper* map, nes_Memory* mem) { static int map003_init(nes_Mapper* map, const ines_Header* hdr, nes_Memory* mem) { - map003_reset(map, mem); + cnrom_set_bank((map003_data*)map->data, mem, + ((map003_data*)map->data)->bank); return 0; } diff --git a/src/map/map004.c b/src/map/map004.c index a1a7594..f72511d 100644 --- a/src/map/map004.c +++ b/src/map/map004.c @@ -129,18 +129,22 @@ static void map004_reset(nes_Mapper* map, nes_Memory* mem) { data->irq_count = 0; data->irq_latch = 0; mmc3_update_rom_mode(data, mem, 0); - mmc3_map_prg(mem, 3, mem->n_rom_banks - 1); mmc3_update_vram(data, mem); } static int map004_init(nes_Mapper* map, const ines_Header* hdr, nes_Memory* mem) { map004_data* data = (map004_data*)map->data; - data->flags = (hdr->flags_6 & ines_Flag_Vert_Mirror) ? - mmc3_Flag_Horizontal : 0; - data->bank_select = mmc3_Bank_Select_PRG | - mmc3_Bank_Select_CHR; - map004_reset(map, mem); + if (hdr) { + data->flags = (hdr->flags_6 & ines_Flag_Vert_Mirror) ? + mmc3_Flag_Horizontal : 0; + } + uint8_t bank_select = data->bank_select; + data->bank_select ^= mmc3_Bank_Select_PRG | + mmc3_Bank_Select_CHR; + mmc3_update_rom_mode(data, mem, bank_select); + mmc3_map_prg(mem, 3, mem->n_rom_banks - 1); + mmc3_update_vram(data, mem); return 0; } diff --git a/src/mapper.c b/src/mapper.c index 3d4f05a..193b80d 100644 --- a/src/mapper.c +++ b/src/mapper.c @@ -1,3 +1,5 @@ +#include + #include "mapper.h" @@ -14,3 +16,25 @@ const nes_Mapper* nes_mappers[256] = { &map003, &map004, }; + + +/* Ser/Des */ + +int mapper_serialize(void* dst, const void* src, int avail, const void*) { + const nes_Mapper* mapper = (const nes_Mapper*)src; + if (avail > mapper->data_size) avail = mapper->data_size; + memcpy(dst, mapper->data, avail); + return avail; +} + +int mapper_deserialize(void* dst, const void* src, int avail, const void*) { + nes_Mapper* mapper = (nes_Mapper*)dst; + if (avail > mapper->data_size) avail = mapper->data_size; + memcpy(mapper->data, src, avail); + return avail; +} + +int mapper_serdes_size(const void* _mapper, const void*) { + const nes_Mapper* mapper = (const nes_Mapper*)_mapper; + return mapper->data_size; +} diff --git a/src/mapper.h b/src/mapper.h index 1faeafd..2d7ecd2 100644 --- a/src/mapper.h +++ b/src/mapper.h @@ -38,4 +38,9 @@ typedef struct nes_Mapper nes_Mapper; extern const nes_Mapper* nes_mappers[256]; +int mapper_serialize(void* dst, const void* src, int avail, const void*); +int mapper_deserialize(void* dst, const void* src, int avail, const void*); +int mapper_serdes_size(const void* src, const void*); + + #endif // NESE_MAPPER_H_ diff --git a/src/memory.c b/src/memory.c new file mode 100644 index 0000000..7874ad7 --- /dev/null +++ b/src/memory.c @@ -0,0 +1,10 @@ +#include "memory.h" + + +const Serdes_Item nes_memory_serdes[] = { + {offsetof(nes_Memory, mapper), mapper_deserialize, mapper_serialize, mapper_serdes_size}, + {offsetof(nes_Memory, ppu), deserialize, serialize, serdes_size, nes_ppu_memory_serdes}, + {offsetof(nes_Memory, input), serdes_copy, serdes_copy, serdes_copy_size, (void*)(sizeof(nes_Memory) - offsetof(nes_Memory, input))}, + {0}, +}; + diff --git a/src/memory.h b/src/memory.h index c905191..7c307a6 100644 --- a/src/memory.h +++ b/src/memory.h @@ -8,6 +8,8 @@ #include "mapper.h" #include "ppu.h" +#include "serdes.h" + #define NES_RAM_SIZE (0x2000U) #define NES_SRAM_SIZE (0x2000U) @@ -23,18 +25,23 @@ struct nes_Memory { #ifdef F6502_FLAT uint8_t ram[65536]; #else - uint8_t ram[NES_RAM_SIZE / 4]; // Mirrored 3x - uint8_t sram[NES_SRAM_SIZE]; - nes_Memory_Flag flags; - uint8_t reserved[3]; + // Dynamic (set on init/reload) uint8_t* sram_bank; // Mapped to 0x6000 - 0x7FFF uint8_t* rom; uint8_t* rom_bank[4]; - int n_rom_banks; - nes_PPU_Memory ppu; + + // Dynamic (specific init/reload) nes_Mapper mapper; + nes_PPU_Memory ppu; + + // Static nes_Input input; nes_APU_Memory apu; + + uint8_t ram[NES_RAM_SIZE / 4]; // Mirrored 3x + uint8_t sram[NES_SRAM_SIZE]; + nes_Memory_Flag flags; + int n_rom_banks; #endif }; typedef struct nes_Memory nes_Memory; @@ -45,4 +52,7 @@ static inline uint8_t* prg_rom_page(nes_Memory* mem, int page) { } +extern const Serdes_Item nes_memory_serdes[]; + + #endif // NESE_MEMORY_H_ diff --git a/src/nes.c b/src/nes.c index ee5cae9..1fc87c8 100644 --- a/src/nes.c +++ b/src/nes.c @@ -3,7 +3,7 @@ #include "nes.h" #include "port.h" -#define NESE_DEBUG "NES" +//#define NESE_DEBUG "NES" #include "log.h" @@ -16,6 +16,10 @@ void nes_init(nes* sys, void* plat) { void nes_reset(nes* sys) { f6502_reset(&sys->core); + nes_Mapper* mapper = &sys->core.memory.mapper; + if (mapper->reset) { + mapper->reset(mapper, &sys->core.memory); + } // TODO: Reset PPU // TODO: Reset APU } @@ -191,3 +195,12 @@ int nes_loop(nes* sys, void* plat) { return status; } + + +/* Ser/Des */ + +const Serdes_Item nes_serdes[] = { + {offsetof(nes, core), deserialize, serialize, serdes_size, f6502_serdes}, + {offsetof(nes, ppu), serdes_copy, serdes_copy, serdes_copy_size, (void*)(sizeof(nes_PPU) + sizeof(nes_APU))}, + {0} +}; diff --git a/src/nes.h b/src/nes.h index e4114d9..99cc700 100644 --- a/src/nes.h +++ b/src/nes.h @@ -8,8 +8,13 @@ typedef struct { + // Already set const ines_Header* cart_header; + + // Specific f6502_Core core; + + // Static nes_PPU ppu; nes_APU apu; } nes; @@ -20,4 +25,7 @@ void nes_done(nes*); int nes_loop(nes*, void*); +extern const Serdes_Item nes_serdes[]; + + #endif // NES_H_ diff --git a/src/port.h b/src/port.h index 122ef28..7977d11 100644 --- a/src/port.h +++ b/src/port.h @@ -6,12 +6,24 @@ #include "input.h" +typedef enum { + Filemap_Mode_Read = 0, + Filemap_Mode_Write, +} Filemap_Mode; + +void* nese_map_file(FILE* file, int size, Filemap_Mode); +void nese_unmap_file(void* mem, int size); + +int nese_mkdir(const char* dirname); + + int nese_frame_start(void*, uint8_t background); int nese_line_ready(void*, uint8_t* buffer, int line); int nese_frame_ready(void*); int nese_update_input(void*, nes_Input*); int nese_get_audio_frequency(void*); + void* nese_alloc_gpu(int); void* nese_alloc_cpu(int); void* nese_alloc(int); diff --git a/src/ppu.c b/src/ppu.c index b626bd6..d6b7297 100644 --- a/src/ppu.c +++ b/src/ppu.c @@ -5,6 +5,8 @@ //#define NESE_DEBUG "PPU" #include "log.h" +#include "serdes.h" + void nes_ppu_init(nes_PPU* ppu, nes_PPU_Memory* mem) { uint8_t* pal = mem->palette; @@ -362,3 +364,40 @@ void nes_ppu_render_line(nes_PPU* ppu, nes_PPU_Memory* mem) { ((y_scroll & 0xF8U) << 2); } } + +static int nes_ppu_chr_ram_size(const void* _ppu, const void*) { + const nes_PPU_Memory* ppu = (const nes_PPU_Memory*)_ppu; + return (ppu->chr_ram ? + ppu->n_chr_banks * NES_CHR_ROM_PAGE_SIZE : + 0); +} + +static int nes_ppu_read_chr_ram(void* dst, const void* src, + int avail, const void*) { + int size = 0; + nes_PPU_Memory* ppu = (nes_PPU_Memory*)dst; + if (ppu->chr_ram) { + size = ppu->n_chr_banks * NES_CHR_ROM_PAGE_SIZE; + if (size > avail) size = avail; + memcpy(ppu->chr_ram, src, size); + } + return size; +} + +static int nes_ppu_write_chr_ram(void* dst, const void* src, + int avail, const void*) { + int size = 0; + const nes_PPU_Memory* ppu = (const nes_PPU_Memory*)src; + if (ppu->chr_ram) { + size = ppu->n_chr_banks * NES_CHR_ROM_PAGE_SIZE; + if (size > avail) size = avail; + memcpy(dst, ppu->chr_ram, size); + } + return size; +} + +const Serdes_Item nes_ppu_memory_serdes[] = { + {0, nes_ppu_read_chr_ram, nes_ppu_write_chr_ram, nes_ppu_chr_ram_size}, + {offsetof(nes_PPU_Memory, ctrl), serdes_copy, serdes_copy, serdes_copy_size, (void*)(sizeof(nes_PPU_Memory) - offsetof(nes_PPU_Memory, ctrl))}, + {0} +}; diff --git a/src/ppu.h b/src/ppu.h index 60f3325..0b16ee4 100644 --- a/src/ppu.h +++ b/src/ppu.h @@ -3,6 +3,8 @@ #include +#include "serdes.h" + #define nes_ppu_render_w (256U) #define nes_ppu_render_h (240U) @@ -83,6 +85,13 @@ typedef enum { #define NES_PPU_PAL_START (0x3F00U) typedef struct { + // Dynamic memory banks + uint8_t* chr; + uint8_t* bank[16]; + + // Nullable buffer + uint8_t* chr_ram; + // Registers &c. uint8_t ctrl; uint8_t mask; @@ -95,11 +104,8 @@ typedef struct { uint8_t latch; // aka "w" - TODO: Could this be a flag? uint8_t addr_inc; // Auto-increment (1 or 32) - // Memory banks - uint8_t* chr; + // Static memory banks int n_chr_banks; - uint8_t* chr_ram; // TODO: Could this be a flag? - uint8_t* bank[16]; uint8_t palette[32]; // Rendering palette with transparency masked uint8_t vram[NES_PPU_VRAM_SIZE]; uint8_t pal_bank[NES_CHR_ROM_PAGE_SIZE]; // Raw palette data mirrored in banks 12-15, also pal @@ -143,4 +149,7 @@ void nes_ppu_find_hit_line(nes_PPU*, nes_PPU_Memory*); void nes_ppu_render_line(nes_PPU*, nes_PPU_Memory*); +extern const Serdes_Item nes_ppu_memory_serdes[]; + + #endif // NESE_PPU_H_ diff --git a/src/save.c b/src/save.c new file mode 100644 index 0000000..53cf6ec --- /dev/null +++ b/src/save.c @@ -0,0 +1,178 @@ +#include +#include + +#include "port.h" +#include "serdes.h" +#include "save.h" + + +/* File Helpers */ + +static const char* basename(const char* filename) { + const char* slash = filename + strlen(filename) - 1; + for ( ; slash >= filename && + *slash != '\\' && + *slash != '/'; + --slash ); + return &slash[1]; +} + +static int make_filename(char* filename, int max_len, + const char* orig_name, + const char* subdir, const char* ext) { + int status = 0; + int remain = max_len; + + const char* orig_base = basename(orig_name); + const char* orig_dot = strrchr(orig_base, '.'); + + int orig_path_len = orig_base - orig_name; + + int orig_base_len = (NULL == orig_dot) ? + strlen(orig_base) : + (orig_dot - orig_base); + + // Part 1/4: Leading path + if (0 == status && orig_path_len <= remain) { + memcpy(filename, orig_name, orig_path_len); + remain -= orig_path_len; + filename += orig_path_len; + } else { + status = -1; + } + + // Part 2/4: Subdirectory + if (0 == status && NULL != subdir) { + int subdir_len = strlen(subdir); + if ((subdir_len + 1) <= remain) { + memcpy(filename, subdir, subdir_len); + filename += subdir_len; + *filename++ = '/'; + remain -= (subdir_len + 1); + } else { + status = -1; + } + } + + // Part 3/4: Basename + if (0 == status && orig_base_len <= remain) { + memcpy(filename, orig_name, orig_base_len); + remain -= orig_base_len; + filename += orig_base_len; + } else { + status = -1; + } + + // Part 4/4: Extension + if (0 == status && NULL != ext) { + int ext_len = strlen(ext); + if ((ext_len + 1) <= remain) { + *filename++ = '.'; + memcpy(filename, ext, ext_len); + remain -= (ext_len + 1); + filename += ext_len; + } else { + status = -1; + } + } + + return (status < 0 ? status : (max_len - remain)); +} + + +/* System State */ + +static int make_state_filename(char* save_filename, int max_len, + const char* cart_filename) { + return make_filename( save_filename, max_len, + basename(cart_filename), + "save", "nese"); +} + +static int state_size(const nes* sys) { + return serdes_size(sys, nes_serdes); +} + +static int state_read(nes* sys, const void* mem, int size) { + return deserialize(sys, mem, size, nes_serdes); +} + +static int state_write(const nes* sys, void* mem, int size) { + return serialize(mem, sys, size, nes_serdes); +} + +int load_state(nes* sys, const char* cart_filename) { + int size = -1; + + char state_filename[FILENAME_MAX] = {0}; + make_state_filename(state_filename, FILENAME_MAX - 1, + cart_filename); + + FILE* file = fopen(state_filename, "rb"); + + if (NULL != file) { +// int expected_size = state_size(sys); + + fseek(file, 0L, SEEK_END); + int file_size = ftell(file); +// rewind(file); +// if (max_size < size) size = max_size; + + void* mem = nese_map_file(file, file_size, + Filemap_Mode_Read); + + if (NULL != mem) { + size = state_read(sys, mem, file_size); + nese_unmap_file(mem, file_size); + } + + fclose(file); + + if (file_size == size) { + nes_Mapper* mapper = &sys->core.memory.mapper; + if (mapper->init) { + size =mapper->init(mapper, NULL, &sys->core.memory); + } else { + size = 0; + } + } else { + size = -1; + } + } + + return size; +} + +int save_state(const nes* sys, const char* cart_filename) { + int size = -1; + + nese_mkdir("save"); + + char state_filename[FILENAME_MAX] = {0}; + make_state_filename(state_filename, FILENAME_MAX - 1, + cart_filename); + + FILE* file = fopen(state_filename, "w+b"); + + if (NULL != file) { + int file_size = state_size(sys); + + fseek(file, file_size - 1, SEEK_SET); + fwrite("", 1, 1, file); + fflush(file); + + void* mem = nese_map_file(file, file_size, + Filemap_Mode_Write); + + if (NULL != mem) { + size = state_write(sys, mem, file_size); + nese_unmap_file(mem, file_size); + } + + fclose(file); + + size = (size == file_size ? 0 : -1); + } + + return size; +} diff --git a/src/save.h b/src/save.h new file mode 100644 index 0000000..1df9c10 --- /dev/null +++ b/src/save.h @@ -0,0 +1,11 @@ +#ifndef NESE_SAVE_H_ +#define NESE_SAVE_H_ + +#include "nes.h" + + +int load_state(nes*, const char* filename); +int save_state(const nes*, const char* filename); + + +#endif // NESE_SAVE_H_ diff --git a/src/serdes.c b/src/serdes.c new file mode 100644 index 0000000..1d30b25 --- /dev/null +++ b/src/serdes.c @@ -0,0 +1,53 @@ +#include + +#include "serdes.h" + + +int serialize(void* dst, const void* src, int avail, + const void* _list) { + const Serdes_Item* list = (const Serdes_Item*)_list; + void* dst_ptr = dst; + for ( ; list->write; ++list) { + const void* src_ptr = src + list->offset; + int count = list->write(dst_ptr, src_ptr, avail, list->arg); + if (count < 0) break; + dst_ptr += count; + avail -= count; + } + return (dst_ptr - dst); +} + +int deserialize(void* dst, const void* src, int avail, + const void* _list) { + const Serdes_Item* list = (const Serdes_Item*)_list; + const void* src_ptr = src; + for ( ; list->read; ++list) { + void* dst_ptr = dst + list->offset; + int count = list->read(dst_ptr, src_ptr, avail, list->arg); + if (count < 0) break; + src_ptr += count; + avail -= count; + } + return (src_ptr - src); +} + +int serdes_size(const void* src, const void* _list) { + const Serdes_Item* list = (const Serdes_Item*)_list; + int size = 0; + for ( ; list->size; ++list) { + size += list->size(src + list->offset, list->arg); + } + return size; +} + +int serdes_copy(void* dst, const void* src, int avail, + const void* arg) { + int size = (long)arg; + if (size > avail) size = avail; + memcpy(dst, src, size); + return size; +} + +int serdes_copy_size(const void*, const void* arg) { + return (long)arg; +} diff --git a/src/serdes.h b/src/serdes.h new file mode 100644 index 0000000..6ce56a7 --- /dev/null +++ b/src/serdes.h @@ -0,0 +1,29 @@ +#ifndef NESE_SERDES_H_ +#define NESE_SERDES_H_ + +#include + + +typedef int(*serdes_io_fn)(void*, const void*, int, const void*); +typedef int(*serdes_size_fn)(const void*, const void*); + +typedef struct { + long offset; + int (*read)(void* dst, const void* src, int avail, const void* arg); + int (*write)(void* dst, const void* src, int avail, const void* arg); + int (*size)(const void*, const void*); + const void* arg; +} Serdes_Item; + + +int serialize(void* dst, const void* src, int avail, const void* list); +int deserialize(void* dst, const void* src, int avail, const void* list); +int serdes_size(const void*, const void* list); + +int serdes_copy(void* dst, const void* src, int avail, const void* arg); +int serdes_copy_size(const void*, const void* arg); + +// TODO: Compression (Maybe just RLE) + + +#endif // NESE_SERDES_H_