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

Add support for loading Boot ROMs #2

Merged
merged 5 commits into from
Oct 9, 2024
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
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
Loading