Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
djhworld committed Sep 15, 2018
0 parents commit 290a634
Show file tree
Hide file tree
Showing 8 changed files with 402 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.sav
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
all: prepare
go install

prepare:
go mod tidy
158 changes: 158 additions & 0 deletions glfw_io.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package main

import (
"log"

"github.com/djhworld/gomeboycolor/types"
"github.com/djhworld/gomeboycolor/inputoutput"

"github.com/go-gl/gl/v2.1/gl"
"github.com/go-gl/glfw/v3.2/glfw"
)

var DefaultControlScheme inputoutput.ControlScheme = inputoutput.ControlScheme{
int(glfw.KeyUp),
int(glfw.KeyDown),
int(glfw.KeyLeft),
int(glfw.KeyRight),
int(glfw.KeyZ),
int(glfw.KeyX),
int(glfw.KeyA),
int(glfw.KeyS),
}

// GlfwIO is for running the emulator using GLFW.
// libglfw3 will be required on the system
type GlfwIO struct {
*inputoutput.CoreIO
glfwDisplay *glfwDisplay
}

func NewGlfwIO(frameRateLock int64, headless bool, displayFps bool) *GlfwIO {
log.Println("Creating GLFW based IO Handler")
glfwDisplay := new(glfwDisplay)

frameRateReporter := func(v float32) {
if displayFps {
log.Printf("Average frame rate\t%.2f\tfps", v)
}
}

return &GlfwIO{
inputoutput.NewCoreIO(frameRateLock, headless, frameRateReporter, glfwDisplay),
glfwDisplay,
}
}

func (i *GlfwIO) Init(title string, screenSize int, onCloseHandler func()) error {
var err error
i.OnCloseHandler = onCloseHandler

if !i.Headless {
err = i.glfwDisplay.init(title, screenSize)
if err != nil {
return err
}

i.glfwDisplay.window.SetCloseCallback(func(w *glfw.Window) {
i.StopChannel <- 1
})

i.KeyHandler.Init(DefaultControlScheme) //TODO: allow user to define controlscheme

i.glfwDisplay.window.SetKeyCallback(func(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
if action == glfw.Repeat {
i.KeyHandler.KeyDown(int(key))
return
}

if action == glfw.Press {
i.KeyHandler.KeyDown(int(key))
} else {
i.KeyHandler.KeyUp(int(key))
}
})
}

return err
}

type glfwDisplay struct {
Name string
ScreenSizeMultiplier int
window *glfw.Window
}

func (s *glfwDisplay) init(title string, screenSizeMultiplier int) error {
var err error

if err := glfw.Init(); err != nil {
log.Fatalln("failed to initialize glfw:", err)
}

s.Name = inputoutput.PREFIX + "-SCREEN"

log.Printf("%s: Initialising display", s.Name)

s.ScreenSizeMultiplier = screenSizeMultiplier
log.Printf("%s: Set screen size multiplier to %dx", s.Name, s.ScreenSizeMultiplier)

glfw.WindowHint(glfw.Resizable, glfw.False)
window, err := glfw.CreateWindow(inputoutput.SCREEN_WIDTH*s.ScreenSizeMultiplier, inputoutput.SCREEN_HEIGHT*s.ScreenSizeMultiplier, "Testing", nil, nil)
if err != nil {
return err
}

window.SetTitle(title)

vidMode := glfw.GetPrimaryMonitor().GetVideoMode()

window.SetPos(vidMode.Width/3, vidMode.Height/3)

window.MakeContextCurrent()

if err := gl.Init(); err != nil {
return err
}

gl.ClearColor(0.255, 0.255, 0.255, 0)

s.window = window

return nil

}

func (s *glfwDisplay) Stop() {
log.Println("Stopping display")
s.window.Destroy()
glfw.Terminate()
}

