From c6bafe3fc34c8ad8401a5d39833a6d93c6d978e3 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 9 Jul 2016 17:34:55 +0300 Subject: [PATCH] Rewrote MBC support --- Core/debugger.c | 2 +- Core/gb.c | 57 ++++--------------- Core/gb.h | 25 ++++++++- Core/mbc.c | 78 ++++++++++++++++++++++++++ Core/mbc.h | 8 +++ Core/memory.c | 145 ++++++++++++++++++------------------------------ 6 files changed, 175 insertions(+), 140 deletions(-) create mode 100644 Core/mbc.c create mode 100644 Core/mbc.h diff --git a/Core/debugger.c b/Core/debugger.c index f289b55..d6573fc 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -855,7 +855,7 @@ static bool mbc(GB_gameboy_t *gb, char *arguments) GB_log(gb, "RAM is curently %s\n", gb->mbc_ram_enable? "enabled" : "disabled"); } if (cartridge->mbc_type == GB_MBC1) { - GB_log(gb, "MBC1 banking mode is %s\n", gb->mbc_ram_banking? "RAM" : "ROM"); + GB_log(gb, "MBC1 banking mode is %s\n", gb->mbc1.mode == 1 ? "RAM" : "ROM"); } } diff --git a/Core/gb.c b/Core/gb.c index 33230ed..90b08da 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -14,50 +14,7 @@ #include "joypad.h" #include "display.h" #include "debugger.h" - -static const GB_cartridge_t cart_defs[256] = { - // From http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header#0147_-_Cartridge_Type - /* MBC RAM BAT. RTC RUMB. */ - { GB_NO_MBC, false, false, false, false}, // 00h ROM ONLY - { GB_MBC1 , false, false, false, false}, // 01h MBC1 - { GB_MBC1 , true , false, false, false}, // 02h MBC1+RAM - { GB_MBC1 , true , true , false, false}, // 03h MBC1+RAM+BATTERY - [5] = - { GB_MBC2 , true , false, false, false}, // 05h MBC2 - { GB_MBC2 , true , true , false, false}, // 06h MBC2+BATTERY - [8] = - { GB_NO_MBC, true , false, false, false}, // 08h ROM+RAM - { GB_NO_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY - [0xB] = - // Todo: What are these? - { GB_NO_MBC, false, false, false, false}, // 0Bh MMM01 - { GB_NO_MBC, false, false, false, false}, // 0Ch MMM01+RAM - { GB_NO_MBC, false, false, false, false}, // 0Dh MMM01+RAM+BATTERY - [0xF] = - { GB_MBC3 , false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY - { GB_MBC3 , true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY - { GB_MBC3 , false, false, false, false}, // 11h MBC3 - { GB_MBC3 , true , false, false, false}, // 12h MBC3+RAM - { GB_MBC3 , true , true , false, false}, // 13h MBC3+RAM+BATTERY - [0x15] = - // Todo: Do these exist? - { GB_MBC4 , false, false, false, false}, // 15h MBC4 - { GB_MBC4 , true , false, false, false}, // 16h MBC4+RAM - { GB_MBC4 , true , true , false, false}, // 17h MBC4+RAM+BATTERY - [0x19] = - { GB_MBC5 , false, false, false, false}, // 19h MBC5 - { GB_MBC5 , true , false, false, false}, // 1Ah MBC5+RAM - { GB_MBC5 , true , true , false, false}, // 1Bh MBC5+RAM+BATTERY - { GB_MBC5 , false, false, false, true }, // 1Ch MBC5+RUMBLE - { GB_MBC5 , true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM - { GB_MBC5 , true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY - [0xFC] = - // Todo: What are these? - { GB_NO_MBC, false, false, false, false}, // FCh POCKET CAMERA - { GB_NO_MBC, false, false, false, false}, // FDh BANDAI TAMA5 - { GB_NO_MBC, false, false, false, false}, // FEh HuC3 - { GB_NO_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY -}; +#include "mbc.h" void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args) { @@ -131,7 +88,7 @@ void GB_init(GB_gameboy_t *gb) gb->sprite_palletes_rgb[5] = gb->sprite_palletes_rgb[1] = gb->background_palletes_rgb[1] = 0xAAAAAAAA; gb->sprite_palletes_rgb[6] = gb->sprite_palletes_rgb[2] = gb->background_palletes_rgb[2] = 0x55555555; gb->input_callback = default_input_callback; - gb->cartridge_type = &cart_defs[0]; // Default cartridge type + gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type gb->io_registers[GB_IO_JOYP] = 0xF; } @@ -155,7 +112,7 @@ void GB_init_cgb(GB_gameboy_t *gb) gb->last_vblank = clock(); gb->cgb_ram_bank = 1; gb->input_callback = default_input_callback; - gb->cartridge_type = &cart_defs[0]; // Default cartridge type + gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type gb->io_registers[GB_IO_JOYP] = 0xF; } @@ -197,12 +154,18 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path) if (!f) return errno; fseek(f, 0, SEEK_END); gb->rom_size = (ftell(f) + 0x3FFF) & ~0x3FFF; /* Round to bank */ + /* And then round to a power of two */ + while (gb->rom_size & (gb->rom_size - 1)) { + /* I promise this works. */ + gb->rom_size |= gb->rom_size >> 1; + gb->rom_size++; + } fseek(f, 0, SEEK_SET); gb->rom = malloc(gb->rom_size); memset(gb->rom, 0xFF, gb->rom_size); /* Pad with 0xFFs */ fread(gb->rom, gb->rom_size, 1, f); fclose(f); - gb->cartridge_type = &cart_defs[gb->rom[0x147]]; + gb->cartridge_type = &GB_cart_defs[gb->rom[0x147]]; if (gb->cartridge_type->has_ram) { static const int ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000}; gb->mbc_ram_size = ram_sizes[gb->rom[0x149]]; diff --git a/Core/gb.h b/Core/gb.h index 168a5cf..aecb463 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -226,7 +226,30 @@ typedef struct GB_gameboy_s { uint8_t mbc_ram_bank; uint32_t mbc_ram_size; bool mbc_ram_enable; - bool mbc_ram_banking; + union { + struct { + uint8_t bank_low:5; + uint8_t bank_high:3; + uint8_t mode:1; + } mbc1; + + struct { + uint8_t rom_bank:4; + } mbc2; + + struct { + uint8_t rom_bank:7; + uint8_t padding:1; + uint8_t ram_bank:4; + } mbc3; + + struct { + uint8_t rom_bank_low; + uint8_t rom_bank_high:1; + uint8_t ram_bank:4; + } mbc5; + }; + uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */ ); diff --git a/Core/mbc.c b/Core/mbc.c new file mode 100644 index 0000000..c03ce7e --- /dev/null +++ b/Core/mbc.c @@ -0,0 +1,78 @@ +#include +#include "gb.h" + +const GB_cartridge_t GB_cart_defs[256] = { + // From http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header#0147_-_Cartridge_Type + /* MBC RAM BAT. RTC RUMB. */ + { GB_NO_MBC, false, false, false, false}, // 00h ROM ONLY + { GB_MBC1 , false, false, false, false}, // 01h MBC1 + { GB_MBC1 , true , false, false, false}, // 02h MBC1+RAM + { GB_MBC1 , true , true , false, false}, // 03h MBC1+RAM+BATTERY + [5] = + { GB_MBC2 , true , false, false, false}, // 05h MBC2 + { GB_MBC2 , true , true , false, false}, // 06h MBC2+BATTERY + [8] = + { GB_NO_MBC, true , false, false, false}, // 08h ROM+RAM + { GB_NO_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY + [0xB] = + // Todo: What are these? + { GB_NO_MBC, false, false, false, false}, // 0Bh MMM01 + { GB_NO_MBC, false, false, false, false}, // 0Ch MMM01+RAM + { GB_NO_MBC, false, false, false, false}, // 0Dh MMM01+RAM+BATTERY + [0xF] = + { GB_MBC3 , false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY + { GB_MBC3 , true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY + { GB_MBC3 , false, false, false, false}, // 11h MBC3 + { GB_MBC3 , true , false, false, false}, // 12h MBC3+RAM + { GB_MBC3 , true , true , false, false}, // 13h MBC3+RAM+BATTERY + [0x15] = + // Todo: Do these exist? + { GB_MBC4 , false, false, false, false}, // 15h MBC4 + { GB_MBC4 , true , false, false, false}, // 16h MBC4+RAM + { GB_MBC4 , true , true , false, false}, // 17h MBC4+RAM+BATTERY + [0x19] = + { GB_MBC5 , false, false, false, false}, // 19h MBC5 + { GB_MBC5 , true , false, false, false}, // 1Ah MBC5+RAM + { GB_MBC5 , true , true , false, false}, // 1Bh MBC5+RAM+BATTERY + { GB_MBC5 , false, false, false, true }, // 1Ch MBC5+RUMBLE + { GB_MBC5 , true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM + { GB_MBC5 , true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY + [0xFC] = + // Todo: What are these? + { GB_NO_MBC, false, false, false, false}, // FCh POCKET CAMERA + { GB_NO_MBC, false, false, false, false}, // FDh BANDAI TAMA5 + { GB_NO_MBC, false, false, false, false}, // FEh HuC3 + { GB_NO_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY +}; + +void GB_update_mbc_mappings(GB_gameboy_t *gb) +{ + switch (gb->cartridge_type->mbc_type) { + case GB_NO_MBC: case GB_MBC4: return; + case GB_MBC1: + /* Standard MBC1 wiring: */ + if (gb->mbc1.mode == 0) { + gb->mbc_rom_bank = gb->mbc1.bank_low | (gb->mbc1.bank_high << 5); + gb->mbc_ram_bank = 0; + } + else { + gb->mbc_rom_bank = gb->mbc1.bank_low; + gb->mbc_ram_bank = gb->mbc1.bank_high; + } + break; + case GB_MBC2: + gb->mbc_rom_bank = gb->mbc2.rom_bank; + break; + case GB_MBC3: + gb->mbc_rom_bank = gb->mbc3.rom_bank; + gb->mbc_ram_bank = gb->mbc3.ram_bank; + break; + case GB_MBC5: + gb->mbc_rom_bank = gb->mbc5.rom_bank_low | (gb->mbc5.rom_bank_high << 8); + gb->mbc_ram_bank = gb->mbc5.ram_bank; + break; + } + if (gb->mbc_rom_bank == 0 && gb->cartridge_type->mbc_type != GB_MBC5) { + gb->mbc_rom_bank = 1; + } +} \ No newline at end of file diff --git a/Core/mbc.h b/Core/mbc.h new file mode 100644 index 0000000..7c64fcf --- /dev/null +++ b/Core/mbc.h @@ -0,0 +1,8 @@ +#ifndef MBC_h +#define MBC_h +#include "gb.h" + +extern const GB_cartridge_t GB_cart_defs[256]; +void GB_update_mbc_mappings(GB_gameboy_t *gb); + +#endif /* MBC_h */ diff --git a/Core/memory.c b/Core/memory.c index 01803fc..647cd6f 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -5,6 +5,7 @@ #include "display.h" #include "memory.h" #include "debugger.h" +#include "mbc.h" typedef uint8_t GB_read_function_t(GB_gameboy_t *gb, uint16_t addr); typedef void GB_write_function_t(GB_gameboy_t *gb, uint16_t addr, uint8_t value); @@ -22,15 +23,14 @@ static uint8_t read_rom(GB_gameboy_t *gb, uint16_t addr) if (!gb->rom_size) { return 0xFF; } - return gb->rom[addr]; + unsigned int effective_address = (addr & 0x3FFF) + gb->mbc_rom0_bank * 0x4000; + return gb->rom[effective_address & (gb->rom_size - 1)]; } static uint8_t read_mbc_rom(GB_gameboy_t *gb, uint16_t addr) { - if (gb->mbc_rom_bank >= gb->rom_size / 0x4000) { - return 0xFF; - } - return gb->rom[(addr & 0x3FFF) + gb->mbc_rom_bank * 0x4000]; + unsigned int effective_address = (addr & 0x3FFF) + gb->mbc_rom_bank * 0x4000; + return gb->rom[effective_address & (gb->rom_size - 1)]; } static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr) @@ -43,22 +43,19 @@ static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr) static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) { + if (!gb->mbc_ram_enable) return 0xFF; + if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { /* RTC read */ gb->rtc_high |= ~0xC1; /* Not all bytes in RTC high are used. */ return gb->rtc_data[gb->mbc_ram_bank - 8]; } - unsigned int ram_index = (addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000; - if (!gb->mbc_ram_enable) - { - GB_log(gb, "Read from %02x:%04x (%06x) (Disabled MBC RAM)\n", gb->mbc_ram_bank, addr, ram_index); + + if (!gb->mbc_ram) { return 0xFF; } - if (ram_index >= gb->mbc_ram_size) { - GB_log(gb, "Read from %02x:%04x (%06x) (Unmapped MBC RAM)\n", gb->mbc_ram_bank, addr, ram_index); - return 0xFF; - } - return gb->mbc_ram[(addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000]; + + return gb->mbc_ram[((addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000) & (gb->mbc_ram_size - 1)]; } static uint8_t read_ram(GB_gameboy_t *gb, uint16_t addr) @@ -205,75 +202,41 @@ uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { - if (gb->cartridge_type->mbc_type == GB_NO_MBC) return; - switch (addr >> 12) { - case 0: - case 1: - gb->mbc_ram_enable = value == 0x0a; - break; - case 2: - bank_low: - /* Bank number, lower bits */ - if (gb->cartridge_type->mbc_type == GB_MBC1) { - value &= 0x1F; - } - if (gb->cartridge_type->mbc_type != GB_MBC5 && !value) { - value++; - } - gb->mbc_rom_bank = (gb->mbc_rom_bank & 0x100) | value; - break; - case 3: - if (gb->cartridge_type->mbc_type != GB_MBC5) goto bank_low; - if (value > 1) { - GB_log(gb, "Bank overflow: [%x] <- %d\n", addr, value); - } - gb->mbc_rom_bank = (gb->mbc_rom_bank & 0xFF) | value << 8; - break; - case 4: - case 5: - if (gb->cartridge_type->mbc_type == GB_MBC1) { - if (gb->mbc_ram_banking) { - gb->mbc_ram_bank = value & 0x3; - } - else { - gb->mbc_rom_bank = (gb->mbc_rom_bank & 0x1F) | ((value & 0x3) << 5); - } - } - else { - gb->mbc_ram_bank = value; - /* Some games assume banks wrap around. We can do this if RAM size is a power of two */ - if (gb->mbc_ram_bank >= gb->mbc_ram_size / 0x2000 && (gb->mbc_ram_size & (gb->mbc_ram_size - 1)) == 0 && gb->mbc_ram_size != 0) { - gb->mbc_ram_bank %= gb->mbc_ram_size / 0x2000; - } + switch (gb->cartridge_type->mbc_type) { + case GB_NO_MBC: case GB_MBC4: return; + case GB_MBC1: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: case 0x3000: gb->mbc1.bank_low = value; break; + case 0x4000: case 0x5000: gb->mbc1.bank_high = value; break; + case 0x6000: case 0x7000: gb->mbc1.mode = value; break; } break; - case 6: - case 7: - if (gb->cartridge_type->mbc_type == GB_MBC1) { - value &= 1; - - if (value & !gb->mbc_ram_banking) { - gb->mbc_ram_bank = gb->mbc_rom_bank >> 5; - gb->mbc_rom_bank &= 0x1F; - } - else if (value & !gb->mbc_ram_banking) { - gb->mbc_rom_bank = gb->mbc_rom_bank | (gb->mbc_ram_bank << 5); - gb->mbc_ram_bank = 0; - } - - gb->mbc_ram_banking = value; + case GB_MBC2: + switch (addr & 0xF000) { + /* Todo: is this correct? */ + case 0x0000: case 0x1000: if (!(addr & 0x100)) gb->mbc_ram_enable = value & 0x1; break; + case 0x2000: case 0x3000: if ( addr & 0x100) gb->mbc2.rom_bank = value; break; + } + break; + case GB_MBC3: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: case 0x3000: gb->mbc3.rom_bank = value; break; + case 0x4000: case 0x5000: gb->mbc3.ram_bank = value; break; + case 0x6000: case 0x7000: /* Todo: Clock latching support */ break; + } + break; + case GB_MBC5: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: gb->mbc5.rom_bank_low = value; break; + case 0x3000: gb->mbc5.rom_bank_high = value; break; + case 0x4000: case 0x5000: gb->mbc5.ram_bank = value; break; } break; } - - /* Some games assume banks wrap around. We can do this if ROM size is a power of two */ - if (gb->mbc_rom_bank >= gb->rom_size / 0x4000 && (gb->rom_size & (gb->rom_size - 1)) == 0 && gb->rom_size != 0) { - gb->mbc_rom_bank %= gb->rom_size / 0x4000; - } - - if (gb->cartridge_type->mbc_type != GB_MBC5 && !gb->mbc_rom_bank) { - gb->mbc_rom_bank = 1; - } + GB_update_mbc_mappings(gb); } static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) @@ -287,23 +250,23 @@ static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) { - if (gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { - /* RTC write*/ + if (!gb->mbc_ram_enable) return; + + if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { + /* RTC read */ gb->rtc_data[gb->mbc_ram_bank - 8] = value; gb->rtc_high |= ~0xC1; /* Not all bytes in RTC high are used. */ + } + + if (gb->cartridge_type->mbc_type == GB_MBC2) { + value &= 0xF; + } + + if (!gb->mbc_ram) { return; } - unsigned int ram_index = (addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000; - if (!gb->mbc_ram_enable) - { - GB_log(gb, "Write to %02x:%04x (%06x) (Disabled MBC RAM)\n", gb->mbc_ram_bank, addr, ram_index); - return; - } - if (ram_index >= gb->mbc_ram_size) { - GB_log(gb, "Write to %02x:%04x (%06x) (Unmapped MBC RAM)\n", gb->mbc_ram_bank, addr, ram_index); - return; - } - gb->mbc_ram[(addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000] = value; + + gb->mbc_ram[((addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000) & (gb->mbc_ram_size - 1)] = value; } static void write_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)