diff --git a/cart/cartridge.go b/cart/cartridge.go index 8937648..e70dbd7 100644 --- a/cart/cartridge.go +++ b/cart/cartridge.go @@ -51,6 +51,10 @@ func (c *Cartridge) LoadCartridge(r *Reader) error { c.mbc = mbc.NewMBC0(rom) case CART_TYPE_MBC1, CART_TYPE_MBC1_RAM, CART_TYPE_MBC1_RAM_BAT: c.mbc = mbc.NewMBC1(rom, ram) + case CART_TYPE_MBC3, CART_TYPE_MBC3_RAM, CART_TYPE_MBC3_RAM_BAT: + c.mbc = mbc.NewMBC3(rom, ram, false) + case CART_TYPE_MBC3_RTC_BAT, CART_TYPE_MBC3_RTC_RAM_BAT: + c.mbc = mbc.NewMBC3(rom, ram, true) case CART_TYPE_MBC5, CART_TYPE_MBC5_RAM, CART_TYPE_MBC5_RAM_BAT: c.mbc = mbc.NewMBC5(rom, ram) default: diff --git a/cart/header.go b/cart/header.go index c755794..0a233e8 100644 --- a/cart/header.go +++ b/cart/header.go @@ -33,35 +33,37 @@ const ( CGB_UNKNOWN = "Unknown" ) +type cartType byte + const ( - CART_TYPE_MBC0 = 0x00 - CART_TYPE_MBC1 = 0x01 - CART_TYPE_MBC1_RAM = 0x02 - CART_TYPE_MBC1_RAM_BAT = 0x03 - CART_TYPE_MBC2 = 0x05 - CART_TYPE_MBC2_BAT = 0x06 - CART_TYPE_UNK_ROM_RAM = 0x08 - CART_TYPE_UNK_ROM_RAM_BAT = 0x09 - CART_TYPE_MMM01 = 0x0B - CART_TYPE_MMM01_RAM = 0x0C - CART_TYPE_MMM01_RAM_BAT = 0x0D - CART_TYPE_MBC3_RTC_BAT = 0x0F - CART_TYPE_MBC3_RTC_RAM_BAT = 0x10 - CART_TYPE_MBC3 = 0x11 - CART_TYPE_MBC3_RAM = 0x12 - CART_TYPE_MBC3_RAM_BAT = 0x13 - CART_TYPE_MBC5 = 0x19 - CART_TYPE_MBC5_RAM = 0x1A - CART_TYPE_MBC5_RAM_BAT = 0x1B - CART_TYPE_MBC5_RUMBLE = 0x1C - CART_TYPE_MBC5_RUMBLE_RAM = 0x1D - CART_TYPE_MBC5_RUMBLE_RAM_BAT = 0x1E - CART_TYPE_MBC6 = 0x20 - CART_TYPE_MBC7_SENSOR_RUMBLE_RAM_BAT = 0x22 - CART_TYPE_POCKET_CAM = 0xFC - CART_TYPE_BANDAI_TAMA5 = 0xFD - CART_TYPE_HUC3 = 0xFE - CART_TYPE_HUC1_RAM_BAT = 0xFF + CART_TYPE_MBC0 cartType = 0x00 + CART_TYPE_MBC1 cartType = 0x01 + CART_TYPE_MBC1_RAM cartType = 0x02 + CART_TYPE_MBC1_RAM_BAT cartType = 0x03 + CART_TYPE_MBC2 cartType = 0x05 + CART_TYPE_MBC2_BAT cartType = 0x06 + CART_TYPE_UNK_ROM_RAM cartType = 0x08 + CART_TYPE_UNK_ROM_RAM_BAT cartType = 0x09 + CART_TYPE_MMM01 cartType = 0x0B + CART_TYPE_MMM01_RAM cartType = 0x0C + CART_TYPE_MMM01_RAM_BAT cartType = 0x0D + CART_TYPE_MBC3_RTC_BAT cartType = 0x0F + CART_TYPE_MBC3_RTC_RAM_BAT cartType = 0x10 + CART_TYPE_MBC3 cartType = 0x11 + CART_TYPE_MBC3_RAM cartType = 0x12 + CART_TYPE_MBC3_RAM_BAT cartType = 0x13 + CART_TYPE_MBC5 cartType = 0x19 + CART_TYPE_MBC5_RAM cartType = 0x1A + CART_TYPE_MBC5_RAM_BAT cartType = 0x1B + CART_TYPE_MBC5_RUMBLE cartType = 0x1C + CART_TYPE_MBC5_RUMBLE_RAM cartType = 0x1D + CART_TYPE_MBC5_RUMBLE_RAM_BAT cartType = 0x1E + CART_TYPE_MBC6 cartType = 0x20 + CART_TYPE_MBC7_SENSOR_RUMBLE_RAM_BAT cartType = 0x22 + CART_TYPE_POCKET_CAM cartType = 0xFC + CART_TYPE_BANDAI_TAMA5 cartType = 0xFD + CART_TYPE_HUC3 cartType = 0xFE + CART_TYPE_HUC1_RAM_BAT cartType = 0xFF ) type Header struct { @@ -69,7 +71,7 @@ type Header struct { cgb byte newLicenseeCode string sgb byte - CartType byte + CartType cartType romSize byte ramSize byte destinationCode byte @@ -85,7 +87,7 @@ func NewHeader(bytes []byte) Header { cgb: bytes[cgbOffset], newLicenseeCode: string(bytes[newLicenseeOffset:sgbOffset]), sgb: bytes[sgbOffset], - CartType: bytes[cartTypeOffset], + CartType: cartType(bytes[cartTypeOffset]), romSize: bytes[romSizeOffset], ramSize: bytes[ramSizeOffset], destinationCode: bytes[destCodeOffset], diff --git a/cart/mbc/mbc3.go b/cart/mbc/mbc3.go new file mode 100644 index 0000000..cbc3184 --- /dev/null +++ b/cart/mbc/mbc3.go @@ -0,0 +1,209 @@ +package mbc + +import ( + "fmt" + + "github.com/maxfierke/gogo-gb/mem" +) + +var ( + MBC3_ROM_BANK_00 = mem.MemRegion{Start: 0x0000, End: 0x3FFF} + MBC3_ROM_BANKS = mem.MemRegion{Start: 0x4000, End: 0x7FFF} + MBC3_RAM_BANKS = mem.MemRegion{Start: 0xA000, End: 0xBFFF} + + MBC3_REG_RTC = mem.MemRegion{Start: 0xA000, End: 0xBFFF} + + MBC3_REG_RAM_RTC_ENABLE = mem.MemRegion{Start: 0x0000, End: 0x1FFF} + MBC3_REG_RAM_RTC_ENABLE_MASK = byte(0xF) + MBC3_REG_RAM_RTC_ENABLED = byte(0xA) + + MBC3_REG_ROM_BANK = mem.MemRegion{Start: 0x2000, End: 0x3FFF} + MBC3_REG_ROM_BANK_SEL_MASK = byte(0x80) + + MBC3_REG_RAM_BANK_OR_RTC_REG_SEL = mem.MemRegion{Start: 0x4000, End: 0x5FFF} + + MBC3_REG_RTC_LATCH_DATA = mem.MemRegion{Start: 0x6000, End: 0x7FFF} +) + +type mbc3RtcReg byte + +const ( + MBC3_RTC_REG_NONE mbc3RtcReg = 0x00 + MBC3_RTC_REG_SECONDS mbc3RtcReg = 0x08 + MBC3_RTC_REG_MINUTES mbc3RtcReg = 0x09 + MBC3_RTC_REG_HOURS mbc3RtcReg = 0x0A + MBC3_RTC_REG_DAY_LOW mbc3RtcReg = 0x0B + MBC3_RTC_REG_DAY_HIGH mbc3RtcReg = 0x0C + + MBC3_RTC_REG_DAY_HIGH_BIT_DAY_MSB = 0 + MBC3_RTC_REG_DAY_HIGH_BIT_HALT = 1 << 6 + MBC3_RTC_REG_DAY_HIGH_BIT_CARRY = 1 << 7 +) + +type MBC3 struct { + curRamBank uint8 + curRomBank uint8 + ram []byte + ramEnabled bool + ramSelected bool + rtcEnabled bool + rom []byte + + rtcAvailable bool + rtcLatchRequested bool + rtcRegSelected mbc3RtcReg + rtcSeconds uint8 + rtcMinutes uint8 + rtcHours uint8 + rtcDays uint + rtcHalt bool + rtcDaysOverflow bool +} + +func NewMBC3(rom []byte, ram []byte, rtcAvailable bool) *MBC3 { + return &MBC3{ + ram: ram, + rom: rom, + rtcAvailable: rtcAvailable, + } +} + +func (m *MBC3) Step(cycles uint8) { + // TODO: tick the RTC +} + +func (m *MBC3) OnRead(mmu *mem.MMU, addr uint16) mem.MemRead { + if MBC3_ROM_BANK_00.Contains(addr, false) { + return mem.ReadReplace(m.rom[addr]) + } else if MBC3_ROM_BANKS.Contains(addr, false) { + romBank := max(m.curRomBank, 1) + bankByte := readBankAddr( + m.rom, + MBC3_ROM_BANKS, + ROM_BANK_SIZE, + uint16(romBank), + addr, + ) + return mem.ReadReplace(bankByte) + } else if MBC3_RAM_BANKS.Contains(addr, false) { + if m.ramEnabled && m.ramSelected { + bankByte := readBankAddr( + m.ram, + MBC3_RAM_BANKS, + RAM_BANK_SIZE, + uint16(m.curRamBank), + addr, + ) + return mem.ReadReplace(bankByte) + } else if m.rtcEnabled && m.rtcRegSelected != MBC3_RTC_REG_NONE { + value := m.readRtcReg(m.rtcRegSelected) + return mem.ReadReplace(value) + } else { + // Docs say this is usually 0xFF, but not guaranteed. Randomness needed? + return mem.ReadReplace(0xFF) + } + } + + return mem.ReadPassthrough() +} + +func (m *MBC3) OnWrite(mmu *mem.MMU, addr uint16, value byte) mem.MemWrite { + if MBC3_REG_RAM_RTC_ENABLE.Contains(addr, false) { + if value&MBC3_REG_RAM_RTC_ENABLE_MASK == MBC3_REG_RAM_RTC_ENABLED { + m.ramEnabled = true + m.rtcEnabled = m.rtcAvailable + } else { + m.ramEnabled = false + m.rtcEnabled = false + } + + return mem.WriteBlock() + } else if MBC3_REG_ROM_BANK.Contains(addr, false) { + m.curRomBank = value & MBC3_REG_ROM_BANK_SEL_MASK + return mem.WriteBlock() + } else if MBC3_REG_RAM_BANK_OR_RTC_REG_SEL.Contains(addr, false) { + if value <= 0x3 { + m.curRamBank = value & 0x3 + m.ramSelected = true + m.rtcRegSelected = MBC3_RTC_REG_NONE + } else if value >= 0x08 && value <= 0x0C { + m.ramSelected = false + m.rtcRegSelected = mbc3RtcReg(value) + } + return mem.WriteBlock() + } else if MBC3_REG_RTC_LATCH_DATA.Contains(addr, false) { + if value == 0x00 && !m.rtcLatchRequested { + m.rtcLatchRequested = true + } else if value == 0x01 && m.rtcLatchRequested { + // TODO: Latch current time into RTC registers (?) + m.rtcLatchRequested = false + } else { + m.rtcLatchRequested = false + } + + return mem.WriteBlock() + } else if MBC3_RAM_BANKS.Contains(addr, false) { + if m.ramEnabled && m.ramSelected { + writeBankAddr( + m.ram, + MBC3_RAM_BANKS, + RAM_BANK_SIZE, + uint16(m.curRamBank), + addr, + value, + ) + } else if m.rtcEnabled && m.rtcRegSelected != MBC3_RTC_REG_NONE { + m.writeRtcReg(m.rtcRegSelected, value) + } + return mem.WriteBlock() + } + + panic(fmt.Sprintf("Attempting to write 0x%02X @ 0x%04X, which is out-of-bounds for MBC3", value, addr)) +} + +func (m *MBC3) readRtcReg(reg mbc3RtcReg) byte { + switch reg { + case MBC3_RTC_REG_SECONDS: + return m.rtcSeconds + case MBC3_RTC_REG_MINUTES: + return m.rtcMinutes + case MBC3_RTC_REG_HOURS: + return m.rtcHours + case MBC3_RTC_REG_DAY_LOW: + return byte(m.rtcDays & 0xFF) + case MBC3_RTC_REG_DAY_HIGH: + dayCounterMsb := byte((m.rtcDays >> 8) & 0b1) + overflow := byte(0x0) + if m.rtcDaysOverflow { + overflow = 1 << 7 + } + + halt := byte(0x0) + if m.rtcHalt { + halt = 1 << 6 + } + + return (overflow | halt | dayCounterMsb) + } + + panic(fmt.Sprintf("Attempting to read RTC reg 0x%02X, which is out-of-bounds for MBC3", reg)) +} + +func (m *MBC3) writeRtcReg(reg mbc3RtcReg, value byte) { + switch reg { + case MBC3_RTC_REG_SECONDS: + m.rtcSeconds = value & 0x3F + case MBC3_RTC_REG_MINUTES: + m.rtcMinutes = value & 0x3F + case MBC3_RTC_REG_HOURS: + m.rtcHours = value & 0x1F + case MBC3_RTC_REG_DAY_LOW: + m.rtcDays = (m.rtcDays & 0x100) | uint(value) + case MBC3_RTC_REG_DAY_HIGH: + m.rtcDays = (m.rtcDays & 0xFF) | (uint(value&0b1) << 8) + m.rtcHalt = ((value >> 6) & 0b1) == 1 + m.rtcDaysOverflow = ((value >> 7) & 0b1) == 1 + default: + panic(fmt.Sprintf("Attempting to write RTC reg 0x%02X with 0x%02X, which is out-of-bounds for MBC3", reg, value)) + } +}