func (s *glfwDisplay) DrawFrame(screenData *types.Screen) {
fw, fh := s.window.GetFramebufferSize()
gl.Viewport(0, 0, int32(fw), int32(fh))
gl.MatrixMode(gl.PROJECTION)
gl.LoadIdentity()
gl.Ortho(0, float64(inputoutput.SCREEN_WIDTH*s.ScreenSizeMultiplier), float64(inputoutput.SCREEN_HEIGHT*s.ScreenSizeMultiplier), 0, -1, 1)
gl.ClearColor(0.255, 0.255, 0.255, 0)
gl.Clear(gl.COLOR_BUFFER_BIT)
gl.MatrixMode(gl.MODELVIEW)
gl.LoadIdentity()

gl.Clear(gl.COLOR_BUFFER_BIT)
gl.Disable(gl.DEPTH_TEST)
gl.PointSize(float32(s.ScreenSizeMultiplier) * 2.0)
gl.Begin(gl.POINTS)
for y := 0; y < inputoutput.SCREEN_HEIGHT; y++ {
for x := 0; x < inputoutput.SCREEN_WIDTH; x++ {
var pixel types.RGB = screenData[y][x]
gl.Color3ub(pixel.Red, pixel.Green, pixel.Blue)
gl.Vertex2i(int32(x*s.ScreenSizeMultiplier), int32(y*s.ScreenSizeMultiplier))
}
}

gl.End()
glfw.PollEvents()
s.window.SwapBuffers()
}
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module github.com/djhworld/gomeboycolor-glfw

require (
github.com/djhworld/gomeboycolor v0.0.0
github.com/go-gl/gl v0.0.0-20180407155706-68e253793080
github.com/go-gl/glfw v0.0.0-20180813204114-2484f3e51bc4
)

replace github.com/djhworld/gomeboycolor => ../gomeboycolor
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-gl/gl v0.0.0-20180407155706-68e253793080 h1:pNxZva3052YM+z2p1aP08FgaTE2NzrRJZ5BHJCmKLzE=
github.com/go-gl/gl v0.0.0-20180407155706-68e253793080/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
github.com/go-gl/glfw v0.0.0-20180813204114-2484f3e51bc4 h1:4gBr8toVw4vfazzm1MpqiTd7ODKRRevdMEDO2yNggOk=
github.com/go-gl/glfw v0.0.0-20180813204114-2484f3e51bc4/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchrcom/testify v1.2.2 h1:R5L+N9NKlaO/Q0Dvq8kBEV/uRKEa/Mt2Wt2WNtMqKTw=
github.com/stretchrcom/testify v1.2.2/go.mod h1:zUrQijuLcfRPyrWG6SBFjct9CuJZz2Ybtack4DGF2Jo=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
169 changes: 169 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package main

import (
"bufio"
"flag"
"fmt"
"log"
"os"
"os/user"
"runtime"
"strings"

"github.com/djhworld/gomeboycolor/cartridge"
"github.com/djhworld/gomeboycolor/config"
"github.com/djhworld/gomeboycolor/gbc"
"github.com/djhworld/gomeboycolor-glfw/saves"
)

const TITLE string = "gomeboycolor"

var VERSION string

const (
SKIP_BOOT_FLAG string = "skipboot"
SCREEN_SIZE_FLAG = "size"
SHOW_FPS_FLAG = "showfps"
TITLE_FLAG = "title"
DUMP_FLAG = "dump"
DEBUGGER_ON_FLAG = "debug"
BREAK_WHEN_FLAG = "b"
COLOR_MODE_FLAG = "color"
HELP_FLAG = "help"
HEADLESS_FLAG = "headless"
FRAME_RATE_LOCK_FLAG = "fpsLock"
)

var title *string = flag.String(TITLE_FLAG, TITLE, "Title to use")
var showFps *bool = flag.Bool(SHOW_FPS_FLAG, false, "Calculate and display frames per second")
var screenSizeMultiplier *int = flag.Int(SCREEN_SIZE_FLAG, 1, "Screen size multiplier")
var skipBoot *bool = flag.Bool(SKIP_BOOT_FLAG, false, "Skip boot sequence")
var colorMode *bool = flag.Bool(COLOR_MODE_FLAG, true, "Emulates Gameboy Color Hardware")
var help *bool = flag.Bool(HELP_FLAG, false, "Show this help message")
var headless *bool = flag.Bool(HEADLESS_FLAG, false, "Run emulator without output")
var frameRatelock *int64 = flag.Int64(FRAME_RATE_LOCK_FLAG, 58, "Lock framerate to this. Going higher than default might be unstable!")

//debug stuff...
var dumpState *bool = flag.Bool(DUMP_FLAG, false, "Print state of machine after each cycle (WARNING - WILL RUN SLOW)")
var debug *bool = flag.Bool(DEBUGGER_ON_FLAG, false, "Enable debugger")
var breakOn *string = flag.String(BREAK_WHEN_FLAG, "0x0000", "Break into debugger when PC equals a given value between 0x0000 and 0xFFFF")

