diff --git a/Makefile b/Makefile index bfbfb93..31c58b1 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/src/cart.c b/src/cart.c index e422b6f..043f937 100644 --- a/src/cart.c +++ b/src/cart.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); } diff --git a/src/cart.h b/src/cart.h index 868a75f..eaa61c5 100644 --- a/src/cart.h +++ b/src/cart.h @@ -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 { diff --git a/src/file.c b/src/file.c new file mode 100644 index 0000000..77e4a27 --- /dev/null +++ b/src/file.c @@ -0,0 +1,69 @@ +#include +#include + +#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; +} diff --git a/src/file.h b/src/file.h new file mode 100644 index 0000000..f7125fa --- /dev/null +++ b/src/file.h @@ -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_ diff --git a/src/map/mmc1.c b/src/map/mmc1.c index 6fea88e..8c9ee14 100644 --- a/src/map/mmc1.c +++ b/src/map/mmc1.c @@ -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, }; diff --git a/src/map/mmc3.c b/src/map/mmc3.c index 329e098..397276d 100644 --- a/src/map/mmc3.c +++ b/src/map/mmc3.c @@ -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, }; diff --git a/src/mapper.h b/src/mapper.h index 2e66892..deef8e5 100644 --- a/src/mapper.h +++ b/src/mapper.h @@ -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, diff --git a/src/nese.c b/src/nese.c index 1afa7e2..d221352 100644 --- a/src/nese.c +++ b/src/nese.c @@ -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; diff --git a/src/save.c b/src/save.c new file mode 100644 index 0000000..9670d08 --- /dev/null +++ b/src/save.c @@ -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; +} diff --git a/src/save.h b/src/save.h new file mode 100644 index 0000000..7f5b310 --- /dev/null +++ b/src/save.h @@ -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_ diff --git a/src/sdl_input.c b/src/sdl_input.c index 7c3a857..53eb4b5 100644 --- a/src/sdl_input.c +++ b/src/sdl_input.c @@ -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;