Arduino gameboy cart reader software, using https://github.com/insidegadgets/GBCartRead
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

227 lines
6.0 KiB

4 years ago
#include "cart.h"
#include <stdint.h>
#include <avr/io.h>
#include <util/delay.h>
#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);
}