#include "cart.h" #include #include #include #include "utils.h" #include "pins.h" #include "spi.h" #include "rom.h" // Most of the pulses described here are arbitrarily chosen, // to satisfy my idiotic UX requirements with the leds. // Here's a pretty cool bus analysis: // https://dhole.github.io/post/gameboy_cartridge_emu_1/ static void cart_address(const uint16_t address) { spi_transfer(address >> 8); spi_transfer(address & 0xFF); writepin(PIN_STORE, HIGH); __asm__ volatile ("nop"); writepin(PIN_STORE, LOW); } uint8_t cart_read(const uint16_t address) { cart_address(address); __asm__ volatile ("nop"); return PINS_DAT; } void cart_write(const uint16_t address, const uint8_t byte) { cart_address(address); PORTB = (PORTB & ~PINS_DATB) | (byte >> 6); PORTD = (PORTD & ~PINS_DATD) | (byte << 2); writepin(PIN_WRITE, LOW); __asm__ volatile ("nop"); writepin(PIN_WRITE, HIGH); } void cart_set_read(void) { // Set the data pins as pull-up inputs DDRB &= ~PINS_DATB; DDRD &= ~PINS_DATD; PORTB |= PINS_DATB; PORTD |= PINS_DATD; writepin(PIN_READ, LOW); writepin(PIN_WRITE, HIGH); } void cart_set_write(void) { // Set the data pins as low outputs DDRB |= PINS_DATB; DDRD |= PINS_DATD; PORTB &= ~PINS_DATB; PORTD &= ~PINS_DATD; writepin(PIN_READ, HIGH); writepin(PIN_WRITE, HIGH); } void cart_set_standby(void) { // Set the data pins as pull-up inputs DDRB &= ~PINS_DATB; DDRD &= ~PINS_DATD; PORTB |= PINS_DATB; PORTD |= PINS_DATD; writepin(PIN_READ, HIGH); writepin(PIN_WRITE, HIGH); } void cart_select_rom(void) { writepin(PIN_CS, HIGH); } void cart_select_ram(void) { writepin(PIN_CS, LOW); } void cart_init(void) { // Set the appropriate pins as output DDRB |= pin(PIN_STORE); DDRC |= pin(PIN_READ) | pin(PIN_WRITE) | pin(PIN_CS); // Initialize the cart as standby cart_set_standby(); cart_select_rom(); // Initialize the shift register writepin(PIN_STORE, LOW); spi_init(SPI_DIV8); cart_address(0); } void cart_off(void) { cart_address(0); spi_off(); PORTC &= ~pin(PIN_READ) & ~pin(PIN_CS) & ~pin(PIN_WRITE); writepin(PIN_STORE, LOW); } int cart_boot(struct cart_info *info) { cart_set_read(); // "simulate" the GB power-up process, by reading the logo once, before comparing. // This might be compatible with carts that hijack the logo on bootup. // There is no point in simulating this more accurately (i.e. timing) // if we don't even simulate the RD/WR pulses properly. // http://gbdev.gg8.se/wiki/articles/Gameboy_Bootstrap_ROM // Read the logo once (to display on the screen) for (unsigned i = rom_offset(logo); i < rom_offset_end(logo); i++) cart_read(i); // Read the logo again (to verify it) for (unsigned i = rom_offset(logo); i < rom_offset_end(logo); i++) { if (rom_logo[i - rom_offset(logo)] != cart_read(i)) { cart_set_standby(); return 1; } } // Read and verify the checksum (and save some info from the header) uint8_t checksum = rom_offset(header_checksum) - rom_offset(title); for (unsigned i = rom_offset(title); i < rom_offset_end(header_checksum); i++) { uint8_t c = cart_read(i); checksum += c; if (i == rom_offset(type)) info->type = c; if (i == rom_offset(rom_size)) info->rom_size = c; if (i == rom_offset(ram_size)) info->ram_size = c; } cart_set_standby(); return checksum; } // https://ia801906.us.archive.org/19/items/GameBoyProgManVer1.1/GameBoyProgManVer1.1.pdf (page 215) // NOTE: These functions only support MBC1, MBC2, MBC3 and MBC5. // Thankfully, that covers most of the licensed carts. void cart_rom_bank(__attribute__((unused)) struct cart_info *info, const unsigned bank) { // MBC1: 0x2000-0x3FFF (bit 0-4) 0x4000-0x5FFF (bit 5-6, if 0x6000-0x7FFF is set to 0) // MBC2: 0x2100-0x21FF (bit 0-3) // MBC3: 0x2000-0x3FFF (bit 0-6) // MBC5: 0x2000-0x2FFF (bit 0-7) 0x3000-0x3FFF (bit 8) cart_set_write(); if (info->type >= ROM_TYPE_MBC1 && info->type <= ROM_TYPE_MBC1_RAM_BATTERY) { cart_write(0x2000, bank & 0x1F); cart_write(0x6000, 0); cart_write(0x4000, bank >> 5); return; } cart_write(0x2100, bank); if (info->type >= ROM_TYPE_MBC5 && info->type <= ROM_TYPE_MBC5_RUMBLE_RAM_BATTERY) { cart_write(0x3000, bank >> 8); } } void cart_ram_enable(__attribute__((unused)) struct cart_info *info) { // MBC1: 0x0000-0x1FFF = 0x0A // MBC2: 0x0000-0x0FFF = 0x0A // MBC3: 0x0000-0x1FFF = 0x0A // MBC5: 0x0000-0x1FFF = 0x0A cart_set_write(); cart_write(0x0000, 0x0A); cart_select_ram(); } void cart_ram_disable(__attribute__((unused)) struct cart_info *info) { // MBC1: 0x0000-0x1FFF = 0x00 // MBC2: 0x0000-0x0FFF = 0x00 // MBC3: 0x0000-0x1FFF = 0x00 // MBC5: 0x0000-0x1FFF = 0x00 cart_set_write(); cart_select_rom(); cart_write(0x0000, 0x00); } void cart_ram_bank(__attribute__((unused)) struct cart_info *info, const unsigned bank) { // MBC1: 0x4000-0x5FFF (bit 0-1, if 0x6000-0x7FFF is set to 1) // MBC2: None, it has only one bank. (Writing to 0x4000-0x5FFF should have no effect) // MBC3: 0x4000-0x5FFF (bit 0-1) NOTE: Writing values 0x08-0x0C opens up possible problems with the RTC! // MBC5: 0x4000-0x5FFF (bit 0-3) cart_set_write(); if (info->type >= ROM_TYPE_MBC1 && info->type <= ROM_TYPE_MBC1_RAM_BATTERY) { cart_write(0x6000, 1); } // Since writing 0x08-0x0C here opens up the timer, // it opens up the possibility for corruption. // As such, it's a good idea to check for it... if (info->type >= ROM_TYPE_MBC3_TIMER_BATTERY && info->type <= ROM_TYPE_MBC3_RAM_BATTERY) { cart_write(0x4000, bank & 0x07); return; } cart_write(0x4000, bank); }