Browse Source

Add MMC1 mapper support

- Scrolling jitter artifacts evident in The Legend of Zelda
master
Nathaniel Walizer 1 year ago
parent
commit
18455ad76c
9 changed files with 229 additions and 88 deletions
  1. +3
    -12
      src/cart.c
  2. +0
    -4
      src/cart.h
  3. +1
    -1
      src/map/map.h
  4. +173
    -31
      src/map/mmc1.c
  5. +31
    -14
      src/map/nrom.c
  6. +14
    -8
      src/mapper.h
  7. +2
    -17
      src/nes.c
  8. +1
    -1
      src/nes.h
  9. +4
    -0
      src/ppu.c

+ 3
- 12
src/cart.c View File

@@ -19,7 +19,7 @@ int nes_cart_init_mem(nes_cart* cart, void* mem, int len) {

if (0 == status) {
int prg_size = ines_prg_rom_chunk * hdr->prg_size_lsb;
if (prg_size > nes_mem_rom_size || prg_size <= 0) {
if (prg_size <= 0) {
INES_ERR("Bad program ROM size: %d / %d",
prg_size, nes_mem_rom_size);
status = -1;
@@ -32,19 +32,10 @@ int nes_cart_init_mem(nes_cart* cart, void* mem, int len) {

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;
}
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];


+ 0
- 4
src/cart.h View File

@@ -5,9 +5,6 @@
#include <stdio.h>


#define nes_mem_wram_size (0x2000U)


typedef enum {
Cart_Flag_Vertical = 0b0,
Cart_Flag_Horizontal = 0b1,
@@ -18,7 +15,6 @@ typedef struct nes_cart_t {
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;



+ 1
- 1
src/map/map.h View File

@@ -1,2 +1,2 @@
#include "../nes.h"
#include "../mapper.h"
#include "../cart.h"

+ 173
- 31
src/map/mmc1.c View File

@@ -1,72 +1,214 @@
#include <stdlib.h>

#include "map.h"


#define MAP_LOG(fmt, ...) //printf("MAP: " fmt "\n" __VA_OPT__(,) __VA_ARGS__)


typedef struct {
// TODO: Does this even support CHR ROM?
uint8_t* prg_rom;
int prg_rom_size;
uint8_t* prg_ram;
int prg_ram_size;
uint8_t* chr_rom;
int chr_rom_size;

uint8_t reg_shift;
uint8_t reg_n_shift;
uint8_t reg_control;
uint8_t reg_chr_0;
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 vram[2][nes_vram_page_size];
uint8_t chr[32][nes_chr_page_size];
uint8_t wram[nes_mem_wram_size];
} mmc1_mapper;

/*
int mmc1_init(nes_mapper* nes_map, nes_cart* cart) {
nrom_mapper* map = malloc(sizeof(nrom_mapper));

static void mmc1_update_vram(mmc1_mapper* map) {
// VRAM selection
int nametable = (map->reg_control & 0b11);
if (nametable == 0) {
MAP_LOG("VRAM: 1-screen, lower bank");
map->vram_bank[0] = map->vram_bank[1] =
map->vram_bank[2] = map->vram_bank[3] =
map->vram[0];
} else if (nametable == 1) {
MAP_LOG("VRAM: 1-screen, upper bank");
map->vram_bank[0] = map->vram_bank[1] =
map->vram_bank[2] = map->vram_bank[3] =
map->vram[1];
} else if (nametable == 2) {
MAP_LOG("VRAM: Vertical mirroring");
map->vram_bank[0] = map->vram_bank[2] = map->vram[0];
map->vram_bank[1] = map->vram_bank[3] = map->vram[1];
} else if (nametable == 3) {
MAP_LOG("VRAM: Horizontal mirroring");
map->vram_bank[0] = map->vram_bank[1] = map->vram[0];
map->vram_bank[2] = map->vram_bank[3] = map->vram[1];
}
}

static void mmc1_update_chr(mmc1_mapper* map) {
// CHR RAM selection
if (!(map->reg_control & 0b10000)) {
int bank = (map->reg_chr_0 & 0b11110);
MAP_LOG("CHR: 8 KB: %d + %d", bank, bank + 1);
map->chr_bank[0] = map->chr[bank];
map->chr_bank[1] = map->chr[bank + 1];
} else {
MAP_LOG("CHR: %d + %d", map->reg_chr_0, map->reg_chr_1);
map->chr_bank[0] = map->chr[map->reg_chr_0];
map->chr_bank[1] = map->chr[map->reg_chr_1];
}
}

static void mmc1_update_prg(mmc1_mapper* map) {
// PRG ROM selection
int mode = (map->reg_control >> 2) & 3;
int bank = (map->reg_prg & 0b01111);
if (!(mode & 0b10)) {
bank = (bank & 0b01110);
MAP_LOG("PRG: 32 KB %d + %d", bank, bank + 1);
bank *= nes_prg_rom_page_size;
map->prg_bank[0] = &map->prg_rom[bank];
map->prg_bank[1] = &map->prg_rom[bank +
nes_prg_rom_page_size];
} else if (mode == 2) {
MAP_LOG("PRG: %d + %d", 0, bank);
bank *= nes_prg_rom_page_size;
map->prg_bank[0] = &map->prg_rom[0];
map->prg_bank[1] = &map->prg_rom[bank];
} else if (mode == 3) {
MAP_LOG("PRG: %d + %d", bank, (map->prg_rom_size /
nes_prg_rom_page_size) - 1);
bank *= nes_prg_rom_page_size;
map->prg_bank[0] = &map->prg_rom[bank];
map->prg_bank[1] = &map->prg_rom[map->prg_rom_size -
nes_prg_rom_page_size];
}
}

static void mmc1_reset(nes_mapper* nes_map) {
mmc1_mapper* map = (mmc1_mapper*)nes_map->data;
map->reg_shift = 0b10000;
map->reg_control = 0;
map->reg_chr_0 = 0;
map->reg_chr_1 = 0;
map->reg_prg = 0;
mmc1_update_prg(map);
mmc1_update_chr(map);
mmc1_update_vram(map);
}

static int mmc1_init(nes_mapper* nes_map, nes_cart* cart) {
mmc1_mapper* map = calloc(1, sizeof(mmc1_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;

map->shift_reg = 0;
mmc1_reset(nes_map);
}
return (NULL == map ? -1 : 0);
}

void nrom_done(nes_mapper* nes_map) {
static void mmc1_done(nes_mapper* nes_map) {
free(nes_map->data);
}

static inline uint8_t* nrom_prg_addr(nrom_mapper* map,
static inline uint8_t* mmc1_prg_addr(mmc1_mapper* map,
uint16_t addr) {
if (addr > map->prg_rom_size) {
addr &= 0x3FFF;
int bank = (addr >> 14) & 1;
addr &= 0x3FFFU;
return &map->prg_bank[bank][addr];
}

static inline uint8_t* mmc1_wram_addr(mmc1_mapper* map,
uint16_t addr) {
return &(map->wram[addr & 0x1FFFU]);
}

static uint8_t mmc1_read(nes_mapper* map, uint16_t addr) {
uint8_t val = 0;
if (addr >= nes_mem_rom_start) {
val = *(mmc1_prg_addr((mmc1_mapper*)map->data, addr));
} else if (addr >= nes_mem_wram_start) {
val = *(mmc1_wram_addr((mmc1_mapper*)map->data, addr));
}
return &(map->prg_rom[addr]);
return val;
}

uint8_t nrom_prg_read(nes_mapper* map, uint16_t addr) {
return *(nrom_prg_addr((nrom_mapper*)map->data, addr));
static void mmc1_write(nes_mapper* nes_map,
uint16_t addr, uint8_t val) {
mmc1_mapper* map = (mmc1_mapper*)nes_map->data;
if (addr >= nes_mem_rom_start) {
MAP_LOG("Write $%04x < %02x", addr, val);
if (val & 0x80U) {
MAP_LOG("Resetting");
map->reg_shift = 0b10000;
map->reg_control |= 0b01100;
mmc1_update_prg(map);
} else {
// TODO: Handle consective-cycle writes?
int done = (map->reg_shift & 1);
map->reg_shift = (map->reg_shift >> 1) |
((val & 1) << 4);
if (done) {
int reg = (addr >> 13) & 0b11;
if (reg == 0) {
MAP_LOG("Control %02x", map->reg_shift);
map->reg_control = map->reg_shift;
mmc1_update_chr(map);
mmc1_update_vram(map);
mmc1_update_prg(map);
} else if (reg == 1) {
MAP_LOG("CHR_0 %02x", map->reg_shift);
map->reg_chr_0 = map->reg_shift;
mmc1_update_chr(map);
} else if (reg == 2) {
MAP_LOG("CHR_1 %02x", map->reg_shift);
map->reg_chr_1 = map->reg_shift;
mmc1_update_chr(map);
} else {
MAP_LOG("PRG %02x", map->reg_shift);
map->reg_prg = map->reg_shift;
mmc1_update_prg(map);
}
map->reg_shift = 0b10000;
}
}

} else if ( addr < nes_mem_rom_start &&
addr >= nes_mem_wram_start) {
*(mmc1_wram_addr(map, addr)) = val;
}
}

void nrom_prg_write(nes_mapper* map, uint16_t addr, uint8_t val) {
// No ROM writes.
static uint8_t* mmc1_chr_addr(nes_mapper* nes_map,
uint16_t addr) {
int page = (addr >> 12) & 1;
addr &= 0xFFFU;
return &((mmc1_mapper*)nes_map->data)->chr_bank[page][addr];
}

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];
static uint8_t* mmc1_vram_addr(nes_mapper* nes_map,
uint16_t addr) {
int page = (addr >> 10) & 3;
int loc = addr & 0x3FFU;
// MAP_LOG("VRAM $%04x -> %p", addr, ((mmc1_mapper*)nes_map->data)->vram_bank[page]);
return &((mmc1_mapper*)nes_map->data)->vram_bank[page][loc];
}

*/

nes_mapper mapper_mmc1 = {
/*
.init = mmc1_init,
.reset = mmc1_reset,
.done = mmc1_done,
.prg_read = mmc1_prg_read,
.prg_write = mmc1_prg_write,
.read = mmc1_read,
.write = mmc1_write,
.chr_addr = mmc1_chr_addr,
.vram_addr = mmc1_chr_addr,
*/
.vram_addr = mmc1_vram_addr,
};

+ 31
- 14
src/map/nrom.c View File

@@ -6,16 +6,17 @@
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;
uint8_t mirror;
uint8_t vram[nes_vram_page_size * 2];
uint8_t wram[nes_mem_wram_size];
} nrom_mapper;


int nrom_init(nes_mapper* nes_map, nes_cart* cart) {
static void nrom_reset(nes_mapper* nes_map) {}

static int nrom_init(nes_mapper* nes_map, nes_cart* cart) {
nrom_mapper* map = calloc(1, sizeof(nrom_mapper));
nes_map->data = map;
if (NULL != map) {
@@ -23,39 +24,54 @@ int nrom_init(nes_mapper* nes_map, nes_cart* cart) {
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;
map->mirror = (cart->flags & Cart_Flag_Horizontal) ? 0 : 1;
}
return (NULL == map ? -1 : 0);
}

void nrom_done(nes_mapper* nes_map) {
static void nrom_done(nes_mapper* nes_map) {
free(nes_map->data);
}

static inline uint8_t* nrom_prg_addr(nrom_mapper* map,
uint16_t addr) {
addr &= 0x7FFFU;
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));
static inline uint8_t* nrom_wram_addr(nrom_mapper* map,
uint16_t addr) {
return &(map->wram[addr & 0x1FFFU]);
}

static uint8_t nrom_read(nes_mapper* map, uint16_t addr) {
uint8_t val = 0;
if (addr >= nes_mem_rom_start) {
val = *(nrom_prg_addr((nrom_mapper*)map->data, addr));
} else if (addr >= nes_mem_wram_start) {
val = *(nrom_wram_addr((nrom_mapper*)map->data, addr));
}
return val;
}

void nrom_prg_write(nes_mapper* map, uint16_t addr, uint8_t val) {
// No ROM writes.
static void nrom_write(nes_mapper* map,
uint16_t addr, uint8_t val) {
if (addr < nes_mem_rom_start && addr >= nes_mem_wram_start) {
*(nrom_wram_addr((nrom_mapper*)map->data, addr)) = val;
}
}

uint8_t* nrom_chr_addr(nes_mapper* nes_map, uint16_t addr) {
static 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];
}

uint8_t* nrom_vram_addr(nes_mapper* nes_map, uint16_t addr) {
static uint8_t* nrom_vram_addr(nes_mapper* nes_map,
uint16_t addr) {
nrom_mapper* map = (nrom_mapper*)nes_map->data;
int page = addr >> 10U;
page >>= map->mirror;
@@ -65,9 +81,10 @@ uint8_t* nrom_vram_addr(nes_mapper* nes_map, uint16_t addr) {

nes_mapper mapper_nrom = {
.init = nrom_init,
.reset = nrom_reset,
.done = nrom_done,
.prg_read = nrom_prg_read,
.prg_write = nrom_prg_write,
.read = nrom_read,
.write = nrom_write,
.chr_addr = nrom_chr_addr,
.vram_addr = nrom_vram_addr,
};

+ 14
- 8
src/mapper.h View File

@@ -4,17 +4,19 @@
#include <stdint.h>


#define nes_chr_page_size (0x1000U)
#define nes_vram_page_size (0x0400U)
#define nes_chr_page_size (0x1000U)
#define nes_vram_page_size (0x0400U)
#define nes_prg_rom_page_size (0x4000U)


struct nes_cart_t;

typedef struct nes_mapper_t {
int (*init)(struct nes_mapper_t*, struct nes_cart_t* cart);
void (*reset)(struct nes_mapper_t*);
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 (*read)(struct nes_mapper_t*, uint16_t addr);
void (*write)(struct nes_mapper_t*, uint16_t addr, uint8_t val);
uint8_t* (*chr_addr)(struct nes_mapper_t*, uint16_t addr);
uint8_t* (*vram_addr)(struct nes_mapper_t*, uint16_t addr);
void* data;
@@ -25,19 +27,23 @@ static inline int nes_map_init(nes_mapper* map,
return map->init(map, cart);
}

static inline void nes_map_reset(nes_mapper* map) {
map->reset(map);
}

static inline void nes_map_done(nes_mapper* map) {
map->done(map);
}

static inline uint8_t nes_map_prg_read(nes_mapper* map,
static inline uint8_t nes_map_read(nes_mapper* map,
uint16_t addr) {
return map->prg_read(map, addr);
return map->read(map, addr);
}

static inline void nes_map_prg_write(nes_mapper* map,
static inline void nes_map_write(nes_mapper* map,
uint16_t addr,
uint8_t val) {
map->prg_write(map, addr, val);
map->write(map, addr, val);
}

static inline uint8_t* nes_map_chr_addr(nes_mapper* map,


+ 2
- 17
src/nes.c View File

@@ -22,16 +22,8 @@ uint8_t nes_mem_read(nes* sys, uint16_t addr) {
} else if (addr < nes_mem_exp_start) {
val = nes_apu_read(&sys->apu, addr);

} else if (addr < nes_mem_wram_start) {
// 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 = nes_map_prg_read(sys->cart.mapper,
addr - nes_mem_rom_start);
val = nes_map_read(sys->cart.mapper, addr);
}

return val;
@@ -61,15 +53,8 @@ void nes_mem_write(nes* sys, uint16_t addr, uint8_t val) {
} else if (addr < nes_mem_exp_start) {
nes_apu_write(&sys->apu, addr, val);

} else if (addr < nes_mem_wram_start) {
// No ROM writes

} else if (addr < nes_mem_rom_start) {
sys->cart.wram[addr - nes_mem_wram_start] = val;

} else {
nes_map_prg_write(sys->cart.mapper,
addr - nes_mem_rom_start, val);
nes_map_write(sys->cart.mapper, addr, val);
}
}



+ 1
- 1
src/nes.h View File

@@ -23,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)



+ 4
- 0
src/ppu.c View File

@@ -159,6 +159,10 @@ void nes_ppu_write(nes_ppu* ppu, uint16_t addr, uint8_t val) {
#endif // DEBUG_VRAM
// printf("PPU: VRAM %04x < %02x\n", vram_addr, val);
nes_vram_write(ppu->mapper, vram_addr, val);

} else {
// printf("PPU: CHR MEM WRITE %04x > %02x\n", ppu->addr, val);
*(nes_map_chr_addr(ppu->mapper, ppu->addr)) = val;
}

ppu->addr += (ppu->control & ppu_Control_VRAM_Inc) ?


Loading…
Cancel
Save