| @@ -53,15 +53,26 @@ int nes_cart_load_mem(void* data, int size, nes* sys) { | |||||
| } | } | ||||
| if (0 == status) { | if (0 == status) { | ||||
| // Unused & Palette | |||||
| for (int page = 12; page < 16; ++page) { | |||||
| mem->ppu.bank[page] = mem->ppu.pal_bank; | |||||
| // PPU Bank 0 - 7: Default pattern tables | |||||
| for (int page = 0; page < 8; ++page) { | |||||
| mem->ppu.bank[page] = chr_page(&mem->ppu, page); | |||||
| } | } | ||||
| // PPU Bank 8 - 11: Mirrored VRAM | |||||
| nes_ppu_set_mirroring( | nes_ppu_set_mirroring( | ||||
| &mem->ppu, hdr->flags_6 & ines_Flag_Vert_Mirror | &mem->ppu, hdr->flags_6 & ines_Flag_Vert_Mirror | ||||
| ); | ); | ||||
| // PPU Bank 12 - 15: Unused & Palette | |||||
| for (int page = 12; page < 16; ++page) { | |||||
| mem->ppu.bank[page] = mem->ppu.pal_bank; | |||||
| } | |||||
| // Map the SRAM bank to battery-backed SRAM if present | |||||
| if (hdr->flags_6 & ines_Flag_Battery) { | |||||
| mem->sram_bank = mem->sram; | |||||
| } | |||||
| int i_mapper = (hdr->flags_7 & ines_Mapper_Nibble_Hi) | | int i_mapper = (hdr->flags_7 & ines_Mapper_Nibble_Hi) | | ||||
| ((hdr->flags_6 & ines_Mapper_Nibble_Lo) >> 4); | ((hdr->flags_6 & ines_Mapper_Nibble_Lo) >> 4); | ||||
| const nes_Mapper *mapper = nes_mappers[i_mapper]; | const nes_Mapper *mapper = nes_mappers[i_mapper]; | ||||
| @@ -293,13 +293,22 @@ static inline int f6502_write(nes_Memory* mem, | |||||
| break; | break; | ||||
| case 0x6000: | case 0x6000: | ||||
| if (mem->sram_bank) { | |||||
| if (mem->mapper.write_sram) { | |||||
| ret = mem->mapper.write_sram(&mem->mapper, | |||||
| mem, addr, val); | |||||
| } else if (mem->sram_bank) { | |||||
| mem->sram_bank[addr & 0x1FFFU] = val; | mem->sram_bank[addr & 0x1FFFU] = val; | ||||
| mem->flags |= nes_Mem_SRAM_Used; | mem->flags |= nes_Mem_SRAM_Used; | ||||
| } | |||||
| break; | |||||
| } else if (mem->mapper.write_sram) { | |||||
| ret = mem->mapper.write_sram(&mem->mapper, | |||||
| addr, val); | |||||
| case 0x8000: | |||||
| case 0xA000: | |||||
| case 0xC000: | |||||
| case 0xE000: | |||||
| if (mem->mapper.write_rom) { | |||||
| ret = mem->mapper.write_rom(&mem->mapper, | |||||
| mem, addr, val); | |||||
| } | } | ||||
| } | } | ||||
| #endif | #endif | ||||
| @@ -15,17 +15,12 @@ static int map000_init(nes_Mapper* map, const ines_Header* hdr, | |||||
| mem->rom_bank[3] = prg_rom_page(mem, 1); | mem->rom_bank[3] = prg_rom_page(mem, 1); | ||||
| } | } | ||||
| int page = 0; | |||||
| // Pattern tables | |||||
| for ( ; page < 8; ++page) { | |||||
| mem->ppu.bank[page] = chr_page(&mem->ppu, page); | |||||
| } | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| const nes_Mapper map000 = { | const nes_Mapper map000 = { | ||||
| .name = "NROM", | |||||
| .max_chr_banks = 8, | .max_chr_banks = 8, | ||||
| .init = map000_init, | .init = map000_init, | ||||
| }; | }; | ||||
| @@ -0,0 +1,162 @@ | |||||
| #include <stdlib.h> | |||||
| #include "memory.h" | |||||
| //#define DEBUG "MMC1" | |||||
| #include "log.h" | |||||
| typedef struct { | |||||
| 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; | |||||
| } map001_data; | |||||
| static inline void mmc1_update_vram(map001_data* data, | |||||
| nes_Memory* mem) { | |||||
| const nes_Nametable_Mirroring mirror[4] = { | |||||
| nes_Mirror_Only_0, | |||||
| nes_Mirror_Only_1, | |||||
| nes_Mirror_Vertical, | |||||
| nes_Mirror_Horizontal, | |||||
| }; | |||||
| LOGD("Mirroring %d", mirror[data->reg_control & 0b11]); | |||||
| nes_ppu_set_mirroring(&mem->ppu, | |||||
| mirror[data->reg_control & 0b11]); | |||||
| } | |||||
| static inline void mmc1_update_chr(map001_data* data, | |||||
| nes_Memory* mem) { | |||||
| int n_banks = mem->ppu.n_chr_banks / 4; | |||||
| int bank_0 = 0, bank_1 = 0; | |||||
| if (!(data->reg_control & 0b10000)) { | |||||
| bank_0 = (data->reg_chr_0 & 0b11110) % n_banks; | |||||
| bank_1 = bank_0 + 1; | |||||
| } else { | |||||
| bank_0 = data->reg_chr_0 % n_banks; | |||||
| bank_1 = data->reg_chr_1 % n_banks; | |||||
| } | |||||
| LOGD("CHR: %d + %d", bank_0, bank_1); | |||||
| mem->ppu.bank[0] = chr_page(&mem->ppu, (bank_0 * 4) + 0); | |||||
| mem->ppu.bank[1] = chr_page(&mem->ppu, (bank_0 * 4) + 1); | |||||
| mem->ppu.bank[2] = chr_page(&mem->ppu, (bank_0 * 4) + 2); | |||||
| mem->ppu.bank[3] = chr_page(&mem->ppu, (bank_0 * 4) + 3); | |||||
| mem->ppu.bank[4] = chr_page(&mem->ppu, (bank_1 * 4) + 0); | |||||
| mem->ppu.bank[5] = chr_page(&mem->ppu, (bank_1 * 4) + 1); | |||||
| mem->ppu.bank[6] = chr_page(&mem->ppu, (bank_1 * 4) + 2); | |||||
| mem->ppu.bank[7] = chr_page(&mem->ppu, (bank_1 * 4) + 3); | |||||
| } | |||||
| static inline void mmc1_update_prg(map001_data* data, | |||||
| nes_Memory* mem) { | |||||
| int n_banks = mem->n_rom_banks / 2; | |||||
| int mode = (data->reg_control >> 2) & 3; | |||||
| int bank_0 = (data->reg_prg & 0b01111) % n_banks; | |||||
| int bank_1 = bank_0; | |||||
| if (!(mode & 0b10)) { | |||||
| bank_0 = (bank_0 & 0b01110); | |||||
| bank_1 = bank_0 + 1; | |||||
| } else if (mode == 2) { | |||||
| bank_0 = 0; | |||||
| } else if (mode == 3) { | |||||
| bank_1 = n_banks - 1; | |||||
| } | |||||
| LOGD("PRG: %d + %d", bank_0, bank_1); | |||||
| mem->rom_bank[0] = prg_rom_page(mem, (bank_0 * 2) + 0); | |||||
| mem->rom_bank[1] = prg_rom_page(mem, (bank_0 * 2) + 1); | |||||
| mem->rom_bank[2] = prg_rom_page(mem, (bank_1 * 2) + 0); | |||||
| mem->rom_bank[3] = prg_rom_page(mem, (bank_1 * 2) + 1); | |||||
| } | |||||
| static void map001_reset(nes_Mapper* map, nes_Memory* mem) { | |||||
| map001_data* data = (map001_data*)map->data; | |||||
| data->reg_shift = 0b10000; | |||||
| data->reg_control = 0b01100; | |||||
| data->reg_chr_0 = 0; | |||||
| data->reg_chr_1 = 0; | |||||
| data->reg_prg = 0; | |||||
| mmc1_update_prg(data, mem); | |||||
| mmc1_update_chr(data, mem); | |||||
| mmc1_update_vram(data, mem); | |||||
| } | |||||
| static int map001_init(nes_Mapper* map, const ines_Header* hdr, | |||||
| nes_Memory* mem) { | |||||
| int status = -1; | |||||
| map->data = calloc(1, sizeof(map001_data)); | |||||
| if (NULL != map->data) { | |||||
| map001_reset(map, mem); | |||||
| status = 0; | |||||
| } | |||||
| return status; | |||||
| } | |||||
| static int map001_write_rom(nes_Mapper* map, nes_Memory* mem, | |||||
| uint16_t addr, uint8_t val) { | |||||
| map001_data* data = (map001_data*)map->data; | |||||
| LOGD("Write $%04x < %02x", addr, val); | |||||
| if (val & 0x80U) { | |||||
| data->reg_shift = 0b10000; | |||||
| data->reg_control |= 0b01100; | |||||
| mmc1_update_prg(data, mem); | |||||
| } else { | |||||
| // TODO: Handle consecutive-cycle writes? | |||||
| int done = (data->reg_shift & 1); | |||||
| data->reg_shift = (data->reg_shift >> 1) | | |||||
| ((val & 1) << 4); | |||||
| if (done) { | |||||
| switch ((addr >> 13) & 3) { | |||||
| case 0: | |||||
| LOGD("Control %02x", data->reg_shift); | |||||
| data->reg_control = data->reg_shift; | |||||
| mmc1_update_chr(data, mem); | |||||
| mmc1_update_vram(data, mem); | |||||
| mmc1_update_prg(data, mem); | |||||
| break; | |||||
| case 1: | |||||
| LOGD("CHR_0 %02x", data->reg_shift); | |||||
| data->reg_chr_0 = data->reg_shift; | |||||
| mmc1_update_chr(data, mem); | |||||
| break; | |||||
| case 2: | |||||
| LOGD("CHR_1 %02x", data->reg_shift); | |||||
| data->reg_chr_1 = data->reg_shift; | |||||
| mmc1_update_chr(data, mem); | |||||
| break; | |||||
| case 3: | |||||
| LOGD("PRG %02x", data->reg_shift); | |||||
| data->reg_prg = data->reg_shift; | |||||
| mmc1_update_prg(data, mem); | |||||
| break; | |||||
| } | |||||
| data->reg_shift = 0b10000; | |||||
| } | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| const nes_Mapper map001 = { | |||||
| .name = "MMC1", | |||||
| .max_chr_banks = 128, | |||||
| .init = map001_init, | |||||
| .reset = map001_reset, | |||||
| .write_rom = map001_write_rom, | |||||
| }; | |||||
| @@ -2,8 +2,9 @@ | |||||
| extern const nes_Mapper map000; | extern const nes_Mapper map000; | ||||
| extern const nes_Mapper map001; | |||||
| const nes_Mapper* nes_mappers[256] = { | const nes_Mapper* nes_mappers[256] = { | ||||
| &map000, | &map000, | ||||
| &map001, | |||||
| }; | }; | ||||
| @@ -9,16 +9,24 @@ | |||||
| struct nes_Memory; | struct nes_Memory; | ||||
| struct nes_Mapper { | struct nes_Mapper { | ||||
| const char* name; | |||||
| int max_chr_banks; | int max_chr_banks; | ||||
| int (*init)(struct nes_Mapper*, const ines_Header*, struct nes_Memory*); | int (*init)(struct nes_Mapper*, const ines_Header*, struct nes_Memory*); | ||||
| bool (*write_rom)(struct nes_Mapper*, uint16_t addr, uint8_t val); | |||||
| bool (*write_sram)(struct nes_Mapper*, uint16_t addr, uint8_t val); | |||||
| bool (*write_apu)(struct nes_Mapper*, uint16_t addr, uint8_t val); | |||||
| uint8_t (*read_apu)(struct nes_Mapper*, uint16_t addr); | |||||
| void (*done)(struct nes_Mapper*); | |||||
| void (*reset)(struct nes_Mapper*, struct nes_Memory*); | |||||
| int (*write_rom)(struct nes_Mapper*, struct nes_Memory*, uint16_t addr, uint8_t val); | |||||
| int (*write_sram)(struct nes_Mapper*, struct nes_Memory*, uint16_t addr, uint8_t val); | |||||
| 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 (*hsync)(struct nes_Mapper*); | ||||
| void (*vsync)(struct nes_Mapper*); | void (*vsync)(struct nes_Mapper*); | ||||
| void (*ppu_bus)(struct nes_Mapper*, uint16_t addr); | void (*ppu_bus)(struct nes_Mapper*, uint16_t addr); | ||||
| void (*ppu_mem_mode)(struct nes_Mapper*, uint8_t mode); | void (*ppu_mem_mode)(struct nes_Mapper*, uint8_t mode); | ||||
| void* data; | void* data; | ||||
| }; | }; | ||||
| typedef struct nes_Mapper nes_Mapper; | typedef struct nes_Mapper nes_Mapper; | ||||
| @@ -23,9 +23,9 @@ struct nes_Memory { | |||||
| uint8_t ram[65536]; | uint8_t ram[65536]; | ||||
| #else | #else | ||||
| uint8_t ram[NES_RAM_SIZE / 4]; // Mirrored 3x | uint8_t ram[NES_RAM_SIZE / 4]; // Mirrored 3x | ||||
| uint8_t sram[NES_SRAM_SIZE]; | |||||
| nes_Memory_Flag flags; | nes_Memory_Flag flags; | ||||
| uint8_t reserved[3]; | uint8_t reserved[3]; | ||||
| uint8_t* sram; // Actual SRAM, if used - could this be a flag? | |||||
| uint8_t* sram_bank; // Mapped to 0x6000 - 0x7FFF | uint8_t* sram_bank; // Mapped to 0x6000 - 0x7FFF | ||||
| uint8_t* rom; | uint8_t* rom; | ||||
| uint8_t* rom_bank[4]; | uint8_t* rom_bank[4]; | ||||