| @@ -30,6 +30,7 @@ MAPDIR = $(SRCDIR)/map | |||||
| NESE_SRC_SRCS = f6502.c f6502_opcodes.c | NESE_SRC_SRCS = f6502.c f6502_opcodes.c | ||||
| NESE_SRC_SRCS += nese.c nes.c cart.c mapper.c | NESE_SRC_SRCS += nese.c nes.c cart.c mapper.c | ||||
| NESE_SRC_SRCS += ppu.c apu.c | NESE_SRC_SRCS += ppu.c apu.c | ||||
| NESE_SRC_SRCS += memory.c serdes.c save.c | |||||
| NESE_SRC_SRCS += $(OS)/port.c | NESE_SRC_SRCS += $(OS)/port.c | ||||
| NESE_MAP_SRCS = $(notdir $(wildcard $(MAPDIR)/*.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 &= ~(ppu_Control_Nametable_Mask << 10); | ||||
| mem->ppu.t |= ((uint16_t)val & 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; | mem->ppu.addr_inc = (val & ppu_Control_VRAM_Inc) ? 32 : 1; | ||||
| // TODO: Trigger NMI if VBlank status is set? | |||||
| break; | break; | ||||
| case ppu_reg_mask: | case ppu_reg_mask: | ||||
| @@ -1725,3 +1724,12 @@ int f6502_step(f6502_Core* core, int clocks) { | |||||
| core->clocks += clocks_elapsed; | core->clocks += clocks_elapsed; | ||||
| return 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_ | #ifndef F6502_H_ | ||||
| #define F6502_H_ | #define F6502_H_ | ||||
| #include "serdes.h" | |||||
| #include <stdbool.h> | #include <stdbool.h> | ||||
| #include <stdint.h> | #include <stdint.h> | ||||
| @@ -41,10 +44,15 @@ typedef enum { | |||||
| } f6502_Interrupt; | } f6502_Interrupt; | ||||
| struct f6502_Core { | struct f6502_Core { | ||||
| uint64_t clocks; | |||||
| // Static | |||||
| f6502_Registers registers; | f6502_Registers registers; | ||||
| f6502_Interrupt interrupts; | f6502_Interrupt interrupts; | ||||
| // Specific | |||||
| nes_Memory memory; | nes_Memory memory; | ||||
| // Don't care | |||||
| uint64_t clocks; | |||||
| }; | }; | ||||
| typedef struct f6502_Core f6502_Core; | 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); | void f6502_set_IRQ(f6502_Core*, bool active); | ||||
| extern const Serdes_Item f6502_serdes[]; | |||||
| #endif // F6502_H_ | #endif // F6502_H_ | ||||
| @@ -1,6 +1,8 @@ | |||||
| #ifndef NESE_INPUT_H_ | #ifndef NESE_INPUT_H_ | ||||
| #define NESE_INPUT_H_ | #define NESE_INPUT_H_ | ||||
| #include <stdint.h> | |||||
| #define nes_controller_bus_mask (0b11111000) | #define nes_controller_bus_mask (0b11111000) | ||||
| @@ -2,14 +2,18 @@ | |||||
| #include <stdio.h> | #include <stdio.h> | ||||
| #include <time.h> | #include <time.h> | ||||
| #include <sys/mman.h> | |||||
| #include <unistd.h> | #include <unistd.h> | ||||
| #include <sys/mman.h> | |||||
| #include <sys/stat.h> | |||||
| #include <SDL.h> | #include <SDL.h> | ||||
| #include "cart.h" | #include "cart.h" | ||||
| #include "nese.h" | #include "nese.h" | ||||
| #include "port.h" | #include "port.h" | ||||
| #include "save.h" | |||||
| #define DEBUG "Port" | #define DEBUG "Port" | ||||
| #include "log.h" | #include "log.h" | ||||
| @@ -20,20 +24,28 @@ | |||||
| * Memory mapping specifically needs to be ported for each OS | * 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) { | static int nese_file_size(FILE* file) { | ||||
| int size = -1; | int size = -1; | ||||
| if (0 == fseek(file, 0, SEEK_END)) { | if (0 == fseek(file, 0, SEEK_END)) { | ||||
| @@ -89,13 +101,26 @@ typedef struct { | |||||
| /* Input */ | /* 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) { | int nese_update_input(void* plat_data, nes_Input* input) { | ||||
| // Gamepad states are already updated in nese_frame_ready() | // Gamepad states are already updated in nese_frame_ready() | ||||
| return 0; | 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] = { | static const int sdl_keycodes[nes_controller_num_buttons] = { | ||||
| SDLK_a, | SDLK_a, | ||||
| @@ -140,6 +165,17 @@ static int process_events(nes* sys) { | |||||
| } else { | } else { | ||||
| input->gamepads[0].buttons &= ~mask; | 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 | // TODO: Menu or other hotkeys | ||||
| } | } | ||||
| @@ -277,7 +313,13 @@ int nese_frame_ready(void* plat_data) { | |||||
| status = process_events(plat->sys); | 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) { | if (0 == status) { | ||||
| plat->t_target += FRAME_TIME_NS; | plat->t_target += FRAME_TIME_NS; | ||||
| @@ -397,7 +439,8 @@ static int load_cart(const char* filename, platform_data* plat) { | |||||
| status = -1; | status = -1; | ||||
| } else { | } else { | ||||
| filesize = nese_file_size(file); | 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) { | if (NULL == cart_data) { | ||||
| fprintf(stderr, "Failed to map %s\n", filename); | fprintf(stderr, "Failed to map %s\n", filename); | ||||
| status = -1; | 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, | static int map001_init(nes_Mapper* map, const ines_Header* hdr, | ||||
| nes_Memory* mem) { | 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; | 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; | int last_bank = (mem->n_rom_banks / 2) - 1; | ||||
| mem->rom_bank[2] = prg_rom_page(mem, (last_bank * 2) + 0); | mem->rom_bank[2] = prg_rom_page(mem, (last_bank * 2) + 0); | ||||
| mem->rom_bank[3] = prg_rom_page(mem, (last_bank * 2) + 1); | 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; | 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, | static int map003_init(nes_Mapper* map, const ines_Header* hdr, | ||||
| nes_Memory* mem) { | nes_Memory* mem) { | ||||
| map003_reset(map, mem); | |||||
| cnrom_set_bank((map003_data*)map->data, mem, | |||||
| ((map003_data*)map->data)->bank); | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| @@ -129,18 +129,22 @@ static void map004_reset(nes_Mapper* map, nes_Memory* mem) { | |||||
| data->irq_count = 0; | data->irq_count = 0; | ||||
| data->irq_latch = 0; | data->irq_latch = 0; | ||||
| mmc3_update_rom_mode(data, mem, 0); | mmc3_update_rom_mode(data, mem, 0); | ||||
| mmc3_map_prg(mem, 3, mem->n_rom_banks - 1); | |||||
| mmc3_update_vram(data, mem); | mmc3_update_vram(data, mem); | ||||
| } | } | ||||
| static int map004_init(nes_Mapper* map, const ines_Header* hdr, | static int map004_init(nes_Mapper* map, const ines_Header* hdr, | ||||
| nes_Memory* mem) { | nes_Memory* mem) { | ||||
| map004_data* data = (map004_data*)map->data; | 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; | return 0; | ||||
| } | } | ||||
| @@ -1,3 +1,5 @@ | |||||
| #include <string.h> | |||||
| #include "mapper.h" | #include "mapper.h" | ||||
| @@ -14,3 +16,25 @@ const nes_Mapper* nes_mappers[256] = { | |||||
| &map003, | &map003, | ||||
| &map004, | &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]; | 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_ | #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 "mapper.h" | ||||
| #include "ppu.h" | #include "ppu.h" | ||||
| #include "serdes.h" | |||||
| #define NES_RAM_SIZE (0x2000U) | #define NES_RAM_SIZE (0x2000U) | ||||
| #define NES_SRAM_SIZE (0x2000U) | #define NES_SRAM_SIZE (0x2000U) | ||||
| @@ -23,18 +25,23 @@ struct nes_Memory { | |||||
| #ifdef F6502_FLAT | #ifdef F6502_FLAT | ||||
| uint8_t ram[65536]; | uint8_t ram[65536]; | ||||
| #else | #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* sram_bank; // Mapped to 0x6000 - 0x7FFF | ||||
| uint8_t* rom; | uint8_t* rom; | ||||
| uint8_t* rom_bank[4]; | uint8_t* rom_bank[4]; | ||||
| int n_rom_banks; | |||||
| nes_PPU_Memory ppu; | |||||
| // Dynamic (specific init/reload) | |||||
| nes_Mapper mapper; | nes_Mapper mapper; | ||||
| nes_PPU_Memory ppu; | |||||
| // Static | |||||
| nes_Input input; | nes_Input input; | ||||
| nes_APU_Memory apu; | 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 | #endif | ||||
| }; | }; | ||||
| typedef struct nes_Memory nes_Memory; | 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_ | #endif // NESE_MEMORY_H_ | ||||
| @@ -3,7 +3,7 @@ | |||||
| #include "nes.h" | #include "nes.h" | ||||
| #include "port.h" | #include "port.h" | ||||
| #define NESE_DEBUG "NES" | |||||
| //#define NESE_DEBUG "NES" | |||||
| #include "log.h" | #include "log.h" | ||||
| @@ -16,6 +16,10 @@ void nes_init(nes* sys, void* plat) { | |||||
| void nes_reset(nes* sys) { | void nes_reset(nes* sys) { | ||||
| f6502_reset(&sys->core); | 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 PPU | ||||
| // TODO: Reset APU | // TODO: Reset APU | ||||
| } | } | ||||
| @@ -191,3 +195,12 @@ int nes_loop(nes* sys, void* plat) { | |||||
| return status; | 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 { | typedef struct { | ||||
| // Already set | |||||
| const ines_Header* cart_header; | const ines_Header* cart_header; | ||||
| // Specific | |||||
| f6502_Core core; | f6502_Core core; | ||||
| // Static | |||||
| nes_PPU ppu; | nes_PPU ppu; | ||||
| nes_APU apu; | nes_APU apu; | ||||
| } nes; | } nes; | ||||
| @@ -20,4 +25,7 @@ void nes_done(nes*); | |||||
| int nes_loop(nes*, void*); | int nes_loop(nes*, void*); | ||||
| extern const Serdes_Item nes_serdes[]; | |||||
| #endif // NES_H_ | #endif // NES_H_ | ||||
| @@ -6,12 +6,24 @@ | |||||
| #include "input.h" | #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_frame_start(void*, uint8_t background); | ||||
| int nese_line_ready(void*, uint8_t* buffer, int line); | int nese_line_ready(void*, uint8_t* buffer, int line); | ||||
| int nese_frame_ready(void*); | int nese_frame_ready(void*); | ||||
| int nese_update_input(void*, nes_Input*); | int nese_update_input(void*, nes_Input*); | ||||
| int nese_get_audio_frequency(void*); | int nese_get_audio_frequency(void*); | ||||
| void* nese_alloc_gpu(int); | void* nese_alloc_gpu(int); | ||||
| void* nese_alloc_cpu(int); | void* nese_alloc_cpu(int); | ||||
| void* nese_alloc(int); | void* nese_alloc(int); | ||||
| @@ -5,6 +5,8 @@ | |||||
| //#define NESE_DEBUG "PPU" | //#define NESE_DEBUG "PPU" | ||||
| #include "log.h" | #include "log.h" | ||||
| #include "serdes.h" | |||||
| void nes_ppu_init(nes_PPU* ppu, nes_PPU_Memory* mem) { | void nes_ppu_init(nes_PPU* ppu, nes_PPU_Memory* mem) { | ||||
| uint8_t* pal = mem->palette; | 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); | ((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 <stdint.h> | ||||
| #include "serdes.h" | |||||
| #define nes_ppu_render_w (256U) | #define nes_ppu_render_w (256U) | ||||
| #define nes_ppu_render_h (240U) | #define nes_ppu_render_h (240U) | ||||
| @@ -83,6 +85,13 @@ typedef enum { | |||||
| #define NES_PPU_PAL_START (0x3F00U) | #define NES_PPU_PAL_START (0x3F00U) | ||||
| typedef struct { | typedef struct { | ||||
| // Dynamic memory banks | |||||
| uint8_t* chr; | |||||
| uint8_t* bank[16]; | |||||
| // Nullable buffer | |||||
| uint8_t* chr_ram; | |||||
| // Registers &c. | // Registers &c. | ||||
| uint8_t ctrl; | uint8_t ctrl; | ||||
| uint8_t mask; | uint8_t mask; | ||||
| @@ -95,11 +104,8 @@ typedef struct { | |||||
| uint8_t latch; // aka "w" - TODO: Could this be a flag? | uint8_t latch; // aka "w" - TODO: Could this be a flag? | ||||
| uint8_t addr_inc; // Auto-increment (1 or 32) | uint8_t addr_inc; // Auto-increment (1 or 32) | ||||
| // Memory banks | |||||
| uint8_t* chr; | |||||
| // Static memory banks | |||||
| int n_chr_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 palette[32]; // Rendering palette with transparency masked | ||||
| uint8_t vram[NES_PPU_VRAM_SIZE]; | 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 | 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*); | void nes_ppu_render_line(nes_PPU*, nes_PPU_Memory*); | ||||
| extern const Serdes_Item nes_ppu_memory_serdes[]; | |||||
| #endif // NESE_PPU_H_ | #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_ | |||||