Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement MBC3 #6

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cart/cartridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
62 changes: 32 additions & 30 deletions cart/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,43 +33,45 @@ 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 {
Title string
cgb byte
newLicenseeCode string
sgb byte
CartType byte
CartType cartType
romSize byte
ramSize byte
destinationCode byte
Expand All @@ -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],
Expand Down
209 changes: 209 additions & 0 deletions cart/mbc/mbc3.go
Original file line number Diff line number Diff line change
@@ -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))
}
}
Loading