| @@ -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)) | |||
| @@ -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} | |||
| }; | |||
| @@ -1,6 +1,9 @@ | |||
| #ifndef F6502_H_ | |||
| #define F6502_H_ | |||
| #include "serdes.h" | |||
| #include <stdbool.h> | |||
| #include <stdint.h> | |||
| @@ -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_ | |||
| @@ -1,6 +1,8 @@ | |||
| #ifndef NESE_INPUT_H_ | |||
| #define NESE_INPUT_H_ | |||
| #include <stdint.h> | |||
| #define nes_controller_bus_mask (0b11111000) | |||
| @@ -2,14 +2,18 @@ | |||
| #include <stdio.h> | |||
| #include <time.h> | |||
| #include <sys/mman.h> | |||
| #include <unistd.h> | |||
| #include <sys/mman.h> | |||
| #include <sys/stat.h> | |||
| #include <SDL.h> | |||
| #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; | |||
| @@ -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; | |||
| } | |||
| @@ -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; | |||
| } | |||
| @@ -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; | |||
| } | |||
| @@ -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; | |||
| } | |||
| @@ -1,3 +1,5 @@ | |||
| #include <string.h> | |||
| #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; | |||
| } | |||
| @@ -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_ | |||
| @@ -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}, | |||
| }; | |||
| @@ -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_ | |||
| @@ -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} | |||
| }; | |||
| @@ -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_ | |||
| @@ -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); | |||
| @@ -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} | |||
| }; | |||
| @@ -3,6 +3,8 @@ | |||
| #include <stdint.h> | |||
| #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_ | |||
| @@ -0,0 +1,178 @@ | |||
| #include <stdio.h> | |||
| #include <string.h> | |||
| #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; | |||
| } | |||
| @@ -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_ | |||
| @@ -0,0 +1,53 @@ | |||
| #include <string.h> | |||
| #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; | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| #ifndef NESE_SERDES_H_ | |||
| #define NESE_SERDES_H_ | |||
| #include <stddef.h> | |||
| 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_ | |||