| @@ -16,9 +16,7 @@ | |||
| #define APU_ERR ERR_LOG | |||
| #define nes_apu_frame_period = 7450; | |||
| typedef enum { | |||
| typedef enum __attribute__ ((__packed__)) { | |||
| apu_Status_Square_0 = 0b00000001, | |||
| apu_Status_Square_1 = 0b00000010, | |||
| apu_Status_Triangle = 0b00000100, | |||
| @@ -85,9 +83,15 @@ typedef enum { | |||
| apu_Channel_Start = 0b00000010, | |||
| } nes_apu_Channel_Flag; | |||
| #define nes_apu_reg_base (0x4000U) | |||
| #define nes_apu_chan_count (5U) | |||
| #define nes_apu_chan_size (4U) | |||
| #define nes_apu_reg_status (0x4015U) | |||
| #define nes_apu_reg_frame (0x4017U) | |||
| typedef struct nes_apu_Channel_t { | |||
| uint8_t reg[4]; | |||
| void(*write)(struct nes_apu_Channel_t*, int reg, uint8_t); | |||
| int gain; | |||
| int length; // Active counter | |||
| int delay; // Cycles until next state change | |||
| @@ -125,13 +129,11 @@ typedef struct nes_apu_Channel_t { | |||
| uint16_t addr; | |||
| }; | |||
| }; | |||
| void(*write)(struct nes_apu_Channel_t*, int reg, uint8_t); | |||
| } nes_apu_Channel; | |||
| typedef struct { | |||
| nes_apu_Channel channels[5]; | |||
| uint8_t (*mem_read)(void*, uint16_t); | |||
| void* arg_mem; | |||
| struct blip_t* blip; | |||
| nes_apu_Status status; | |||
| uint8_t frame_reg; | |||
| int frame; | |||
| @@ -139,6 +141,12 @@ typedef struct { | |||
| int frame_period; | |||
| int time; | |||
| int frame_time_elapsed; | |||
| nes_apu_Channel channels[nes_apu_chan_count]; | |||
| uint8_t (*mem_read)(void*, uint16_t); | |||
| void* arg_mem; | |||
| struct blip_t* blip; | |||
| } nes_apu; | |||
| typedef enum { | |||
| @@ -146,12 +154,6 @@ typedef enum { | |||
| apu_Result_IRQ, | |||
| } nes_apu_Result; | |||
| #define nes_apu_reg_base (0x4000U) | |||
| #define nes_apu_chan_count (5U) | |||
| #define nes_apu_chan_size (4U) | |||
| #define nes_apu_reg_status (0x4015U) | |||
| #define nes_apu_reg_frame (0x4017U) | |||
| int nes_apu_init(nes_apu*, int clock, int frequency, | |||
| uint8_t(*)(void*, uint16_t), void*); | |||
| void nes_apu_done(nes_apu*); | |||
| @@ -90,7 +90,7 @@ int nes_cart_init_file(nes_cart* cart, FILE* file) { | |||
| // Map file | |||
| if (0 == status) { | |||
| mem = map_file(file, size); | |||
| mem = map_file(file, size, Filemap_Mode_Read); | |||
| if (NULL == mem) { | |||
| INES_ERR("Failed to map file (%d bytes)", size); | |||
| status = -1; | |||
| @@ -2,7 +2,13 @@ | |||
| #define NESE_FILEMAP_H_ | |||
| void* map_file(FILE* file, int size); | |||
| typedef enum { | |||
| Filemap_Mode_Read = 0, | |||
| Filemap_Mode_Write, | |||
| } Filemap_Mode; | |||
| void* map_file(FILE* file, int size, Filemap_Mode); | |||
| void unmap_file(void* mem, int size); | |||
| @@ -12,6 +12,8 @@ typedef enum { | |||
| input_Result_OK = 0, | |||
| input_Result_Quit = 1, | |||
| input_Result_Reset = 2, | |||
| input_Result_Save = 3, | |||
| input_Result_Load = 4, | |||
| } nes_Input_Result; | |||
| #define nes_controller_num_buttons (8U) | |||
| @@ -1,3 +1,4 @@ | |||
| #include <errno.h> | |||
| #include <stdio.h> | |||
| #include <sys/mman.h> | |||
| @@ -5,9 +6,17 @@ | |||
| #include "filemap.h" | |||
| void* map_file(FILE* file, int size) { | |||
| return mmap(NULL, size, PROT_READ, MAP_PRIVATE, | |||
| fileno(file), 0); | |||
| void* 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 mem; | |||
| } | |||
| void unmap_file(void* mem, int size) { | |||
| @@ -1,4 +1,5 @@ | |||
| #include <stdlib.h> | |||
| #include <string.h> | |||
| #include "map.h" | |||
| @@ -9,15 +10,23 @@ typedef struct { | |||
| uint8_t* chr_rom; | |||
| int chr_rom_banks; | |||
| uint8_t mirror; | |||
| uint32_t bank; | |||
| uint8_t vram[nes_vram_page_size * 2]; | |||
| uint8_t* chr_bank; | |||
| } cnrom_mapper; | |||
| static void cnrom_set_bank(cnrom_mapper* map, uint8_t bank) { | |||
| map->bank = (bank % map->chr_rom_banks); | |||
| map->chr_bank = &map->chr_rom[ | |||
| map->bank * (nes_chr_page_size * 2) | |||
| ]; | |||
| } | |||
| static void cnrom_reset(nes_mapper* nes_map) { | |||
| cnrom_mapper* map = (cnrom_mapper*)nes_map->data; | |||
| map->chr_bank = 0; | |||
| cnrom_set_bank(map, 0); | |||
| } | |||
| static int cnrom_init(nes_mapper* nes_map, nes_cart* cart) { | |||
| @@ -60,8 +69,7 @@ static void cnrom_write(nes_mapper* nes_map, | |||
| uint16_t addr, uint8_t val) { | |||
| cnrom_mapper* map = (cnrom_mapper*)nes_map->data; | |||
| if (addr >= nes_mem_rom_start) { | |||
| map->chr_bank = &map->chr_rom[(val % map->chr_rom_banks) * | |||
| (nes_chr_page_size * 2)]; | |||
| cnrom_set_bank(map, val); | |||
| } | |||
| } | |||
| @@ -84,6 +92,41 @@ static void cnrom_chr_write(nes_mapper* nes_map, | |||
| // ROM only. | |||
| } | |||
| /* Save State */ | |||
| int cnrom_state_size(const nes_mapper* nes_map) { | |||
| cnrom_mapper* map = (cnrom_mapper*)nes_map->data; | |||
| return (sizeof(map->bank) + sizeof(map->vram)); | |||
| } | |||
| int cnrom_state_read(nes_mapper* nes_map, const void* data) { | |||
| cnrom_mapper* map = (cnrom_mapper*)nes_map->data; | |||
| const void* ptr = data; | |||
| memcpy(&map->bank, ptr, sizeof(map->bank)); | |||
| ptr += sizeof(map->bank); | |||
| memcpy(map->vram, ptr, sizeof(map->vram)); | |||
| ptr += sizeof(map->vram); | |||
| return (ptr - data); | |||
| } | |||
| int cnrom_state_write(const nes_mapper* nes_map, void* data) { | |||
| cnrom_mapper* map = (cnrom_mapper*)nes_map->data; | |||
| void* ptr = data; | |||
| memcpy(ptr, &map->bank, sizeof(map->bank)); | |||
| ptr += sizeof(map->bank); | |||
| memcpy(ptr, map->vram, sizeof(map->vram)); | |||
| ptr += sizeof(map->vram); | |||
| return (ptr - data); | |||
| } | |||
| nes_mapper mapper_cnrom = { | |||
| .name = "CNROM", | |||
| .init = cnrom_init, | |||
| @@ -94,4 +137,8 @@ nes_mapper mapper_cnrom = { | |||
| .chr_addr = cnrom_chr_addr, | |||
| .vram_addr = cnrom_vram_addr, | |||
| .chr_write = cnrom_chr_write, | |||
| .state_size = cnrom_state_size, | |||
| .state_read = cnrom_state_read, | |||
| .state_write = cnrom_state_write, | |||
| }; | |||
| @@ -1,4 +1,5 @@ | |||
| #include <stdlib.h> | |||
| #include <string.h> | |||
| #include "map.h" | |||
| @@ -11,6 +12,12 @@ typedef struct { | |||
| uint8_t battery; | |||
| uint8_t* chr_bank[2]; | |||
| uint8_t* vram_bank[4]; | |||
| uint8_t* prg_bank[2]; | |||
| uint8_t* chr_ram; | |||
| uint8_t* chr; | |||
| uint8_t reg_shift; | |||
| uint8_t reg_n_shift; | |||
| uint8_t reg_control; | |||
| @@ -18,13 +25,6 @@ typedef struct { | |||
| uint8_t reg_chr_1; | |||
| uint8_t reg_prg; | |||
| uint8_t* chr_bank[2]; | |||
| uint8_t* vram_bank[4]; | |||
| uint8_t* prg_bank[2]; | |||
| uint8_t* chr_ram; | |||
| uint8_t* chr; | |||
| uint8_t vram[2][nes_vram_page_size]; | |||
| uint8_t wram[nes_mem_wram_size]; | |||
| } mmc1_mapper; | |||
| @@ -235,6 +235,30 @@ static int mmc1_sram_size(nes_mapper* nes_map) { | |||
| } | |||
| /* Save State */ | |||
| int mmc1_state_size(const nes_mapper* nes_map) { | |||
| mmc1_mapper* map = (mmc1_mapper*)nes_map->data; | |||
| return ( (void*)map + | |||
| sizeof(*map) - | |||
| (void*)&(map->reg_shift)); | |||
| } | |||
| int mmc1_state_read(nes_mapper* nes_map, const void* data) { | |||
| mmc1_mapper* map = (mmc1_mapper*)nes_map->data; | |||
| int size = mmc1_state_size(nes_map); | |||
| memcpy(&(map->reg_shift), data, size); | |||
| return size; | |||
| } | |||
| int mmc1_state_write(const nes_mapper* nes_map, void* data) { | |||
| mmc1_mapper* map = (mmc1_mapper*)nes_map->data; | |||
| int size = mmc1_state_size(nes_map); | |||
| memcpy(data, &(map->reg_shift), size); | |||
| return size; | |||
| } | |||
| nes_mapper mapper_mmc1 = { | |||
| .name = "MMC1", | |||
| .init = mmc1_init, | |||
| @@ -248,4 +272,8 @@ nes_mapper mapper_mmc1 = { | |||
| .sram_size = mmc1_sram_size, | |||
| .sram = mmc1_sram, | |||
| .state_size = mmc1_state_size, | |||
| .state_read = mmc1_state_read, | |||
| .state_write = mmc1_state_write, | |||
| }; | |||
| @@ -1,4 +1,5 @@ | |||
| #include <stdlib.h> | |||
| #include <string.h> | |||
| #include "map.h" | |||
| @@ -29,7 +30,7 @@ typedef struct { | |||
| uint8_t* vram_bank[4]; | |||
| uint8_t r[8]; | |||
| mmc3_Flag flags; | |||
| uint8_t flags; | |||
| uint8_t bank_select; | |||
| uint8_t irq_count; | |||
| uint8_t irq_latch; | |||
| @@ -106,7 +107,7 @@ static inline void mmc3_update_chr(mmc3_mapper* map, | |||
| } | |||
| static inline void mmc3_update_rom_mode(mmc3_mapper* map, int val) { | |||
| uint8_t delta = map->bank_select ^ val; | |||
| uint8_t delta = (map->bank_select ^ val); | |||
| map->bank_select = val; | |||
| @@ -119,7 +120,6 @@ static inline void mmc3_update_rom_mode(mmc3_mapper* map, int val) { | |||
| mmc3_map_prg(map, 0, map->prg_rom_banks - 2); | |||
| mmc3_map_prg(map, 2, map->r[6]); | |||
| } | |||
| } | |||
| if (delta & mmc3_Bank_Select_CHR) { | |||
| @@ -344,6 +344,30 @@ static int mmc3_sram_size(nes_mapper* nes_map) { | |||
| } | |||
| /* Save State */ | |||
| int mmc3_state_size(const nes_mapper* nes_map) { | |||
| mmc3_mapper* map = (mmc3_mapper*)nes_map->data; | |||
| return ( (map->wram - map->r) + | |||
| sizeof(map->wram) + | |||
| sizeof(map->vram)); | |||
| } | |||
| int mmc3_state_read(nes_mapper* nes_map, const void* data) { | |||
| mmc3_mapper* map = (mmc3_mapper*)nes_map->data; | |||
| int size = mmc3_state_size(nes_map); | |||
| memcpy(map->r, data, size); | |||
| return size; | |||
| } | |||
| int mmc3_state_write(const nes_mapper* nes_map, void* data) { | |||
| mmc3_mapper* map = (mmc3_mapper*)nes_map->data; | |||
| int size = mmc3_state_size(nes_map); | |||
| memcpy(data, map->r, size); | |||
| return size; | |||
| } | |||
| nes_mapper mapper_mmc3 = { | |||
| .name = "MMC3", | |||
| .init = mmc3_init, | |||
| @@ -358,4 +382,8 @@ nes_mapper mapper_mmc3 = { | |||
| .sram_size = mmc3_sram_size, | |||
| .sram = mmc3_sram, | |||
| .state_size = mmc3_state_size, | |||
| .state_read = mmc3_state_read, | |||
| .state_write = mmc3_state_write, | |||
| }; | |||
| @@ -1,4 +1,5 @@ | |||
| #include <stdlib.h> | |||
| #include <string.h> | |||
| #include "map.h" | |||
| @@ -97,6 +98,29 @@ static void nrom_chr_write(nes_mapper* nes_map, | |||
| // printf("CHR ROM Write: $%04x < %02x\n", addr, val); | |||
| } | |||
| /* Save State */ | |||
| int nrom_state_size(const nes_mapper* nes_map) { | |||
| nrom_mapper* map = (nrom_mapper*)nes_map->data; | |||
| return (sizeof(map->vram) + sizeof(map->wram)); | |||
| } | |||
| int nrom_state_read(nes_mapper* nes_map, const void* data) { | |||
| nrom_mapper* map = (nrom_mapper*)nes_map->data; | |||
| memcpy(map->vram, data, sizeof(map->vram)); | |||
| memcpy(map->wram, data + sizeof(map->vram), sizeof(map->wram)); | |||
| return (sizeof(map->vram) + sizeof(map->wram)); | |||
| } | |||
| int nrom_state_write(const nes_mapper* nes_map, void* data) { | |||
| nrom_mapper* map = (nrom_mapper*)nes_map->data; | |||
| memcpy(data, map->vram, sizeof(map->vram)); | |||
| memcpy(data + sizeof(map->vram), map->wram, sizeof(map->wram)); | |||
| return (sizeof(map->vram) + sizeof(map->wram)); | |||
| } | |||
| nes_mapper mapper_nrom = { | |||
| .name = "NROM", | |||
| .init = nrom_init, | |||
| @@ -107,4 +131,8 @@ nes_mapper mapper_nrom = { | |||
| .chr_addr = nrom_chr_addr, | |||
| .vram_addr = nrom_vram_addr, | |||
| .chr_write = nrom_chr_write, | |||
| .state_size = nrom_state_size, | |||
| .state_read = nrom_state_read, | |||
| .state_write = nrom_state_write, | |||
| }; | |||
| @@ -1,4 +1,5 @@ | |||
| #include <stdlib.h> | |||
| #include <string.h> | |||
| #include "map.h" | |||
| @@ -7,6 +8,8 @@ typedef struct { | |||
| uint8_t* prg_rom; | |||
| int prg_rom_banks; | |||
| uint8_t mirror; | |||
| uint8_t bank; | |||
| uint8_t vram[nes_vram_page_size * 2]; | |||
| uint8_t chr_ram[8 * 1024]; | |||
| @@ -14,9 +17,16 @@ typedef struct { | |||
| uint8_t* prg_bank_hi; | |||
| } uxrom_mapper; | |||
| static void uxrom_set_bank(uxrom_mapper* map, uint8_t bank) { | |||
| map->bank = (bank % map->prg_rom_banks); | |||
| map->prg_bank_lo = &map->prg_rom[ | |||
| map->bank * nes_prg_rom_page_size | |||
| ]; | |||
| } | |||
| static void uxrom_reset(nes_mapper* nes_map) { | |||
| uxrom_mapper* map = (uxrom_mapper*)nes_map->data; | |||
| uxrom_set_bank(map, 0); | |||
| map->prg_bank_lo = map->prg_rom; | |||
| } | |||
| @@ -58,10 +68,7 @@ static void uxrom_write(nes_mapper* nes_map, | |||
| uint16_t addr, uint8_t val) { | |||
| uxrom_mapper* map = (uxrom_mapper*)nes_map->data; | |||
| if (addr >= nes_mem_rom_start) { | |||
| map->prg_bank_lo = &map->prg_rom[ | |||
| (val % map->prg_rom_banks) * | |||
| nes_prg_rom_page_size | |||
| ]; | |||
| uxrom_set_bank(map, val); | |||
| } | |||
| } | |||
| @@ -84,6 +91,50 @@ static void uxrom_chr_write(nes_mapper* nes_map, | |||
| ((uxrom_mapper*)nes_map->data)->chr_ram[addr] = val; | |||
| } | |||
| /* Save State */ | |||
| int uxrom_state_size(const nes_mapper* nes_map) { | |||
| uxrom_mapper* map = (uxrom_mapper*)nes_map->data; | |||
| return ( sizeof(uint32_t) + | |||
| sizeof(map->vram) + | |||
| sizeof(map->chr_ram)); | |||
| } | |||
| int uxrom_state_read(nes_mapper* nes_map, const void* data) { | |||
| uxrom_mapper* map = (uxrom_mapper*)nes_map->data; | |||
| const void* ptr = data; | |||
| map->bank = *(uint32_t*)ptr; | |||
| ptr += sizeof(uint32_t); | |||
| memcpy(map->vram, ptr, sizeof(map->vram)); | |||
| ptr += sizeof(map->vram); | |||
| memcpy(map->chr_ram, ptr, sizeof(map->chr_ram)); | |||
| ptr += sizeof(map->chr_ram); | |||
| return (ptr - data); | |||
| } | |||
| int uxrom_state_write(const nes_mapper* nes_map, void* data) { | |||
| uxrom_mapper* map = (uxrom_mapper*)nes_map->data; | |||
| void* ptr = data; | |||
| uint32_t bank = map->bank; | |||
| memcpy(ptr, &bank, sizeof(bank)); | |||
| ptr += sizeof(bank); | |||
| memcpy(ptr, map->vram, sizeof(map->vram)); | |||
| ptr += sizeof(map->vram); | |||
| memcpy(ptr, map->chr_ram, sizeof(map->chr_ram)); | |||
| ptr += sizeof(map->chr_ram); | |||
| return (ptr - data); | |||
| } | |||
| nes_mapper mapper_uxrom = { | |||
| .name = "UxROM", | |||
| .init = uxrom_init, | |||
| @@ -94,4 +145,8 @@ nes_mapper mapper_uxrom = { | |||
| .chr_addr = uxrom_chr_addr, | |||
| .vram_addr = uxrom_vram_addr, | |||
| .chr_write = uxrom_chr_write, | |||
| .state_size = uxrom_state_size, | |||
| .state_read = uxrom_state_read, | |||
| .state_write = uxrom_state_write, | |||
| }; | |||
| @@ -34,8 +34,9 @@ typedef struct nes_mapper_t { | |||
| void* (*sram)(struct nes_mapper_t*); | |||
| int (*sram_size)(struct nes_mapper_t*); | |||
| void (*save)(struct nes_mapper_t**); | |||
| int (*save_size)(struct nes_mapper_t*); | |||
| int (*state_size)(const struct nes_mapper_t*); | |||
| int (*state_read)(struct nes_mapper_t*, const void*); | |||
| int (*state_write)(const struct nes_mapper_t*, void*); | |||
| } nes_mapper; | |||
| @@ -99,7 +99,6 @@ int main(int argc, char* argv[]) { | |||
| t_target += elapsed_us; | |||
| time_us slept_us = time_sleep_until(t_target); | |||
| (void)slept_us; | |||
| if (slept_us <= -elapsed_us) { | |||
| // We're way out of sync. | |||
| @@ -114,6 +113,14 @@ int main(int argc, char* argv[]) { | |||
| if (input_Result_Reset == status) { | |||
| nes_reset(&sys); | |||
| status = 0; | |||
| } else if (input_Result_Load == status) { | |||
| load_state(&sys, cart_filename); | |||
| status = 0; | |||
| } else if (input_Result_Save == status) { | |||
| save_state(&sys, cart_filename); | |||
| status = 0; | |||
| } | |||
| if (status == 0) { | |||
| @@ -122,7 +122,6 @@ typedef enum { | |||
| typedef struct { | |||
| // Memory | |||
| struct nes_mapper_t* mapper; | |||
| oam_sprite oam[nes_ppu_oam_sprite_count]; | |||
| uint8_t palette[nes_ppu_mem_pal_size]; | |||
| @@ -134,6 +133,9 @@ typedef struct { | |||
| int hit_line; | |||
| int hit_dot; | |||
| // Internal Registers | |||
| uint8_t latch; | |||
| // External registers | |||
| uint8_t control; | |||
| uint8_t mask; | |||
| @@ -147,9 +149,8 @@ typedef struct { | |||
| uint8_t data; | |||
| uint8_t oam_addr; | |||
| // Internal Registers | |||
| uint8_t latch; | |||
| // System Interface | |||
| struct nes_mapper_t* mapper; | |||
| } nes_ppu; | |||
| uint8_t nes_ppu_read(nes_ppu* ppu, uint16_t addr); | |||
| @@ -1,7 +1,13 @@ | |||
| #include <inttypes.h> | |||
| #include <string.h> | |||
| #include "save.h" | |||
| #include "file.h" | |||
| #include "mapper.h" | |||
| #include "filemap.h" | |||
| /* SRAM */ | |||
| static int make_sram_filename(char* sram_filename, int max_len, | |||
| const char* cart_filename) { | |||
| @@ -45,3 +51,339 @@ int save_sram(const nes_cart* cart, const char* cart_filename) { | |||
| return status; | |||
| } | |||
| /* System State */ | |||
| static int make_state_filename(char* sram_filename, int max_len, | |||
| const char* cart_filename) { | |||
| return make_filename( sram_filename, max_len, | |||
| basename(cart_filename), | |||
| "save", "nese"); | |||
| } | |||
| 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 file_size = state_size(sys); | |||
| void* mem = map_file(file, file_size, Filemap_Mode_Read); | |||
| if (NULL != mem) { | |||
| size = state_read(sys, mem, file_size); | |||
| unmap_file(mem, file_size); | |||
| } | |||
| fclose(file); | |||
| } | |||
| return size; | |||
| } | |||
| int save_state(const 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, "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 = map_file(file, file_size, Filemap_Mode_Write); | |||
| if (NULL != mem) { | |||
| size = state_write(sys, mem, file_size); | |||
| unmap_file(mem, file_size); | |||
| } | |||
| fclose(file); | |||
| } | |||
| return size; | |||
| } | |||
| #define tag_def(X) ( \ | |||
| X[0] | (X[1] << 8) | (X[2] << 16) | (X[3] << 24) \ | |||
| ) | |||
| #define tag_NESE tag_def("NESE") | |||
| #define tag_cpu tag_def("tCPU") | |||
| #define tag_ppu tag_def("tPPU") | |||
| #define tag_apu tag_def("tAPU") | |||
| #define tag_ram tag_def("WRAM") | |||
| #define tag_mapper tag_def("MAPP") | |||
| #define szChunkTag sizeof(uint32_t) | |||
| #define szChunkHeader (szChunkTag + sizeof(uint32_t)) | |||
| static int cpu_state_size(const e6502_Core* core) { | |||
| return (sizeof(core->registers) + sizeof(core->pins)); | |||
| } | |||
| static int cpu_state_read(e6502_Core* core, const void* mem) { | |||
| memcpy(&core->registers, mem, sizeof(core->registers)); | |||
| memcpy(&core->pins, mem + sizeof(core->registers), | |||
| sizeof(core->pins)); | |||
| return (sizeof(core->registers) + sizeof(core->pins)); | |||
| } | |||
| static int cpu_state_write(const e6502_Core* core, void* mem) { | |||
| memcpy(mem, &core->registers, sizeof(core->registers)); | |||
| memcpy(mem + sizeof(core->registers), &core->pins, | |||
| sizeof(core->pins)); | |||
| return (sizeof(core->registers) + sizeof(core->pins)); | |||
| } | |||
| static int ppu_state_size(const nes_ppu* ppu) { | |||
| return ((void*)&(ppu->mapper) - (void*)ppu); | |||
| } | |||
| static int ppu_state_read(nes_ppu* ppu, const void* mem) { | |||
| int size = ppu_state_size(ppu); | |||
| memcpy(ppu, mem, size); | |||
| return size; | |||
| } | |||
| static int ppu_state_write(const nes_ppu* ppu, void* mem) { | |||
| int size = ppu_state_size(ppu); | |||
| memcpy(mem, ppu, size); | |||
| return size; | |||
| } | |||
| static int apu_state_size(const nes_apu* apu) { | |||
| return ( ((void*)(apu->channels) - (void*)apu) + | |||
| (5 * ( (void*)&(apu->channels->write) - | |||
| (void*)(apu->channels) | |||
| ) ) | |||
| ); | |||
| } | |||
| static int apu_state_read(nes_apu* apu, const void* mem) { | |||
| int size = ((void*)(apu->channels) - (void*)apu); | |||
| memcpy(apu, mem, size); | |||
| const void* ptr = (mem + size); | |||
| size = ( (void*)&(apu->channels->write) - | |||
| (void*)(apu->channels)); | |||
| for (int chan = 0; chan < nes_apu_chan_count; ++chan) { | |||
| memcpy(&apu->channels[chan], ptr, size); | |||
| ptr += size; | |||
| } | |||
| return (ptr - mem); | |||
| } | |||
| static int apu_state_write(const nes_apu* apu, void* mem) { | |||
| int size = ((void*)(apu->channels) - (void*)apu); | |||
| memcpy(mem, apu, size); | |||
| void* ptr = (mem + size); | |||
| size = ( (void*)&(apu->channels->write) - | |||
| (void*)(apu->channels)); | |||
| for (int chan = 0; chan < nes_apu_chan_count; ++chan) { | |||
| memcpy(ptr, &apu->channels[chan], size); | |||
| ptr += size; | |||
| } | |||
| return (ptr - mem); | |||
| } | |||
| static int ram_state_size(const nes* sys) { | |||
| return sizeof(sys->ram); | |||
| } | |||
| static int ram_state_read(nes* sys, const void* mem) { | |||
| memcpy(sys->ram, mem, sizeof(sys->ram)); | |||
| return sizeof(sys->ram); | |||
| } | |||
| static int ram_state_write(const nes* sys, void* mem) { | |||
| memcpy(mem, sys->ram, sizeof(sys->ram)); | |||
| return sizeof(sys->ram); | |||
| } | |||
| int state_size(const nes* sys) { | |||
| int size = szChunkHeader; | |||
| size += szChunkHeader + cpu_state_size(&sys->cpu); | |||
| size += szChunkHeader + ppu_state_size(&sys->ppu); | |||
| size += szChunkHeader + apu_state_size(&sys->apu); | |||
| // Ignoring input | |||
| size += szChunkHeader + ram_state_size(sys); | |||
| // Cart should already be loaded | |||
| size += szChunkHeader + | |||
| sys->cart.mapper->state_size(sys->cart.mapper); | |||
| return size; | |||
| } | |||
| static inline void* write_header(void* mem, | |||
| uint32_t tag, | |||
| uint32_t size) { | |||
| memcpy(mem, &tag, szChunkTag); | |||
| mem += szChunkTag; | |||
| memcpy(mem, &size, sizeof(size)); | |||
| mem += sizeof(size); | |||
| return mem; | |||
| } | |||
| static inline const void* read_header(const void* mem, | |||
| uint32_t* tag, | |||
| uint32_t* size) { | |||
| memcpy(tag, mem, szChunkTag); | |||
| mem += szChunkTag; | |||
| memcpy(size, mem, sizeof(size[0])); | |||
| mem += sizeof(size[0]); | |||
| return mem; | |||
| } | |||
| int state_write(const nes* sys, void* mem, int size) { | |||
| void* ptr = mem; | |||
| ptr = write_header(ptr, tag_NESE, state_size(sys)); | |||
| ptr = write_header(ptr, tag_cpu, cpu_state_size(&sys->cpu)); | |||
| ptr += cpu_state_write(&sys->cpu, ptr); | |||
| ptr = write_header(ptr, tag_ppu, ppu_state_size(&sys->ppu)); | |||
| ptr += ppu_state_write(&sys->ppu, ptr); | |||
| ptr = write_header(ptr, tag_apu, apu_state_size(&sys->apu)); | |||
| ptr += apu_state_write(&sys->apu, ptr); | |||
| ptr = write_header(ptr, tag_ram, ram_state_size(sys)); | |||
| ptr += ram_state_write(sys, ptr); | |||
| const nes_mapper* mapper = sys->cart.mapper; | |||
| ptr = write_header(ptr, tag_mapper, mapper->state_size(mapper)); | |||
| ptr += mapper->state_write(mapper, ptr); | |||
| return (ptr - mem); | |||
| } | |||
| typedef enum { | |||
| Component_CPU = 0b00000001, | |||
| Component_PPU = 0b00000010, | |||
| Component_APU = 0b00000100, | |||
| Component_RAM = 0b00001000, | |||
| Component_Mapper = 0b00010000, | |||
| } nes_Component; | |||
| int state_read(nes* sys, const void* mem, int mem_size) { | |||
| int result = 0; | |||
| nes_Component loaded = 0; | |||
| const void* ptr = mem; | |||
| const void* end = (mem + mem_size); | |||
| uint32_t tag = 0; | |||
| uint32_t size = 0; | |||
| ptr = read_header(mem, &tag, &size); | |||
| if (tag_NESE != tag) { | |||
| result = -1; | |||
| fprintf(stderr, | |||
| "Bad save state magic: %.4s\n", | |||
| (char*)&tag); | |||
| } | |||
| while (0 == result && ptr < end) { | |||
| ptr = read_header(ptr, &tag, &size); | |||
| if ((ptr + size) > end) { | |||
| result = -1; | |||
| fprintf(stderr, | |||
| "Unusually large chunk: %.4s: +%"PRIu64"\n", | |||
| (char*)&tag, | |||
| ((ptr + size) - end)); | |||
| break; | |||
| } | |||
| int n_read = 0; | |||
| if (tag_cpu == tag) { | |||
| n_read = cpu_state_read(&sys->cpu, ptr); | |||
| loaded |= Component_CPU; | |||
| } else if (tag_ppu == tag) { | |||
| n_read = ppu_state_read(&sys->ppu, ptr); | |||
| loaded |= Component_PPU; | |||
| } else if (tag_apu == tag) { | |||
| n_read = apu_state_read(&sys->apu, ptr); | |||
| loaded |= Component_APU; | |||
| } else if (tag_ram == tag) { | |||
| n_read = ram_state_read(sys, ptr); | |||
| loaded |= Component_RAM; | |||
| } else if (tag_mapper == tag) { | |||
| n_read = sys->cart.mapper->state_read( | |||
| sys->cart.mapper, ptr | |||
| ); | |||
| loaded |= Component_Mapper; | |||
| } else { | |||
| fprintf(stderr, | |||
| "Strange chunk: %.4s\n", | |||
| (char*)&tag); | |||
| } | |||
| if (n_read != size) { | |||
| result = -1; | |||
| fprintf(stderr, | |||
| "Chunk %.4s: read %d, expected %d\n", | |||
| (char*)&tag, n_read, size); | |||
| } | |||
| ptr += size; | |||
| } | |||
| if (0 == result) { | |||
| if (!(loaded | Component_CPU)) { | |||
| result = -1; | |||
| fprintf(stderr, "Missing %s state\n", "CPU"); | |||
| } | |||
| if (!(loaded | Component_PPU)) { | |||
| result = -1; | |||
| fprintf(stderr, "Missing %s state\n", "PPU"); | |||
| } | |||
| if (!(loaded | Component_APU)) { | |||
| result = -1; | |||
| fprintf(stderr, "Missing %s state\n", "APU"); | |||
| } | |||
| if (!(loaded | Component_RAM)) { | |||
| result = -1; | |||
| fprintf(stderr, "Missing %s state\n", "RAM"); | |||
| } | |||
| if (!(loaded | Component_Mapper)) { | |||
| result = -1; | |||
| fprintf(stderr, "Missing %s state\n", "Mapper"); | |||
| } | |||
| } | |||
| if (0 == result) { | |||
| result = (ptr - mem); | |||
| } | |||
| return result; | |||
| } | |||
| @@ -1,11 +1,18 @@ | |||
| #ifndef NESE_SAVE_H_ | |||
| #define NESE_SAVE_H_ | |||
| #include "cart.h" | |||
| #include "nes.h" | |||
| int load_sram(nes_cart* cart, const char* filename); | |||
| int save_sram(const nes_cart* cart, const char* filename); | |||
| int state_size(const nes*); | |||
| int state_read(nes*, const void* mem, int size); | |||
| int state_write(const nes*, void* mem, int size); | |||
| int load_state(nes*, const char* filename); | |||
| int save_state(const nes*, const char* filename); | |||
| #endif // NESE_SAVE_H_ | |||
| @@ -5,7 +5,9 @@ | |||
| static SDL_GameController* sdl_find_gamepad() { | |||
| int i = SDL_NumJoysticks() - 1; | |||
| printf("Found %d joysticks\n", i + 1); | |||
| for ( ; i >= 0 && !SDL_IsGameController(i); --i); | |||
| if (i >= 0) printf("Joystick %d is a gamepad\n", i); | |||
| return (i < 0 ? NULL : SDL_GameControllerOpen(i)); | |||
| } | |||
| @@ -52,6 +54,8 @@ static void sdl_input_done(nes_Input_Reader* input) { | |||
| } | |||
| static const int sdl_reset_key = SDLK_ESCAPE; | |||
| static const int sdl_save_key = SDLK_F1; | |||
| static const int sdl_load_key = SDLK_F2; | |||
| static const int sdl_keycodes[nes_controller_num_buttons] = { | |||
| SDLK_a, | |||
| @@ -102,9 +106,17 @@ static int sdl_input_update(nes_Input_Reader* reader, | |||
| input->controllers[0].buttons &= ~mask; | |||
| } | |||
| } else if ( event.key.keysym.sym == sdl_reset_key && | |||
| } else if ( sdl_reset_key == event.key.keysym.sym && | |||
| SDL_KEYDOWN == event.type) { | |||
| status = input_Result_Reset; | |||
| } else if ( sdl_save_key == event.key.keysym.sym && | |||
| SDL_KEYDOWN == event.type) { | |||
| status = input_Result_Save; | |||
| } else if ( sdl_load_key == event.key.keysym.sym && | |||
| SDL_KEYDOWN == event.type) { | |||
| status = input_Result_Load; | |||
| } | |||
| } else if ( SDL_CONTROLLERBUTTONDOWN == event.type || | |||
| @@ -150,14 +162,22 @@ static int sdl_input_update(nes_Input_Reader* reader, | |||
| } else if (SDL_CONTROLLERDEVICEADDED == event.type) { | |||
| if (NULL == reader->data) { | |||
| printf("New gamepad connected\n"); | |||
| reader->data = sdl_find_gamepad(); | |||
| if (reader->data) printf("Using new gamepad\n"); | |||
| } else { | |||
| printf("Redundant gamepad connected\n"); | |||
| } | |||
| } else if (SDL_CONTROLLERDEVICEREMOVED == event.type) { | |||
| if (sdl_match_gamepad(event.cdevice.which, | |||
| reader->data)) { | |||
| printf("Gamepad disconnected\n"); | |||
| sdl_lose_gamepad(reader->data); | |||
| reader->data = sdl_find_gamepad(); | |||
| if (reader->data) printf("Using another gamepad\n"); | |||
| } else { | |||
| printf("Redundant gamepad disconnected\n"); | |||
| } | |||
| } | |||
| } | |||
| @@ -47,17 +47,22 @@ static void store_map_handle(const void* mem, HANDLE handle) { | |||
| } | |||
| void* map_file(FILE* file, int size) { | |||
| void* map_file(FILE* file, int size, Filemap_Mode map_mode) { | |||
| void* mem = NULL; | |||
| DWORD protect = ( Filemap_Mode_Write == map_mode ? | |||
| PAGE_READWRITE : PAGE_READONLY); | |||
| DWORD access = ( Filemap_Mode_Write == map_mode ? | |||
| FILE_MAP_WRITE : FILE_MAP_READ); | |||
| HANDLE hFile = (HANDLE)_get_osfhandle(fileno(file)); | |||
| HANDLE hMap = CreateFileMappingA( | |||
| hFile, 0, PAGE_READONLY, 0, size, 0 | |||
| hFile, 0, protect, 0, size, 0 | |||
| ); | |||
| if (NULL != hMap) { | |||
| mem = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, size); | |||
| mem = MapViewOfFile(hMap, access, 0, 0, size); | |||
| if (NULL == mem) { | |||
| CloseHandle(hMap); | |||
| } else { | |||