diff --git a/README.md b/README.md index 7a8bc04..3757eb2 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,6 @@ $ ./build/darwin-amd64/mettaur XXXX.gba ## ToDo - [ ] Sound -- [ ] SRAM Save - [ ] Window - [ ] Mosaic - [ ] Blend diff --git a/cmd/main.go b/cmd/main.go index 3fc7f3a..8ae3867 100755 --- a/cmd/main.go +++ b/cmd/main.go @@ -8,6 +8,7 @@ import ( "os" "os/signal" "path/filepath" + "strings" "syscall" "github.com/hajimehoshi/ebiten/v2" @@ -53,8 +54,9 @@ func main() { // Run program func Run() ExitCode { var ( - showVersion = flag.Bool("v", false, "show version") - showCartInfo = flag.Bool("c", false, "show cartridge info") + showVersion = flag.Bool("v", false, "show version") + showBIOSIntro = flag.Bool("b", false, "show BIOS intro") + showCartInfo = flag.Bool("c", false, "show cartridge info") ) flag.Parse() @@ -72,6 +74,7 @@ func Run() ExitCode { emu := &Emulator{ gba: gba.New(data), + rom: path, } if *showCartInfo { fmt.Println(emu.gba.CartInfo()) @@ -79,8 +82,12 @@ func Run() ExitCode { } emu.SetupCloseHandler() - emu.gba.Reset() - // emu.gba.SoftReset() + emu.loadSav() + if *showBIOSIntro { + emu.gba.Reset() + } else { + emu.gba.SoftReset() + } ebiten.SetWindowResizable(true) ebiten.SetWindowTitle(title) @@ -112,6 +119,7 @@ func readROM(path string) ([]byte, error) { type Emulator struct { gba *gba.GBA + rom string } func (e *Emulator) Update() error { @@ -123,11 +131,16 @@ func (e *Emulator) Update() error { } }() e.gba.Update() + if e.gba.DoSav && e.gba.Frame%60 == 0 { + e.writeSav() + } return nil } + func (e *Emulator) Draw(screen *ebiten.Image) { screen.DrawImage(ebiten.NewImageFromImage(e.gba.Draw()), nil) } + func (e *Emulator) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) { return 240, 160 } @@ -141,3 +154,18 @@ func (e *Emulator) SetupCloseHandler() { os.Exit(0) }() } + +func (e *Emulator) writeSav() { + path := strings.ReplaceAll(e.rom, ".gba", ".sav") + os.WriteFile(path, e.gba.RAM.SRAM[:], os.ModePerm) + e.gba.DoSav = false +} + +func (e *Emulator) loadSav() { + path := strings.ReplaceAll(e.rom, ".gba", ".sav") + if f, err := os.Stat(path); os.IsNotExist(err) || f.IsDir() { + return + } else if sav, err := os.ReadFile(path); err == nil { + e.gba.LoadSav(sav) + } +} diff --git a/pkg/gba/gba.go b/pkg/gba/gba.go index cd7a2a2..8262dc5 100644 --- a/pkg/gba/gba.go +++ b/pkg/gba/gba.go @@ -49,13 +49,14 @@ type GBA struct { RAM ram.RAM inst Inst cycle int - frame uint + Frame uint line int halt bool pipe Pipe timers timer.Timers dma [4]*DMA joypad joypad.Joypad + DoSav bool } type Pipe struct { @@ -180,11 +181,11 @@ func (g *GBA) Update() { // line 227 g.scanline() - if g.frame%2 == 0 { + if g.Frame%2 == 0 { g.joypad.Read() } - g.frame++ + g.Frame++ } func (g *GBA) scanline() { @@ -323,3 +324,12 @@ func (g *GBA) CartInfo() string { ROM size: %s` return fmt.Sprintf(str, g.CartHeader, util.FormatSize(uint(g.RAM.ROMSize))) } + +func (g *GBA) LoadSav(bs []byte) { + if len(bs) > 65536 { + return + } + for i, b := range bs { + g.RAM.SRAM[i] = b + } +} diff --git a/pkg/gba/io.go b/pkg/gba/io.go index 38784ce..8920b63 100644 --- a/pkg/gba/io.go +++ b/pkg/gba/io.go @@ -78,8 +78,6 @@ func (g *GBA) setRAM8(addr uint32, b byte, s bool) { g._setRAM8(addr, b) } -var ctr = 0 - func (g *GBA) _setRAM8(addr uint32, b byte) { defer func() { if err := recover(); err != nil { @@ -132,6 +130,9 @@ func (g *GBA) _setRAM8(addr uint32, b byte) { g.GPU.VRAM[ram.VRAMOffset(addr)] = b case ram.OAM(addr): g.GPU.OAM[ram.OAMOffset(addr)] = b + case ram.SRAM(addr): + g.RAM.Set8(addr, b) + g.DoSav = true default: g.RAM.Set8(addr, b) }