Skip to content

Commit

Permalink
Merge pull request #6 from maxfierke/mf-mbc3_no_rtc
Browse files Browse the repository at this point in the history
Implement MBC3/MBC30 (w/o RTC)
  • Loading branch information
maxfierke authored Dec 31, 2024
2 parents 2311c45 + be75f17 commit 5f1fbbf
Show file tree
Hide file tree
Showing 10 changed files with 477 additions and 74 deletions.
31 changes: 26 additions & 5 deletions cart/cartridge.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package cart

import (
"bytes"
"errors"
"fmt"
"io"
"log"

"github.com/maxfierke/gogo-gb/cart/mbc"
Expand Down Expand Up @@ -37,20 +37,41 @@ func (c *Cartridge) LoadCartridge(r *Reader) error {

c.Header = r.Header

rom := make([]byte, r.Header.RomSizeBytes())
copy(rom, r.headerBuf[:])
_, err := io.ReadFull(r, rom[HEADER_END+1:])
romSize := r.Header.RomSizeBytes()
romBuffer := new(bytes.Buffer)
romBuffer.Grow(int(romSize))
headerBytes, err := romBuffer.Write(r.headerBuf[:])
if err != nil {
return err
return fmt.Errorf("copying cartridge header: %w. read %d bytes", err, headerBytes)
}

n, err := romBuffer.ReadFrom(r)
if err != nil {
return fmt.Errorf("reading cartridge ROM: %w. read %d bytes", err, n)
}

rom := make([]byte, romSize)
copy(rom, romBuffer.Bytes())

ram := make([]byte, r.Header.RamSizeBytes())

switch r.Header.CartType {
case CART_TYPE_MBC0:
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:
if r.Header.IsMBC30() {
c.mbc = mbc.NewMBC30(rom, ram, false)
} else {
c.mbc = mbc.NewMBC3(rom, ram, false)
}
case CART_TYPE_MBC3_RTC_BAT, CART_TYPE_MBC3_RTC_RAM_BAT:
if r.Header.IsMBC30() {
c.mbc = mbc.NewMBC30(rom, ram, true)
} else {
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
108 changes: 62 additions & 46 deletions cart/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const (
globalChkOffset = 0x14E // 0x14E-0x14F
HEADER_START = 0x100
HEADER_END = 0x14F
HEADER_SIZE = HEADER_END + 1
)

const (
Expand All @@ -33,43 +34,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 +88,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 Expand Up @@ -123,25 +126,25 @@ func (hdr *Header) CartTypeName() string {
case CART_TYPE_MBC3_RTC_BAT:
return "MBC3+TIMER+BATTERY"
case CART_TYPE_MBC3_RTC_RAM_BAT:
if hdr.ramSize == 0x05 {
if hdr.IsMBC30() {
return "MBC30+TIMER+RAM+BATTERY"
} else {
return "MBC3+TIMER+RAM+BATTERY"
}
return "MBC3+TIMER+RAM+BATTERY"
case CART_TYPE_MBC3:
if hdr.IsMBC30() {
return "MBC30"
}
return "MBC3"
case CART_TYPE_MBC3_RAM:
if hdr.ramSize == 0x05 {
if hdr.IsMBC30() {
return "MBC30+RAM"
} else {
return "MBC3+RAM"
}
return "MBC3+RAM"
case CART_TYPE_MBC3_RAM_BAT:
if hdr.ramSize == 0x05 {
if hdr.IsMBC30() {
return "MBC30+RAM+BATTERY"
} else {
return "MBC3+RAM+BATTERY"
}
return "MBC3+RAM+BATTERY"
case CART_TYPE_MBC5:
return "MBC5"
case CART_TYPE_MBC5_RAM:
Expand Down Expand Up @@ -191,6 +194,19 @@ func (hdr *Header) Destination() string {
}
}

func (hdr *Header) IsMBC30() bool {
switch hdr.CartType {
case CART_TYPE_MBC3,
CART_TYPE_MBC3_RAM,
CART_TYPE_MBC3_RAM_BAT,
CART_TYPE_MBC3_RTC_BAT,
CART_TYPE_MBC3_RTC_RAM_BAT:
return hdr.ramSize == 0x05 || hdr.romSize == 0x07
default:
return false
}
}

func (hdr *Header) Sgb() bool {
return hdr.sgb == 0x03
}
Expand Down Expand Up @@ -223,16 +239,16 @@ func (hdr *Header) RamSizeBytes() uint {
}

func (hdr *Header) DebugPrint(logger *log.Logger) {
logger.Printf("== Cartridge Info ==\n\n")

logger.Printf("Title: %s\n", hdr.Title)
logger.Printf("== Cartridge Info ==\n")
logger.Printf("\n")
logger.Printf("Title: %s\n", hdr.Title)
logger.Printf("Licensee: %s\n", hdr.Licensee())
logger.Printf("Color: %s (0x%x)\n", hdr.Cgb(), hdr.cgb)
logger.Printf("Color: %s (0x%x)\n", hdr.Cgb(), hdr.cgb)
logger.Printf("TV-Ready: %s (0x%x)\n", hdr.SgbMode(), hdr.sgb)
logger.Printf("Cart Type: %s (0x%x)\n", hdr.CartTypeName(), hdr.CartType)
logger.Printf("ROM Size: %d KiB\n", hdr.RomSizeBytes()/1024)
logger.Printf("RAM Size: %d KiB\n", hdr.RamSizeBytes()/1024)
logger.Printf("Destination: %s (0x%x)\n", hdr.Destination(), hdr.destinationCode)
logger.Printf("ROM Size: %d KiB (0x%x)\n", hdr.RomSizeBytes()/1024, hdr.romSize)
logger.Printf("RAM Size: %d KiB (0x%x)\n", hdr.RamSizeBytes()/1024, hdr.ramSize)
logger.Printf("Destination: %s (0x%x)\n", hdr.Destination(), hdr.destinationCode)
logger.Printf("Mask ROM Version: 0x%x\n", hdr.maskROMVersion)
logger.Printf("Header Checksum: 0x%x\n", hdr.HeaderChecksum)
logger.Printf("Global Checksum: 0x%x\n", hdr.GlobalChecksum)
Expand Down
8 changes: 4 additions & 4 deletions cart/mbc/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ const (
)

func readBankAddr(memory []byte, banksRegion mem.MemRegion, bankSize uint16, currentBank uint16, addr uint16) byte {
bankBaseAddr := currentBank * bankSize
bankSlotAddr := addr - banksRegion.Start
bankBaseAddr := uint(currentBank) * uint(bankSize)
bankSlotAddr := uint(addr) - uint(banksRegion.Start)
return memory[bankBaseAddr+bankSlotAddr]
}

func writeBankAddr(memory []byte, banksRegion mem.MemRegion, bankSize uint16, currentBank uint16, addr uint16, value byte) {
bankBaseAddr := currentBank * bankSize
bankSlotAddr := addr - banksRegion.Start
bankBaseAddr := uint(currentBank) * uint(bankSize)
bankSlotAddr := uint(addr) - uint(banksRegion.Start)
memory[bankBaseAddr+bankSlotAddr] = value
}
97 changes: 97 additions & 0 deletions cart/mbc/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package mbc

import (
"testing"

"github.com/maxfierke/gogo-gb/mem"
"github.com/stretchr/testify/assert"
)

func makeRom(banks int) []byte {
rom := make([]byte, ROM_BANK_SIZE*banks)

for bankNum := 0; bankNum < banks; bankNum++ {
for bankSlot := 0; bankSlot < ROM_BANK_SIZE; bankSlot++ {
rom[(ROM_BANK_SIZE*bankNum)+bankSlot] = byte(bankNum)
}
}

return rom
}

func TestReadBankAddr(t *testing.T) {
assert := assert.New(t)
rom := makeRom(8)

banksRegion := mem.MemRegion{
Start: 0x0000,
End: 0x3FFF,
}

assert.Equal(
byte(0x0),
readBankAddr(rom, banksRegion, ROM_BANK_SIZE, 0, 0),
)
assert.Equal(
byte(0x0),
readBankAddr(rom, banksRegion, ROM_BANK_SIZE, 0, ROM_BANK_SIZE-1),
)
assert.Equal(
byte(0x1),
readBankAddr(rom, banksRegion, ROM_BANK_SIZE, 1, 0),
)
assert.Equal(
byte(0x1),
readBankAddr(rom, banksRegion, ROM_BANK_SIZE, 1, ROM_BANK_SIZE-1),
)
assert.Equal(
byte(0x6),
readBankAddr(rom, banksRegion, ROM_BANK_SIZE, 6, 0),
)
assert.Equal(
byte(0x6),
readBankAddr(rom, banksRegion, ROM_BANK_SIZE, 6, ROM_BANK_SIZE-1),
)
}

func TestWriteBankAddr(t *testing.T) {
assert := assert.New(t)
rom := makeRom(8)

banksRegion := mem.MemRegion{
Start: 0x0000,
End: 0x3FFF,
}

writeBankAddr(rom, banksRegion, ROM_BANK_SIZE, 0, 0, 0xFF)
writeBankAddr(rom, banksRegion, ROM_BANK_SIZE, 0, ROM_BANK_SIZE-1, 0xFF)
writeBankAddr(rom, banksRegion, ROM_BANK_SIZE, 1, 0, 0xFE)
writeBankAddr(rom, banksRegion, ROM_BANK_SIZE, 1, ROM_BANK_SIZE-1, 0xFE)
writeBankAddr(rom, banksRegion, ROM_BANK_SIZE, 6, 0, 0xFD)
writeBankAddr(rom, banksRegion, ROM_BANK_SIZE, 6, ROM_BANK_SIZE-1, 0xFD)

assert.Equal(
byte(0xFF),
rom[0],
)
assert.Equal(
byte(0xFF),
rom[ROM_BANK_SIZE-1],
)
assert.Equal(
byte(0xFE),
rom[ROM_BANK_SIZE*1],
)
assert.Equal(
byte(0xFE),
rom[ROM_BANK_SIZE*1+(ROM_BANK_SIZE-1)],
)
assert.Equal(
byte(0xFD),
rom[ROM_BANK_SIZE*6],
)
assert.Equal(
byte(0xFD),
rom[ROM_BANK_SIZE*6+(ROM_BANK_SIZE-1)],
)
}
Loading

0 comments on commit 5f1fbbf

Please sign in to comment.