diff --git a/src/cart.c b/src/cart.c index 56e80a6..f0c14da 100644 --- a/src/cart.c +++ b/src/cart.c @@ -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"); diff --git a/src/f6502.c b/src/f6502.c index 9f7377b..0751de7 100644 --- a/src/f6502.c +++ b/src/f6502.c @@ -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 diff --git a/src/map/map004.c b/src/map/map004.c new file mode 100644 index 0000000..3203cef --- /dev/null +++ b/src/map/map004.c @@ -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, +}; diff --git a/src/mapper.c b/src/mapper.c index 54ea98c..3d4f05a 100644 --- a/src/mapper.c +++ b/src/mapper.c @@ -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, }; diff --git a/src/mapper.h b/src/mapper.h index 598889b..1faeafd 100644 --- a/src/mapper.h +++ b/src/mapper.h @@ -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; diff --git a/src/nes.c b/src/nes.c index b2f6d17..8b12a71 100644 --- a/src/nes.c +++ b/src/nes.c @@ -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); } } diff --git a/src/ppu.c b/src/ppu.c index 7afcd0a..ec5c697 100644 --- a/src/ppu.c +++ b/src/ppu.c @@ -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;