| @@ -43,8 +43,8 @@ SRC_SRCS_1 = nese.c ines.c | |||
| SRC_SRCS_1 += nes.c ppu.c input.c | |||
| SRC_SRCS_1 += cart.c mapper.c | |||
| SRC_SRCS_1 += apu.c audio.c | |||
| SRC_SRCS_1 += sdl_render.c sdl_input.c sdl_audio.c | |||
| SRC_SRCS_1 += sdl_timer.c | |||
| SRC_SRCS_1 += file.c save.c | |||
| SRC_SRCS_1 += sdl_render.c sdl_input.c sdl_audio.c sdl_timer.c | |||
| PLAT_SRCS_1 = filemap.c | |||
| @@ -57,6 +57,10 @@ int nes_cart_init_mem(nes_cart* cart, void* mem, int len) { | |||
| cart->flags &= ~Cart_Flag_Horizontal; | |||
| } | |||
| if (hdr->flags_6 & ines_Flag_Battery) { | |||
| cart->flags |= Cart_Flag_Battery; | |||
| } | |||
| // Don't initialize the mapper until all flags are set! | |||
| status = nes_map_init(cart->mapper, cart); | |||
| } | |||
| @@ -6,8 +6,9 @@ | |||
| typedef enum { | |||
| Cart_Flag_Vertical = 0b0, | |||
| Cart_Flag_Horizontal = 0b1, | |||
| Cart_Flag_Vertical = 0b00000000, | |||
| Cart_Flag_Horizontal = 0b00000001, | |||
| Cart_Flag_Battery = 0b00000010, | |||
| } nes_Cart_Flags; | |||
| typedef struct nes_cart_t { | |||
| @@ -0,0 +1,69 @@ | |||
| #include <stdio.h> | |||
| #include <string.h> | |||
| #include "file.h" | |||
| const char* basename(const char* filename) { | |||
| const char* slash = filename; | |||
| for ( ; *slash && *slash != '\\' && *slash != '/'; ++slash); | |||
| return (*slash ? (&slash[1]) : filename); | |||
| } | |||
| int replace_extension(char* filename, int max_len, | |||
| const char* orig_name, 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_base_len = (NULL == orig_dot) ? | |||
| strlen(orig_name) : | |||
| (orig_dot - orig_name); | |||
| if (orig_base_len <= remain) { | |||
| strncpy(filename, orig_name, orig_base_len); | |||
| remain -= orig_base_len; | |||
| filename += orig_base_len; | |||
| } else { | |||
| status = -1; | |||
| } | |||
| if (0 == status && NULL != ext) { | |||
| int ext_len = strlen(ext); | |||
| if ((ext_len + 1) <= remain) { | |||
| *filename++ = '.'; | |||
| strncpy(filename, ext, ext_len); | |||
| remain -= (ext_len + 1); | |||
| } else { | |||
| status = -1; | |||
| } | |||
| } | |||
| return (status < 0 ? status : (max_len - remain)); | |||
| } | |||
| int write_file(const char* filename, const void* data, int len) { | |||
| int status = -1; | |||
| FILE* file = fopen(filename, "wb"); | |||
| if (NULL != file && 1 == fwrite(data, len, 1, file)) { | |||
| status = 0; | |||
| } | |||
| return status; | |||
| } | |||
| int read_file(const char* filename, void* data, int len) { | |||
| int status = -1; | |||
| FILE* file = fopen(filename, "rb"); | |||
| if (NULL != file && 1 == fread(data, len, 1, file)) { | |||
| status = 0; | |||
| } | |||
| return status; | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| #ifndef NESE_FILE_H_ | |||
| #define NESE_FILE_H_ | |||
| // Return pointer to filename omitting path | |||
| const char* basename(const char* filename); | |||
| int replace_extension(char* filename, int max_len, | |||
| const char* orig_name, const char* ext); | |||
| int write_file(const char* filename, const void* data, int len); | |||
| int read_file(const char* filename, void* data, int len); | |||
| #endif // NESE_FILE_H_ | |||
| @@ -9,6 +9,8 @@ typedef struct { | |||
| uint8_t* chr_rom; | |||
| int chr_rom_banks; | |||
| uint8_t battery; | |||
| uint8_t reg_shift; | |||
| uint8_t reg_n_shift; | |||
| uint8_t reg_control; | |||
| @@ -117,6 +119,8 @@ static int mmc1_init(nes_mapper* nes_map, nes_cart* cart) { | |||
| map->chr = map->chr_ram; | |||
| } | |||
| map->battery = !!(cart->flags & Cart_Flag_Battery); | |||
| mmc1_reset(nes_map); | |||
| } | |||
| return (NULL == map ? -1 : 0); | |||
| @@ -220,6 +224,17 @@ static uint8_t* mmc1_vram_addr(nes_mapper* nes_map, | |||
| } | |||
| static void* mmc1_sram(nes_mapper* nes_map) { | |||
| mmc1_mapper* map = (mmc1_mapper*)nes_map->data; | |||
| return (map->battery ? map->wram : NULL); | |||
| } | |||
| static int mmc1_sram_size(nes_mapper* nes_map) { | |||
| mmc1_mapper* map = (mmc1_mapper*)nes_map->data; | |||
| return (map->battery ? sizeof(map->wram) : 0); | |||
| } | |||
| nes_mapper mapper_mmc1 = { | |||
| .name = "MMC1", | |||
| .init = mmc1_init, | |||
| @@ -230,4 +245,7 @@ nes_mapper mapper_mmc1 = { | |||
| .chr_addr = mmc1_chr_addr, | |||
| .vram_addr = mmc1_vram_addr, | |||
| .chr_write = mmc1_chr_write, | |||
| .sram_size = mmc1_sram_size, | |||
| .sram = mmc1_sram, | |||
| }; | |||
| @@ -7,6 +7,7 @@ typedef enum { | |||
| mmc3_Flag_IRQ_Enabled = 0b00000010, | |||
| mmc3_Flag_IRQ_Reload = 0b00000100, | |||
| mmc3_Flag_CHR_RAM = 0b00001000, | |||
| mmc3_Flag_Battery = 0b00100000, | |||
| mmc3_Flag_WRAM_Protect = 0b01000000, | |||
| mmc3_Flag_WRAM_Enabled = 0b10000000, | |||
| } mmc3_Flag; | |||
| @@ -170,8 +171,14 @@ static int mmc3_init(nes_mapper* nes_map, nes_cart* cart) { | |||
| map->chr_rom = cart->chr_rom; | |||
| map->chr_rom_banks = cart->chr_rom_banks * 4; | |||
| } | |||
| if (cart->flags & Cart_Flag_Battery) { | |||
| map->flags |= mmc3_Flag_Battery; | |||
| } | |||
| map->bank_select = mmc3_Bank_Select_PRG | | |||
| mmc3_Bank_Select_CHR; | |||
| mmc3_reset(nes_map); | |||
| } | |||
| return (NULL == map ? -1 : 0); | |||
| @@ -323,6 +330,20 @@ static void mmc3_chr_write(nes_mapper* nes_map, | |||
| // MAP_LOG("CHR ROM Write: $%04x < %02x\n", addr, val); | |||
| } | |||
| static void* mmc3_sram(nes_mapper* nes_map) { | |||
| mmc3_mapper* map = (mmc3_mapper*)nes_map->data; | |||
| return ( (map->flags & mmc3_Flag_Battery) ? | |||
| map->wram : NULL); | |||
| } | |||
| static int mmc3_sram_size(nes_mapper* nes_map) { | |||
| mmc3_mapper* map = (mmc3_mapper*)nes_map->data; | |||
| return ( (map->flags & mmc3_Flag_Battery) ? | |||
| sizeof(map->wram) : 0); | |||
| } | |||
| nes_mapper mapper_mmc3 = { | |||
| .name = "MMC3", | |||
| .init = mmc3_init, | |||
| @@ -334,4 +355,7 @@ nes_mapper mapper_mmc3 = { | |||
| .vram_addr = mmc3_vram_addr, | |||
| .chr_write = mmc3_chr_write, | |||
| .scanline = mmc3_scanline, | |||
| .sram_size = mmc3_sram_size, | |||
| .sram = mmc3_sram, | |||
| }; | |||
| @@ -31,6 +31,12 @@ typedef struct nes_mapper_t { | |||
| void (*irq_callback)(void*, int); | |||
| void* irq_arg; | |||
| 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*); | |||
| } nes_mapper; | |||
| static inline int nes_map_init(nes_mapper* map, | |||
| @@ -7,6 +7,8 @@ | |||
| #include "render.h" | |||
| #include "input.h" | |||
| #include "audio.h" | |||
| #include "mapper.h" | |||
| #include "save.h" | |||
| #define audio_freq (44100U) | |||
| @@ -25,17 +27,29 @@ static nes sys = {0}; | |||
| int main(int argc, char* argv[]) { | |||
| int status = 0; | |||
| FILE* cart_file = stdin; | |||
| if (argc > 1) cart_file = fopen(argv[1], "rb"); | |||
| if (NULL == cart_file) { | |||
| FILE* cart_file = NULL; | |||
| const char* cart_filename = NULL; | |||
| if (argc > 1) { | |||
| cart_filename = argv[1]; | |||
| cart_file = fopen(argv[1], "rb"); | |||
| if (NULL == cart_file) { | |||
| status = -1; | |||
| fprintf(stderr, "Could not open %s\n", argv[1]); | |||
| } | |||
| } else { | |||
| status = -1; | |||
| fprintf(stderr, "Could not open %s\n", argv[1]); | |||
| fprintf(stderr, "Missing cartridge file\n"); | |||
| } | |||
| if (status == 0) { | |||
| status = nes_cart_init_file(&sys.cart, cart_file); | |||
| } | |||
| if (status == 0) { | |||
| // Failure might mean there's nothing to load | |||
| load_sram(&sys.cart, cart_filename); | |||
| } | |||
| nes_Renderer* rend = &sdl_renderer; | |||
| if (status == 0) { | |||
| status = nes_render_init(rend); | |||
| @@ -113,6 +127,13 @@ int main(int argc, char* argv[]) { | |||
| fprintf(stdout, "Ran %f ms, %"PRIu64" master cycles (%s)\n", | |||
| ms_run, total_cycles, | |||
| status == 0 ? "OK" : "Halted"); | |||
| // Failure might mean there's nothing to save | |||
| save_sram(&sys.cart, cart_filename); | |||
| } | |||
| if (cart_file != NULL) { | |||
| fclose(cart_file); | |||
| } | |||
| return status; | |||
| @@ -0,0 +1,46 @@ | |||
| #include "save.h" | |||
| #include "file.h" | |||
| #include "mapper.h" | |||
| static int make_sram_filename(char* sram_filename, int max_len, | |||
| const char* cart_filename) { | |||
| return replace_extension( sram_filename, max_len, | |||
| basename(cart_filename), "sram"); | |||
| } | |||
| int load_sram(nes_cart* cart, const char* cart_filename) { | |||
| int status = -1; | |||
| int sram_size = cart->mapper->sram_size ? | |||
| cart->mapper->sram_size(cart->mapper) : 0; | |||
| void* sram = cart->mapper->sram ? | |||
| cart->mapper->sram(cart->mapper) : NULL; | |||
| if (sram_size > 0 && NULL != sram) { | |||
| char sram_filename[FILENAME_MAX] = {0}; | |||
| make_sram_filename(sram_filename, FILENAME_MAX - 1, | |||
| cart_filename); | |||
| status = read_file(sram_filename, sram, sram_size); | |||
| } | |||
| return status; | |||
| } | |||
| int save_sram(const nes_cart* cart, const char* cart_filename) { | |||
| int status = -1; | |||
| int sram_size = cart->mapper->sram_size ? | |||
| cart->mapper->sram_size(cart->mapper) : 0; | |||
| const void* sram = cart->mapper->sram ? | |||
| cart->mapper->sram(cart->mapper) : NULL; | |||
| if (sram_size > 0 && NULL != sram) { | |||
| char sram_filename[FILENAME_MAX] = {0}; | |||
| make_sram_filename(sram_filename, FILENAME_MAX - 1, | |||
| cart_filename); | |||
| status = write_file(sram_filename, sram, sram_size); | |||
| } | |||
| return status; | |||
| } | |||
| @@ -0,0 +1,11 @@ | |||
| #ifndef NESE_SAVE_H_ | |||
| #define NESE_SAVE_H_ | |||
| #include "cart.h" | |||
| int load_sram(nes_cart* cart, const char* filename); | |||
| int save_sram(const nes_cart* cart, const char* filename); | |||
| #endif // NESE_SAVE_H_ | |||
| @@ -39,6 +39,9 @@ static int sdl_input_init(nes_Input_Reader* reader) { | |||
| if (status == 0) { | |||
| reader->data = sdl_find_gamepad(); | |||
| // SDL_SetEventFilter(sdl_event_filter, NULL); | |||
| if (NULL != reader->data) { | |||
| printf("Gamepad found\n"); | |||
| } | |||
| } | |||
| return status; | |||