func PrintHelp() {
fmt.Println("\nUsage: -\n")
fmt.Println("To launch the emulator, simply run and pass it the location of your ROM file, e.g. ")
fmt.Println("\n\tgomeboycolor location/of/romfile.gbc\n")
fmt.Println("Flags: -\n")
fmt.Println(" -help -> Show this help message")
fmt.Println(" -skipboot -> Disables the boot sequence and will boot you straight into the ROM you have provided. Defaults to false")
fmt.Println(" -color -> Turns color GB features on. Defaults to true")
fmt.Println(" -showfps -> Prints average frames per second to the console. Defaults to false")
fmt.Println(" -dump -> Dump CPU state after every cycle. Will be very SLOW and resource intensive. Defaults to false")
fmt.Println(" -size=(1-6) -> Set screen size. Defaults to 1.")
fmt.Println(" -headless -> Runs emulator without output")
fmt.Println(" -fpsLock -> Lock framerate to this. Going higher than default might be unstable!")
fmt.Println(" -title=(title) -> Change window title. Defaults to 'gomeboycolor'.")
fmt.Println("\nYou can pass an option argument to the boolean flags if you want to enable that particular option. e.g. to disable the boot screen you would do the following")
fmt.Println("\n\tgomeboycolor -skipboot=false location/of/romfile.gbc\n")
}

func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
fmt.Printf("%s. %s\n", TITLE, VERSION)
fmt.Println("Copyright (c) 2018. Daniel James Harper.")
fmt.Println("http://djhworld.github.io/gomeboycolor")
fmt.Println(strings.Repeat("*", 120))

flag.Usage = PrintHelp

flag.Parse()

if *help {
PrintHelp()
os.Exit(1)
}

if flag.NArg() != 1 {
log.Fatalf("Please specify the location of a ROM to boot")
return
}

//Parse and validate settings file (if found)
conf := &config.Config{
Title: TITLE,
ScreenSize: *screenSizeMultiplier,
SkipBoot: *skipBoot,
DisplayFPS: *showFps,
ColorMode: *colorMode,
Debug: *debug,
BreakOn: *breakOn,
DumpState: *dumpState,
Headless: *headless,
FrameRateLock: *frameRatelock,
}
fmt.Println(conf)

cart, err := createCartridge(flag.Arg(0))
if err != nil {
log.Println(err)
return
}

log.Println("Starting emulator")

emulator, err := gbc.Init(cart, getSaveStore(), conf, NewGlfwIO(conf.FrameRateLock, conf.Headless, conf.DisplayFPS))
if err != nil {
log.Println(err)
return
}

//Starts emulator code in a goroutine
go emulator.Run()

//lock the OS thread here
runtime.LockOSThread()

//set the IO controller to run indefinitely (it waits for screen updates)
emulator.RunIO()

log.Println("Goodbye!")
}

func getSaveStore() *saves.FileSystemStore {
user, _ := user.Current()
saveDir := user.HomeDir + "/.gomeboycolor/saves"

os.MkdirAll(saveDir, os.ModeDir)

return saves.NewFileSystemStore(saveDir)
}

func createCartridge(romFilename string) (*cartridge.Cartridge, error) {
romContents, err := retrieveROM(romFilename)
if err != nil {
return nil, err
}

return cartridge.NewCartridge(romFilename, romContents)
}

func retrieveROM(filename string) ([]byte, error) {
file, err := os.Open(filename)

if err != nil {
return nil, err
}
defer file.Close()

stats, statsErr := file.Stat()
if statsErr != nil {
return nil, statsErr
}

var size int64 = stats.Size()
bytes := make([]byte, size)

bufr := bufio.NewReader(file)
_, err = bufr.Read(bytes)

return bytes, err
}
31 changes: 31 additions & 0 deletions saves/filesystem_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package saves

import (
"io"
"os"
"path/filepath"
"fmt"
)

type FileSystemStore struct {
baseDir string
}

func NewFileSystemStore(baseDir string) *FileSystemStore {
f := new(FileSystemStore)
f.baseDir = baseDir
return f
}

func (f *FileSystemStore) Open(game string) (io.ReadCloser, error) {
location := filepath.Join(f.baseDir, game+".sav")

fmt.Println(location)
return os.Open(location)
}

func (f *FileSystemStore) Create(game string) (io.WriteCloser, error) {
location := filepath.Join(f.baseDir, game+".sav")
fmt.Println(location)
return os.Create(location)
}
Loading

0 comments on commit 290a634

Please sign in to comment.