| @@ -43,8 +43,8 @@ SRC_SRCS_1 = nese.c ines.c | |||||
| SRC_SRCS_1 += nes.c ppu.c input.c | SRC_SRCS_1 += nes.c ppu.c input.c | ||||
| SRC_SRCS_1 += cart.c mapper.c | SRC_SRCS_1 += cart.c mapper.c | ||||
| SRC_SRCS_1 += apu.c audio.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 | 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; | 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! | // Don't initialize the mapper until all flags are set! | ||||
| status = nes_map_init(cart->mapper, cart); | status = nes_map_init(cart->mapper, cart); | ||||
| } | } | ||||
| @@ -6,8 +6,9 @@ | |||||
| typedef enum { | 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; | } nes_Cart_Flags; | ||||
| typedef struct nes_cart_t { | 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; | uint8_t* chr_rom; | ||||
| int chr_rom_banks; | int chr_rom_banks; | ||||
| uint8_t battery; | |||||
| uint8_t reg_shift; | uint8_t reg_shift; | ||||
| uint8_t reg_n_shift; | uint8_t reg_n_shift; | ||||
| uint8_t reg_control; | 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->chr = map->chr_ram; | ||||
| } | } | ||||
| map->battery = !!(cart->flags & Cart_Flag_Battery); | |||||
| mmc1_reset(nes_map); | mmc1_reset(nes_map); | ||||
| } | } | ||||
| return (NULL == map ? -1 : 0); | 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 = { | nes_mapper mapper_mmc1 = { | ||||
| .name = "MMC1", | .name = "MMC1", | ||||
| .init = mmc1_init, | .init = mmc1_init, | ||||
| @@ -230,4 +245,7 @@ nes_mapper mapper_mmc1 = { | |||||
| .chr_addr = mmc1_chr_addr, | .chr_addr = mmc1_chr_addr, | ||||
| .vram_addr = mmc1_vram_addr, | .vram_addr = mmc1_vram_addr, | ||||
| .chr_write = mmc1_chr_write, | .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_Enabled = 0b00000010, | ||||
| mmc3_Flag_IRQ_Reload = 0b00000100, | mmc3_Flag_IRQ_Reload = 0b00000100, | ||||
| mmc3_Flag_CHR_RAM = 0b00001000, | mmc3_Flag_CHR_RAM = 0b00001000, | ||||
| mmc3_Flag_Battery = 0b00100000, | |||||
| mmc3_Flag_WRAM_Protect = 0b01000000, | mmc3_Flag_WRAM_Protect = 0b01000000, | ||||
| mmc3_Flag_WRAM_Enabled = 0b10000000, | mmc3_Flag_WRAM_Enabled = 0b10000000, | ||||
| } mmc3_Flag; | } 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 = cart->chr_rom; | ||||
| map->chr_rom_banks = cart->chr_rom_banks * 4; | 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 | | map->bank_select = mmc3_Bank_Select_PRG | | ||||
| mmc3_Bank_Select_CHR; | mmc3_Bank_Select_CHR; | ||||
| mmc3_reset(nes_map); | mmc3_reset(nes_map); | ||||
| } | } | ||||
| return (NULL == map ? -1 : 0); | 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); | // 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 = { | nes_mapper mapper_mmc3 = { | ||||
| .name = "MMC3", | .name = "MMC3", | ||||
| .init = mmc3_init, | .init = mmc3_init, | ||||
| @@ -334,4 +355,7 @@ nes_mapper mapper_mmc3 = { | |||||
| .vram_addr = mmc3_vram_addr, | .vram_addr = mmc3_vram_addr, | ||||
| .chr_write = mmc3_chr_write, | .chr_write = mmc3_chr_write, | ||||
| .scanline = mmc3_scanline, | .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_callback)(void*, int); | ||||
| void* irq_arg; | 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; | } nes_mapper; | ||||
| static inline int nes_map_init(nes_mapper* map, | static inline int nes_map_init(nes_mapper* map, | ||||
| @@ -7,6 +7,8 @@ | |||||
| #include "render.h" | #include "render.h" | ||||
| #include "input.h" | #include "input.h" | ||||
| #include "audio.h" | #include "audio.h" | ||||
| #include "mapper.h" | |||||
| #include "save.h" | |||||
| #define audio_freq (44100U) | #define audio_freq (44100U) | ||||
| @@ -25,17 +27,29 @@ static nes sys = {0}; | |||||
| int main(int argc, char* argv[]) { | int main(int argc, char* argv[]) { | ||||
| int status = 0; | 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; | status = -1; | ||||
| fprintf(stderr, "Could not open %s\n", argv[1]); | |||||
| fprintf(stderr, "Missing cartridge file\n"); | |||||
| } | } | ||||
| if (status == 0) { | if (status == 0) { | ||||
| status = nes_cart_init_file(&sys.cart, cart_file); | 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; | nes_Renderer* rend = &sdl_renderer; | ||||
| if (status == 0) { | if (status == 0) { | ||||
| status = nes_render_init(rend); | 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", | fprintf(stdout, "Ran %f ms, %"PRIu64" master cycles (%s)\n", | ||||
| ms_run, total_cycles, | ms_run, total_cycles, | ||||
| status == 0 ? "OK" : "Halted"); | 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; | 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) { | if (status == 0) { | ||||
| reader->data = sdl_find_gamepad(); | reader->data = sdl_find_gamepad(); | ||||
| // SDL_SetEventFilter(sdl_event_filter, NULL); | // SDL_SetEventFilter(sdl_event_filter, NULL); | ||||
| if (NULL != reader->data) { | |||||
| printf("Gamepad found\n"); | |||||
| } | |||||
| } | } | ||||
| return status; | return status; | ||||