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 cartridge SRAM save/restore #9

Merged
merged 1 commit into from
Jan 2, 2025
Merged
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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# gogo-gb
a gameboy emulator for funsies

Current status: Games are playable, but slow. Graphics are buggy. No audio.

## TODO

- [X] Pass all of Blargg's `cpu_instrs` ROMs via `gameboy-doctor` (expect `02-interrupts.gb`, which isn't verifyable via `gameboy-doctor`)
Expand All @@ -15,9 +17,10 @@ a gameboy emulator for funsies
- [ ] Pass all of Blargg's `mem_timing-2` ROMs (manually verified)
- [X] Implement Joypad
- [ ] Implement RTC for MBC3
- [ ] Implement SRAM save & restore
- [X] Implement SRAM save & restore
- [ ] Pass `dmg-acid2` test ROM
- [ ] Implement Sound/APU
- [ ] Implement GBC

## Maybe Never?

Expand All @@ -26,6 +29,7 @@ Just being realistic about my likelihood of getting to these:
- [ ] FIFO-based rendering PPU (currently scanline)
- [ ] Implement emulation for every known DMG bug
- [ ] Implement SGB mode
- [ ] Implement MBC2
- [ ] Implement MBC6
- [ ] Implement MBC7
- [ ] Implement any multicarts or Hudson carts
Expand Down
11 changes: 10 additions & 1 deletion cart/cartridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"errors"
"fmt"
"io"
"log"

