Skip to content

Commit

Permalink
Add initial MBC0 loading support
Browse files Browse the repository at this point in the history
  • Loading branch information
maxfierke committed Dec 16, 2023
1 parent fcb1947 commit 9b654ca
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 21 deletions.
47 changes: 47 additions & 0 deletions cart/cartridge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package cart

import (
"errors"
"fmt"

"github.com/maxfierke/gogo-gb/cart/mbc"
"github.com/maxfierke/gogo-gb/mem"
)

var (
ErrUnsupportedMbc = errors.New("unsupported or unknown MBC type")
)

type Cartridge struct {
Header Header
mbc mem.MemHandler
}

func NewCartridge(r *Reader) (*Cartridge, error) {
cartridge := new(Cartridge)
cartridge.Header = r.Header

rom := make([]byte, r.Header.RomSizeBytes())
copy(rom, r.headerBuf[:])

switch r.Header.CartType {
case CART_TYPE_MBC0:
cartridge.mbc = mbc.NewMBC0(rom)
default:
return nil, fmt.Errorf("unsupported or unknown MBC type: %s", r.Header.CartTypeName())
}

return cartridge, nil
}

func (c *Cartridge) DebugPrint() {
c.Header.DebugPrint()
}

func (c *Cartridge) OnRead(mmu *mem.MMU, addr uint16) mem.MemRead {
return c.mbc.OnRead(mmu, addr)
}

func (c *Cartridge) OnWrite(mmu *mem.MMU, addr uint16, value byte) mem.MemWrite {
return c.mbc.OnWrite(mmu, addr, value)
}
44 changes: 44 additions & 0 deletions cart/mbc/mbc0.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package mbc

import (
"fmt"

"github.com/maxfierke/gogo-gb/mem"
)

const (
mbc0_rom_bank_start = 0x0000
mbc0_rom_bank_end = 0x7FFF
mbc0_ram_bank_start = 0xA000
mbc0_ram_bank_end = 0xBFFF
)

type MBC0 struct {
rom []byte
}

func NewMBC0(rom []byte) *MBC0 {
return &MBC0{rom: rom}
}

func (m *MBC0) OnRead(mmu *mem.MMU, addr uint16) mem.MemRead {
if addr <= mbc0_rom_bank_end {
return mem.ReadReplace(m.rom[addr])
}

return mem.ReadPassthrough()
}

