Browse Source

Add Mapper 4 (MMC3); rearrange hsync timing

v2
Nathaniel Walizer 9 months ago
parent
commit
34a0628fe0
7 changed files with 299 additions and 14 deletions
  1. +2
    -0
      src/cart.c
  2. +2
    -1
      src/f6502.c
  3. +257
    -0
      src/map/map004.c
  4. +2
    -0
      src/mapper.c
  5. +5
    -2
      src/mapper.h
  6. +29
    -11
      src/nes.c
  7. +2
    -0
      src/ppu.c

+ 2
- 0
src/cart.c View File

@@ -73,6 +73,8 @@ int nes_cart_load_mem(void* data, int size, nes* sys) {

LOGI("Mapper %d: %s", i_mapper, mapper->name);
mem->mapper = *mapper;
mem->mapper.irq_arg = &sys->core;
mem->mapper.irq_callback = (void(*)(void*, bool))f6502_set_IRQ;
mem->mapper.data = calloc(1, mapper->data_size);
if (NULL == mem->mapper.data) {
LOGE("Failed to allocate mapper data");


+ 2
- 1
src/f6502.c View File

@@ -277,7 +277,8 @@ static inline int f6502_write(nes_Memory* mem,
if (NULL != src) {
memcpy(mem->ppu.oam, src, NES_PPU_OAM_SIZE);
}
ret = 513;
// TODO: Why does this throw off MMC3?
// ret = 513;
} break;

case 0x16: // Reset Gamepad


+ 257
- 0
src/map/map004.c View File

@@ -0,0 +1,257 @@
#include "memory.h"

#define DEBUG "MMC3"
#include "log.h"


typedef enum {
mmc3_Flag_Horizontal = 0b00000001,
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;

typedef enum {
mmc3_Bank_Select_Reg = 0b00000111,
mmc3_Bank_Select_PRG = 0b01000000,
mmc3_Bank_Select_CHR = 0b10000000,
} mmc3_Bank_Select;

typedef struct {
uint8_t r[8];
uint8_t flags;
uint8_t bank_select;
uint8_t irq_count;
uint8_t irq_latch;
} map004_data;


static inline void mmc3_update_vram(map004_data* data,
nes_Memory* mem) {
nes_ppu_set_mirroring(
&mem->ppu,
data->flags & mmc3_Flag_Horizontal ?
nes_Mirror_Horizontal : nes_Mirror_Vertical
);
}

static inline void mmc3_map_prg(nes_Memory* mem,
int bank, int page) {
LOGD("PRG ROM: 8k $%04x <- bank %d", 0x8000 + (bank << 13), page);
mem->rom_bank[bank] = prg_rom_page(mem,
page % mem->n_rom_banks);
}


static inline void mmc3_update_prg(map004_data* data,
nes_Memory* mem,
int reg, uint8_t bank) {
if (reg == 7) {
mmc3_map_prg(mem, 1, bank);
} else {
if (!(data->bank_select & mmc3_Bank_Select_PRG)) {
mmc3_map_prg(mem, 0, bank);
} else {
mmc3_map_prg(mem, 2, bank);
}
}
}

static inline void mmc3_map_2k_chr(nes_Memory* mem,
int reg, int bank) {
bank = (bank & ~1) % mem->ppu.n_chr_banks;
LOGD("CHR ROM: 2k $%04x <- bank %d + %d", reg << 10, bank, bank | 1);
mem->ppu.bank[reg + 0] = chr_page(&mem->ppu, bank);
mem->ppu.bank[reg + 1] = chr_page(&mem->ppu, bank | 1);
}

static inline void mmc3_map_1k_chr(nes_Memory* mem,
int reg, int bank) {
bank = bank % mem->ppu.n_chr_banks;
LOGD("CHR ROM: 1k $%04x <- bank %d", reg << 10, bank);
mem->ppu.bank[reg] = chr_page(&mem->ppu, bank);
}


static inline void mmc3_update_chr(map004_data* data,
nes_Memory* mem,
int reg, uint8_t bank) {
if (!(data->bank_select & mmc3_Bank_Select_CHR)) {
if (1 >= reg) {
mmc3_map_2k_chr(mem, reg * 2, bank);
} else {
mmc3_map_1k_chr(mem, reg + 2, bank);
}

} else {
if (1 >= reg) {
mmc3_map_2k_chr(mem, 4 + (reg * 2), bank);
} else {
mmc3_map_1k_chr(mem, reg - 2, bank);
}
}
}

static inline void mmc3_update_rom_mode(map004_data* data,
nes_Memory* mem,
uint8_t val) {
uint8_t delta = (data->bank_select ^ val);

data->bank_select = val;

if (delta & mmc3_Bank_Select_PRG) {
mmc3_map_prg(mem, 1, data->r[7]);
if (!(val & mmc3_Bank_Select_PRG)) {
mmc3_map_prg(mem, 0, data->r[6]);
mmc3_map_prg(mem, 2, mem->n_rom_banks - 2);
} else {
mmc3_map_prg(mem, 0, mem->n_rom_banks - 2);
mmc3_map_prg(mem, 2, data->r[6]);
}
}

if (delta & mmc3_Bank_Select_CHR) {
mmc3_update_chr(data, mem, 0, data->r[0]);
mmc3_update_chr(data, mem, 1, data->r[1]);
mmc3_update_chr(data, mem, 2, data->r[2]);
mmc3_update_chr(data, mem, 3, data->r[3]);
mmc3_update_chr(data, mem, 4, data->r[4]);
mmc3_update_chr(data, mem, 5, data->r[5]);
}
}


static void map004_reset(nes_Mapper* map, nes_Memory* mem) {
map004_data* data = (map004_data*)map->data;
data->irq_count = 0;
data->irq_latch = 0;
mmc3_update_rom_mode(data, mem, 0);
mmc3_map_prg(mem, 3, mem->n_rom_banks - 1);
mmc3_update_vram(data, mem);
}

static int map004_init(nes_Mapper* map, const ines_Header* hdr,
nes_Memory* mem) {
map004_data* data = (map004_data*)map->data;
data->flags = (hdr->flags_6 & ines_Flag_Vert_Mirror) ?
mmc3_Flag_Horizontal : 0;
data->bank_select = mmc3_Bank_Select_PRG |
mmc3_Bank_Select_CHR;
map004_reset(map, mem);
return 0;
}

static int map004_protect_sram(nes_Mapper* map, nes_Memory* mem,
uint16_t addr, uint8_t val) {
// Intercept any writes!
return 0;
}

static int map004_write_rom(nes_Mapper* map, nes_Memory* mem,
uint16_t addr, uint8_t val) {
map004_data* data = (map004_data*)map->data;

LOGD("$%04x < %02x", (int)addr, (int)val);

switch ((addr >> 13) & 3) {
case 0: // 0x8000 - 0x9FFF
if (addr & 1) {
// Bank data
int reg = (data->bank_select & mmc3_Bank_Select_Reg);
if (reg >= 6) {
mmc3_update_prg(data, mem, reg, val);
} else {
mmc3_update_chr(data, mem, reg, val);
}
data->r[reg] = val;
} else {
// Bank Select
mmc3_update_rom_mode(data, mem, val);
}
break;

case 1: // 0xA000 - 0xBFFF
if (addr & 1) {
LOGD("SRAM %s, %s", (val & mmc3_Flag_WRAM_Enabled) ? "enabled" : "disabled", (val & mmc3_Flag_WRAM_Protect) ? "protected" : "writable");
// WRAM protection
data->flags &= ~(mmc3_Flag_WRAM_Enabled |
mmc3_Flag_WRAM_Protect);
data->flags |= (val & (mmc3_Flag_WRAM_Enabled |
mmc3_Flag_WRAM_Protect));
// TODO: Set these on load / reset
if (val & mmc3_Flag_WRAM_Enabled) {
mem->sram_bank = mem->sram;
} else {
mem->sram_bank = NULL;
}
if (val & mmc3_Flag_WRAM_Protect) {
map->write_sram = map004_protect_sram;
} else {
map->write_sram = NULL;
}
} else {
// Mirroring
data->flags &= ~mmc3_Flag_Horizontal;
data->flags |= (val & mmc3_Flag_Horizontal);
mmc3_update_vram(data, mem);
}
break;

case 2: // 0xC000 - 0xDFFF
if (addr & 1) {
LOGD("IRQ Reload");
data->flags |= mmc3_Flag_IRQ_Reload;
} else {
LOGD("IRQ Latch: %d", val);
data->irq_latch = val;
}
break;

case 3: // 0xE000 - 0xFFFF
LOGD("IRQ %s", (addr & 1) ? "Enable" : "Disable");
if (addr & 1) {
data->flags |= mmc3_Flag_IRQ_Enabled;
} else {
data->flags &= ~mmc3_Flag_IRQ_Enabled;
map->irq_callback(map->irq_arg, 0);
}
break;
}

return 0;
}

static int map004_hsync(nes_Mapper* map) {
map004_data* data = (map004_data*)map->data;

if ( data->irq_count <= 0 ||
(data->flags & mmc3_Flag_IRQ_Reload)) {
data->irq_count = data->irq_latch;
data->flags &= ~mmc3_Flag_IRQ_Reload;
} else {
data->irq_count--;
}

if ( data->irq_count <= 0 &&
(data->flags & mmc3_Flag_IRQ_Enabled)) {
LOGD("IRQ Trigger");
map->irq_callback(map->irq_arg, 1);
data->irq_count = 0;
}

return 0;
}


const nes_Mapper map004 = {
.name = "MMC3",
.max_chr_banks = 256,
.data_size = sizeof(map004_data),
.init = map004_init,
.reset = map004_reset,
.write_rom = map004_write_rom,
.hsync = map004_hsync,
};

+ 2
- 0
src/mapper.c View File

@@ -5,10 +5,12 @@ extern const nes_Mapper map000;
extern const nes_Mapper map001;
extern const nes_Mapper map002;
extern const nes_Mapper map003;
extern const nes_Mapper map004;

const nes_Mapper* nes_mappers[256] = {
&map000,
&map001,
&map002,
&map003,
&map004,
};

+ 5
- 2
src/mapper.h View File

@@ -23,11 +23,14 @@ struct nes_Mapper {
int (*write_apu)(struct nes_Mapper*, struct nes_Memory*, uint16_t addr, uint8_t val);
uint8_t (*read_apu)(struct nes_Mapper*, struct nes_Memory*, uint16_t addr);

void (*hsync)(struct nes_Mapper*);
void (*vsync)(struct nes_Mapper*);
int (*hsync)(struct nes_Mapper*);
int (*vsync)(struct nes_Mapper*);

void (*ppu_bus)(struct nes_Mapper*, uint16_t addr);
void (*ppu_mem_mode)(struct nes_Mapper*, uint8_t mode);

void* irq_arg;
void (*irq_callback)(void* irq_arg, bool enable);
};
typedef struct nes_Mapper nes_Mapper;



+ 29
- 11
src/nes.c View File

@@ -56,6 +56,7 @@ static int nes_hsync(nes* sys, void* plat) {
sys->core.memory.ppu.addr =
sys->core.memory.ppu.t;
}

} else {
nes_ppu_render_line(&sys->ppu,
&sys->core.memory.ppu);
@@ -105,35 +106,52 @@ static int nes_hsync(nes* sys, void* plat) {
return status;
}

#define mapper_hsync_dot (300U)

int nes_loop(nes* sys, void* plat) {
int status = 0;
int dot = 0;
bool mapper_hsync = false;

while (0 == status) {
// TODO: Move to inline PPU function
int dots_remaining = nes_ppu_scanline_dots - dot;
// TODO: Move to inline PPU function?
int target_dot = nes_ppu_scanline_dots;

if ( sys->core.memory.mapper.hsync &&
sys->ppu.scanline > nes_ppu_prerender_line &&
sys->ppu.scanline < nes_ppu_postrender_line &&
dot < mapper_hsync_dot) {
target_dot = mapper_hsync_dot;
mapper_hsync = true;
}

if ( sys->ppu.hit_line == sys->ppu.scanline &&
(sys->core.memory.ppu.mask & (ppu_Mask_Sprite | ppu_Mask_Back)) &&
( sys->core.memory.ppu.mask &
(ppu_Mask_Sprite | ppu_Mask_Back)) &&
!(sys->core.memory.ppu.status & ppu_Status_Hit)) {
int hit_dot = ((oam_sprite*)sys->core.memory.ppu.oam)->x;
if (dot >= hit_dot) {
// printf("Line %d sprite 0 hit @ %d\n", sys->ppu.hit_line, hit_dot);
LOGD("Line %d sprite 0 hit @ %d", sys->ppu.hit_line, hit_dot);
sys->core.memory.ppu.status |= ppu_Status_Hit;
} else {
dots_remaining = hit_dot - dot;
target_dot = hit_dot;
}
}

int cpu_cycles = (dots_remaining + 2) / 3;
dot += 3 * f6502_step(&sys->core, cpu_cycles);
if (target_dot > dot) {
int cpu_cycles = (target_dot - dot + 2) / 3;
dot += 3 * f6502_step(&sys->core, cpu_cycles);
}

if (mapper_hsync && dot >= mapper_hsync_dot) {
nes_Memory* mem = &sys->core.memory;
mem->mapper.hsync(&mem->mapper);
mapper_hsync = false;
}

// It's possible we returned due to a pending interrupt.
if (dot >= nes_ppu_scanline_dots) {
dot -= nes_ppu_scanline_dots;
nes_Memory* mem = &sys->core.memory;
if (NULL != mem->mapper.hsync) {
mem->mapper.hsync(&mem->mapper);
}
status = nes_hsync(sys, plat);
}
}


+ 2
- 0
src/ppu.c View File

@@ -275,6 +275,8 @@ void nes_ppu_render_line(nes_PPU* ppu, nes_PPU_Memory* mem) {
pat1 = sprite_rev[tmp];
}

// TODO: Handle column 0 sprite mask

switch (over_x) {
default:
{ int pal_idx = (pat0 >> 0) & 3;


Loading…
Cancel
Save