Skip to content

Commit

Permalink
Merge pull request #2 from maxfierke/mf-load_bootroms
Browse files Browse the repository at this point in the history
Add support for loading Boot ROMs
  • Loading branch information
maxfierke authored Oct 9, 2024
2 parents 4cde17b + e492363 commit 41474e3
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 15 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
.vscode/

# Binaries for programs and plugins
*.bin
*.exe
*.exe~
*.dll
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ cpu_instrs: bin/gogo-gb tests/gameboy-doctor/gameboy-doctor tests/gb-test-roms/c
test_num=$$((10#$${test_name%-*})); \
echo "=== Starting cpu_instrs test $$file ==="; \
bin/gogo-gb --cart "tests/gb-test-roms/cpu_instrs/individual/$$file" \
--skip-bootrom \
--debugger=gameboy-doctor \
--log=stderr | \
./tests/gameboy-doctor/gameboy-doctor - cpu_instrs "$$test_num" || \
Expand Down
46 changes: 46 additions & 0 deletions devices/boot_rom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package devices

import (
"fmt"
"log"

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

const (
REG_BOOTROM_EN = 0xFF50
)

type BootROM struct {
enabled bool
rom []byte
}

func NewBootROM() *BootROM {
return &BootROM{enabled: false, rom: []byte{}}
}

func (br *BootROM) LoadROM(rom []byte) {
br.enabled = true
br.rom = rom
}

func (br *BootROM) OnRead(mmu *mem.MMU, addr uint16) mem.MemRead {
if br.enabled {
return mem.ReadReplace(br.rom[addr])
} else {
return mem.ReadPassthrough()
}
}

func (br *BootROM) OnWrite(mmu *mem.MMU, addr uint16, value byte) mem.MemWrite {
if addr == REG_BOOTROM_EN && br.enabled {
br.enabled = value == 0x00
log.Printf("Unloaded boot ROM")
return mem.WriteBlock()
} else if br.enabled {
panic(fmt.Sprintf("Attempting to write 0x%02X @ 0x%04X, which is not allowed for boot ROM", value, addr))
} else {
return mem.WritePassthrough()
}
}
40 changes: 35 additions & 5 deletions hardware/dmg.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package hardware

import (
"fmt"
"io"
"log"
"time"

Expand All @@ -13,15 +14,40 @@ import (
)

const (
DMG_CPU_HZ = 4194304
DMG_RAM_SIZE = 0xFFFF + 1
DMG_BOOTROM_SIZE = 0x100
DMG_CPU_HZ = 4194304
DMG_RAM_SIZE = 0x10000
)

type DMGOption func(dmg *DMG)
type DMGOption func(dmg *DMG) error

func WithBootROM(r io.Reader) DMGOption {
return func(dmg *DMG) error {
rom := make([]byte, DMG_BOOTROM_SIZE)
if _, err := r.Read(rom); err != nil {
return fmt.Errorf("unable to load boot ROM: %w", err)
}

dmg.bootROM = devices.NewBootROM()
dmg.bootROM.LoadROM(rom)

dmg.mmu.AddHandler(mem.MemRegion{Start: 0x0000, End: 0x00FF}, dmg.bootROM)

return nil
}
}

func WithDebugger(debugger debug.Debugger) DMGOption {
return func(dmg *DMG) {
return func(dmg *DMG) error {
dmg.AttachDebugger(debugger)
return nil
}
}

func WithFakeBootROM() DMGOption {
return func(dmg *DMG) error {
dmg.cpu.ResetToBootROM()
return nil
}
}

Expand All @@ -36,6 +62,7 @@ type DMG struct {
timer *devices.Timer

// Non-components
bootROM *devices.BootROM
debugger debug.Debugger
debuggerHandler mem.MemHandlerHandle
}
Expand Down Expand Up @@ -63,7 +90,10 @@ func NewDMG(opts ...DMGOption) (*DMG, error) {
}

for _, opt := range opts {
opt(dmg)
err = opt(dmg)
if err != nil {
return nil, err
}
}

mmu.AddHandler(mem.MemRegion{Start: 0x0000, End: 0x7FFF}, dmg.cartridge) // MBCs ROM Banks
Expand Down
79 changes: 69 additions & 10 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,26 @@ import (
)

type CLIOptions struct {
cartPath string
debugger string
debugPrint string
logPath string
logger *log.Logger
serialPort string
ui bool
bootRomPath string
cartPath string
debugger string
debugPrint string
logPath string
logger *log.Logger
serialPort string
skipBootRom bool
ui bool
}

const LOG_PREFIX = ""

var DEFAULT_BOOT_ROM_PATHS = []string{
"gb_bios.bin",
"dmg_bios.bin",
"mgb_bios.bin",
"dmg0_bios.bin",
}

func main() {
options := CLIOptions{}

Expand Down Expand Up @@ -57,11 +66,13 @@ func main() {
}

func parseOptions(options *CLIOptions) {
flag.StringVar(&options.bootRomPath, "bootrom", "", "Path to boot ROM file (dmg_bios.bin, mgb_bios.bin, etc.). Defaults to a lookup on common boot ROM filenames in current directory")
flag.StringVar(&options.cartPath, "cart", "", "Path to cartridge file (.gb, .gbc)")
flag.StringVar(&options.serialPort, "serial-port", "", "Path to serial port IO (could be a file, UNIX socket, etc.)")
flag.StringVar(&options.debugger, "debugger", "none", "Specify debugger to use (\"none\", \"gameboy-doctor\", \"interactive\")")
flag.StringVar(&options.debugPrint, "debug-print", "", "Print out something for debugging purposes (\"cart-header\", \"opcodes\")")
flag.StringVar(&options.logPath, "log", "", "Path to log file. Default/empty implies stdout")
flag.StringVar(&options.serialPort, "serial-port", "", "Path to serial port IO (could be a file, UNIX socket, etc.)")
flag.BoolVar(&options.skipBootRom, "skip-bootrom", false, "Skip loading a boot ROM")
flag.BoolVar(&options.ui, "ui", false, "Launch with UI")
flag.Parse()
}
Expand Down Expand Up @@ -147,16 +158,64 @@ func initDMG(options *CLIOptions) (*hardware.DMG, error) {
return nil, fmt.Errorf("unable to initialize Debugger: %w", err)
}

dmg, err := hardware.NewDMG(
opts := []hardware.DMGOption{
hardware.WithDebugger(debugger),
)
}

if options.skipBootRom {
opts = append(opts, hardware.WithFakeBootROM())
} else {
bootRomFile, err := loadBootROM(options)
if err != nil {
return nil, fmt.Errorf("unable to load boot ROM: %w", err)
}
if bootRomFile == nil {
opts = append(opts, hardware.WithFakeBootROM())
} else {
defer bootRomFile.Close()
opts = append(opts, hardware.WithBootROM(bootRomFile))
}
}

dmg, err := hardware.NewDMG(opts...)
if err != nil {
return nil, fmt.Errorf("unable to initialize DMG: %w", err)
}

return dmg, nil
}

func loadBootROM(options *CLIOptions) (*os.File, error) {
logger := options.logger
bootRomPath := options.bootRomPath

var bootRomFile *os.File
var err error

if bootRomPath == "" {
for _, romPath := range DEFAULT_BOOT_ROM_PATHS {
if bootRomFile, err = os.Open(romPath); err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, err
} else if bootRomFile != nil {
// yay! we found one!
break
}
}
} else if bootRomFile, err = os.Open(bootRomPath); err != nil {
return nil, err
}

if bootRomFile == nil {
// Bail out if no boot ROM loaded
logger.Printf("WARN: No boot ROM provided. Some emulation functionality may be incorrect.")
return nil, nil
}

logger.Printf("Loaded boot ROM: %s\n", bootRomFile.Name())

return bootRomFile, nil
}

func loadCart(dmg *hardware.DMG, options *CLIOptions) error {
if options.cartPath == "" {
return nil
Expand Down

0 comments on commit 41474e3

Please sign in to comment.