func (m *MBC0) OnWrite(mmu *mem.MMU, addr uint16, value byte) mem.MemWrite {
if addr <= mbc0_rom_bank_end {
// Put the Read-Only in ROM
return mem.WriteBlock()
}

if addr >= mbc0_ram_bank_start && addr <= mbc0_ram_bank_end {
// RAM is RAM and this is a fake cartridge, so...
return mem.WritePassthrough()
}

panic(fmt.Sprintf("Attempting to write 0x%x @ 0x%x, which is out-of-bounds for MBC0", value, addr))
}
26 changes: 8 additions & 18 deletions cart/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ type byteReader interface {

// An io.Reader that mostly cribs from compress/gzip's gunzip.Reader
type Reader struct {
Header Header
r byteReader
decoder Decoder
err error
buf [512]byte
Header Header
r byteReader
err error
headerBuf [HEADER_END + 1]byte
}

// NewReader creates a new Reader reading the given reader.
Expand All @@ -50,9 +49,7 @@ func NewReader(r io.Reader) (*Reader, error) {
// result of its original state from NewReader, but reading from r instead.
// This permits reusing a Reader rather than allocating a new one.
func (cr *Reader) Reset(r io.Reader) error {
*cr = Reader{
decoder: cr.decoder,
}
*cr = Reader{}
if rr, ok := r.(byteReader); ok {
cr.r = rr
} else {
Expand All @@ -63,18 +60,18 @@ func (cr *Reader) Reset(r io.Reader) error {
}

func (cr *Reader) readHeader() (hdr Header, err error) {
if _, err = io.ReadFull(cr.r, cr.buf[:HEADER_END+1]); err != nil {
if _, err = io.ReadFull(cr.r, cr.headerBuf[:]); err != nil {
return hdr, err
}

hdr = NewHeader(cr.buf[:])
hdr = NewHeader(cr.headerBuf[:])

// Check actual checksum against expected. Computed according to
// https://gbdev.io/pandocs/The_Cartridge_Header.html#014d--header-checksum
// The BootROM does this, but so can we. Earlier.
var hdrChksum byte
for addr := titleOffset; addr <= maskRomVerOffset; addr++ {
hdrChksum = hdrChksum - cr.buf[addr] - 1
hdrChksum = hdrChksum - cr.headerBuf[addr] - 1
}

if hdrChksum != hdr.HeaderChecksum {
Expand All @@ -88,10 +85,3 @@ func (cr *Reader) readHeader() (hdr Header, err error) {
func (cr *Reader) Read(p []byte) (n int, err error) {
return cr.r.Read(p)
}

// Close closes the Reader. It does not close the underlying io.Reader.
// In order for the cart checksum to be verified, the reader must be
// fully consumed until the io.EOF.
func (cr *Reader) Close() error {
return cr.decoder.Close()
}
35 changes: 33 additions & 2 deletions hardware/dmg.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
package hardware

import (
"errors"

"github.com/maxfierke/gogo-gb/cart"
"github.com/maxfierke/gogo-gb/cpu"
"github.com/maxfierke/gogo-gb/mem"
)

var (
ErrCartridgeAlreadyLoaded = errors.New("cartridge already loaded")
)

type DMG struct {
cpu *cpu.CPU
mmu *mem.MMU
cpu *cpu.CPU
mmu *mem.MMU
cartridge *cart.Cartridge
}

func NewDMG() (*DMG, error) {
Expand All @@ -24,3 +32,26 @@ func NewDMG() (*DMG, error) {
mmu: mmu,
}, nil
}

func (dmg *DMG) LoadCartridge(r *cart.Reader) error {
if dmg.cartridge != nil {
return ErrCartridgeAlreadyLoaded
}

cartridge, err := cart.NewCartridge(r)
if err != nil {
return err
}

dmg.cartridge = cartridge
dmg.mmu.AddHandler(mem.MemRegion{Start: 0x0000, End: 0x7FFF}, dmg.cartridge) // MBCs ROM Banks
dmg.mmu.AddHandler(mem.MemRegion{Start: 0x0A00, End: 0xBFFF}, dmg.cartridge) // MBCs RAM Banks

return nil
}

func (dmg *DMG) DebugPrint() {
if dmg.cartridge != nil {
dmg.cartridge.DebugPrint()
}
}
31 changes: 30 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/maxfierke/gogo-gb/cart"
"github.com/maxfierke/gogo-gb/cpu/isa"
"github.com/maxfierke/gogo-gb/hardware"
)

func main() {
Expand All @@ -17,7 +18,7 @@ func main() {
debugPrintPtr := flag.String("debug-print", "", "Print out something for debugging purposes. Currently just 'cart-header', 'opcodes'")
flag.Parse()

if debugPrintPtr != nil {
if debugPrintPtr != nil && *debugPrintPtr != "" {
if *debugPrintPtr == "cart-header" {
cartFile, err := os.Open(*cartPath)
if *cartPath == "" || err != nil {
Expand All @@ -43,5 +44,33 @@ func main() {

opcodes.DebugPrint()
}
} else {
dmg, err := hardware.NewDMG()
if err != nil {
log.Fatalf("Unable to initialize DMG: %v\n", err)
}

cartFile, err := os.Open(*cartPath)
if *cartPath == "" || err != nil {
log.Fatalf("Unable to load cartridge. Please ensure it's inserted correctly (exists): %v\n", err)
}
defer cartFile.Close()

cartReader, err := cart.NewReader(cartFile)
if err == cart.ErrHeader {
log.Printf("Warning: Cartridge header does not match expected checksum. Continuing, but subsequent operations may fail")
} else if err != nil {
log.Fatalf("Unable to load cartridge. Please ensure it's inserted correctly (exists): %v\n", err)
}

err = dmg.LoadCartridge(cartReader)
if err == cart.ErrHeader {
log.Printf("Warning: Cartridge header does not match expected checksum. Continuing, but subsequent operations may fail")
} else if err != nil {
log.Fatalf("Unable to load cartridge: %v\n", err)
}

log.Println("Loaded cartridge successfully")
dmg.DebugPrint()
}
}

0 comments on commit 9b654ca

Please sign in to comment.