"github.com/maxfierke/gogo-gb/cart/mbc"
Expand All @@ -17,7 +18,7 @@ var (

type Cartridge struct {
Header Header
mbc mem.MemHandler
mbc mbc.MBC
}

func NewCartridge() *Cartridge {
Expand Down Expand Up @@ -81,6 +82,14 @@ func (c *Cartridge) LoadCartridge(r *Reader) error {
return nil
}

func (c *Cartridge) Save(w io.Writer) error {
return c.mbc.Save(w)
}

func (c *Cartridge) LoadSave(r io.Reader) error {
return c.mbc.LoadSave(r)
}

func (c *Cartridge) OnRead(mmu *mem.MMU, addr uint16) mem.MemRead {
if c.mbc == nil {
return mem.ReadPassthrough()
Expand Down
4 changes: 4 additions & 0 deletions cart/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,10 @@ func (hdr *Header) RamSizeBytes() uint {
}
}

func (hdr *Header) SupportsSaving() bool {
return hdr.RamSizeBytes() > 0
}

func (hdr *Header) DebugPrint(logger *log.Logger) {
logger.Printf("== Cartridge Info ==\n")
logger.Printf("\n")
Expand Down
12 changes: 11 additions & 1 deletion cart/mbc/common.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package mbc

import "github.com/maxfierke/gogo-gb/mem"
import (
"io"

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

const (
RAM_BANK_SIZE = 0x2000
Expand All @@ -18,3 +22,9 @@ func writeBankAddr(memory []byte, banksRegion mem.MemRegion, bankSize uint16, cu
bankSlotAddr := uint(addr) - uint(banksRegion.Start)
memory[bankBaseAddr+bankSlotAddr] = value
}

type MBC interface {
mem.MemHandler
Save(w io.Writer) error
LoadSave(r io.Reader) error
}
11 changes: 11 additions & 0 deletions cart/mbc/mbc0.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mbc

import (
"fmt"
"io"

"github.com/maxfierke/gogo-gb/mem"
)
Expand All @@ -15,6 +16,8 @@ type MBC0 struct {
rom []byte
}

var _ MBC = (*MBC0)(nil)

func NewMBC0(rom []byte) *MBC0 {
return &MBC0{rom: rom}
}
Expand All @@ -40,3 +43,11 @@ func (m *MBC0) OnWrite(mmu *mem.MMU, addr uint16, value byte) mem.MemWrite {

panic(fmt.Sprintf("Attempting to write 0x%02X @ 0x%04X, which is out-of-bounds for MBC0", value, addr))
}

func (m *MBC0) Save(w io.Writer) error {
return nil
}

func (m *MBC0) LoadSave(r io.Reader) error {
return nil
}
29 changes: 29 additions & 0 deletions cart/mbc/mbc1.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mbc

import (
"fmt"
"io"

"github.com/maxfierke/gogo-gb/mem"
)
Expand Down Expand Up @@ -34,6 +35,8 @@ type MBC1 struct {
rom []byte
}

var _ MBC = (*MBC1)(nil)

func NewMBC1(rom []byte, ram []byte) *MBC1 {
return &MBC1{
curRamBank: 0,
Expand Down Expand Up @@ -128,3 +131,29 @@ func (m *MBC1) OnWrite(mmu *mem.MMU, addr uint16, value byte) mem.MemWrite {

panic(fmt.Sprintf("Attempting to write 0x%02X @ 0x%04X, which is out-of-bounds for MBC1", value, addr))
}

func (m *MBC1) Save(w io.Writer) error {
if len(m.ram) == 0 {
return nil
}

n, err := w.Write(m.ram)
if err != nil {
return fmt.Errorf("mbc1: saving SRAM: %w. wrote %d bytes", err, n)
}

return nil
}

func (m *MBC1) LoadSave(r io.Reader) error {
if len(m.ram) == 0 {
return nil
}

n, err := io.ReadFull(r, m.ram)
if err != nil {
return fmt.Errorf("mbc1: loading save into SRAM: %w. read %d bytes", err, n)
}

return nil
}
41 changes: 41 additions & 0 deletions cart/mbc/mbc3.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mbc

import (
"fmt"
"io"

"github.com/maxfierke/gogo-gb/mem"
)
Expand Down Expand Up @@ -60,6 +61,8 @@ type MBC3 struct {
rtcDaysOverflow bool
}

var _ MBC = (*MBC3)(nil)

func NewMBC3(rom []byte, ram []byte, rtcAvailable bool) *MBC3 {
return &MBC3{
ram: ram,
Expand Down Expand Up @@ -210,6 +213,36 @@ func (m *MBC3) writeRtcReg(reg mbc3RtcReg, value byte) {
}
}

func (m *MBC3) Save(w io.Writer) error {
if len(m.ram) == 0 {
return nil
}

n, err := w.Write(m.ram)
if err != nil {
return fmt.Errorf("mbc3: saving SRAM: %w. wrote %d bytes", err, n)
}

// TODO: Write RTC registers

return nil
}

func (m *MBC3) LoadSave(r io.Reader) error {
if len(m.ram) == 0 {
return nil
}

n, err := io.ReadFull(r, m.ram)
if err != nil {
return fmt.Errorf("mbc3: loading save into SRAM: %w. read %d bytes", err, n)
}

// TODO: Read RTC registers

return nil
}

var (
MBC30_ROM_BANKS = mem.MemRegion{Start: 0x4000, End: 0x7FFF}

Expand Down Expand Up @@ -269,3 +302,11 @@ func (m *MBC30) OnWrite(mmu *mem.MMU, addr uint16, value byte) mem.MemWrite {

return m.MBC3.OnWrite(mmu, addr, value)
}

func (m *MBC30) Save(w io.Writer) error {
return m.MBC3.Save(w)
}

func (m *MBC30) LoadSave(r io.Reader) error {
return m.MBC3.LoadSave(r)
}
29 changes: 29 additions & 0 deletions cart/mbc/mbc5.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mbc

import (
"fmt"
"io"

"github.com/maxfierke/gogo-gb/mem"
)
Expand Down Expand Up @@ -34,6 +35,8 @@ type MBC5 struct {
rom []byte
}

var _ MBC = (*MBC5)(nil)

func NewMBC5(rom []byte, ram []byte) *MBC5 {
return &MBC5{
curRamBank: 0,
Expand Down Expand Up @@ -114,3 +117,29 @@ func (m *MBC5) OnWrite(mmu *mem.MMU, addr uint16, value byte) mem.MemWrite {

panic(fmt.Sprintf("Attempting to write 0x%02X @ 0x%04X, which is out-of-bounds for MBC5", value, addr))
}

func (m *MBC5) Save(w io.Writer) error {
if len(m.ram) == 0 {
return nil
}

n, err := w.Write(m.ram)
if err != nil {
return fmt.Errorf("mbc5: saving SRAM: %w. wrote %d bytes", err, n)
}

return nil
}

func (m *MBC5) LoadSave(r io.Reader) error {
if len(m.ram) == 0 {
return nil
}

n, err := io.ReadFull(r, m.ram)
if err != nil {
return fmt.Errorf("mbc5: loading save into SRAM: %w. read %d bytes", err, n)
}

return nil
}
5 changes: 5 additions & 0 deletions hardware/console.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package hardware

import (
"io"

"github.com/maxfierke/gogo-gb/cart"
"github.com/maxfierke/gogo-gb/debug"
"github.com/maxfierke/gogo-gb/devices"
Expand All @@ -9,7 +11,10 @@ import (
type Console interface {
AttachDebugger(debugger debug.Debugger)
DetachDebugger()
CartridgeHeader() cart.Header
LoadCartridge(r *cart.Reader) error
Save(w io.Writer) error
LoadSave(r io.Reader) error
Step() error
Run(host devices.HostInterface) error
}
33 changes: 32 additions & 1 deletion hardware/dmg.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,39 @@ func (dmg *DMG) DetachDebugger() {
dmg.debugger = debug.NewNullDebugger()
}

func (dmg *DMG) CartridgeHeader() cart.Header {
if dmg.cartridge == nil {
return cart.Header{}
}

return dmg.cartridge.Header
}

func (dmg *DMG) LoadCartridge(r *cart.Reader) error {
return dmg.cartridge.LoadCartridge(r)
err := dmg.cartridge.LoadCartridge(r)
if err != nil {
return fmt.Errorf("dmg: loading cartridge: %w", err)
}

return nil
}

func (dmg *DMG) LoadSave(r io.Reader) error {
err := dmg.cartridge.LoadSave(r)
if err != nil {
return fmt.Errorf("dmg: loading save: %w", err)
}

return nil
}

func (dmg *DMG) Save(w io.Writer) error {
err := dmg.cartridge.Save(w)
if err != nil {
return fmt.Errorf("dmg: writing save: %w", err)
}

return nil
}

func (dmg *DMG) DebugPrint(logger *log.Logger) {
Expand Down
Loading
Loading