- Scrolling jitter artifacts evident in The Legend of Zeldamaster
| @@ -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]; | |||
| @@ -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,2 +1,2 @@ | |||
| #include "../nes.h" | |||
| #include "../mapper.h" | |||
| #include "../cart.h" | |||
| @@ -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, | |||
| }; | |||
| @@ -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, | |||
| }; | |||
| @@ -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, | |||
| @@ -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); | |||
| } | |||
| } | |||
| @@ -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) | |||
| @@ -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) ? | |||