aboutsummaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
authorcrecher <crecher@crecher.net>2026-10-15 07:39:17 +0200
committercrecher <crecher@crecher.net>2026-10-15 07:39:17 +0200
commitf357378fcb55e0c0d002a10bc1c19a1e89c53797 (patch)
tree073bad74a0dcbb6a958cd6dc4b4ac2bf13783e3d /main.go
parent99cae5a5a37f41f878a84857361eda47910303ba (diff)
downloadgones-f357378fcb55e0c0d002a10bc1c19a1e89c53797.tar.gz
gones-f357378fcb55e0c0d002a10bc1c19a1e89c53797.zip
move main.go to root so go build actually builds
Diffstat (limited to 'main.go')
-rw-r--r--main.go153
1 files changed, 153 insertions, 0 deletions
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..2ce3745
--- /dev/null
+++ b/main.go
@@ -0,0 +1,153 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+ "runtime"
+ "time"
+
+ "github.com/veandco/go-sdl2/sdl"
+
+ "crecher.net/gones/v2/cart"
+ "crecher.net/gones/v2/nes"
+ "crecher.net/gones/v2/ppu"
+)
+
+const (
+ windowScale = 3
+
+ // nes: 1789773 cpu cycles per sec / 29780.5 cycles per frame
+ framesPerSecond = 60.0988
+)
+
+func init() {
+ // sdl wants to be driven from the main thread
+ runtime.LockOSThread()
+}
+
+func main() {
+ if len(os.Args) != 2 {
+ fmt.Fprintf(os.Stderr, "usage: %s <rom.nes>\n", os.Args[0])
+ os.Exit(2)
+ }
+ if err := run(os.Args[1]); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func run(romPath string) error {
+ c, err := cart.Load(romPath)
+ if err != nil {
+ return err
+ }
+ console := nes.New(c)
+ name := filepath.Base(romPath)
+
+ if err := sdl.Init(sdl.INIT_VIDEO); err != nil {
+ return err
+ }
+ defer sdl.Quit()
+
+ window, err := sdl.CreateWindow("gones - "+name,
+ sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED,
+ ppu.ScreenWidth*windowScale, ppu.ScreenHeight*windowScale,
+ sdl.WINDOW_SHOWN|sdl.WINDOW_RESIZABLE)
+ if err != nil {
+ return err
+ }
+ defer window.Destroy()
+
+ renderer, err := sdl.CreateRenderer(window, -1, sdl.RENDERER_ACCELERATED|sdl.RENDERER_PRESENTVSYNC)
+ if err != nil {
+ return err
+ }
+ defer renderer.Destroy()
+
+ // the window can be resized freely, sdl scales (and letterboxes) the 256x240 picture
+ if err := renderer.SetLogicalSize(ppu.ScreenWidth, ppu.ScreenHeight); err != nil {
+ return err
+ }
+
+ // framebuffer is ARGB8888, streaming since we rewrite it every frame
+ texture, err := renderer.CreateTexture(sdl.PIXELFORMAT_ARGB8888, sdl.TEXTUREACCESS_STREAMING,
+ ppu.ScreenWidth, ppu.ScreenHeight)
+ if err != nil {
+ return err
+ }
+ defer texture.Destroy()
+
+ fps := framesPerSecond // a variable, a constant would have to be a whole number of nanoseconds
+ frameTime := time.Duration(float64(time.Second) / fps)
+ next := time.Now()
+ fpsStart, frames := next, 0
+ jamShown := false
+
+ for {
+ for e := sdl.PollEvent(); e != nil; e = sdl.PollEvent() {
+ switch ev := e.(type) {
+ case *sdl.QuitEvent:
+ return nil
+ case *sdl.KeyboardEvent:
+ if ev.Type == sdl.KEYDOWN && ev.Keysym.Sym == sdl.K_ESCAPE {
+ return nil
+ }
+ }
+ }
+
+ console.SetButtons(0, heldButtons())
+ console.StepFrame()
+
+ // UpdateRGBA multiplies the pitch by 4 itself, so this is in pixels, not bytes
+ if err := texture.UpdateRGBA(nil, console.PPU.Framebuffer(), ppu.ScreenWidth); err != nil {
+ return err
+ }
+ renderer.Clear()
+ renderer.Copy(texture, nil, nil)
+ renderer.Present()
+
+ if console.CPU.Jammed && !jamShown {
+ window.SetTitle("gones - " + name + " - CPU jammed (reset needed)")
+ jamShown = true
+ }
+
+ frames++
+ if elapsed := time.Since(fpsStart); elapsed >= time.Second && !jamShown {
+ window.SetTitle(fmt.Sprintf("gones - %s - %.1f fps", name, float64(frames)/elapsed.Seconds()))
+ fpsStart, frames = time.Now(), 0
+ }
+
+ // vsync alone would run too fast on a 120/144hz monitor, so pace to the nes frame rate
+ next = next.Add(frameTime)
+ if wait := time.Until(next); wait > 0 {
+ time.Sleep(wait)
+ } else if wait < -100*time.Millisecond {
+ next = time.Now() // fell far behind (window drag etc), don't try to catch up
+ }
+ }
+}
+
+// keyboard layout for controller 1
+// TODO: add remapping
+var keyMap = map[sdl.Scancode]uint8{
+ sdl.SCANCODE_X: nes.ButtonA,
+ sdl.SCANCODE_Y: nes.ButtonB,
+ sdl.SCANCODE_RSHIFT: nes.ButtonSelect,
+ sdl.SCANCODE_RETURN: nes.ButtonStart,
+ sdl.SCANCODE_UP: nes.ButtonUp,
+ sdl.SCANCODE_DOWN: nes.ButtonDown,
+ sdl.SCANCODE_LEFT: nes.ButtonLeft,
+ sdl.SCANCODE_RIGHT: nes.ButtonRight,
+}
+
+func heldButtons() uint8 {
+ keys := sdl.GetKeyboardState()
+ var b uint8
+ for scancode, button := range keyMap {
+ if keys[scancode] != 0 {
+ b |= button
+ }
+ }
+ return b
+}