From bfb9e3df5f420887e89b67ee02f68df6438e8277 Mon Sep 17 00:00:00 2001 From: Nathaniel Walizer Date: Wed, 4 Dec 2024 16:18:32 -0800 Subject: [PATCH] Add basic mapper support --- Makefile | 16 +++++-- src/cart.c | 114 +++++++++++++++++++++++++++++++++++++++++++++++ src/cart.h | 35 +++++++++++++++ src/ines.c | 80 +++++---------------------------- src/ines.h | 14 +++++- src/map/map.h | 2 + src/map/mmc1.c | 8 ++++ src/map/nrom.c | 62 ++++++++++++++++++++++++++ src/mapper.c | 11 +++++ src/mapper.h | 47 +++++++++++++++++++ src/nes.c | 12 ++--- src/nes.h | 21 +-------- src/nese.c | 4 +- src/ppu.c | 15 ++++--- src/ppu.h | 15 ++++--- src/sdl_render.c | 13 ++++-- 16 files changed, 354 insertions(+), 115 deletions(-) create mode 100644 src/cart.c create mode 100644 src/cart.h create mode 100644 src/map/map.h create mode 100644 src/map/mmc1.c create mode 100644 src/map/nrom.c create mode 100644 src/mapper.c create mode 100644 src/mapper.h diff --git a/Makefile b/Makefile index c557550..97863cf 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ CC = gcc LD = $(CC) -CFLAGS = -g -Wall -Werror -Wshadow -I.. #-DE6502_DEBUG -LDFLAGS = +PFLAGS = -g +CFLAGS = $(PFLAGS) -Wall -Werror -Wshadow -I.. +LDFLAGS = $(PFLAGS) OBJDIR = obj SRCDIR = src @@ -13,13 +14,20 @@ BINDIR = bin TARGET_1 = nese LDLIBS_1 = -lSDL2 -SRC_SRCS_1 = nese.c ines.c nes.c ppu.c input.c vram.c +SRC_SRCS_1 = nese.c ines.c +SRC_SRCS_1 += nes.c ppu.c cart.c input.c +SRC_SRCS_1 += vram.c mapper.c SRC_SRCS_1 += sdl_render.c sdl_input.c +MAPDIR = src/map +MAP_SRCS_1 = nrom.c mmc1.c + EXT_SRCS_1 = e6502/e6502.c -SRCS_1 = $(SRC_SRCS_1:%=$(SRCDIR)/%) $(EXT_SRCS_1) +SRCS_1 = $(SRC_SRCS_1:%=$(SRCDIR)/%) +SRCS_1 += $(MAP_SRCS_1:%=$(MAPDIR)/%) +SRCS_1 += $(EXT_SRCS_1) OBJS_1 = $(SRCS_1:%.c=$(OBJDIR)/%.o) diff --git a/src/cart.c b/src/cart.c new file mode 100644 index 0000000..0a3f4cb --- /dev/null +++ b/src/cart.c @@ -0,0 +1,114 @@ +#include + +#include "cart.h" +#include "ines.h" +#include "mapper.h" + + +int nes_cart_init_mem(nes_cart* cart, void* mem, int len) { + int status = 0; + ines_Header *hdr = (ines_Header*)mem; + void* ptr = &hdr[1]; + + status = ines_check_mem(hdr); + + if (0 == status && (hdr->flags_6 & ines_Flag_Trainer)) { + // Skip trainer + ptr += ines_trainer_size; + } + + if (0 == status) { + int prg_size = ines_prg_rom_chunk * hdr->prg_size_lsb; + if (prg_size > nes_mem_rom_size || prg_size <= 0) { + INES_ERR("Bad program ROM size: %d / %d", + prg_size, nes_mem_rom_size); + status = -1; + } else { + cart->prg_rom = ptr; + cart->prg_rom_size = prg_size; + } + ptr += prg_size; + } + + if (0 == status) { + int chr_size = ines_chr_rom_chunk * hdr->chr_size_lsb; + if (chr_size > nes_ppu_mem_size || chr_size <= 0) { + INES_ERR("Bad sprite ROM size: %d / %d", + chr_size, nes_ppu_mem_size); + status = -1; + + } else { + cart->chr_rom = ptr; + cart->chr_rom_size = chr_size; + } + ptr += chr_size; + } + + if (0 == status) { + int index = (hdr->flags_6 & ines_Mapper_Nibble_Lo) >> 4 | + (hdr->flags_7 & ines_Mapper_Nibble_Hi); + cart->mapper = nes_mappers[index]; + if (NULL == cart->mapper) { + INES_ERR("No mapper found for type %d", index); + status = -1; + } + } + + if (0 == status) { + status = nes_map_init(cart->mapper, cart); + } + + if (0 == status) { + if (hdr->flags_6 & ines_Flag_Horizontal) { + cart->flags |= Cart_Flag_Horizontal; + } else { + cart->flags &= ~Cart_Flag_Horizontal; + } + + cart->ines_mem = mem; + cart->ines_size = len; + } + + return status; +} + +void nes_cart_done(nes_cart* cart) { + if (NULL != cart->ines_mem) { + munmap(cart->ines_mem, cart->ines_size); + cart->ines_mem = NULL; + } +} + +int nes_cart_init_file(nes_cart* cart, FILE* file) { + int status = 0; + int size = -1; + void* mem = NULL; + + // Get file size + status = fseek(file, 0, SEEK_END); + if (0 != status) { + INES_ERR("Failed to check file size"); + } else { + size = ftell(file); + } + + // Map file + if (0 == status) { + mem = mmap(NULL, size, PROT_READ, MAP_PRIVATE, + fileno(file), 0); + if (NULL == mem) { + INES_ERR("Failed to map file (%d bytes)", size); + status = -1; + } + } + + // Check in memory; unmap on failure + if (0 == status) { + status = nes_cart_init_mem(cart, mem, size); + if (0 != status) { + munmap(mem, size); + } + } + + return status; +} diff --git a/src/cart.h b/src/cart.h new file mode 100644 index 0000000..334fe27 --- /dev/null +++ b/src/cart.h @@ -0,0 +1,35 @@ +#ifndef NES_CART_H_ +#define NES_CART_H_ + +#include +#include + + +#define nes_mem_wram_size (0x2000U) + + +typedef enum { + Cart_Flag_Vertical = 0b0, + Cart_Flag_Horizontal = 0b1, +} nes_Cart_Flags; + +typedef struct nes_cart_t { + uint8_t* prg_rom; + int prg_rom_size; + uint8_t* chr_rom; + int chr_rom_size; + uint8_t wram[nes_mem_wram_size]; + nes_Cart_Flags flags; + struct nes_mapper_t* mapper; + + void* ines_mem; + int ines_size; +} nes_cart; + + +int nes_cart_init_file(nes_cart*, FILE* file); +int nes_cart_init_mem(nes_cart*, void*, int len); +void nes_cart_done(nes_cart*); + + +#endif // NES_CART_H_ diff --git a/src/ines.c b/src/ines.c index 80bfac9..ff40bb6 100644 --- a/src/ines.c +++ b/src/ines.c @@ -4,90 +4,34 @@ #include "nes.h" -#define INES_LOG(tag, fmt, ...) \ - fprintf(stderr, tag ": iNES: " fmt "\n" __VA_OPT__(,) __VA_ARGS__) -#define INES_ERR(...) INES_LOG("E", __VA_ARGS__) - - -#define ines_trainer_size (512U) -#define ines_prg_rom_chunk (16384U) -#define ines_chr_rom_chunk (8192U) - - -int ines_check(ines_Header* ret, FILE* file) { +int ines_check_mem(void* mem) { int status = 0; - ines_Header hdr = {0}; + ines_Header *hdr = (ines_Header*)mem; - if (1 != fread(&hdr, sizeof(ines_Header), 1, file)) { - INES_ERR("Failed to read header"); - status = -1; - } - - if (0 == status && 0 != memcmp(hdr.magic, - ines_magic, - sizeof(hdr.magic))) { + if (0 != memcmp(hdr->magic, ines_magic, sizeof(hdr->magic))) { INES_ERR("Bad file magic: expected '%.4s', got '%.4s'", - ines_magic, hdr.magic); - status =-1; - } - - if (0 == status && NULL != ret) { - *ret = hdr; + ines_magic, hdr->magic); + status = -1; } return status; } -int ines_load(nes_cart* cart, FILE* file) { +int ines_check(ines_Header* ret, FILE* file) { int status = 0; ines_Header hdr = {0}; - status = ines_check(&hdr, file); - - if (0 == status && (hdr.flags_6 & ines_Flag_Trainer)) { - // Skip trainer - status = fseek(file, ines_trainer_size, SEEK_CUR); - } - - if (0 == status) { - int prg_size = ines_prg_rom_chunk * hdr.prg_size_lsb; - if (prg_size > nes_mem_rom_size) { - INES_ERR("Program ROM too large: %d > %d", - prg_size, nes_mem_rom_size); - status = -1; - - } else if (1 != fread(cart->rom.prg, prg_size, 1, file)) { - INES_ERR("Failed to read program ROM"); - status = -1; - - } else if (1U == hdr.prg_size_lsb) { - // If there's only one PRG_ROM chunk, duplicate it - memcpy(cart->rom.prg + ines_prg_rom_chunk, - cart->rom.prg, ines_prg_rom_chunk); - } + if (1 != fread(&hdr, sizeof(ines_Header), 1, file)) { + INES_ERR("Failed to read header"); + status = -1; } if (0 == status) { - int chr_size = ines_chr_rom_chunk * hdr.chr_size_lsb; - if (chr_size > nes_ppu_mem_size) { - INES_ERR("Sprite ROM too large: %d > %d", - chr_size, nes_ppu_mem_size); - status = -1; - - } else if (1 != fread(cart->rom.chr, chr_size, 1, file)) { - INES_ERR("Failed to read sprite ROM"); - status = -1; - } + status = ines_check_mem(&hdr); } - if (0 == status) { - if (hdr.flags_6 & ines_Flag_Horizontal) { - cart->flags |= Cart_Flag_Horizontal; - } else { - cart->flags &= ~Cart_Flag_Horizontal; - } - cart->mapper = (hdr.flags_6 & ines_Mapper_Nibble_Lo) >> 4 | - (hdr.flags_7 & ines_Mapper_Nibble_Hi); + if (0 == status && NULL != ret) { + *ret = hdr; } return status; diff --git a/src/ines.h b/src/ines.h index 91bad63..9505218 100644 --- a/src/ines.h +++ b/src/ines.h @@ -7,6 +7,16 @@ #include "nes.h" +#define INES_LOG(tag, fmt, ...) \ + fprintf(stderr, tag ": iNES: " fmt "\n" __VA_OPT__(,) __VA_ARGS__) +#define INES_ERR(...) INES_LOG("E", __VA_ARGS__) + + +#define ines_trainer_size (512U) +#define ines_prg_rom_chunk (16384U) +#define ines_chr_rom_chunk (8192U) + + #define ines_magic "NES\x1a" typedef enum { @@ -45,9 +55,9 @@ typedef struct { } __attribute__ (( packed )) ines_Header; -int ines_check(ines_Header*, FILE*); +int ines_check_file(ines_Header*, FILE*); -int ines_load(nes_cart*, FILE*); +int ines_check_mem(void*); #endif // INES_H_ diff --git a/src/map/map.h b/src/map/map.h new file mode 100644 index 0000000..a98997c --- /dev/null +++ b/src/map/map.h @@ -0,0 +1,2 @@ +#include "../mapper.h" +#include "../cart.h" diff --git a/src/map/mmc1.c b/src/map/mmc1.c new file mode 100644 index 0000000..97ba62f --- /dev/null +++ b/src/map/mmc1.c @@ -0,0 +1,8 @@ +#include "map.h" + +// Stub + + +nes_mapper mapper_mmc1 = { + +}; diff --git a/src/map/nrom.c b/src/map/nrom.c new file mode 100644 index 0000000..a01a05a --- /dev/null +++ b/src/map/nrom.c @@ -0,0 +1,62 @@ +#include + +#include "map.h" + + +typedef struct { + uint8_t* prg_rom; + int prg_rom_size; + uint8_t* prg_ram; + int prg_ram_size; + uint8_t* chr_rom; + int chr_rom_size; +} nrom_mapper; + +// TODO: PRG_ROM region mirroring + +int nrom_init(nes_mapper* nes_map, nes_cart* cart) { + nrom_mapper* map = malloc(sizeof(nrom_mapper)); + nes_map->data = map; + if (NULL != map) { + map->prg_rom = cart->prg_rom; + map->prg_rom_size = cart->prg_rom_size; + map->chr_rom = cart->chr_rom; + map->chr_rom_size = cart->chr_rom_size; + map->prg_ram = cart->wram; + map->prg_ram_size = nes_mem_wram_size; + } + return (NULL == map ? -1 : 0); +} + +void nrom_done(nes_mapper* nes_map) { + free(nes_map->data); +} + +static inline uint8_t* nrom_prg_addr(nrom_mapper* map, + uint16_t addr) { + if (addr > map->prg_rom_size) { + addr &= 0x3FFF; + } + return &(map->prg_rom[addr]); +} + +uint8_t nrom_prg_read(nes_mapper* map, uint16_t addr) { + return *(nrom_prg_addr((nrom_mapper*)map->data, addr)); +} + +void nrom_prg_write(nes_mapper* map, uint16_t addr, uint8_t val) { + // No ROM writes. +} + +uint8_t* nrom_chr_addr(nes_mapper* nes_map, uint16_t addr) { + nrom_mapper* map = (nrom_mapper*)nes_map->data; + return &map->chr_rom[addr % map->chr_rom_size]; +} + +nes_mapper mapper_nrom = { + .init = nrom_init, + .done = nrom_done, + .prg_read = nrom_prg_read, + .prg_write = nrom_prg_write, + .chr_addr = nrom_chr_addr, +}; diff --git a/src/mapper.c b/src/mapper.c new file mode 100644 index 0000000..57f1b4a --- /dev/null +++ b/src/mapper.c @@ -0,0 +1,11 @@ +#include "mapper.h" + + +extern nes_mapper mapper_nrom; +extern nes_mapper mapper_mmc1; + + +nes_mapper* nes_mappers[256] = { + [ 0] = &mapper_nrom, + [ 1] = &mapper_mmc1, +}; diff --git a/src/mapper.h b/src/mapper.h new file mode 100644 index 0000000..7b514e2 --- /dev/null +++ b/src/mapper.h @@ -0,0 +1,47 @@ +#ifndef NES_MAPPER_H_ +#define NES_MAPPER_H_ + +#include + + +struct nes_cart_t; + +typedef struct nes_mapper_t { + int (*init)(struct nes_mapper_t*, struct nes_cart_t* cart); + void (*done)(struct nes_mapper_t*); + uint8_t (*prg_read)(struct nes_mapper_t*, uint16_t addr); + void (*prg_write)(struct nes_mapper_t*, uint16_t addr, uint8_t val); + uint8_t* (*chr_addr)(struct nes_mapper_t*, uint16_t addr); + void* data; +} nes_mapper; + +static inline int nes_map_init(nes_mapper* map, + struct nes_cart_t* cart) { + return map->init(map, cart); +} + +static inline void nes_map_done(nes_mapper* map) { + map->done(map); +} + +static inline uint8_t nes_map_prg_read(nes_mapper* map, + uint16_t addr) { + return map->prg_read(map, addr); +} + +static inline void nes_map_prg_write(nes_mapper* map, + uint16_t addr, + uint8_t val) { + map->prg_write(map, addr, val); +} + +static inline uint8_t* nes_map_chr_addr(nes_mapper* map, + uint16_t addr) { + return map->chr_addr(map, addr); +} + + +extern nes_mapper* nes_mappers[]; + + +#endif diff --git a/src/nes.c b/src/nes.c index 6c8ffd9..aeb6993 100644 --- a/src/nes.c +++ b/src/nes.c @@ -1,6 +1,7 @@ #include #include "nes.h" +#include "mapper.h" uint8_t nes_mem_read(nes* sys, uint16_t addr) { @@ -25,10 +26,12 @@ uint8_t nes_mem_read(nes* sys, uint16_t addr) { // TODO: Expansion ROM support } else if (addr < nes_mem_rom_start) { + // TODO: Send to mapper? val = sys->cart.wram[addr - nes_mem_wram_start]; } else { - val = sys->cart.rom.prg[addr - nes_mem_rom_start]; + val = nes_map_prg_read(sys->cart.mapper, + addr - nes_mem_rom_start); } return val; @@ -65,16 +68,15 @@ void nes_mem_write(nes* sys, uint16_t addr, uint8_t val) { sys->cart.wram[addr - nes_mem_wram_start] = val; } else { - // No ROM writes + nes_map_prg_write(sys->cart.mapper, + addr - nes_mem_rom_start, val); } } int nes_init(nes* sys) { e6502_init(&sys->cpu, (e6502_Read*)nes_mem_read, (e6502_Write*)nes_mem_write, sys); - return nes_ppu_init(&sys->ppu, sys->cart.rom.chr, - (sys->cart.flags & Cart_Flag_Horizontal), - sys->cart.mapper); + return nes_ppu_init(&sys->ppu, &sys->cart); } void nes_reset(nes* sys) { diff --git a/src/nes.h b/src/nes.h index e1b1e2a..dcd654d 100644 --- a/src/nes.h +++ b/src/nes.h @@ -1,6 +1,7 @@ #ifndef NES_H_ #define NES_H_ +#include "cart.h" #include "apu.h" #include "ppu.h" #include "input.h" @@ -22,7 +23,7 @@ #define nes_mem_exp_start (0x4020U) #define nes_mem_exp_size (0x1FE0U) #define nes_mem_wram_start (0x6000U) -#define nes_mem_wram_size (0x2000U) +//#define nes_mem_wram_size (0x2000U) #define nes_mem_rom_start (0x8000U) #define nes_mem_rom_size (0x8000U) @@ -37,24 +38,6 @@ #define nes_apu_map_size (0x20U) -typedef struct { - // TODO: Dynamic size support - uint8_t prg[nes_mem_rom_size]; - uint8_t chr[nes_ppu_mem_size]; -} nes_cart_rom; - -typedef enum { - Cart_Flag_Vertical = 0b0, - Cart_Flag_Horizontal = 0b1, -} nes_Cart_Flags; - -typedef struct { - nes_cart_rom rom; - uint8_t wram[nes_mem_wram_size]; - nes_Cart_Flags flags; - int mapper; -} nes_cart; - typedef struct { e6502_Core cpu; nes_ppu ppu; diff --git a/src/nese.c b/src/nese.c index 5cd71b3..b69ea5f 100644 --- a/src/nese.c +++ b/src/nese.c @@ -2,7 +2,7 @@ #include #include -#include "ines.h" +#include "nes.h" #include "render.h" #include "input.h" @@ -68,7 +68,7 @@ int main(int argc, char* argv[]) { int n_loops = (argc > 1) ? atoi(argv[1]) : 0; if (n_loops <= 0) n_loops = INT_MAX; - status = ines_load(&sys.cart, stdin); + status = nes_cart_init_file(&sys.cart, stdin); nes_Renderer* rend = &sdl_renderer; if (status == 0) { diff --git a/src/ppu.c b/src/ppu.c index 27dae16..e73266a 100644 --- a/src/ppu.c +++ b/src/ppu.c @@ -1,6 +1,8 @@ #include #include "ppu.h" +#include "mapper.h" +#include "cart.h" // TODO: Retain open bus bits? @@ -41,7 +43,8 @@ uint8_t nes_ppu_read(nes_ppu* ppu, uint16_t addr) { if (ppu->addr < nes_ppu_mem_vram_start) { // printf("PPU: CHR MEM READ %04x > %02x\n", ppu->addr, val); - ppu->data = ppu->chr_mem[ppu->addr]; + ppu->data = *(nes_map_chr_addr(ppu->mapper, + ppu->addr)); } else if (ppu->addr < nes_ppu_mem_vram_start + nes_ppu_mem_vram_size) { VRAM_LOG("PPU: VRAM READ %04x > %02x\n", ppu->addr, val); @@ -51,7 +54,8 @@ uint8_t nes_ppu_read(nes_ppu* ppu, uint16_t addr) { ); } else if (ppu->addr < nes_ppu_mem_pal_start) { // printf("PPU: BLANK READ %04x > %02x\n", ppu->addr, val); - ppu->data = ppu->chr_mem[ppu->addr]; + ppu->data = *(nes_map_chr_addr(ppu->mapper, + ppu->addr)); } } @@ -174,13 +178,12 @@ void nes_ppu_reset(nes_ppu* ppu) { ppu->cycle = 0; } -int nes_ppu_init(nes_ppu* ppu, uint8_t* chr_mem, - int horizontal, int mapper) { - ppu->chr_mem = chr_mem; +int nes_ppu_init(nes_ppu* ppu, const nes_cart* cart) { + ppu->mapper = cart->mapper; ppu->status = 0; ppu->oam_addr = 0; ppu->addr = 0; - ppu->vram_map = ( horizontal ? + ppu->vram_map = ( (cart->flags & Cart_Flag_Horizontal) ? &vram_horizontal : &vram_vertical ); nes_ppu_reset(ppu); diff --git a/src/ppu.h b/src/ppu.h index 8f38cfd..846a169 100644 --- a/src/ppu.h +++ b/src/ppu.h @@ -1,11 +1,15 @@ -#ifndef ENES_PPU_H_ -#define ENES_PPU_H_ +#ifndef NES_PPU_H_ +#define NES_PPU_H_ #include #include "vram.h" +struct nes_mapper_t; +struct nes_cart_t; + + #define DBG_LOG(...) printf(__VA_ARGS__) #ifdef DEBUG_OAM @@ -103,7 +107,7 @@ typedef enum { typedef struct { // Memory - uint8_t* chr_mem; + struct nes_mapper_t* mapper; uint8_t oam[nes_ppu_oam_size]; uint8_t palette[nes_ppu_mem_pal_size]; nes_vram_map* vram_map; @@ -134,8 +138,7 @@ typedef struct { uint8_t nes_ppu_read(nes_ppu* ppu, uint16_t addr); void nes_ppu_write(nes_ppu* ppu, uint16_t addr, uint8_t val); void nes_ppu_reset(nes_ppu* ppu); -int nes_ppu_init(nes_ppu* ppu, uint8_t* chr_mem, - int horizonal, int mapper); +int nes_ppu_init(nes_ppu* ppu, const struct nes_cart_t*); typedef enum { ppu_Result_Halt = -1, @@ -148,4 +151,4 @@ typedef enum { nes_ppu_Result nes_ppu_run(nes_ppu* ppu, int cycles); -#endif // ENES_PPU_H_ +#endif // NES_PPU_H_ diff --git a/src/sdl_render.c b/src/sdl_render.c index 5882afe..d2ba645 100644 --- a/src/sdl_render.c +++ b/src/sdl_render.c @@ -2,6 +2,7 @@ #include "render.h" #include "ppu.h" +#include "mapper.h" static SDL_Color nes_palette[64] = { @@ -27,6 +28,12 @@ static SDL_Color nes_palette[64] = { }; +static inline uint8_t* chr_mem(const nes_ppu* ppu, + uint16_t addr) { + return ppu->mapper->chr_addr(ppu->mapper, addr); +} + + typedef struct { SDL_Window* window; SDL_Renderer* renderer; @@ -164,7 +171,7 @@ static void sdl_render_done(nes_Renderer* rend) { static void render_bg_sprite(const nes_ppu* ppu, int index, const uint8_t* pal, void* loc, int pitch) { - uint8_t* sprite = &ppu->chr_mem[index * 16U]; + uint8_t* sprite = chr_mem(ppu, index * 16U); uint8_t* dst_line = (uint8_t*)loc; for (int y = 8; y > 0; --y) { @@ -263,7 +270,7 @@ typedef enum { static void render_sprite(nes_ppu* ppu, int index, const uint8_t* pal, uint8_t attr, void* loc, int pitch) { - uint8_t* sprite = &ppu->chr_mem[index * 16U]; + uint8_t* sprite = chr_mem(ppu, index * 16U); uint8_t* dst_line = (uint8_t*)loc; int dx = 1; if (attr & oam_Attr_Flip_X) { @@ -407,7 +414,7 @@ static void update_sprite_hit(nes_ppu* ppu, int block_line, if (ppu->control & ppu_Control_Sprite_Bank) { index += 0x100U; } - const uint8_t* chr = &ppu->chr_mem[index * 16U]; + const uint8_t* chr = chr_mem(ppu, index * 16U); int render_line = block_line * 8U; int start_y = (sprite->y + 1) + y_fine - render_line; int end_y = start_y + 8;