diff --git a/README.md b/README.md index 9d169612..d3c238a3 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ You can try hosting your own service following the instruction in the next sessi Screenshot | Screenshot :-------------------------:|:-------------------------: ![screenshot](document/img/landing-page-ps-hm.png)|![screenshot](document/img/landing-page-ps-x4.png) -![screenshot](document/img/landing-page-gb.png)|![screenshot](document/img/landing-page.gif) +![screenshot](document/img/landing-page-gb.png)|![screenshot](document/img/landing-page-front.png) ## Feature 1. Cloud gaming: Game logic is hosted on a remote server. User doesn't have to install or setup anything. Images and audio are streamed to user in the most optimal way. @@ -63,8 +63,9 @@ And run * *Pion* Webrtc team for the incredible Golang Webrtc library and their supports https://github.com/pion/webrtc/. * *Nanoarch* Golang RetroArch https://github.com/libretro/go-nanoarch and https://retroarch.com. -* *fogleman* for the awesome NES emulator https://github.com/fogleman/nes. +* *gen2brain* for the h264 go encoder https://github.com/gen2brain/x264-go * *poi5305* for the video encoding https://github.com/poi5305/go-yuv2webRTC. +* *fogleman* for the NES emulator https://github.com/fogleman/nes. * And last but not least, my longtime friend Tri as the co-author. ## Contributor diff --git a/config/config.go b/config/config.go index e982931b..a450af55 100644 --- a/config/config.go +++ b/config/config.go @@ -23,6 +23,8 @@ var WSWait = 20 * time.Second var MatchWorkerRandom = false var ProdEnv = "prod" +const NumKeys = 10 + var Codec = CODEC_H264 //var Codec = CODEC_VP8 @@ -61,8 +63,9 @@ var EmulatorConfig = map[string]EmulatorMeta{ Height: 240, }, "nes": EmulatorMeta{ - Width: 0, - Height: 0, + Path: "libretro/cores/nestopia_libretro.so", + Width: 256, + Height: 240, }, "snes": EmulatorMeta{ Path: "libretro/cores/mednafen_snes_libretro.so", diff --git a/document/img/landing-page-front.png b/document/img/landing-page-front.png new file mode 100644 index 00000000..f68a0a3e Binary files /dev/null and b/document/img/landing-page-front.png differ diff --git a/document/img/landing-page-ps-hm.png b/document/img/landing-page-ps-hm.png index c35da9cf..3ccab8d4 100644 Binary files a/document/img/landing-page-ps-hm.png and b/document/img/landing-page-ps-hm.png differ diff --git a/document/img/landing-page-ps-x4.png b/document/img/landing-page-ps-x4.png index 7f0e8f13..3e351475 100644 Binary files a/document/img/landing-page-ps-x4.png and b/document/img/landing-page-ps-x4.png differ diff --git a/emulator/director.go b/emulator/director.go deleted file mode 100644 index 73bcda12..00000000 --- a/emulator/director.go +++ /dev/null @@ -1,159 +0,0 @@ -package emulator - -import ( - "image" - "log" - "time" - - "github.com/giongto35/cloud-game/config" - "github.com/giongto35/cloud-game/emulator/nes" - "github.com/giongto35/cloud-game/util" -) - -// Director is the nes emulator -type Director struct { - // audio *Audio - view *GameView - timestamp float64 - imageChannel chan<- *image.RGBA - audioChannel chan<- float32 - inputChannel <-chan int - Done chan struct{} - - gamePath string - roomID string -} - -const fps = 300 - -// NewDirector returns a new director -func NewDirector(roomID string, imageChannel chan<- *image.RGBA, audioChannel chan<- float32, inputChannel <-chan int) CloudEmulator { - // TODO: return image channel from where it write - director := Director{} - director.Done = make(chan struct{}, 1) - director.audioChannel = audioChannel - director.imageChannel = imageChannel - director.inputChannel = inputChannel - director.roomID = roomID - return &director -} - -// SetView ... -func (d *Director) SetView(view *GameView) { - if d.view != nil { - d.view.Exit() - } - d.view = view - if d.view != nil { - d.view.Enter() - } - d.timestamp = float64(time.Now().Nanosecond()) / float64(time.Second) -} - -//func (d *Director) UpdateInput(input int) { -//d.view.UpdateInput(input) -//} - -func (d *Director) LoadMeta(path string) config.EmulatorMeta { - log.Println("Start game: ", path) - - d.gamePath = path - return config.EmulatorMeta{ - AudioSampleRate: 48000, - Fps: 300, - Width: 256, - Height: 240, - } -} - -// Start ... -func (d *Director) Start() { - // portaudio.Initialize() - // defer portaudio.Terminate() - - // audio := NewAudio() - // audio.Start() - // d.audio = audio - log.Println("Start game: ", d.gamePath) - - d.playGame(d.gamePath) - d.run() -} - -// step ... -func (d *Director) step() { - timestamp := float64(time.Now().Nanosecond()) / float64(time.Second) - dt := timestamp - d.timestamp - d.timestamp = timestamp - if d.view != nil { - d.view.Update(timestamp, dt) - } -} - -// run ... -func (d *Director) run() { - c := time.Tick(time.Second / fps) -L: - for range c { - // for { - // quit game - // TODO: How to not using select because it will slow down - select { - // if there is event from close channel => the game is ended - //case input := <-d.inputChannel: - //d.UpdateInput(input) - case <-d.Done: - log.Println("Closing Director") - break L - default: - } - - d.step() - } - d.SetView(nil) - log.Println("Closed Director") -} - -// PalyGame starts a game given a rom path -func (d *Director) playGame(path string) { - console, err := nes.NewConsole(path) - if err != nil { - log.Println("Err: Cannot load path, Got:", err) - } - // Set GameView as current view - d.SetView(NewGameView(console, path, d.roomID, d.imageChannel, d.audioChannel, d.inputChannel)) -} - -// SaveGame creates save events and doing extra step for load -func (d *Director) SaveGame(saveExtraFunc func() error) error { - if d.roomID != "" { - d.view.Save(saveExtraFunc) - return nil - } - - return nil -} - -// LoadGame creates load events and doing extra step for load -func (d *Director) LoadGame() error { - if d.roomID != "" { - d.view.Load() - return nil - } - - return nil -} - -// GetHashPath return the full path to hash file -func (d *Director) GetHashPath() string { - return util.GetSavePath(d.roomID) -} - -func (d *Director) GetSampleRate() uint { - return SampleRate -} - -// Close -func (d *Director) Close() { - close(d.Done) -} diff --git a/emulator/font.go b/emulator/font.go deleted file mode 100644 index e41c7448..00000000 --- a/emulator/font.go +++ /dev/null @@ -1,153 +0,0 @@ -// credit to https://github.com/fogleman/nes -package emulator - -import ( - "bytes" - "image" - "image/color" - "image/draw" - "image/png" - "log" - "strings" -) - -var fontMask image.Image - -func init() { - im, err := png.Decode(bytes.NewBuffer(fontData)) - if err != nil { - log.Fatalln(err) - } - size := im.Bounds().Size() - mask := image.NewRGBA(im.Bounds()) - for y := 0; y < size.Y; y++ { - for x := 0; x < size.X; x++ { - r, _, _, _ := im.At(x, y).RGBA() - if r > 0 { - mask.Set(x, y, color.Opaque) - } - } - } - fontMask = mask -} - -func WordWrap(text string, maxLength int) []string { - var rows []string - words := strings.Fields(text) - if len(words) == 0 { - return rows - } - row := words[0] - for _, word := range words[1:] { - newRow := row + " " + word - if len(newRow) <= maxLength { - row = newRow - } else { - rows = append(rows, row) - row = word - } - } - rows = append(rows, row) - return rows -} - -func DrawCenteredText(dst draw.Image, text string, dx, dy int, c color.Color) { - rows := WordWrap(text, 15) - for i, row := range rows { - x := 128 - len(row)*8 - y := 120 - len(rows)*12 + i*24 - DrawText(dst, x+dx, y+dy, row, c) - } -} - -func DrawCharacter(dst draw.Image, x, y int, ch byte, c color.Color) { - if ch < 32 || ch > 128 { - return - } - cx := int((ch-32)%16) * 16 - cy := int((ch-32)/16) * 16 - r := image.Rect(x, y, x+16, y+16) - src := &image.Uniform{c} - sp := image.Pt(cx, cy) - draw.DrawMask(dst, r, src, sp, fontMask, sp, draw.Over) -} - -func DrawText(dst draw.Image, x, y int, text string, c color.Color) { - for i := range text { - DrawCharacter(dst, x, y, text[i], c) - x += 16 - } -} - -var fontData = []byte{ - 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, - 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x60, - 0x02, 0x03, 0x00, 0x00, 0x00, 0x8F, 0x9F, 0x44, 0x1B, 0x00, 0x00, 0x00, - 0x06, 0x50, 0x4C, 0x54, 0x45, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFE, 0x6A, - 0x62, 0xC8, 0x2E, 0x00, 0x00, 0x00, 0x01, 0x62, 0x4B, 0x47, 0x44, 0x00, - 0x88, 0x05, 0x1D, 0x48, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, - 0x00, 0x00, 0x0E, 0xC4, 0x00, 0x00, 0x0E, 0xC4, 0x01, 0x95, 0x2B, 0x0E, - 0x1B, 0x00, 0x00, 0x02, 0xD2, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0xC5, - 0x98, 0x49, 0x62, 0x23, 0x21, 0x0C, 0x45, 0xB5, 0xE1, 0x7E, 0xDA, 0xFC, - 0xFB, 0x5F, 0xA5, 0x4B, 0xE8, 0x4B, 0x88, 0x72, 0x2A, 0x1D, 0x07, 0xB9, - 0x9B, 0xC4, 0x2E, 0x8C, 0xE1, 0x19, 0x8D, 0x0C, 0x22, 0x56, 0x86, 0x5E, - 0xFF, 0x98, 0x2F, 0x81, 0x78, 0x81, 0xB5, 0xC5, 0x77, 0xF2, 0x50, 0xE2, - 0x9B, 0x63, 0x00, 0x90, 0x80, 0x71, 0xD5, 0xAD, 0xE6, 0x9F, 0xC5, 0x81, - 0x90, 0xD9, 0x92, 0xF0, 0x2C, 0xF9, 0x6B, 0x4D, 0x00, 0x00, 0xEA, 0xFD, - 0x31, 0x45, 0x40, 0x4C, 0xD2, 0xDE, 0x50, 0xC4, 0xBB, 0x4B, 0xD0, 0x02, - 0xF0, 0xCF, 0x26, 0x82, 0xBA, 0xE2, 0xE0, 0xDF, 0x06, 0x00, 0x5E, 0xD8, - 0x1D, 0xBB, 0x04, 0xE7, 0x00, 0x4E, 0xC5, 0x44, 0x98, 0xAD, 0xF6, 0x3F, - 0x20, 0xF2, 0x17, 0x11, 0x96, 0x79, 0xBB, 0x00, 0xAE, 0x48, 0xA5, 0x11, - 0xCB, 0x34, 0xAB, 0x19, 0xB1, 0x2C, 0x08, 0x69, 0x03, 0x24, 0x87, 0xD3, - 0xB7, 0xEE, 0x48, 0xEA, 0xEE, 0xCA, 0x05, 0xA0, 0x7D, 0x80, 0xE7, 0x68, - 0x79, 0x0C, 0x9F, 0x9F, 0xB4, 0xFE, 0x10, 0x90, 0x8E, 0x33, 0x03, 0xC9, - 0x2A, 0xEE, 0x28, 0x70, 0xD7, 0x66, 0xBB, 0xBC, 0x2A, 0x5C, 0xDC, 0xE9, - 0x8E, 0x01, 0x23, 0x03, 0x67, 0xD0, 0x89, 0x90, 0x41, 0x54, 0x82, 0x7C, - 0x99, 0x70, 0x33, 0x23, 0x1A, 0x00, 0x48, 0x3B, 0x79, 0x0E, 0x63, 0x58, - 0xB3, 0x1F, 0xE4, 0x0E, 0x70, 0xAF, 0x66, 0xA5, 0x17, 0x10, 0x8A, 0xA3, - 0x00, 0xD1, 0x29, 0x95, 0x7B, 0xF3, 0xA3, 0x45, 0xEB, 0x02, 0xB8, 0x4D, - 0xE7, 0x40, 0x95, 0x32, 0x6D, 0x57, 0xEE, 0x4B, 0xE0, 0xC6, 0xE0, 0x06, - 0xC0, 0x15, 0x36, 0x9E, 0xB2, 0x62, 0x36, 0x37, 0xC5, 0x2D, 0xF3, 0x2E, - 0x33, 0xA2, 0x26, 0x97, 0x63, 0xC0, 0xB4, 0x9E, 0xA5, 0x73, 0xBA, 0xED, - 0x1C, 0x12, 0xEE, 0x6B, 0x7A, 0x02, 0x13, 0x6D, 0x00, 0x8A, 0x2B, 0x3B, - 0xE4, 0x18, 0xF0, 0x46, 0x79, 0x23, 0xEA, 0xDF, 0x00, 0xF8, 0x54, 0xB5, - 0x84, 0xB0, 0xFA, 0x12, 0xE7, 0x15, 0xF5, 0xF4, 0x36, 0xE5, 0xF4, 0x7A, - 0x09, 0xB2, 0x39, 0xF6, 0x18, 0x80, 0x4C, 0xE2, 0xF6, 0xF4, 0x5A, 0xD5, - 0xD7, 0x0A, 0xB6, 0xE8, 0x65, 0xBF, 0x67, 0x7F, 0x69, 0xEA, 0x53, 0x00, - 0xA7, 0xC6, 0x0D, 0x8E, 0xAC, 0xE7, 0xEE, 0xEA, 0x13, 0x90, 0x71, 0x67, - 0x22, 0xB6, 0x03, 0x80, 0x1C, 0x00, 0x44, 0x52, 0xA1, 0x32, 0x03, 0xB0, - 0x12, 0x8A, 0x82, 0x0A, 0x6E, 0x02, 0x00, 0x0F, 0x22, 0x70, 0x9A, 0x54, - 0x76, 0xB0, 0x30, 0x4C, 0x9C, 0x36, 0x40, 0xE9, 0xF8, 0x6A, 0xC6, 0x1B, - 0x60, 0xA0, 0xA4, 0xFB, 0x2E, 0xC0, 0x80, 0xA6, 0xE2, 0x36, 0x57, 0xF6, - 0x3D, 0x4F, 0xBA, 0xF2, 0xA0, 0x52, 0x07, 0x96, 0xD9, 0x19, 0x0B, 0x87, - 0x80, 0xFF, 0x5F, 0xC0, 0xF4, 0x05, 0xA6, 0xAE, 0x3A, 0xC5, 0x4D, 0xB9, - 0x53, 0x0C, 0x6E, 0x7F, 0x7D, 0x59, 0xEF, 0x02, 0x6C, 0x9D, 0xDD, 0x57, - 0xB7, 0xB6, 0x19, 0xB8, 0x1E, 0xC4, 0xB9, 0xC4, 0xF8, 0xC7, 0x8F, 0x00, - 0x64, 0x07, 0x0C, 0x37, 0xD5, 0x04, 0x8C, 0x72, 0x00, 0xE1, 0x36, 0xA0, - 0x1B, 0xC0, 0x85, 0x25, 0x00, 0x1A, 0x89, 0x35, 0x96, 0xFE, 0x75, 0x08, - 0x8B, 0x3E, 0x4D, 0x00, 0x33, 0xE1, 0x0C, 0xE9, 0x75, 0xC6, 0x0B, 0x37, - 0xCD, 0xE5, 0xCE, 0x25, 0xE0, 0x10, 0xEC, 0x2B, 0xCC, 0x39, 0x80, 0x5B, - 0x29, 0x0B, 0xD5, 0x1A, 0xBA, 0x73, 0xB1, 0x71, 0x41, 0x34, 0x96, 0xFF, - 0x3C, 0x04, 0xB5, 0x03, 0x2E, 0x09, 0x36, 0x25, 0xFA, 0xF3, 0x96, 0xD2, - 0xE8, 0xCA, 0x57, 0x89, 0x67, 0x13, 0xE0, 0x30, 0x92, 0xD1, 0x00, 0xC0, - 0xC3, 0x11, 0x8E, 0xD1, 0x52, 0xF5, 0x95, 0x8E, 0xF4, 0xD5, 0x01, 0xBC, - 0x1F, 0x50, 0x1F, 0x7C, 0xFB, 0x10, 0x80, 0x1B, 0x6D, 0xDF, 0xAE, 0xF8, - 0xB6, 0x46, 0xD6, 0x2A, 0xAA, 0x37, 0x11, 0x34, 0x0E, 0xE6, 0xEC, 0xDF, - 0x01, 0xE0, 0xE4, 0x06, 0x13, 0x57, 0xD6, 0x81, 0x78, 0x16, 0x11, 0x06, - 0x3D, 0xD9, 0xF6, 0x1A, 0x31, 0xE2, 0x23, 0x00, 0xC4, 0xF1, 0x87, 0xAD, - 0x09, 0xD0, 0x7F, 0x05, 0x58, 0x97, 0x0E, 0x3B, 0x20, 0x82, 0xAD, 0x1D, - 0x60, 0x99, 0xE3, 0x66, 0xC6, 0x32, 0xED, 0xF1, 0x22, 0x4E, 0x00, 0xD2, - 0x8C, 0xE7, 0x80, 0xE7, 0x78, 0xFD, 0xBA, 0xFE, 0x46, 0xC0, 0xFF, 0x02, - 0xF0, 0x5D, 0x3F, 0xE6, 0xF6, 0x99, 0x60, 0xF1, 0x74, 0x7E, 0xF9, 0x28, - 0x60, 0x9D, 0x01, 0xFC, 0xA2, 0x6A, 0x74, 0x00, 0xC2, 0x7C, 0xB6, 0x9C, - 0x4A, 0x6C, 0x75, 0x96, 0x63, 0xC1, 0x5F, 0x74, 0xEF, 0x02, 0x58, 0xF7, - 0x2F, 0xC7, 0x80, 0x0C, 0x5F, 0x8A, 0x84, 0xEA, 0xDA, 0xB6, 0xDE, 0x84, - 0xCB, 0x22, 0x77, 0x35, 0x32, 0xD6, 0xF5, 0x61, 0x23, 0x40, 0x25, 0xAF, - 0xFC, 0x36, 0x80, 0x75, 0x56, 0xD4, 0xE3, 0xBE, 0x48, 0xD9, 0xDD, 0x34, - 0x00, 0x52, 0x89, 0xB9, 0xC3, 0xF2, 0x04, 0x7E, 0xA5, 0xAF, 0x10, 0xA1, - 0xDC, 0x56, 0x7C, 0x02, 0xA0, 0x92, 0x8B, 0x06, 0xF7, 0xFF, 0xB2, 0xD2, - 0x3A, 0xAF, 0x21, 0xFD, 0x88, 0xAC, 0xD5, 0x95, 0xF3, 0x0A, 0xB5, 0x0D, - 0xF0, 0xEB, 0x7B, 0x83, 0x53, 0xC0, 0x1F, 0xEF, 0x0D, 0xA2, 0x4D, 0x77, - 0x69, 0xB8, 0xB7, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, - 0x42, 0x60, 0x82, -} diff --git a/emulator/gameview.go b/emulator/gameview.go deleted file mode 100644 index 5a1f26a5..00000000 --- a/emulator/gameview.go +++ /dev/null @@ -1,206 +0,0 @@ -// credit to https://github.com/fogleman/nes -package emulator - -import ( - "image" - - "github.com/giongto35/cloud-game/emulator/nes" - "github.com/giongto35/cloud-game/util" -) - -// List key pressed -const ( - a1 = iota - b1 - select1 - start1 - up1 - down1 - left1 - right1 - - a2 - b2 - select2 - start2 - up2 - down2 - left2 - right2 -) -const NumKeys = 10 - -// Audio consts -const ( - //SampleRate = 16000 - SampleRate = 48000 - //SampleRate = 32768 - Channels = 2 - TimeFrame = 40 - AppAudio = 1 -) - -var bindNESKeys = map[int]int{ - 0: nes.ButtonA, - 1: nes.ButtonB, - 2: -1, - 3: -1, - 4: nes.ButtonSelect, - 5: nes.ButtonStart, - 6: nes.ButtonUp, - 7: nes.ButtonDown, - 8: nes.ButtonLeft, - 9: nes.ButtonRight, -} - -type GameView struct { - console *nes.Console - title string - - // saveFile is the filename gameview save to - saveFile string - // equivalent to the list key pressed const above - keyPressed [NumKeys * 2]bool - - savingJob *job - loadingJob *job - - imageChannel chan<- *image.RGBA - audioChannel chan<- float32 - inputChannel <-chan int -} - -type job struct { - path string - extraFunc func() error -} - -func NewGameView(console *nes.Console, title, saveFile string, imageChannel chan<- *image.RGBA, audioChannel chan<- float32, inputChannel <-chan int) *GameView { - gameview := &GameView{ - console: console, - title: title, - saveFile: saveFile, - keyPressed: [NumKeys * 2]bool{false}, - imageChannel: imageChannel, - audioChannel: audioChannel, - inputChannel: inputChannel, - } - - go gameview.ListenToInputChannel() - return gameview -} - -// ListenToInputChannel listen from input channel streamm, which is exposed to WebRTC session -func (view *GameView) ListenToInputChannel() { - for keysInBinary := range view.inputChannel { - for i := 0; i < NumKeys; i++ { - - key, ok := bindNESKeys[i] - isPressed := ((keysInBinary & 1) == 1) - keysInBinary = keysInBinary >> 1 - if !ok || key == -1 { - continue - } - view.keyPressed[key] = (view.keyPressed[key] && isPressed) || isPressed - - } - } -} - -// Enter enter the game view. -func (view *GameView) Enter() { - view.console.SetAudioSampleRate(SampleRate) - view.console.SetAudioChannel(view.audioChannel) - - // load state if the saveFile file existed in the server (Join the old room) - if err := view.console.LoadState(util.GetSavePath(view.saveFile)); err == nil { - return - } else { - view.console.Reset() - } - - // load sram - cartridge := view.console.Cartridge - if cartridge.Battery != 0 { - if sram, err := readSRAM(util.GetSRAMPath(view.saveFile)); err == nil { - cartridge.SRAM = sram - } - } -} - -// Exit ... -func (view *GameView) Exit() { - view.console.SetAudioChannel(nil) - view.console.SetAudioSampleRate(0) - // save sram - cartridge := view.console.Cartridge - if cartridge.Battery != 0 { - writeSRAM(util.GetSRAMPath(view.saveFile), cartridge.SRAM) - } - - // close producer - close(view.imageChannel) - close(view.audioChannel) -} - -// Update is called for every period of time, dt is the elapsed time from the last frame -func (view *GameView) Update(t, dt float64) { - if dt > 1 { - dt = 0 - } - console := view.console - view.updateControllers() - view.UpdateEvents() - console.StepSeconds(dt) - - // fps to set frame - view.imageChannel <- console.Buffer() -} - -func (view *GameView) Save(extraSaveFunc func() error) { - // put saving event to queue, process in updateEvent - view.savingJob = &job{ - path: util.GetSavePath(view.saveFile), - extraFunc: extraSaveFunc, - } -} - -func (view *GameView) Load() { - // put saving event to queue, process in updateEvent - view.loadingJob = &job{ - path: util.GetSavePath(view.saveFile), - extraFunc: nil, - } -} - -func (view *GameView) UpdateEvents() { - // If there is saving event, save and discard the save event - if view.savingJob != nil { - view.console.SaveState(view.savingJob.path) - // Run extra function (online saving for example) - go view.savingJob.extraFunc() - view.savingJob = nil - } - // If there is loading event, save and discard the load event - if view.loadingJob != nil { - view.console.LoadState(view.loadingJob.path) - // Run extra function (online saving for example) - if view.loadingJob.extraFunc != nil { - go view.loadingJob.extraFunc() - } - view.loadingJob = nil - } -} - -func (view *GameView) updateControllers() { - // Divide keyPressed to player 1 and player 2 - // First 8 keys are player 1 - var player1Keys [8]bool - copy(player1Keys[:], view.keyPressed[:8]) - - var player2Keys [8]bool - copy(player2Keys[:], view.keyPressed[8:]) - - view.console.Controller1.SetButtons(player1Keys) - view.console.Controller2.SetButtons(player2Keys) -} diff --git a/emulator/nes/apu.go b/emulator/nes/apu.go deleted file mode 100644 index 20f666cc..00000000 --- a/emulator/nes/apu.go +++ /dev/null @@ -1,869 +0,0 @@ -package nes - -import ( - "encoding/gob" -) - -const frameCounterRate = CPUFrequency / 240.0 - -var lengthTable = []byte{ - 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, - 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30, -} - -var dutyTable = [][]byte{ - {0, 1, 0, 0, 0, 0, 0, 0}, - {0, 1, 1, 0, 0, 0, 0, 0}, - {0, 1, 1, 1, 1, 0, 0, 0}, - {1, 0, 0, 1, 1, 1, 1, 1}, -} - -var triangleTable = []byte{ - 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, -} - -var noiseTable = []uint16{ - 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068, -} - -var dmcTable = []byte{ - 214, 190, 170, 160, 143, 127, 113, 107, 95, 80, 71, 64, 53, 42, 36, 27, -} - -var pulseTable [31]float32 -var tndTable [203]float32 - -func init() { - for i := 0; i < 31; i++ { - pulseTable[i] = 95.52 / (8128.0/float32(i) + 100) - } - for i := 0; i < 203; i++ { - tndTable[i] = 163.67 / (24329.0/float32(i) + 100) - } -} - -// APU - -type APU struct { - console *Console - channel chan<- float32 - sampleRate float64 - pulse1 Pulse - pulse2 Pulse - triangle Triangle - noise Noise - dmc DMC - cycle uint64 - framePeriod byte - frameValue byte - frameIRQ bool - filterChain FilterChain -} - -func NewAPU(console *Console) *APU { - apu := APU{} - apu.console = console - apu.noise.shiftRegister = 1 - apu.pulse1.channel = 1 - apu.pulse2.channel = 2 - apu.dmc.cpu = console.CPU - return &apu -} - -func (apu *APU) Save(encoder *gob.Encoder) error { - encoder.Encode(apu.cycle) - encoder.Encode(apu.framePeriod) - encoder.Encode(apu.frameValue) - encoder.Encode(apu.frameIRQ) - apu.pulse1.Save(encoder) - apu.pulse2.Save(encoder) - apu.triangle.Save(encoder) - apu.noise.Save(encoder) - apu.dmc.Save(encoder) - return nil -} - -func (apu *APU) Load(decoder *gob.Decoder) error { - decoder.Decode(&apu.cycle) - decoder.Decode(&apu.framePeriod) - decoder.Decode(&apu.frameValue) - decoder.Decode(&apu.frameIRQ) - apu.pulse1.Load(decoder) - apu.pulse2.Load(decoder) - apu.triangle.Load(decoder) - apu.noise.Load(decoder) - apu.dmc.Load(decoder) - return nil -} - -func (apu *APU) Step() { - cycle1 := apu.cycle - apu.cycle++ - cycle2 := apu.cycle - apu.stepTimer() - f1 := int(float64(cycle1) / frameCounterRate) - f2 := int(float64(cycle2) / frameCounterRate) - if f1 != f2 { - apu.stepFrameCounter() - } - s1 := int(float64(cycle1) / apu.sampleRate) - s2 := int(float64(cycle2) / apu.sampleRate) - if s1 != s2 { - apu.sendSample() - } -} - -func (apu *APU) sendSample() { - output := apu.filterChain.Step(apu.output()) - //stereo - select { - case apu.channel <- output: - default: - } - select { - case apu.channel <- output: - default: - } -} - -func (apu *APU) output() float32 { - p1 := apu.pulse1.output() - p2 := apu.pulse2.output() - t := apu.triangle.output() - n := apu.noise.output() - d := apu.dmc.output() - pulseOut := pulseTable[p1+p2] - tndOut := tndTable[3*t+2*n+d] - return pulseOut + tndOut -} - -// mode 0: mode 1: function -// --------- ----------- ----------------------------- -// - - - f - - - - - IRQ (if bit 6 is clear) -// - l - l l - l - - Length counter and sweep -// e e e e e e e e - Envelope and linear counter -func (apu *APU) stepFrameCounter() { - switch apu.framePeriod { - case 4: - apu.frameValue = (apu.frameValue + 1) % 4 - switch apu.frameValue { - case 0, 2: - apu.stepEnvelope() - case 1: - apu.stepEnvelope() - apu.stepSweep() - apu.stepLength() - case 3: - apu.stepEnvelope() - apu.stepSweep() - apu.stepLength() - apu.fireIRQ() - } - case 5: - apu.frameValue = (apu.frameValue + 1) % 5 - switch apu.frameValue { - case 0, 2: - apu.stepEnvelope() - case 1, 3: - apu.stepEnvelope() - apu.stepSweep() - apu.stepLength() - } - } -} - -func (apu *APU) stepTimer() { - if apu.cycle%2 == 0 { - apu.pulse1.stepTimer() - apu.pulse2.stepTimer() - apu.noise.stepTimer() - apu.dmc.stepTimer() - } - apu.triangle.stepTimer() -} - -func (apu *APU) stepEnvelope() { - apu.pulse1.stepEnvelope() - apu.pulse2.stepEnvelope() - apu.triangle.stepCounter() - apu.noise.stepEnvelope() -} - -func (apu *APU) stepSweep() { - apu.pulse1.stepSweep() - apu.pulse2.stepSweep() -} - -func (apu *APU) stepLength() { - apu.pulse1.stepLength() - apu.pulse2.stepLength() - apu.triangle.stepLength() - apu.noise.stepLength() -} - -func (apu *APU) fireIRQ() { - if apu.frameIRQ { - apu.console.CPU.triggerIRQ() - } -} - -func (apu *APU) readRegister(address uint16) byte { - switch address { - case 0x4015: - return apu.readStatus() - // default: - // log.Fatalf("unhandled apu register read at address: 0x%04X", address) - } - return 0 -} - -func (apu *APU) writeRegister(address uint16, value byte) { - switch address { - case 0x4000: - apu.pulse1.writeControl(value) - case 0x4001: - apu.pulse1.writeSweep(value) - case 0x4002: - apu.pulse1.writeTimerLow(value) - case 0x4003: - apu.pulse1.writeTimerHigh(value) - case 0x4004: - apu.pulse2.writeControl(value) - case 0x4005: - apu.pulse2.writeSweep(value) - case 0x4006: - apu.pulse2.writeTimerLow(value) - case 0x4007: - apu.pulse2.writeTimerHigh(value) - case 0x4008: - apu.triangle.writeControl(value) - case 0x4009: - case 0x4010: - apu.dmc.writeControl(value) - case 0x4011: - apu.dmc.writeValue(value) - case 0x4012: - apu.dmc.writeAddress(value) - case 0x4013: - apu.dmc.writeLength(value) - case 0x400A: - apu.triangle.writeTimerLow(value) - case 0x400B: - apu.triangle.writeTimerHigh(value) - case 0x400C: - apu.noise.writeControl(value) - case 0x400D: - case 0x400E: - apu.noise.writePeriod(value) - case 0x400F: - apu.noise.writeLength(value) - case 0x4015: - apu.writeControl(value) - case 0x4017: - apu.writeFrameCounter(value) - // default: - // log.Fatalf("unhandled apu register write at address: 0x%04X", address) - } -} - -func (apu *APU) readStatus() byte { - var result byte - if apu.pulse1.lengthValue > 0 { - result |= 1 - } - if apu.pulse2.lengthValue > 0 { - result |= 2 - } - if apu.triangle.lengthValue > 0 { - result |= 4 - } - if apu.noise.lengthValue > 0 { - result |= 8 - } - if apu.dmc.currentLength > 0 { - result |= 16 - } - return result -} - -func (apu *APU) writeControl(value byte) { - apu.pulse1.enabled = value&1 == 1 - apu.pulse2.enabled = value&2 == 2 - apu.triangle.enabled = value&4 == 4 - apu.noise.enabled = value&8 == 8 - apu.dmc.enabled = value&16 == 16 - if !apu.pulse1.enabled { - apu.pulse1.lengthValue = 0 - } - if !apu.pulse2.enabled { - apu.pulse2.lengthValue = 0 - } - if !apu.triangle.enabled { - apu.triangle.lengthValue = 0 - } - if !apu.noise.enabled { - apu.noise.lengthValue = 0 - } - if !apu.dmc.enabled { - apu.dmc.currentLength = 0 - } else { - if apu.dmc.currentLength == 0 { - apu.dmc.restart() - } - } -} - -func (apu *APU) writeFrameCounter(value byte) { - apu.framePeriod = 4 + (value>>7)&1 - apu.frameIRQ = (value>>6)&1 == 0 - // apu.frameValue = 0 - if apu.framePeriod == 5 { - apu.stepEnvelope() - apu.stepSweep() - apu.stepLength() - } -} - -// Pulse - -type Pulse struct { - enabled bool - channel byte - lengthEnabled bool - lengthValue byte - timerPeriod uint16 - timerValue uint16 - dutyMode byte - dutyValue byte - sweepReload bool - sweepEnabled bool - sweepNegate bool - sweepShift byte - sweepPeriod byte - sweepValue byte - envelopeEnabled bool - envelopeLoop bool - envelopeStart bool - envelopePeriod byte - envelopeValue byte - envelopeVolume byte - constantVolume byte -} - -func (p *Pulse) Save(encoder *gob.Encoder) error { - encoder.Encode(p.enabled) - encoder.Encode(p.channel) - encoder.Encode(p.lengthEnabled) - encoder.Encode(p.lengthValue) - encoder.Encode(p.timerPeriod) - encoder.Encode(p.timerValue) - encoder.Encode(p.dutyMode) - encoder.Encode(p.dutyValue) - encoder.Encode(p.sweepReload) - encoder.Encode(p.sweepEnabled) - encoder.Encode(p.sweepNegate) - encoder.Encode(p.sweepShift) - encoder.Encode(p.sweepPeriod) - encoder.Encode(p.sweepValue) - encoder.Encode(p.envelopeEnabled) - encoder.Encode(p.envelopeLoop) - encoder.Encode(p.envelopeStart) - encoder.Encode(p.envelopePeriod) - encoder.Encode(p.envelopeValue) - encoder.Encode(p.envelopeVolume) - encoder.Encode(p.constantVolume) - return nil -} - -func (p *Pulse) Load(decoder *gob.Decoder) error { - decoder.Decode(&p.enabled) - decoder.Decode(&p.channel) - decoder.Decode(&p.lengthEnabled) - decoder.Decode(&p.lengthValue) - decoder.Decode(&p.timerPeriod) - decoder.Decode(&p.timerValue) - decoder.Decode(&p.dutyMode) - decoder.Decode(&p.dutyValue) - decoder.Decode(&p.sweepReload) - decoder.Decode(&p.sweepEnabled) - decoder.Decode(&p.sweepNegate) - decoder.Decode(&p.sweepShift) - decoder.Decode(&p.sweepPeriod) - decoder.Decode(&p.sweepValue) - decoder.Decode(&p.envelopeEnabled) - decoder.Decode(&p.envelopeLoop) - decoder.Decode(&p.envelopeStart) - decoder.Decode(&p.envelopePeriod) - decoder.Decode(&p.envelopeValue) - decoder.Decode(&p.envelopeVolume) - decoder.Decode(&p.constantVolume) - return nil -} - -func (p *Pulse) writeControl(value byte) { - p.dutyMode = (value >> 6) & 3 - p.lengthEnabled = (value>>5)&1 == 0 - p.envelopeLoop = (value>>5)&1 == 1 - p.envelopeEnabled = (value>>4)&1 == 0 - p.envelopePeriod = value & 15 - p.constantVolume = value & 15 - p.envelopeStart = true -} - -func (p *Pulse) writeSweep(value byte) { - p.sweepEnabled = (value>>7)&1 == 1 - p.sweepPeriod = (value>>4)&7 + 1 - p.sweepNegate = (value>>3)&1 == 1 - p.sweepShift = value & 7 - p.sweepReload = true -} - -func (p *Pulse) writeTimerLow(value byte) { - p.timerPeriod = (p.timerPeriod & 0xFF00) | uint16(value) -} - -func (p *Pulse) writeTimerHigh(value byte) { - p.lengthValue = lengthTable[value>>3] - p.timerPeriod = (p.timerPeriod & 0x00FF) | (uint16(value&7) << 8) - p.envelopeStart = true - p.dutyValue = 0 -} - -func (p *Pulse) stepTimer() { - if p.timerValue == 0 { - p.timerValue = p.timerPeriod - p.dutyValue = (p.dutyValue + 1) % 8 - } else { - p.timerValue-- - } -} - -func (p *Pulse) stepEnvelope() { - if p.envelopeStart { - p.envelopeVolume = 15 - p.envelopeValue = p.envelopePeriod - p.envelopeStart = false - } else if p.envelopeValue > 0 { - p.envelopeValue-- - } else { - if p.envelopeVolume > 0 { - p.envelopeVolume-- - } else if p.envelopeLoop { - p.envelopeVolume = 15 - } - p.envelopeValue = p.envelopePeriod - } -} - -func (p *Pulse) stepSweep() { - if p.sweepReload { - if p.sweepEnabled && p.sweepValue == 0 { - p.sweep() - } - p.sweepValue = p.sweepPeriod - p.sweepReload = false - } else if p.sweepValue > 0 { - p.sweepValue-- - } else { - if p.sweepEnabled { - p.sweep() - } - p.sweepValue = p.sweepPeriod - } -} - -func (p *Pulse) stepLength() { - if p.lengthEnabled && p.lengthValue > 0 { - p.lengthValue-- - } -} - -func (p *Pulse) sweep() { - delta := p.timerPeriod >> p.sweepShift - if p.sweepNegate { - p.timerPeriod -= delta - if p.channel == 1 { - p.timerPeriod-- - } - } else { - p.timerPeriod += delta - } -} - -func (p *Pulse) output() byte { - if !p.enabled { - return 0 - } - if p.lengthValue == 0 { - return 0 - } - if dutyTable[p.dutyMode][p.dutyValue] == 0 { - return 0 - } - if p.timerPeriod < 8 || p.timerPeriod > 0x7FF { - return 0 - } - // if !p.sweepNegate && p.timerPeriod+(p.timerPeriod>>p.sweepShift) > 0x7FF { - // return 0 - // } - if p.envelopeEnabled { - return p.envelopeVolume - } else { - return p.constantVolume - } -} - -// Triangle - -type Triangle struct { - enabled bool - lengthEnabled bool - lengthValue byte - timerPeriod uint16 - timerValue uint16 - dutyValue byte - counterPeriod byte - counterValue byte - counterReload bool -} - -func (t *Triangle) Save(encoder *gob.Encoder) error { - encoder.Encode(t.enabled) - encoder.Encode(t.lengthEnabled) - encoder.Encode(t.lengthValue) - encoder.Encode(t.timerPeriod) - encoder.Encode(t.timerValue) - encoder.Encode(t.dutyValue) - encoder.Encode(t.counterPeriod) - encoder.Encode(t.counterValue) - encoder.Encode(t.counterReload) - return nil -} - -func (t *Triangle) Load(decoder *gob.Decoder) error { - decoder.Decode(&t.enabled) - decoder.Decode(&t.lengthEnabled) - decoder.Decode(&t.lengthValue) - decoder.Decode(&t.timerPeriod) - decoder.Decode(&t.timerValue) - decoder.Decode(&t.dutyValue) - decoder.Decode(&t.counterPeriod) - decoder.Decode(&t.counterValue) - decoder.Decode(&t.counterReload) - return nil -} - -func (t *Triangle) writeControl(value byte) { - t.lengthEnabled = (value>>7)&1 == 0 - t.counterPeriod = value & 0x7F -} - -func (t *Triangle) writeTimerLow(value byte) { - t.timerPeriod = (t.timerPeriod & 0xFF00) | uint16(value) -} - -func (t *Triangle) writeTimerHigh(value byte) { - t.lengthValue = lengthTable[value>>3] - t.timerPeriod = (t.timerPeriod & 0x00FF) | (uint16(value&7) << 8) - t.timerValue = t.timerPeriod - t.counterReload = true -} - -func (t *Triangle) stepTimer() { - if t.timerValue == 0 { - t.timerValue = t.timerPeriod - if t.lengthValue > 0 && t.counterValue > 0 { - t.dutyValue = (t.dutyValue + 1) % 32 - } - } else { - t.timerValue-- - } -} - -func (t *Triangle) stepLength() { - if t.lengthEnabled && t.lengthValue > 0 { - t.lengthValue-- - } -} - -func (t *Triangle) stepCounter() { - if t.counterReload { - t.counterValue = t.counterPeriod - } else if t.counterValue > 0 { - t.counterValue-- - } - if t.lengthEnabled { - t.counterReload = false - } -} - -func (t *Triangle) output() byte { - if !t.enabled { - return 0 - } - if t.lengthValue == 0 { - return 0 - } - if t.counterValue == 0 { - return 0 - } - return triangleTable[t.dutyValue] -} - -// Noise - -type Noise struct { - enabled bool - mode bool - shiftRegister uint16 - lengthEnabled bool - lengthValue byte - timerPeriod uint16 - timerValue uint16 - envelopeEnabled bool - envelopeLoop bool - envelopeStart bool - envelopePeriod byte - envelopeValue byte - envelopeVolume byte - constantVolume byte -} - -func (n *Noise) Save(encoder *gob.Encoder) error { - encoder.Encode(n.enabled) - encoder.Encode(n.mode) - encoder.Encode(n.shiftRegister) - encoder.Encode(n.lengthEnabled) - encoder.Encode(n.lengthValue) - encoder.Encode(n.timerPeriod) - encoder.Encode(n.timerValue) - encoder.Encode(n.envelopeEnabled) - encoder.Encode(n.envelopeLoop) - encoder.Encode(n.envelopeStart) - encoder.Encode(n.envelopePeriod) - encoder.Encode(n.envelopeValue) - encoder.Encode(n.envelopeVolume) - encoder.Encode(n.constantVolume) - return nil -} - -func (n *Noise) Load(decoder *gob.Decoder) error { - decoder.Decode(&n.enabled) - decoder.Decode(&n.mode) - decoder.Decode(&n.shiftRegister) - decoder.Decode(&n.lengthEnabled) - decoder.Decode(&n.lengthValue) - decoder.Decode(&n.timerPeriod) - decoder.Decode(&n.timerValue) - decoder.Decode(&n.envelopeEnabled) - decoder.Decode(&n.envelopeLoop) - decoder.Decode(&n.envelopeStart) - decoder.Decode(&n.envelopePeriod) - decoder.Decode(&n.envelopeValue) - decoder.Decode(&n.envelopeVolume) - decoder.Decode(&n.constantVolume) - return nil -} - -func (n *Noise) writeControl(value byte) { - n.lengthEnabled = (value>>5)&1 == 0 - n.envelopeLoop = (value>>5)&1 == 1 - n.envelopeEnabled = (value>>4)&1 == 0 - n.envelopePeriod = value & 15 - n.constantVolume = value & 15 - n.envelopeStart = true -} - -func (n *Noise) writePeriod(value byte) { - n.mode = value&0x80 == 0x80 - n.timerPeriod = noiseTable[value&0x0F] -} - -func (n *Noise) writeLength(value byte) { - n.lengthValue = lengthTable[value>>3] - n.envelopeStart = true -} - -func (n *Noise) stepTimer() { - if n.timerValue == 0 { - n.timerValue = n.timerPeriod - var shift byte - if n.mode { - shift = 6 - } else { - shift = 1 - } - b1 := n.shiftRegister & 1 - b2 := (n.shiftRegister >> shift) & 1 - n.shiftRegister >>= 1 - n.shiftRegister |= (b1 ^ b2) << 14 - } else { - n.timerValue-- - } -} - -func (n *Noise) stepEnvelope() { - if n.envelopeStart { - n.envelopeVolume = 15 - n.envelopeValue = n.envelopePeriod - n.envelopeStart = false - } else if n.envelopeValue > 0 { - n.envelopeValue-- - } else { - if n.envelopeVolume > 0 { - n.envelopeVolume-- - } else if n.envelopeLoop { - n.envelopeVolume = 15 - } - n.envelopeValue = n.envelopePeriod - } -} - -func (n *Noise) stepLength() { - if n.lengthEnabled && n.lengthValue > 0 { - n.lengthValue-- - } -} - -func (n *Noise) output() byte { - if !n.enabled { - return 0 - } - if n.lengthValue == 0 { - return 0 - } - if n.shiftRegister&1 == 1 { - return 0 - } - if n.envelopeEnabled { - return n.envelopeVolume - } else { - return n.constantVolume - } -} - -// DMC - -type DMC struct { - cpu *CPU - enabled bool - value byte - sampleAddress uint16 - sampleLength uint16 - currentAddress uint16 - currentLength uint16 - shiftRegister byte - bitCount byte - tickPeriod byte - tickValue byte - loop bool - irq bool -} - -func (d *DMC) Save(encoder *gob.Encoder) error { - encoder.Encode(d.enabled) - encoder.Encode(d.value) - encoder.Encode(d.sampleAddress) - encoder.Encode(d.sampleLength) - encoder.Encode(d.currentAddress) - encoder.Encode(d.currentLength) - encoder.Encode(d.shiftRegister) - encoder.Encode(d.bitCount) - encoder.Encode(d.tickPeriod) - encoder.Encode(d.tickValue) - encoder.Encode(d.loop) - encoder.Encode(d.irq) - return nil -} - -func (d *DMC) Load(decoder *gob.Decoder) error { - decoder.Decode(&d.enabled) - decoder.Decode(&d.value) - decoder.Decode(&d.sampleAddress) - decoder.Decode(&d.sampleLength) - decoder.Decode(&d.currentAddress) - decoder.Decode(&d.currentLength) - decoder.Decode(&d.shiftRegister) - decoder.Decode(&d.bitCount) - decoder.Decode(&d.tickPeriod) - decoder.Decode(&d.tickValue) - decoder.Decode(&d.loop) - decoder.Decode(&d.irq) - return nil -} - -func (d *DMC) writeControl(value byte) { - d.irq = value&0x80 == 0x80 - d.loop = value&0x40 == 0x40 - d.tickPeriod = dmcTable[value&0x0F] -} - -func (d *DMC) writeValue(value byte) { - d.value = value & 0x7F -} - -func (d *DMC) writeAddress(value byte) { - // Sample address = %11AAAAAA.AA000000 - d.sampleAddress = 0xC000 | (uint16(value) << 6) -} - -func (d *DMC) writeLength(value byte) { - // Sample length = %0000LLLL.LLLL0001 - d.sampleLength = (uint16(value) << 4) | 1 -} - -func (d *DMC) restart() { - d.currentAddress = d.sampleAddress - d.currentLength = d.sampleLength -} - -func (d *DMC) stepTimer() { - if !d.enabled { - return - } - d.stepReader() - if d.tickValue == 0 { - d.tickValue = d.tickPeriod - d.stepShifter() - } else { - d.tickValue-- - } -} - -func (d *DMC) stepReader() { - if d.currentLength > 0 && d.bitCount == 0 { - d.cpu.stall += 4 - d.shiftRegister = d.cpu.Read(d.currentAddress) - d.bitCount = 8 - d.currentAddress++ - if d.currentAddress == 0 { - d.currentAddress = 0x8000 - } - d.currentLength-- - if d.currentLength == 0 && d.loop { - d.restart() - } - } -} - -func (d *DMC) stepShifter() { - if d.bitCount == 0 { - return - } - if d.shiftRegister&1 == 1 { - if d.value <= 125 { - d.value += 2 - } - } else { - if d.value >= 2 { - d.value -= 2 - } - } - d.shiftRegister >>= 1 - d.bitCount-- -} - -func (d *DMC) output() byte { - return d.value -} diff --git a/emulator/nes/cartridge.go b/emulator/nes/cartridge.go deleted file mode 100644 index 8f7f1271..00000000 --- a/emulator/nes/cartridge.go +++ /dev/null @@ -1,33 +0,0 @@ -package nes - -import "encoding/gob" - -type Cartridge struct { - PRG []byte // PRG-ROM banks - CHR []byte // CHR-ROM banks - SRAM []byte // Save RAM - Mapper byte // mapper type - Mirror byte // mirroring mode - Battery byte // battery present -} - -func NewCartridge(prg, chr []byte, mapper, mirror, battery byte) *Cartridge { - sram := make([]byte, 0x2000) - return &Cartridge{prg, chr, sram, mapper, mirror, battery} -} - -func (cartridge *Cartridge) Save(encoder *gob.Encoder) error { - encoder.Encode(cartridge.PRG) - encoder.Encode(cartridge.CHR) - encoder.Encode(cartridge.SRAM) - encoder.Encode(cartridge.Mirror) - return nil -} - -func (cartridge *Cartridge) Load(decoder *gob.Decoder) error { - decoder.Decode(&cartridge.PRG) - decoder.Decode(&cartridge.CHR) - decoder.Decode(&cartridge.SRAM) - decoder.Decode(&cartridge.Mirror) - return nil -} diff --git a/emulator/nes/console.go b/emulator/nes/console.go deleted file mode 100644 index d884a865..00000000 --- a/emulator/nes/console.go +++ /dev/null @@ -1,156 +0,0 @@ -package nes - -import ( - "encoding/gob" - "image" - "image/color" - "os" - "path" -) - -type Console struct { - CPU *CPU - APU *APU - PPU *PPU - Cartridge *Cartridge - Controller1 *Controller - Controller2 *Controller - Mapper Mapper - RAM []byte -} - -func NewConsole(path string) (*Console, error) { - cartridge, err := LoadNESFile(path) - if err != nil { - return nil, err - } - ram := make([]byte, 2048) - controller1 := NewController() - controller2 := NewController() - console := Console{ - nil, nil, nil, cartridge, controller1, controller2, nil, ram} - mapper, err := NewMapper(&console) - if err != nil { - return nil, err - } - console.Mapper = mapper - console.CPU = NewCPU(&console) - console.APU = NewAPU(&console) - console.PPU = NewPPU(&console) - return &console, nil -} - -func (console *Console) Reset() { - console.CPU.Reset() -} - -func (console *Console) Step() int { - cpuCycles := console.CPU.Step() - ppuCycles := cpuCycles * 3 - for i := 0; i < ppuCycles; i++ { - console.PPU.Step() - console.Mapper.Step() - } - for i := 0; i < cpuCycles; i++ { - console.APU.Step() - } - return cpuCycles -} - -func (console *Console) StepFrame() int { - cpuCycles := 0 - frame := console.PPU.Frame - for frame == console.PPU.Frame { - cpuCycles += console.Step() - } - return cpuCycles -} - -func (console *Console) StepSeconds(seconds float64) { - cycles := int(CPUFrequency * seconds) - for cycles > 0 { - cycles -= console.Step() - } -} - -func (console *Console) Buffer() *image.RGBA { - return console.PPU.front -} - -func (console *Console) BackgroundColor() color.RGBA { - return Palette[console.PPU.readPalette(0)%64] -} - -func (console *Console) SetButtons1(buttons [8]bool) { - console.Controller1.SetButtons(buttons) -} - -func (console *Console) SetButtons2(buttons [8]bool) { - console.Controller2.SetButtons(buttons) -} - -func (console *Console) SetAudioChannel(channel chan<- float32) { - console.APU.channel = channel -} - -func (console *Console) SetAudioSampleRate(sampleRate float64) { - if sampleRate != 0 { - // Convert samples per second to cpu steps per sample - console.APU.sampleRate = CPUFrequency / sampleRate - // Initialize filters - console.APU.filterChain = FilterChain{ - HighPassFilter(float32(sampleRate), 90), - HighPassFilter(float32(sampleRate), 440), - LowPassFilter(float32(sampleRate), 14000), - } - } else { - console.APU.filterChain = nil - } -} -func (console *Console) SaveState(filename string) error { - dir, _ := path.Split(filename) - if err := os.MkdirAll(dir, 0755); err != nil { - return err - } - file, err := os.Create(filename) - if err != nil { - return err - } - defer file.Close() - encoder := gob.NewEncoder(file) - return console.Save(encoder) -} - -func (console *Console) Save(encoder *gob.Encoder) error { - encoder.Encode(console.RAM) - console.CPU.Save(encoder) - console.APU.Save(encoder) - console.PPU.Save(encoder) - console.Cartridge.Save(encoder) - console.Mapper.Save(encoder) - return encoder.Encode(true) -} - -func (console *Console) LoadState(filename string) error { - file, err := os.Open(filename) - if err != nil { - return err - } - defer file.Close() - decoder := gob.NewDecoder(file) - return console.Load(decoder) -} - -func (console *Console) Load(decoder *gob.Decoder) error { - decoder.Decode(&console.RAM) - console.CPU.Load(decoder) - console.APU.Load(decoder) - console.PPU.Load(decoder) - console.Cartridge.Load(decoder) - console.Mapper.Load(decoder) - var dummy bool - if err := decoder.Decode(&dummy); err != nil { - return err - } - return nil -} diff --git a/emulator/nes/controller.go b/emulator/nes/controller.go deleted file mode 100644 index ba38cd07..00000000 --- a/emulator/nes/controller.go +++ /dev/null @@ -1,45 +0,0 @@ -package nes - -const ( - ButtonA = iota - ButtonB - ButtonSelect - ButtonStart - ButtonUp - ButtonDown - ButtonLeft - ButtonRight -) - -type Controller struct { - buttons [8]bool - index byte - strobe byte -} - -func NewController() *Controller { - return &Controller{} -} - -func (c *Controller) SetButtons(buttons [8]bool) { - c.buttons = buttons -} - -func (c *Controller) Read() byte { - value := byte(0) - if c.index < 8 && c.buttons[c.index] { - value = 1 - } - c.index++ - if c.strobe&1 == 1 { - c.index = 0 - } - return value -} - -func (c *Controller) Write(value byte) { - c.strobe = value - if c.strobe&1 == 1 { - c.index = 0 - } -} diff --git a/emulator/nes/cpu.go b/emulator/nes/cpu.go deleted file mode 100644 index 5a980f25..00000000 --- a/emulator/nes/cpu.go +++ /dev/null @@ -1,975 +0,0 @@ -package nes - -import ( - "encoding/gob" - "fmt" -) - -const CPUFrequency = 1789773 - -// interrupt types -const ( - _ = iota - interruptNone - interruptNMI - interruptIRQ -) - -// addressing modes -const ( - _ = iota - modeAbsolute - modeAbsoluteX - modeAbsoluteY - modeAccumulator - modeImmediate - modeImplied - modeIndexedIndirect - modeIndirect - modeIndirectIndexed - modeRelative - modeZeroPage - modeZeroPageX - modeZeroPageY -) - -// instructionModes indicates the addressing mode for each instruction -var instructionModes = [256]byte{ - 6, 7, 6, 7, 11, 11, 11, 11, 6, 5, 4, 5, 1, 1, 1, 1, - 10, 9, 6, 9, 12, 12, 12, 12, 6, 3, 6, 3, 2, 2, 2, 2, - 1, 7, 6, 7, 11, 11, 11, 11, 6, 5, 4, 5, 1, 1, 1, 1, - 10, 9, 6, 9, 12, 12, 12, 12, 6, 3, 6, 3, 2, 2, 2, 2, - 6, 7, 6, 7, 11, 11, 11, 11, 6, 5, 4, 5, 1, 1, 1, 1, - 10, 9, 6, 9, 12, 12, 12, 12, 6, 3, 6, 3, 2, 2, 2, 2, - 6, 7, 6, 7, 11, 11, 11, 11, 6, 5, 4, 5, 8, 1, 1, 1, - 10, 9, 6, 9, 12, 12, 12, 12, 6, 3, 6, 3, 2, 2, 2, 2, - 5, 7, 5, 7, 11, 11, 11, 11, 6, 5, 6, 5, 1, 1, 1, 1, - 10, 9, 6, 9, 12, 12, 13, 13, 6, 3, 6, 3, 2, 2, 3, 3, - 5, 7, 5, 7, 11, 11, 11, 11, 6, 5, 6, 5, 1, 1, 1, 1, - 10, 9, 6, 9, 12, 12, 13, 13, 6, 3, 6, 3, 2, 2, 3, 3, - 5, 7, 5, 7, 11, 11, 11, 11, 6, 5, 6, 5, 1, 1, 1, 1, - 10, 9, 6, 9, 12, 12, 12, 12, 6, 3, 6, 3, 2, 2, 2, 2, - 5, 7, 5, 7, 11, 11, 11, 11, 6, 5, 6, 5, 1, 1, 1, 1, - 10, 9, 6, 9, 12, 12, 12, 12, 6, 3, 6, 3, 2, 2, 2, 2, -} - -// instructionSizes indicates the size of each instruction in bytes -var instructionSizes = [256]byte{ - 2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, - 2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0, - 3, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, - 2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0, - 1, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, - 2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0, - 1, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, - 2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0, - 2, 2, 0, 0, 2, 2, 2, 0, 1, 0, 1, 0, 3, 3, 3, 0, - 2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 0, 3, 0, 0, - 2, 2, 2, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, - 2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0, - 2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, - 2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0, - 2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, - 2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0, -} - -// instructionCycles indicates the number of cycles used by each instruction, -// not including conditional cycles -var instructionCycles = [256]byte{ - 7, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 4, 4, 6, 6, - 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, - 6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 4, 4, 6, 6, - 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, - 6, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 3, 4, 6, 6, - 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, - 6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 5, 4, 6, 6, - 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, - 2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4, - 2, 6, 2, 6, 4, 4, 4, 4, 2, 5, 2, 5, 5, 5, 5, 5, - 2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4, - 2, 5, 2, 5, 4, 4, 4, 4, 2, 4, 2, 4, 4, 4, 4, 4, - 2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6, - 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, - 2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6, - 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, -} - -// instructionPageCycles indicates the number of cycles used by each -// instruction when a page is crossed -var instructionPageCycles = [256]byte{ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, -} - -// instructionNames indicates the name of each instruction -var instructionNames = [256]string{ - "BRK", "ORA", "KIL", "SLO", "NOP", "ORA", "ASL", "SLO", - "PHP", "ORA", "ASL", "ANC", "NOP", "ORA", "ASL", "SLO", - "BPL", "ORA", "KIL", "SLO", "NOP", "ORA", "ASL", "SLO", - "CLC", "ORA", "NOP", "SLO", "NOP", "ORA", "ASL", "SLO", - "JSR", "AND", "KIL", "RLA", "BIT", "AND", "ROL", "RLA", - "PLP", "AND", "ROL", "ANC", "BIT", "AND", "ROL", "RLA", - "BMI", "AND", "KIL", "RLA", "NOP", "AND", "ROL", "RLA", - "SEC", "AND", "NOP", "RLA", "NOP", "AND", "ROL", "RLA", - "RTI", "EOR", "KIL", "SRE", "NOP", "EOR", "LSR", "SRE", - "PHA", "EOR", "LSR", "ALR", "JMP", "EOR", "LSR", "SRE", - "BVC", "EOR", "KIL", "SRE", "NOP", "EOR", "LSR", "SRE", - "CLI", "EOR", "NOP", "SRE", "NOP", "EOR", "LSR", "SRE", - "RTS", "ADC", "KIL", "RRA", "NOP", "ADC", "ROR", "RRA", - "PLA", "ADC", "ROR", "ARR", "JMP", "ADC", "ROR", "RRA", - "BVS", "ADC", "KIL", "RRA", "NOP", "ADC", "ROR", "RRA", - "SEI", "ADC", "NOP", "RRA", "NOP", "ADC", "ROR", "RRA", - "NOP", "STA", "NOP", "SAX", "STY", "STA", "STX", "SAX", - "DEY", "NOP", "TXA", "XAA", "STY", "STA", "STX", "SAX", - "BCC", "STA", "KIL", "AHX", "STY", "STA", "STX", "SAX", - "TYA", "STA", "TXS", "TAS", "SHY", "STA", "SHX", "AHX", - "LDY", "LDA", "LDX", "LAX", "LDY", "LDA", "LDX", "LAX", - "TAY", "LDA", "TAX", "LAX", "LDY", "LDA", "LDX", "LAX", - "BCS", "LDA", "KIL", "LAX", "LDY", "LDA", "LDX", "LAX", - "CLV", "LDA", "TSX", "LAS", "LDY", "LDA", "LDX", "LAX", - "CPY", "CMP", "NOP", "DCP", "CPY", "CMP", "DEC", "DCP", - "INY", "CMP", "DEX", "AXS", "CPY", "CMP", "DEC", "DCP", - "BNE", "CMP", "KIL", "DCP", "NOP", "CMP", "DEC", "DCP", - "CLD", "CMP", "NOP", "DCP", "NOP", "CMP", "DEC", "DCP", - "CPX", "SBC", "NOP", "ISC", "CPX", "SBC", "INC", "ISC", - "INX", "SBC", "NOP", "SBC", "CPX", "SBC", "INC", "ISC", - "BEQ", "SBC", "KIL", "ISC", "NOP", "SBC", "INC", "ISC", - "SED", "SBC", "NOP", "ISC", "NOP", "SBC", "INC", "ISC", -} - -type CPU struct { - Memory // memory interface - Cycles uint64 // number of cycles - PC uint16 // program counter - SP byte // stack pointer - A byte // accumulator - X byte // x register - Y byte // y register - C byte // carry flag - Z byte // zero flag - I byte // interrupt disable flag - D byte // decimal mode flag - B byte // break command flag - U byte // unused flag - V byte // overflow flag - N byte // negative flag - interrupt byte // interrupt type to perform - stall int // number of cycles to stall - table [256]func(*stepInfo) -} - -func NewCPU(console *Console) *CPU { - cpu := CPU{Memory: NewCPUMemory(console)} - cpu.createTable() - cpu.Reset() - return &cpu -} - -// createTable builds a function table for each instruction -func (c *CPU) createTable() { - c.table = [256]func(*stepInfo){ - c.brk, c.ora, c.kil, c.slo, c.nop, c.ora, c.asl, c.slo, - c.php, c.ora, c.asl, c.anc, c.nop, c.ora, c.asl, c.slo, - c.bpl, c.ora, c.kil, c.slo, c.nop, c.ora, c.asl, c.slo, - c.clc, c.ora, c.nop, c.slo, c.nop, c.ora, c.asl, c.slo, - c.jsr, c.and, c.kil, c.rla, c.bit, c.and, c.rol, c.rla, - c.plp, c.and, c.rol, c.anc, c.bit, c.and, c.rol, c.rla, - c.bmi, c.and, c.kil, c.rla, c.nop, c.and, c.rol, c.rla, - c.sec, c.and, c.nop, c.rla, c.nop, c.and, c.rol, c.rla, - c.rti, c.eor, c.kil, c.sre, c.nop, c.eor, c.lsr, c.sre, - c.pha, c.eor, c.lsr, c.alr, c.jmp, c.eor, c.lsr, c.sre, - c.bvc, c.eor, c.kil, c.sre, c.nop, c.eor, c.lsr, c.sre, - c.cli, c.eor, c.nop, c.sre, c.nop, c.eor, c.lsr, c.sre, - c.rts, c.adc, c.kil, c.rra, c.nop, c.adc, c.ror, c.rra, - c.pla, c.adc, c.ror, c.arr, c.jmp, c.adc, c.ror, c.rra, - c.bvs, c.adc, c.kil, c.rra, c.nop, c.adc, c.ror, c.rra, - c.sei, c.adc, c.nop, c.rra, c.nop, c.adc, c.ror, c.rra, - c.nop, c.sta, c.nop, c.sax, c.sty, c.sta, c.stx, c.sax, - c.dey, c.nop, c.txa, c.xaa, c.sty, c.sta, c.stx, c.sax, - c.bcc, c.sta, c.kil, c.ahx, c.sty, c.sta, c.stx, c.sax, - c.tya, c.sta, c.txs, c.tas, c.shy, c.sta, c.shx, c.ahx, - c.ldy, c.lda, c.ldx, c.lax, c.ldy, c.lda, c.ldx, c.lax, - c.tay, c.lda, c.tax, c.lax, c.ldy, c.lda, c.ldx, c.lax, - c.bcs, c.lda, c.kil, c.lax, c.ldy, c.lda, c.ldx, c.lax, - c.clv, c.lda, c.tsx, c.las, c.ldy, c.lda, c.ldx, c.lax, - c.cpy, c.cmp, c.nop, c.dcp, c.cpy, c.cmp, c.dec, c.dcp, - c.iny, c.cmp, c.dex, c.axs, c.cpy, c.cmp, c.dec, c.dcp, - c.bne, c.cmp, c.kil, c.dcp, c.nop, c.cmp, c.dec, c.dcp, - c.cld, c.cmp, c.nop, c.dcp, c.nop, c.cmp, c.dec, c.dcp, - c.cpx, c.sbc, c.nop, c.isc, c.cpx, c.sbc, c.inc, c.isc, - c.inx, c.sbc, c.nop, c.sbc, c.cpx, c.sbc, c.inc, c.isc, - c.beq, c.sbc, c.kil, c.isc, c.nop, c.sbc, c.inc, c.isc, - c.sed, c.sbc, c.nop, c.isc, c.nop, c.sbc, c.inc, c.isc, - } -} - -func (cpu *CPU) Save(encoder *gob.Encoder) error { - encoder.Encode(cpu.Cycles) - encoder.Encode(cpu.PC) - encoder.Encode(cpu.SP) - encoder.Encode(cpu.A) - encoder.Encode(cpu.X) - encoder.Encode(cpu.Y) - encoder.Encode(cpu.C) - encoder.Encode(cpu.Z) - encoder.Encode(cpu.I) - encoder.Encode(cpu.D) - encoder.Encode(cpu.B) - encoder.Encode(cpu.U) - encoder.Encode(cpu.V) - encoder.Encode(cpu.N) - encoder.Encode(cpu.interrupt) - encoder.Encode(cpu.stall) - return nil -} - -func (cpu *CPU) Load(decoder *gob.Decoder) error { - decoder.Decode(&cpu.Cycles) - decoder.Decode(&cpu.PC) - decoder.Decode(&cpu.SP) - decoder.Decode(&cpu.A) - decoder.Decode(&cpu.X) - decoder.Decode(&cpu.Y) - decoder.Decode(&cpu.C) - decoder.Decode(&cpu.Z) - decoder.Decode(&cpu.I) - decoder.Decode(&cpu.D) - decoder.Decode(&cpu.B) - decoder.Decode(&cpu.U) - decoder.Decode(&cpu.V) - decoder.Decode(&cpu.N) - decoder.Decode(&cpu.interrupt) - decoder.Decode(&cpu.stall) - return nil -} - -// Reset resets the CPU to its initial powerup state -func (cpu *CPU) Reset() { - cpu.PC = cpu.Read16(0xFFFC) - cpu.SP = 0xFD - cpu.SetFlags(0x24) -} - -// PrintInstruction prints the current CPU state -func (cpu *CPU) PrintInstruction() { - opcode := cpu.Read(cpu.PC) - bytes := instructionSizes[opcode] - name := instructionNames[opcode] - w0 := fmt.Sprintf("%02X", cpu.Read(cpu.PC+0)) - w1 := fmt.Sprintf("%02X", cpu.Read(cpu.PC+1)) - w2 := fmt.Sprintf("%02X", cpu.Read(cpu.PC+2)) - if bytes < 2 { - w1 = " " - } - if bytes < 3 { - w2 = " " - } - fmt.Printf( - "%4X %s %s %s %s %21s"+ - "A:%02X X:%02X Y:%02X P:%02X SP:%02X CYC:%3d\n", - cpu.PC, w0, w1, w2, name, "", - cpu.A, cpu.X, cpu.Y, cpu.Flags(), cpu.SP, (cpu.Cycles*3)%341) -} - -// pagesDiffer returns true if the two addresses reference different pages -func pagesDiffer(a, b uint16) bool { - return a&0xFF00 != b&0xFF00 -} - -// addBranchCycles adds a cycle for taking a branch and adds another cycle -// if the branch jumps to a new page -func (cpu *CPU) addBranchCycles(info *stepInfo) { - cpu.Cycles++ - if pagesDiffer(info.pc, info.address) { - cpu.Cycles++ - } -} - -func (cpu *CPU) compare(a, b byte) { - cpu.setZN(a - b) - if a >= b { - cpu.C = 1 - } else { - cpu.C = 0 - } -} - -// Read16 reads two bytes using Read to return a double-word value -func (cpu *CPU) Read16(address uint16) uint16 { - lo := uint16(cpu.Read(address)) - hi := uint16(cpu.Read(address + 1)) - return hi<<8 | lo -} - -// read16bug emulates a 6502 bug that caused the low byte to wrap without -// incrementing the high byte -func (cpu *CPU) read16bug(address uint16) uint16 { - a := address - b := (a & 0xFF00) | uint16(byte(a)+1) - lo := cpu.Read(a) - hi := cpu.Read(b) - return uint16(hi)<<8 | uint16(lo) -} - -// push pushes a byte onto the stack -func (cpu *CPU) push(value byte) { - cpu.Write(0x100|uint16(cpu.SP), value) - cpu.SP-- -} - -// pull pops a byte from the stack -func (cpu *CPU) pull() byte { - cpu.SP++ - return cpu.Read(0x100 | uint16(cpu.SP)) -} - -// push16 pushes two bytes onto the stack -func (cpu *CPU) push16(value uint16) { - hi := byte(value >> 8) - lo := byte(value & 0xFF) - cpu.push(hi) - cpu.push(lo) -} - -// pull16 pops two bytes from the stack -func (cpu *CPU) pull16() uint16 { - lo := uint16(cpu.pull()) - hi := uint16(cpu.pull()) - return hi<<8 | lo -} - -// Flags returns the processor status flags -func (cpu *CPU) Flags() byte { - var flags byte - flags |= cpu.C << 0 - flags |= cpu.Z << 1 - flags |= cpu.I << 2 - flags |= cpu.D << 3 - flags |= cpu.B << 4 - flags |= cpu.U << 5 - flags |= cpu.V << 6 - flags |= cpu.N << 7 - return flags -} - -// SetFlags sets the processor status flags -func (cpu *CPU) SetFlags(flags byte) { - cpu.C = (flags >> 0) & 1 - cpu.Z = (flags >> 1) & 1 - cpu.I = (flags >> 2) & 1 - cpu.D = (flags >> 3) & 1 - cpu.B = (flags >> 4) & 1 - cpu.U = (flags >> 5) & 1 - cpu.V = (flags >> 6) & 1 - cpu.N = (flags >> 7) & 1 -} - -// setZ sets the zero flag if the argument is zero -func (cpu *CPU) setZ(value byte) { - if value == 0 { - cpu.Z = 1 - } else { - cpu.Z = 0 - } -} - -// setN sets the negative flag if the argument is negative (high bit is set) -func (cpu *CPU) setN(value byte) { - if value&0x80 != 0 { - cpu.N = 1 - } else { - cpu.N = 0 - } -} - -// setZN sets the zero flag and the negative flag -func (cpu *CPU) setZN(value byte) { - cpu.setZ(value) - cpu.setN(value) -} - -// triggerNMI causes a non-maskable interrupt to occur on the next cycle -func (cpu *CPU) triggerNMI() { - cpu.interrupt = interruptNMI -} - -// triggerIRQ causes an IRQ interrupt to occur on the next cycle -func (cpu *CPU) triggerIRQ() { - if cpu.I == 0 { - cpu.interrupt = interruptIRQ - } -} - -// stepInfo contains information that the instruction functions use -type stepInfo struct { - address uint16 - pc uint16 - mode byte -} - -// Step executes a single CPU instruction -func (cpu *CPU) Step() int { - if cpu.stall > 0 { - cpu.stall-- - return 1 - } - - cycles := cpu.Cycles - - switch cpu.interrupt { - case interruptNMI: - cpu.nmi() - case interruptIRQ: - cpu.irq() - } - cpu.interrupt = interruptNone - - opcode := cpu.Read(cpu.PC) - mode := instructionModes[opcode] - - var address uint16 - var pageCrossed bool - switch mode { - case modeAbsolute: - address = cpu.Read16(cpu.PC + 1) - case modeAbsoluteX: - address = cpu.Read16(cpu.PC+1) + uint16(cpu.X) - pageCrossed = pagesDiffer(address-uint16(cpu.X), address) - case modeAbsoluteY: - address = cpu.Read16(cpu.PC+1) + uint16(cpu.Y) - pageCrossed = pagesDiffer(address-uint16(cpu.Y), address) - case modeAccumulator: - address = 0 - case modeImmediate: - address = cpu.PC + 1 - case modeImplied: - address = 0 - case modeIndexedIndirect: - address = cpu.read16bug(uint16(cpu.Read(cpu.PC+1) + cpu.X)) - case modeIndirect: - address = cpu.read16bug(cpu.Read16(cpu.PC + 1)) - case modeIndirectIndexed: - address = cpu.read16bug(uint16(cpu.Read(cpu.PC+1))) + uint16(cpu.Y) - pageCrossed = pagesDiffer(address-uint16(cpu.Y), address) - case modeRelative: - offset := uint16(cpu.Read(cpu.PC + 1)) - if offset < 0x80 { - address = cpu.PC + 2 + offset - } else { - address = cpu.PC + 2 + offset - 0x100 - } - case modeZeroPage: - address = uint16(cpu.Read(cpu.PC + 1)) - case modeZeroPageX: - address = uint16(cpu.Read(cpu.PC+1)+cpu.X) & 0xff - case modeZeroPageY: - address = uint16(cpu.Read(cpu.PC+1)+cpu.Y) & 0xff - } - - cpu.PC += uint16(instructionSizes[opcode]) - cpu.Cycles += uint64(instructionCycles[opcode]) - if pageCrossed { - cpu.Cycles += uint64(instructionPageCycles[opcode]) - } - info := &stepInfo{address, cpu.PC, mode} - cpu.table[opcode](info) - - return int(cpu.Cycles - cycles) -} - -// NMI - Non-Maskable Interrupt -func (cpu *CPU) nmi() { - cpu.push16(cpu.PC) - cpu.php(nil) - cpu.PC = cpu.Read16(0xFFFA) - cpu.I = 1 - cpu.Cycles += 7 -} - -// IRQ - IRQ Interrupt -func (cpu *CPU) irq() { - cpu.push16(cpu.PC) - cpu.php(nil) - cpu.PC = cpu.Read16(0xFFFE) - cpu.I = 1 - cpu.Cycles += 7 -} - -// ADC - Add with Carry -func (cpu *CPU) adc(info *stepInfo) { - a := cpu.A - b := cpu.Read(info.address) - c := cpu.C - cpu.A = a + b + c - cpu.setZN(cpu.A) - if int(a)+int(b)+int(c) > 0xFF { - cpu.C = 1 - } else { - cpu.C = 0 - } - if (a^b)&0x80 == 0 && (a^cpu.A)&0x80 != 0 { - cpu.V = 1 - } else { - cpu.V = 0 - } -} - -// AND - Logical AND -func (cpu *CPU) and(info *stepInfo) { - cpu.A = cpu.A & cpu.Read(info.address) - cpu.setZN(cpu.A) -} - -// ASL - Arithmetic Shift Left -func (cpu *CPU) asl(info *stepInfo) { - if info.mode == modeAccumulator { - cpu.C = (cpu.A >> 7) & 1 - cpu.A <<= 1 - cpu.setZN(cpu.A) - } else { - value := cpu.Read(info.address) - cpu.C = (value >> 7) & 1 - value <<= 1 - cpu.Write(info.address, value) - cpu.setZN(value) - } -} - -// BCC - Branch if Carry Clear -func (cpu *CPU) bcc(info *stepInfo) { - if cpu.C == 0 { - cpu.PC = info.address - cpu.addBranchCycles(info) - } -} - -// BCS - Branch if Carry Set -func (cpu *CPU) bcs(info *stepInfo) { - if cpu.C != 0 { - cpu.PC = info.address - cpu.addBranchCycles(info) - } -} - -// BEQ - Branch if Equal -func (cpu *CPU) beq(info *stepInfo) { - if cpu.Z != 0 { - cpu.PC = info.address - cpu.addBranchCycles(info) - } -} - -// BIT - Bit Test -func (cpu *CPU) bit(info *stepInfo) { - value := cpu.Read(info.address) - cpu.V = (value >> 6) & 1 - cpu.setZ(value & cpu.A) - cpu.setN(value) -} - -// BMI - Branch if Minus -func (cpu *CPU) bmi(info *stepInfo) { - if cpu.N != 0 { - cpu.PC = info.address - cpu.addBranchCycles(info) - } -} - -// BNE - Branch if Not Equal -func (cpu *CPU) bne(info *stepInfo) { - if cpu.Z == 0 { - cpu.PC = info.address - cpu.addBranchCycles(info) - } -} - -// BPL - Branch if Positive -func (cpu *CPU) bpl(info *stepInfo) { - if cpu.N == 0 { - cpu.PC = info.address - cpu.addBranchCycles(info) - } -} - -// BRK - Force Interrupt -func (cpu *CPU) brk(info *stepInfo) { - cpu.push16(cpu.PC) - cpu.php(info) - cpu.sei(info) - cpu.PC = cpu.Read16(0xFFFE) -} - -// BVC - Branch if Overflow Clear -func (cpu *CPU) bvc(info *stepInfo) { - if cpu.V == 0 { - cpu.PC = info.address - cpu.addBranchCycles(info) - } -} - -// BVS - Branch if Overflow Set -func (cpu *CPU) bvs(info *stepInfo) { - if cpu.V != 0 { - cpu.PC = info.address - cpu.addBranchCycles(info) - } -} - -// CLC - Clear Carry Flag -func (cpu *CPU) clc(info *stepInfo) { - cpu.C = 0 -} - -// CLD - Clear Decimal Mode -func (cpu *CPU) cld(info *stepInfo) { - cpu.D = 0 -} - -// CLI - Clear Interrupt Disable -func (cpu *CPU) cli(info *stepInfo) { - cpu.I = 0 -} - -// CLV - Clear Overflow Flag -func (cpu *CPU) clv(info *stepInfo) { - cpu.V = 0 -} - -// CMP - Compare -func (cpu *CPU) cmp(info *stepInfo) { - value := cpu.Read(info.address) - cpu.compare(cpu.A, value) -} - -// CPX - Compare X Register -func (cpu *CPU) cpx(info *stepInfo) { - value := cpu.Read(info.address) - cpu.compare(cpu.X, value) -} - -// CPY - Compare Y Register -func (cpu *CPU) cpy(info *stepInfo) { - value := cpu.Read(info.address) - cpu.compare(cpu.Y, value) -} - -// DEC - Decrement Memory -func (cpu *CPU) dec(info *stepInfo) { - value := cpu.Read(info.address) - 1 - cpu.Write(info.address, value) - cpu.setZN(value) -} - -// DEX - Decrement X Register -func (cpu *CPU) dex(info *stepInfo) { - cpu.X-- - cpu.setZN(cpu.X) -} - -// DEY - Decrement Y Register -func (cpu *CPU) dey(info *stepInfo) { - cpu.Y-- - cpu.setZN(cpu.Y) -} - -// EOR - Exclusive OR -func (cpu *CPU) eor(info *stepInfo) { - cpu.A = cpu.A ^ cpu.Read(info.address) - cpu.setZN(cpu.A) -} - -// INC - Increment Memory -func (cpu *CPU) inc(info *stepInfo) { - value := cpu.Read(info.address) + 1 - cpu.Write(info.address, value) - cpu.setZN(value) -} - -// INX - Increment X Register -func (cpu *CPU) inx(info *stepInfo) { - cpu.X++ - cpu.setZN(cpu.X) -} - -// INY - Increment Y Register -func (cpu *CPU) iny(info *stepInfo) { - cpu.Y++ - cpu.setZN(cpu.Y) -} - -// JMP - Jump -func (cpu *CPU) jmp(info *stepInfo) { - cpu.PC = info.address -} - -// JSR - Jump to Subroutine -func (cpu *CPU) jsr(info *stepInfo) { - cpu.push16(cpu.PC - 1) - cpu.PC = info.address -} - -// LDA - Load Accumulator -func (cpu *CPU) lda(info *stepInfo) { - cpu.A = cpu.Read(info.address) - cpu.setZN(cpu.A) -} - -// LDX - Load X Register -func (cpu *CPU) ldx(info *stepInfo) { - cpu.X = cpu.Read(info.address) - cpu.setZN(cpu.X) -} - -// LDY - Load Y Register -func (cpu *CPU) ldy(info *stepInfo) { - cpu.Y = cpu.Read(info.address) - cpu.setZN(cpu.Y) -} - -// LSR - Logical Shift Right -func (cpu *CPU) lsr(info *stepInfo) { - if info.mode == modeAccumulator { - cpu.C = cpu.A & 1 - cpu.A >>= 1 - cpu.setZN(cpu.A) - } else { - value := cpu.Read(info.address) - cpu.C = value & 1 - value >>= 1 - cpu.Write(info.address, value) - cpu.setZN(value) - } -} - -// NOP - No Operation -func (cpu *CPU) nop(info *stepInfo) { -} - -// ORA - Logical Inclusive OR -func (cpu *CPU) ora(info *stepInfo) { - cpu.A = cpu.A | cpu.Read(info.address) - cpu.setZN(cpu.A) -} - -// PHA - Push Accumulator -func (cpu *CPU) pha(info *stepInfo) { - cpu.push(cpu.A) -} - -// PHP - Push Processor Status -func (cpu *CPU) php(info *stepInfo) { - cpu.push(cpu.Flags() | 0x10) -} - -// PLA - Pull Accumulator -func (cpu *CPU) pla(info *stepInfo) { - cpu.A = cpu.pull() - cpu.setZN(cpu.A) -} - -// PLP - Pull Processor Status -func (cpu *CPU) plp(info *stepInfo) { - cpu.SetFlags(cpu.pull()&0xEF | 0x20) -} - -// ROL - Rotate Left -func (cpu *CPU) rol(info *stepInfo) { - if info.mode == modeAccumulator { - c := cpu.C - cpu.C = (cpu.A >> 7) & 1 - cpu.A = (cpu.A << 1) | c - cpu.setZN(cpu.A) - } else { - c := cpu.C - value := cpu.Read(info.address) - cpu.C = (value >> 7) & 1 - value = (value << 1) | c - cpu.Write(info.address, value) - cpu.setZN(value) - } -} - -// ROR - Rotate Right -func (cpu *CPU) ror(info *stepInfo) { - if info.mode == modeAccumulator { - c := cpu.C - cpu.C = cpu.A & 1 - cpu.A = (cpu.A >> 1) | (c << 7) - cpu.setZN(cpu.A) - } else { - c := cpu.C - value := cpu.Read(info.address) - cpu.C = value & 1 - value = (value >> 1) | (c << 7) - cpu.Write(info.address, value) - cpu.setZN(value) - } -} - -// RTI - Return from Interrupt -func (cpu *CPU) rti(info *stepInfo) { - cpu.SetFlags(cpu.pull()&0xEF | 0x20) - cpu.PC = cpu.pull16() -} - -// RTS - Return from Subroutine -func (cpu *CPU) rts(info *stepInfo) { - cpu.PC = cpu.pull16() + 1 -} - -// SBC - Subtract with Carry -func (cpu *CPU) sbc(info *stepInfo) { - a := cpu.A - b := cpu.Read(info.address) - c := cpu.C - cpu.A = a - b - (1 - c) - cpu.setZN(cpu.A) - if int(a)-int(b)-int(1-c) >= 0 { - cpu.C = 1 - } else { - cpu.C = 0 - } - if (a^b)&0x80 != 0 && (a^cpu.A)&0x80 != 0 { - cpu.V = 1 - } else { - cpu.V = 0 - } -} - -// SEC - Set Carry Flag -func (cpu *CPU) sec(info *stepInfo) { - cpu.C = 1 -} - -// SED - Set Decimal Flag -func (cpu *CPU) sed(info *stepInfo) { - cpu.D = 1 -} - -// SEI - Set Interrupt Disable -func (cpu *CPU) sei(info *stepInfo) { - cpu.I = 1 -} - -// STA - Store Accumulator -func (cpu *CPU) sta(info *stepInfo) { - cpu.Write(info.address, cpu.A) -} - -// STX - Store X Register -func (cpu *CPU) stx(info *stepInfo) { - cpu.Write(info.address, cpu.X) -} - -// STY - Store Y Register -func (cpu *CPU) sty(info *stepInfo) { - cpu.Write(info.address, cpu.Y) -} - -// TAX - Transfer Accumulator to X -func (cpu *CPU) tax(info *stepInfo) { - cpu.X = cpu.A - cpu.setZN(cpu.X) -} - -// TAY - Transfer Accumulator to Y -func (cpu *CPU) tay(info *stepInfo) { - cpu.Y = cpu.A - cpu.setZN(cpu.Y) -} - -// TSX - Transfer Stack Pointer to X -func (cpu *CPU) tsx(info *stepInfo) { - cpu.X = cpu.SP - cpu.setZN(cpu.X) -} - -// TXA - Transfer X to Accumulator -func (cpu *CPU) txa(info *stepInfo) { - cpu.A = cpu.X - cpu.setZN(cpu.A) -} - -// TXS - Transfer X to Stack Pointer -func (cpu *CPU) txs(info *stepInfo) { - cpu.SP = cpu.X -} - -// TYA - Transfer Y to Accumulator -func (cpu *CPU) tya(info *stepInfo) { - cpu.A = cpu.Y - cpu.setZN(cpu.A) -} - -// illegal opcodes below - -func (cpu *CPU) ahx(info *stepInfo) { -} - -func (cpu *CPU) alr(info *stepInfo) { -} - -func (cpu *CPU) anc(info *stepInfo) { -} - -func (cpu *CPU) arr(info *stepInfo) { -} - -func (cpu *CPU) axs(info *stepInfo) { -} - -func (cpu *CPU) dcp(info *stepInfo) { -} - -func (cpu *CPU) isc(info *stepInfo) { -} - -func (cpu *CPU) kil(info *stepInfo) { -} - -func (cpu *CPU) las(info *stepInfo) { -} - -func (cpu *CPU) lax(info *stepInfo) { -} - -func (cpu *CPU) rla(info *stepInfo) { -} - -func (cpu *CPU) rra(info *stepInfo) { -} - -func (cpu *CPU) sax(info *stepInfo) { -} - -func (cpu *CPU) shx(info *stepInfo) { -} - -func (cpu *CPU) shy(info *stepInfo) { -} - -func (cpu *CPU) slo(info *stepInfo) { -} - -func (cpu *CPU) sre(info *stepInfo) { -} - -func (cpu *CPU) tas(info *stepInfo) { -} - -func (cpu *CPU) xaa(info *stepInfo) { -} diff --git a/emulator/nes/filter.go b/emulator/nes/filter.go deleted file mode 100644 index 3c03b60a..00000000 --- a/emulator/nes/filter.go +++ /dev/null @@ -1,57 +0,0 @@ -package nes - -import "math" - -type Filter interface { - Step(x float32) float32 -} - -// First order filters are defined by the following parameters. -// y[n] = B0*x[n] + B1*x[n-1] - A1*y[n-1] -type FirstOrderFilter struct { - B0 float32 - B1 float32 - A1 float32 - prevX float32 - prevY float32 -} - -func (f *FirstOrderFilter) Step(x float32) float32 { - y := f.B0*x + f.B1*f.prevX - f.A1*f.prevY - f.prevY = y - f.prevX = x - return y -} - -// sampleRate: samples per second -// cutoffFreq: oscillations per second -func LowPassFilter(sampleRate float32, cutoffFreq float32) Filter { - c := sampleRate / math.Pi / cutoffFreq - a0i := 1 / (1 + c) - return &FirstOrderFilter{ - B0: a0i, - B1: a0i, - A1: (1 - c) * a0i, - } -} - -func HighPassFilter(sampleRate float32, cutoffFreq float32) Filter { - c := sampleRate / math.Pi / cutoffFreq - a0i := 1 / (1 + c) - return &FirstOrderFilter{ - B0: c * a0i, - B1: -c * a0i, - A1: (1 - c) * a0i, - } -} - -type FilterChain []Filter - -func (fc FilterChain) Step(x float32) float32 { - if fc != nil { - for i := range fc { - x = fc[i].Step(x) - } - } - return x -} diff --git a/emulator/nes/ines.go b/emulator/nes/ines.go deleted file mode 100644 index 1f105a88..00000000 --- a/emulator/nes/ines.go +++ /dev/null @@ -1,84 +0,0 @@ -package nes - -import ( - "encoding/binary" - "errors" - "io" - "os" -) - -const iNESFileMagic = 0x1a53454e - -type iNESFileHeader struct { - Magic uint32 // iNES magic number - NumPRG byte // number of PRG-ROM banks (16KB each) - NumCHR byte // number of CHR-ROM banks (8KB each) - Control1 byte // control bits - Control2 byte // control bits - NumRAM byte // PRG-RAM size (x 8KB) - _ [7]byte // unused padding -} - -// LoadNESFile reads an iNES file (.nes) and returns a Cartridge on success. -// http://wiki.nesdev.com/w/index.php/INES -// http://nesdev.com/NESDoc.pdf (page 28) -func LoadNESFile(path string) (*Cartridge, error) { - // open file - file, err := os.Open(path) - if err != nil { - return nil, err - } - defer file.Close() - - // read file header - header := iNESFileHeader{} - if err := binary.Read(file, binary.LittleEndian, &header); err != nil { - return nil, err - } - - // verify header magic number - if header.Magic != iNESFileMagic { - return nil, errors.New("invalid .nes file") - } - - // mapper type - mapper1 := header.Control1 >> 4 - mapper2 := header.Control2 >> 4 - mapper := mapper1 | mapper2<<4 - - // mirroring type - mirror1 := header.Control1 & 1 - mirror2 := (header.Control1 >> 3) & 1 - mirror := mirror1 | mirror2<<1 - - // battery-backed RAM - battery := (header.Control1 >> 1) & 1 - - // read trainer if present (unused) - if header.Control1&4 == 4 { - trainer := make([]byte, 512) - if _, err := io.ReadFull(file, trainer); err != nil { - return nil, err - } - } - - // read prg-rom bank(s) - prg := make([]byte, int(header.NumPRG)*16384) - if _, err := io.ReadFull(file, prg); err != nil { - return nil, err - } - - // read chr-rom bank(s) - chr := make([]byte, int(header.NumCHR)*8192) - if _, err := io.ReadFull(file, chr); err != nil { - return nil, err - } - - // provide chr-rom/ram if not in file - if header.NumCHR == 0 { - chr = make([]byte, 8192) - } - - // success - return NewCartridge(prg, chr, mapper, mirror, battery), nil -} diff --git a/emulator/nes/mapper.go b/emulator/nes/mapper.go deleted file mode 100644 index 6fe430f3..00000000 --- a/emulator/nes/mapper.go +++ /dev/null @@ -1,36 +0,0 @@ -package nes - -import ( - "encoding/gob" - "fmt" -) - -type Mapper interface { - Read(address uint16) byte - Write(address uint16, value byte) - Step() - Save(encoder *gob.Encoder) error - Load(decoder *gob.Decoder) error -} - -func NewMapper(console *Console) (Mapper, error) { - cartridge := console.Cartridge - switch cartridge.Mapper { - case 0: - return NewMapper2(cartridge), nil - case 1: - return NewMapper1(cartridge), nil - case 2: - return NewMapper2(cartridge), nil - case 3: - return NewMapper3(cartridge), nil - case 4: - return NewMapper4(console, cartridge), nil - case 7: - return NewMapper7(cartridge), nil - case 225: - return NewMapper225(cartridge), nil - } - err := fmt.Errorf("unsupported mapper: %d", cartridge.Mapper) - return nil, err -} diff --git a/emulator/nes/mapper1.go b/emulator/nes/mapper1.go deleted file mode 100644 index d13c787d..00000000 --- a/emulator/nes/mapper1.go +++ /dev/null @@ -1,205 +0,0 @@ -package nes - -import ( - "encoding/gob" - "log" -) - -type Mapper1 struct { - *Cartridge - shiftRegister byte - control byte - prgMode byte - chrMode byte - prgBank byte - chrBank0 byte - chrBank1 byte - prgOffsets [2]int - chrOffsets [2]int -} - -func NewMapper1(cartridge *Cartridge) Mapper { - m := Mapper1{} - m.Cartridge = cartridge - m.shiftRegister = 0x10 - m.prgOffsets[1] = m.prgBankOffset(-1) - return &m -} - -func (m *Mapper1) Save(encoder *gob.Encoder) error { - encoder.Encode(m.shiftRegister) - encoder.Encode(m.control) - encoder.Encode(m.prgMode) - encoder.Encode(m.chrMode) - encoder.Encode(m.prgBank) - encoder.Encode(m.chrBank0) - encoder.Encode(m.chrBank1) - encoder.Encode(m.prgOffsets) - encoder.Encode(m.chrOffsets) - return nil -} - -func (m *Mapper1) Load(decoder *gob.Decoder) error { - decoder.Decode(&m.shiftRegister) - decoder.Decode(&m.control) - decoder.Decode(&m.prgMode) - decoder.Decode(&m.chrMode) - decoder.Decode(&m.prgBank) - decoder.Decode(&m.chrBank0) - decoder.Decode(&m.chrBank1) - decoder.Decode(&m.prgOffsets) - decoder.Decode(&m.chrOffsets) - return nil -} - -func (m *Mapper1) Step() { -} - -func (m *Mapper1) Read(address uint16) byte { - switch { - case address < 0x2000: - bank := address / 0x1000 - offset := address % 0x1000 - return m.CHR[m.chrOffsets[bank]+int(offset)] - case address >= 0x8000: - address = address - 0x8000 - bank := address / 0x4000 - offset := address % 0x4000 - return m.PRG[m.prgOffsets[bank]+int(offset)] - case address >= 0x6000: - return m.SRAM[int(address)-0x6000] - default: - log.Fatalf("unhandled mapper1 read at address: 0x%04X", address) - } - return 0 -} - -func (m *Mapper1) Write(address uint16, value byte) { - switch { - case address < 0x2000: - bank := address / 0x1000 - offset := address % 0x1000 - m.CHR[m.chrOffsets[bank]+int(offset)] = value - case address >= 0x8000: - m.loadRegister(address, value) - case address >= 0x6000: - m.SRAM[int(address)-0x6000] = value - default: - log.Fatalf("unhandled mapper1 write at address: 0x%04X", address) - } -} - -func (m *Mapper1) loadRegister(address uint16, value byte) { - if value&0x80 == 0x80 { - m.shiftRegister = 0x10 - m.writeControl(m.control | 0x0C) - } else { - complete := m.shiftRegister&1 == 1 - m.shiftRegister >>= 1 - m.shiftRegister |= (value & 1) << 4 - if complete { - m.writeRegister(address, m.shiftRegister) - m.shiftRegister = 0x10 - } - } -} - -func (m *Mapper1) writeRegister(address uint16, value byte) { - switch { - case address <= 0x9FFF: - m.writeControl(value) - case address <= 0xBFFF: - m.writeCHRBank0(value) - case address <= 0xDFFF: - m.writeCHRBank1(value) - case address <= 0xFFFF: - m.writePRGBank(value) - } -} - -// Control (internal, $8000-$9FFF) -func (m *Mapper1) writeControl(value byte) { - m.control = value - m.chrMode = (value >> 4) & 1 - m.prgMode = (value >> 2) & 3 - mirror := value & 3 - switch mirror { - case 0: - m.Cartridge.Mirror = MirrorSingle0 - case 1: - m.Cartridge.Mirror = MirrorSingle1 - case 2: - m.Cartridge.Mirror = MirrorVertical - case 3: - m.Cartridge.Mirror = MirrorHorizontal - } - m.updateOffsets() -} - -// CHR bank 0 (internal, $A000-$BFFF) -func (m *Mapper1) writeCHRBank0(value byte) { - m.chrBank0 = value - m.updateOffsets() -} - -// CHR bank 1 (internal, $C000-$DFFF) -func (m *Mapper1) writeCHRBank1(value byte) { - m.chrBank1 = value - m.updateOffsets() -} - -// PRG bank (internal, $E000-$FFFF) -func (m *Mapper1) writePRGBank(value byte) { - m.prgBank = value & 0x0F - m.updateOffsets() -} - -func (m *Mapper1) prgBankOffset(index int) int { - if index >= 0x80 { - index -= 0x100 - } - index %= len(m.PRG) / 0x4000 - offset := index * 0x4000 - if offset < 0 { - offset += len(m.PRG) - } - return offset -} - -func (m *Mapper1) chrBankOffset(index int) int { - if index >= 0x80 { - index -= 0x100 - } - index %= len(m.CHR) / 0x1000 - offset := index * 0x1000 - if offset < 0 { - offset += len(m.CHR) - } - return offset -} - -// PRG ROM bank mode (0, 1: switch 32 KB at $8000, ignoring low bit of bank number; -// 2: fix first bank at $8000 and switch 16 KB bank at $C000; -// 3: fix last bank at $C000 and switch 16 KB bank at $8000) -// CHR ROM bank mode (0: switch 8 KB at a time; 1: switch two separate 4 KB banks) -func (m *Mapper1) updateOffsets() { - switch m.prgMode { - case 0, 1: - m.prgOffsets[0] = m.prgBankOffset(int(m.prgBank & 0xFE)) - m.prgOffsets[1] = m.prgBankOffset(int(m.prgBank | 0x01)) - case 2: - m.prgOffsets[0] = 0 - m.prgOffsets[1] = m.prgBankOffset(int(m.prgBank)) - case 3: - m.prgOffsets[0] = m.prgBankOffset(int(m.prgBank)) - m.prgOffsets[1] = m.prgBankOffset(-1) - } - switch m.chrMode { - case 0: - m.chrOffsets[0] = m.chrBankOffset(int(m.chrBank0 & 0xFE)) - m.chrOffsets[1] = m.chrBankOffset(int(m.chrBank0 | 0x01)) - case 1: - m.chrOffsets[0] = m.chrBankOffset(int(m.chrBank0)) - m.chrOffsets[1] = m.chrBankOffset(int(m.chrBank1)) - } -} diff --git a/emulator/nes/mapper2.go b/emulator/nes/mapper2.go deleted file mode 100644 index 36e2c1d5..00000000 --- a/emulator/nes/mapper2.go +++ /dev/null @@ -1,70 +0,0 @@ -package nes - -import ( - "encoding/gob" - "log" -) - -type Mapper2 struct { - *Cartridge - prgBanks int - prgBank1 int - prgBank2 int -} - -func NewMapper2(cartridge *Cartridge) Mapper { - prgBanks := len(cartridge.PRG) / 0x4000 - prgBank1 := 0 - prgBank2 := prgBanks - 1 - return &Mapper2{cartridge, prgBanks, prgBank1, prgBank2} -} - -func (m *Mapper2) Save(encoder *gob.Encoder) error { - encoder.Encode(m.prgBanks) - encoder.Encode(m.prgBank1) - encoder.Encode(m.prgBank2) - return nil -} - -func (m *Mapper2) Load(decoder *gob.Decoder) error { - decoder.Decode(&m.prgBanks) - decoder.Decode(&m.prgBank1) - decoder.Decode(&m.prgBank2) - return nil -} - -func (m *Mapper2) Step() { -} - -func (m *Mapper2) Read(address uint16) byte { - switch { - case address < 0x2000: - return m.CHR[address] - case address >= 0xC000: - index := m.prgBank2*0x4000 + int(address-0xC000) - return m.PRG[index] - case address >= 0x8000: - index := m.prgBank1*0x4000 + int(address-0x8000) - return m.PRG[index] - case address >= 0x6000: - index := int(address) - 0x6000 - return m.SRAM[index] - default: - log.Fatalf("unhandled mapper2 read at address: 0x%04X", address) - } - return 0 -} - -func (m *Mapper2) Write(address uint16, value byte) { - switch { - case address < 0x2000: - m.CHR[address] = value - case address >= 0x8000: - m.prgBank1 = int(value) % m.prgBanks - case address >= 0x6000: - index := int(address) - 0x6000 - m.SRAM[index] = value - default: - log.Fatalf("unhandled mapper2 write at address: 0x%04X", address) - } -} diff --git a/emulator/nes/mapper225.go b/emulator/nes/mapper225.go deleted file mode 100644 index be43f7ab..00000000 --- a/emulator/nes/mapper225.go +++ /dev/null @@ -1,85 +0,0 @@ -package nes - -import ( - "encoding/gob" - "log" -) - -// https://github.com/asfdfdfd/fceux/blob/master/src/boards/225.cpp -// https://wiki.nesdev.com/w/index.php/INES_Mapper_225 - -type Mapper225 struct { - *Cartridge - chrBank int - prgBank1 int - prgBank2 int -} - -func NewMapper225(cartridge *Cartridge) Mapper { - prgBanks := len(cartridge.PRG) / 0x4000 - return &Mapper225{cartridge, 0, 0, prgBanks - 1} -} - -func (m *Mapper225) Save(encoder *gob.Encoder) error { - encoder.Encode(m.chrBank) - encoder.Encode(m.prgBank1) - encoder.Encode(m.prgBank2) - return nil -} - -func (m *Mapper225) Load(decoder *gob.Decoder) error { - decoder.Decode(&m.chrBank) - decoder.Decode(&m.prgBank1) - decoder.Decode(&m.prgBank2) - return nil -} - -func (m *Mapper225) Step() { -} - -func (m *Mapper225) Read(address uint16) byte { - switch { - case address < 0x2000: - index := m.chrBank*0x2000 + int(address) - return m.CHR[index] - case address >= 0xC000: - index := m.prgBank2*0x4000 + int(address-0xC000) - return m.PRG[index] - case address >= 0x8000: - index := m.prgBank1*0x4000 + int(address-0x8000) - return m.PRG[index] - case address >= 0x6000: - index := int(address) - 0x6000 - return m.SRAM[index] - default: - log.Fatalf("unhandled Mapper225 read at address: 0x%04X", address) - } - return 0 -} - -func (m *Mapper225) Write(address uint16, value byte) { - if (address < 0x8000) { - return - } - - A := int(address) - bank := (A >> 14) & 1 - m.chrBank = (A & 0x3f) | (bank << 6) - prg := ((A >> 6) & 0x3f) | (bank << 6) - mode := (A >> 12) & 1; - if (mode == 1) { - m.prgBank1 = prg - m.prgBank2 = prg - } else { - m.prgBank1 = prg - m.prgBank2 = prg + 1 - } - mirr := (A >> 13) & 1 - if (mirr == 1) { - m.Cartridge.Mirror = MirrorHorizontal - } else { - m.Cartridge.Mirror = MirrorVertical - } - - // fmt.Println(address, mirr, mode, prg) -} diff --git a/emulator/nes/mapper3.go b/emulator/nes/mapper3.go deleted file mode 100644 index 6f20d52b..00000000 --- a/emulator/nes/mapper3.go +++ /dev/null @@ -1,70 +0,0 @@ -package nes - -import ( - "encoding/gob" - "log" -) - -type Mapper3 struct { - *Cartridge - chrBank int - prgBank1 int - prgBank2 int -} - -func NewMapper3(cartridge *Cartridge) Mapper { - prgBanks := len(cartridge.PRG) / 0x4000 - return &Mapper3{cartridge, 0, 0, prgBanks - 1} -} - -func (m *Mapper3) Save(encoder *gob.Encoder) error { - encoder.Encode(m.chrBank) - encoder.Encode(m.prgBank1) - encoder.Encode(m.prgBank2) - return nil -} - -func (m *Mapper3) Load(decoder *gob.Decoder) error { - decoder.Decode(&m.chrBank) - decoder.Decode(&m.prgBank1) - decoder.Decode(&m.prgBank2) - return nil -} - -func (m *Mapper3) Step() { -} - -func (m *Mapper3) Read(address uint16) byte { - switch { - case address < 0x2000: - index := m.chrBank*0x2000 + int(address) - return m.CHR[index] - case address >= 0xC000: - index := m.prgBank2*0x4000 + int(address-0xC000) - return m.PRG[index] - case address >= 0x8000: - index := m.prgBank1*0x4000 + int(address-0x8000) - return m.PRG[index] - case address >= 0x6000: - index := int(address) - 0x6000 - return m.SRAM[index] - default: - log.Fatalf("unhandled mapper3 read at address: 0x%04X", address) - } - return 0 -} - -func (m *Mapper3) Write(address uint16, value byte) { - switch { - case address < 0x2000: - index := m.chrBank*0x2000 + int(address) - m.CHR[index] = value - case address >= 0x8000: - m.chrBank = int(value & 3) - case address >= 0x6000: - index := int(address) - 0x6000 - m.SRAM[index] = value - default: - log.Fatalf("unhandled mapper3 write at address: 0x%04X", address) - } -} diff --git a/emulator/nes/mapper4.go b/emulator/nes/mapper4.go deleted file mode 100644 index 29e8787b..00000000 --- a/emulator/nes/mapper4.go +++ /dev/null @@ -1,234 +0,0 @@ -package nes - -import ( - "encoding/gob" - "log" -) - -type Mapper4 struct { - *Cartridge - console *Console - register byte - registers [8]byte - prgMode byte - chrMode byte - prgOffsets [4]int - chrOffsets [8]int - reload byte - counter byte - irqEnable bool -} - -func NewMapper4(console *Console, cartridge *Cartridge) Mapper { - m := Mapper4{Cartridge: cartridge, console: console} - m.prgOffsets[0] = m.prgBankOffset(0) - m.prgOffsets[1] = m.prgBankOffset(1) - m.prgOffsets[2] = m.prgBankOffset(-2) - m.prgOffsets[3] = m.prgBankOffset(-1) - return &m -} - -func (m *Mapper4) Save(encoder *gob.Encoder) error { - encoder.Encode(m.register) - encoder.Encode(m.registers) - encoder.Encode(m.prgMode) - encoder.Encode(m.chrMode) - encoder.Encode(m.prgOffsets) - encoder.Encode(m.chrOffsets) - encoder.Encode(m.reload) - encoder.Encode(m.counter) - encoder.Encode(m.irqEnable) - return nil -} - -func (m *Mapper4) Load(decoder *gob.Decoder) error { - decoder.Decode(&m.register) - decoder.Decode(&m.registers) - decoder.Decode(&m.prgMode) - decoder.Decode(&m.chrMode) - decoder.Decode(&m.prgOffsets) - decoder.Decode(&m.chrOffsets) - decoder.Decode(&m.reload) - decoder.Decode(&m.counter) - decoder.Decode(&m.irqEnable) - return nil -} - -func (m *Mapper4) Step() { - ppu := m.console.PPU - if ppu.Cycle != 280 { // TODO: this *should* be 260 - return - } - if ppu.ScanLine > 239 && ppu.ScanLine < 261 { - return - } - if ppu.flagShowBackground == 0 && ppu.flagShowSprites == 0 { - return - } - m.HandleScanLine() -} - -func (m *Mapper4) HandleScanLine() { - if m.counter == 0 { - m.counter = m.reload - } else { - m.counter-- - if m.counter == 0 && m.irqEnable { - m.console.CPU.triggerIRQ() - } - } -} - -func (m *Mapper4) Read(address uint16) byte { - switch { - case address < 0x2000: - bank := address / 0x0400 - offset := address % 0x0400 - return m.CHR[m.chrOffsets[bank]+int(offset)] - case address >= 0x8000: - address = address - 0x8000 - bank := address / 0x2000 - offset := address % 0x2000 - return m.PRG[m.prgOffsets[bank]+int(offset)] - case address >= 0x6000: - return m.SRAM[int(address)-0x6000] - default: - log.Fatalf("unhandled mapper4 read at address: 0x%04X", address) - } - return 0 -} - -func (m *Mapper4) Write(address uint16, value byte) { - switch { - case address < 0x2000: - bank := address / 0x0400 - offset := address % 0x0400 - m.CHR[m.chrOffsets[bank]+int(offset)] = value - case address >= 0x8000: - m.writeRegister(address, value) - case address >= 0x6000: - m.SRAM[int(address)-0x6000] = value - default: - log.Fatalf("unhandled mapper4 write at address: 0x%04X", address) - } -} - -func (m *Mapper4) writeRegister(address uint16, value byte) { - switch { - case address <= 0x9FFF && address%2 == 0: - m.writeBankSelect(value) - case address <= 0x9FFF && address%2 == 1: - m.writeBankData(value) - case address <= 0xBFFF && address%2 == 0: - m.writeMirror(value) - case address <= 0xBFFF && address%2 == 1: - m.writeProtect(value) - case address <= 0xDFFF && address%2 == 0: - m.writeIRQLatch(value) - case address <= 0xDFFF && address%2 == 1: - m.writeIRQReload(value) - case address <= 0xFFFF && address%2 == 0: - m.writeIRQDisable(value) - case address <= 0xFFFF && address%2 == 1: - m.writeIRQEnable(value) - } -} - -func (m *Mapper4) writeBankSelect(value byte) { - m.prgMode = (value >> 6) & 1 - m.chrMode = (value >> 7) & 1 - m.register = value & 7 - m.updateOffsets() -} - -func (m *Mapper4) writeBankData(value byte) { - m.registers[m.register] = value - m.updateOffsets() -} - -func (m *Mapper4) writeMirror(value byte) { - switch value & 1 { - case 0: - m.Cartridge.Mirror = MirrorVertical - case 1: - m.Cartridge.Mirror = MirrorHorizontal - } -} - -func (m *Mapper4) writeProtect(value byte) { -} - -func (m *Mapper4) writeIRQLatch(value byte) { - m.reload = value -} - -func (m *Mapper4) writeIRQReload(value byte) { - m.counter = 0 -} - -func (m *Mapper4) writeIRQDisable(value byte) { - m.irqEnable = false -} - -func (m *Mapper4) writeIRQEnable(value byte) { - m.irqEnable = true -} - -func (m *Mapper4) prgBankOffset(index int) int { - if index >= 0x80 { - index -= 0x100 - } - index %= len(m.PRG) / 0x2000 - offset := index * 0x2000 - if offset < 0 { - offset += len(m.PRG) - } - return offset -} - -func (m *Mapper4) chrBankOffset(index int) int { - if index >= 0x80 { - index -= 0x100 - } - index %= len(m.CHR) / 0x0400 - offset := index * 0x0400 - if offset < 0 { - offset += len(m.CHR) - } - return offset -} - -func (m *Mapper4) updateOffsets() { - switch m.prgMode { - case 0: - m.prgOffsets[0] = m.prgBankOffset(int(m.registers[6])) - m.prgOffsets[1] = m.prgBankOffset(int(m.registers[7])) - m.prgOffsets[2] = m.prgBankOffset(-2) - m.prgOffsets[3] = m.prgBankOffset(-1) - case 1: - m.prgOffsets[0] = m.prgBankOffset(-2) - m.prgOffsets[1] = m.prgBankOffset(int(m.registers[7])) - m.prgOffsets[2] = m.prgBankOffset(int(m.registers[6])) - m.prgOffsets[3] = m.prgBankOffset(-1) - } - switch m.chrMode { - case 0: - m.chrOffsets[0] = m.chrBankOffset(int(m.registers[0] & 0xFE)) - m.chrOffsets[1] = m.chrBankOffset(int(m.registers[0] | 0x01)) - m.chrOffsets[2] = m.chrBankOffset(int(m.registers[1] & 0xFE)) - m.chrOffsets[3] = m.chrBankOffset(int(m.registers[1] | 0x01)) - m.chrOffsets[4] = m.chrBankOffset(int(m.registers[2])) - m.chrOffsets[5] = m.chrBankOffset(int(m.registers[3])) - m.chrOffsets[6] = m.chrBankOffset(int(m.registers[4])) - m.chrOffsets[7] = m.chrBankOffset(int(m.registers[5])) - case 1: - m.chrOffsets[0] = m.chrBankOffset(int(m.registers[2])) - m.chrOffsets[1] = m.chrBankOffset(int(m.registers[3])) - m.chrOffsets[2] = m.chrBankOffset(int(m.registers[4])) - m.chrOffsets[3] = m.chrBankOffset(int(m.registers[5])) - m.chrOffsets[4] = m.chrBankOffset(int(m.registers[0] & 0xFE)) - m.chrOffsets[5] = m.chrBankOffset(int(m.registers[0] | 0x01)) - m.chrOffsets[6] = m.chrBankOffset(int(m.registers[1] & 0xFE)) - m.chrOffsets[7] = m.chrBankOffset(int(m.registers[1] | 0x01)) - } -} diff --git a/emulator/nes/mapper7.go b/emulator/nes/mapper7.go deleted file mode 100644 index 7fff4016..00000000 --- a/emulator/nes/mapper7.go +++ /dev/null @@ -1,64 +0,0 @@ -package nes - -import ( - "encoding/gob" - "log" -) - -type Mapper7 struct { - *Cartridge - prgBank int -} - -func NewMapper7(cartridge *Cartridge) Mapper { - return &Mapper7{cartridge, 0} -} - -func (m *Mapper7) Save(encoder *gob.Encoder) error { - encoder.Encode(m.prgBank) - return nil -} - -func (m *Mapper7) Load(decoder *gob.Decoder) error { - decoder.Decode(&m.prgBank) - return nil -} - -func (m *Mapper7) Step() { -} - -func (m *Mapper7) Read(address uint16) byte { - switch { - case address < 0x2000: - return m.CHR[address] - case address >= 0x8000: - index := m.prgBank*0x8000 + int(address-0x8000) - return m.PRG[index] - case address >= 0x6000: - index := int(address) - 0x6000 - return m.SRAM[index] - default: - log.Fatalf("unhandled mapper7 read at address: 0x%04X", address) - } - return 0 -} - -func (m *Mapper7) Write(address uint16, value byte) { - switch { - case address < 0x2000: - m.CHR[address] = value - case address >= 0x8000: - m.prgBank = int(value & 7) - switch value & 0x10 { - case 0x00: - m.Cartridge.Mirror = MirrorSingle0 - case 0x10: - m.Cartridge.Mirror = MirrorSingle1 - } - case address >= 0x6000: - index := int(address) - 0x6000 - m.SRAM[index] = value - default: - log.Fatalf("unhandled mapper7 write at address: 0x%04X", address) - } -} diff --git a/emulator/nes/memory.go b/emulator/nes/memory.go deleted file mode 100644 index 58342b96..00000000 --- a/emulator/nes/memory.go +++ /dev/null @@ -1,134 +0,0 @@ -package nes - -import "log" - -type Memory interface { - Read(address uint16) byte - Write(address uint16, value byte) -} - -// CPU Memory Map - -type cpuMemory struct { - console *Console -} - -func NewCPUMemory(console *Console) Memory { - return &cpuMemory{console} -} - -func (mem *cpuMemory) Read(address uint16) byte { - switch { - case address < 0x2000: - return mem.console.RAM[address%0x0800] - case address < 0x4000: - return mem.console.PPU.readRegister(0x2000 + address%8) - case address == 0x4014: - return mem.console.PPU.readRegister(address) - case address == 0x4015: - return mem.console.APU.readRegister(address) - case address == 0x4016: - return mem.console.Controller1.Read() - case address == 0x4017: - return mem.console.Controller2.Read() - case address < 0x6000: - // TODO: I/O registers - case address >= 0x6000: - return mem.console.Mapper.Read(address) - default: - log.Fatalf("unhandled cpu memory read at address: 0x%04X", address) - } - return 0 -} - -func (mem *cpuMemory) Write(address uint16, value byte) { - switch { - case address < 0x2000: - mem.console.RAM[address%0x0800] = value - case address < 0x4000: - mem.console.PPU.writeRegister(0x2000+address%8, value) - case address < 0x4014: - mem.console.APU.writeRegister(address, value) - case address == 0x4014: - mem.console.PPU.writeRegister(address, value) - case address == 0x4015: - mem.console.APU.writeRegister(address, value) - case address == 0x4016: - mem.console.Controller1.Write(value) - mem.console.Controller2.Write(value) - case address == 0x4017: - mem.console.APU.writeRegister(address, value) - case address < 0x6000: - // TODO: I/O registers - case address >= 0x6000: - mem.console.Mapper.Write(address, value) - default: - log.Fatalf("unhandled cpu memory write at address: 0x%04X", address) - } -} - -// PPU Memory Map - -type ppuMemory struct { - console *Console -} - -func NewPPUMemory(console *Console) Memory { - return &ppuMemory{console} -} - -func (mem *ppuMemory) Read(address uint16) byte { - address = address % 0x4000 - switch { - case address < 0x2000: - return mem.console.Mapper.Read(address) - case address < 0x3F00: - mode := mem.console.Cartridge.Mirror - return mem.console.PPU.nameTableData[MirrorAddress(mode, address)%2048] - case address < 0x4000: - return mem.console.PPU.readPalette(address % 32) - default: - log.Fatalf("unhandled ppu memory read at address: 0x%04X", address) - } - return 0 -} - -func (mem *ppuMemory) Write(address uint16, value byte) { - address = address % 0x4000 - switch { - case address < 0x2000: - mem.console.Mapper.Write(address, value) - case address < 0x3F00: - mode := mem.console.Cartridge.Mirror - mem.console.PPU.nameTableData[MirrorAddress(mode, address)%2048] = value - case address < 0x4000: - mem.console.PPU.writePalette(address%32, value) - default: - log.Fatalf("unhandled ppu memory write at address: 0x%04X", address) - } -} - -// Mirroring Modes - -const ( - MirrorHorizontal = 0 - MirrorVertical = 1 - MirrorSingle0 = 2 - MirrorSingle1 = 3 - MirrorFour = 4 -) - -var MirrorLookup = [...][4]uint16{ - {0, 0, 1, 1}, - {0, 1, 0, 1}, - {0, 0, 0, 0}, - {1, 1, 1, 1}, - {0, 1, 2, 3}, -} - -func MirrorAddress(mode byte, address uint16) uint16 { - address = (address - 0x2000) % 0x1000 - table := address / 0x0400 - offset := address % 0x0400 - return 0x2000 + MirrorLookup[mode][table]*0x0400 + offset -} diff --git a/emulator/nes/palette.go b/emulator/nes/palette.go deleted file mode 100644 index f11eff6a..00000000 --- a/emulator/nes/palette.go +++ /dev/null @@ -1,24 +0,0 @@ -package nes - -import "image/color" - -var Palette [64]color.RGBA - -func init() { - colors := []uint32{ - 0x666666, 0x002A88, 0x1412A7, 0x3B00A4, 0x5C007E, 0x6E0040, 0x6C0600, 0x561D00, - 0x333500, 0x0B4800, 0x005200, 0x004F08, 0x00404D, 0x000000, 0x000000, 0x000000, - 0xADADAD, 0x155FD9, 0x4240FF, 0x7527FE, 0xA01ACC, 0xB71E7B, 0xB53120, 0x994E00, - 0x6B6D00, 0x388700, 0x0C9300, 0x008F32, 0x007C8D, 0x000000, 0x000000, 0x000000, - 0xFFFEFF, 0x64B0FF, 0x9290FF, 0xC676FF, 0xF36AFF, 0xFE6ECC, 0xFE8170, 0xEA9E22, - 0xBCBE00, 0x88D800, 0x5CE430, 0x45E082, 0x48CDDE, 0x4F4F4F, 0x000000, 0x000000, - 0xFFFEFF, 0xC0DFFF, 0xD3D2FF, 0xE8C8FF, 0xFBC2FF, 0xFEC4EA, 0xFECCC5, 0xF7D8A5, - 0xE4E594, 0xCFEF96, 0xBDF4AB, 0xB3F3CC, 0xB5EBF2, 0xB8B8B8, 0x000000, 0x000000, - } - for i, c := range colors { - r := byte(c >> 16) - g := byte(c >> 8) - b := byte(c) - Palette[i] = color.RGBA{r, g, b, 0xFF} - } -} diff --git a/emulator/nes/ppu.go b/emulator/nes/ppu.go deleted file mode 100644 index f4800cd2..00000000 --- a/emulator/nes/ppu.go +++ /dev/null @@ -1,740 +0,0 @@ -package nes - -import ( - "encoding/gob" - "image" -) - -type PPU struct { - Memory // memory interface - console *Console // reference to parent object - - Cycle int // 0-340 - ScanLine int // 0-261, 0-239=visible, 240=post, 241-260=vblank, 261=pre - Frame uint64 // frame counter - - // storage variables - paletteData [32]byte - nameTableData [2048]byte - oamData [256]byte - front *image.RGBA - back *image.RGBA - - // PPU registers - v uint16 // current vram address (15 bit) - t uint16 // temporary vram address (15 bit) - x byte // fine x scroll (3 bit) - w byte // write toggle (1 bit) - f byte // even/odd frame flag (1 bit) - - register byte - - // NMI flags - nmiOccurred bool - nmiOutput bool - nmiPrevious bool - nmiDelay byte - - // background temporary variables - nameTableByte byte - attributeTableByte byte - lowTileByte byte - highTileByte byte - tileData uint64 - - // sprite temporary variables - spriteCount int - spritePatterns [8]uint32 - spritePositions [8]byte - spritePriorities [8]byte - spriteIndexes [8]byte - - // $2000 PPUCTRL - flagNameTable byte // 0: $2000; 1: $2400; 2: $2800; 3: $2C00 - flagIncrement byte // 0: add 1; 1: add 32 - flagSpriteTable byte // 0: $0000; 1: $1000; ignored in 8x16 mode - flagBackgroundTable byte // 0: $0000; 1: $1000 - flagSpriteSize byte // 0: 8x8; 1: 8x16 - flagMasterSlave byte // 0: read EXT; 1: write EXT - - // $2001 PPUMASK - flagGrayscale byte // 0: color; 1: grayscale - flagShowLeftBackground byte // 0: hide; 1: show - flagShowLeftSprites byte // 0: hide; 1: show - flagShowBackground byte // 0: hide; 1: show - flagShowSprites byte // 0: hide; 1: show - flagRedTint byte // 0: normal; 1: emphasized - flagGreenTint byte // 0: normal; 1: emphasized - flagBlueTint byte // 0: normal; 1: emphasized - - // $2002 PPUSTATUS - flagSpriteZeroHit byte - flagSpriteOverflow byte - - // $2003 OAMADDR - oamAddress byte - - // $2007 PPUDATA - bufferedData byte // for buffered reads -} - -func NewPPU(console *Console) *PPU { - ppu := PPU{Memory: NewPPUMemory(console), console: console} - ppu.front = image.NewRGBA(image.Rect(0, 0, 256, 240)) - ppu.back = image.NewRGBA(image.Rect(0, 0, 256, 240)) - ppu.Reset() - return &ppu -} - -func (ppu *PPU) Save(encoder *gob.Encoder) error { - encoder.Encode(ppu.Cycle) - encoder.Encode(ppu.ScanLine) - encoder.Encode(ppu.Frame) - encoder.Encode(ppu.paletteData) - encoder.Encode(ppu.nameTableData) - encoder.Encode(ppu.oamData) - encoder.Encode(ppu.v) - encoder.Encode(ppu.t) - encoder.Encode(ppu.x) - encoder.Encode(ppu.w) - encoder.Encode(ppu.f) - encoder.Encode(ppu.register) - encoder.Encode(ppu.nmiOccurred) - encoder.Encode(ppu.nmiOutput) - encoder.Encode(ppu.nmiPrevious) - encoder.Encode(ppu.nmiDelay) - encoder.Encode(ppu.nameTableByte) - encoder.Encode(ppu.attributeTableByte) - encoder.Encode(ppu.lowTileByte) - encoder.Encode(ppu.highTileByte) - encoder.Encode(ppu.tileData) - encoder.Encode(ppu.spriteCount) - encoder.Encode(ppu.spritePatterns) - encoder.Encode(ppu.spritePositions) - encoder.Encode(ppu.spritePriorities) - encoder.Encode(ppu.spriteIndexes) - encoder.Encode(ppu.flagNameTable) - encoder.Encode(ppu.flagIncrement) - encoder.Encode(ppu.flagSpriteTable) - encoder.Encode(ppu.flagBackgroundTable) - encoder.Encode(ppu.flagSpriteSize) - encoder.Encode(ppu.flagMasterSlave) - encoder.Encode(ppu.flagGrayscale) - encoder.Encode(ppu.flagShowLeftBackground) - encoder.Encode(ppu.flagShowLeftSprites) - encoder.Encode(ppu.flagShowBackground) - encoder.Encode(ppu.flagShowSprites) - encoder.Encode(ppu.flagRedTint) - encoder.Encode(ppu.flagGreenTint) - encoder.Encode(ppu.flagBlueTint) - encoder.Encode(ppu.flagSpriteZeroHit) - encoder.Encode(ppu.flagSpriteOverflow) - encoder.Encode(ppu.oamAddress) - encoder.Encode(ppu.bufferedData) - return nil -} - -func (ppu *PPU) Load(decoder *gob.Decoder) error { - decoder.Decode(&ppu.Cycle) - decoder.Decode(&ppu.ScanLine) - decoder.Decode(&ppu.Frame) - decoder.Decode(&ppu.paletteData) - decoder.Decode(&ppu.nameTableData) - decoder.Decode(&ppu.oamData) - decoder.Decode(&ppu.v) - decoder.Decode(&ppu.t) - decoder.Decode(&ppu.x) - decoder.Decode(&ppu.w) - decoder.Decode(&ppu.f) - decoder.Decode(&ppu.register) - decoder.Decode(&ppu.nmiOccurred) - decoder.Decode(&ppu.nmiOutput) - decoder.Decode(&ppu.nmiPrevious) - decoder.Decode(&ppu.nmiDelay) - decoder.Decode(&ppu.nameTableByte) - decoder.Decode(&ppu.attributeTableByte) - decoder.Decode(&ppu.lowTileByte) - decoder.Decode(&ppu.highTileByte) - decoder.Decode(&ppu.tileData) - decoder.Decode(&ppu.spriteCount) - decoder.Decode(&ppu.spritePatterns) - decoder.Decode(&ppu.spritePositions) - decoder.Decode(&ppu.spritePriorities) - decoder.Decode(&ppu.spriteIndexes) - decoder.Decode(&ppu.flagNameTable) - decoder.Decode(&ppu.flagIncrement) - decoder.Decode(&ppu.flagSpriteTable) - decoder.Decode(&ppu.flagBackgroundTable) - decoder.Decode(&ppu.flagSpriteSize) - decoder.Decode(&ppu.flagMasterSlave) - decoder.Decode(&ppu.flagGrayscale) - decoder.Decode(&ppu.flagShowLeftBackground) - decoder.Decode(&ppu.flagShowLeftSprites) - decoder.Decode(&ppu.flagShowBackground) - decoder.Decode(&ppu.flagShowSprites) - decoder.Decode(&ppu.flagRedTint) - decoder.Decode(&ppu.flagGreenTint) - decoder.Decode(&ppu.flagBlueTint) - decoder.Decode(&ppu.flagSpriteZeroHit) - decoder.Decode(&ppu.flagSpriteOverflow) - decoder.Decode(&ppu.oamAddress) - decoder.Decode(&ppu.bufferedData) - return nil -} - -func (ppu *PPU) Reset() { - ppu.Cycle = 340 - ppu.ScanLine = 240 - ppu.Frame = 0 - ppu.writeControl(0) - ppu.writeMask(0) - ppu.writeOAMAddress(0) -} - -func (ppu *PPU) readPalette(address uint16) byte { - if address >= 16 && address%4 == 0 { - address -= 16 - } - return ppu.paletteData[address] -} - -func (ppu *PPU) writePalette(address uint16, value byte) { - if address >= 16 && address%4 == 0 { - address -= 16 - } - ppu.paletteData[address] = value -} - -func (ppu *PPU) readRegister(address uint16) byte { - switch address { - case 0x2002: - return ppu.readStatus() - case 0x2004: - return ppu.readOAMData() - case 0x2007: - return ppu.readData() - } - return 0 -} - -func (ppu *PPU) writeRegister(address uint16, value byte) { - ppu.register = value - switch address { - case 0x2000: - ppu.writeControl(value) - case 0x2001: - ppu.writeMask(value) - case 0x2003: - ppu.writeOAMAddress(value) - case 0x2004: - ppu.writeOAMData(value) - case 0x2005: - ppu.writeScroll(value) - case 0x2006: - ppu.writeAddress(value) - case 0x2007: - ppu.writeData(value) - case 0x4014: - ppu.writeDMA(value) - } -} - -// $2000: PPUCTRL -func (ppu *PPU) writeControl(value byte) { - ppu.flagNameTable = (value >> 0) & 3 - ppu.flagIncrement = (value >> 2) & 1 - ppu.flagSpriteTable = (value >> 3) & 1 - ppu.flagBackgroundTable = (value >> 4) & 1 - ppu.flagSpriteSize = (value >> 5) & 1 - ppu.flagMasterSlave = (value >> 6) & 1 - ppu.nmiOutput = (value>>7)&1 == 1 - ppu.nmiChange() - // t: ....BA.. ........ = d: ......BA - ppu.t = (ppu.t & 0xF3FF) | ((uint16(value) & 0x03) << 10) -} - -// $2001: PPUMASK -func (ppu *PPU) writeMask(value byte) { - ppu.flagGrayscale = (value >> 0) & 1 - ppu.flagShowLeftBackground = (value >> 1) & 1 - ppu.flagShowLeftSprites = (value >> 2) & 1 - ppu.flagShowBackground = (value >> 3) & 1 - ppu.flagShowSprites = (value >> 4) & 1 - ppu.flagRedTint = (value >> 5) & 1 - ppu.flagGreenTint = (value >> 6) & 1 - ppu.flagBlueTint = (value >> 7) & 1 -} - -// $2002: PPUSTATUS -func (ppu *PPU) readStatus() byte { - result := ppu.register & 0x1F - result |= ppu.flagSpriteOverflow << 5 - result |= ppu.flagSpriteZeroHit << 6 - if ppu.nmiOccurred { - result |= 1 << 7 - } - ppu.nmiOccurred = false - ppu.nmiChange() - // w: = 0 - ppu.w = 0 - return result -} - -// $2003: OAMADDR -func (ppu *PPU) writeOAMAddress(value byte) { - ppu.oamAddress = value -} - -// $2004: OAMDATA (read) -func (ppu *PPU) readOAMData() byte { - return ppu.oamData[ppu.oamAddress] -} - -// $2004: OAMDATA (write) -func (ppu *PPU) writeOAMData(value byte) { - ppu.oamData[ppu.oamAddress] = value - ppu.oamAddress++ -} - -// $2005: PPUSCROLL -func (ppu *PPU) writeScroll(value byte) { - if ppu.w == 0 { - // t: ........ ...HGFED = d: HGFED... - // x: CBA = d: .....CBA - // w: = 1 - ppu.t = (ppu.t & 0xFFE0) | (uint16(value) >> 3) - ppu.x = value & 0x07 - ppu.w = 1 - } else { - // t: .CBA..HG FED..... = d: HGFEDCBA - // w: = 0 - ppu.t = (ppu.t & 0x8FFF) | ((uint16(value) & 0x07) << 12) - ppu.t = (ppu.t & 0xFC1F) | ((uint16(value) & 0xF8) << 2) - ppu.w = 0 - } -} - -// $2006: PPUADDR -func (ppu *PPU) writeAddress(value byte) { - if ppu.w == 0 { - // t: ..FEDCBA ........ = d: ..FEDCBA - // t: .X...... ........ = 0 - // w: = 1 - ppu.t = (ppu.t & 0x80FF) | ((uint16(value) & 0x3F) << 8) - ppu.w = 1 - } else { - // t: ........ HGFEDCBA = d: HGFEDCBA - // v = t - // w: = 0 - ppu.t = (ppu.t & 0xFF00) | uint16(value) - ppu.v = ppu.t - ppu.w = 0 - } -} - -// $2007: PPUDATA (read) -func (ppu *PPU) readData() byte { - value := ppu.Read(ppu.v) - // emulate buffered reads - if ppu.v%0x4000 < 0x3F00 { - buffered := ppu.bufferedData - ppu.bufferedData = value - value = buffered - } else { - ppu.bufferedData = ppu.Read(ppu.v - 0x1000) - } - // increment address - if ppu.flagIncrement == 0 { - ppu.v += 1 - } else { - ppu.v += 32 - } - return value -} - -// $2007: PPUDATA (write) -func (ppu *PPU) writeData(value byte) { - ppu.Write(ppu.v, value) - if ppu.flagIncrement == 0 { - ppu.v += 1 - } else { - ppu.v += 32 - } -} - -// $4014: OAMDMA -func (ppu *PPU) writeDMA(value byte) { - cpu := ppu.console.CPU - address := uint16(value) << 8 - for i := 0; i < 256; i++ { - ppu.oamData[ppu.oamAddress] = cpu.Read(address) - ppu.oamAddress++ - address++ - } - cpu.stall += 513 - if cpu.Cycles%2 == 1 { - cpu.stall++ - } -} - -// NTSC Timing Helper Functions - -func (ppu *PPU) incrementX() { - // increment hori(v) - // if coarse X == 31 - if ppu.v&0x001F == 31 { - // coarse X = 0 - ppu.v &= 0xFFE0 - // switch horizontal nametable - ppu.v ^= 0x0400 - } else { - // increment coarse X - ppu.v++ - } -} - -func (ppu *PPU) incrementY() { - // increment vert(v) - // if fine Y < 7 - if ppu.v&0x7000 != 0x7000 { - // increment fine Y - ppu.v += 0x1000 - } else { - // fine Y = 0 - ppu.v &= 0x8FFF - // let y = coarse Y - y := (ppu.v & 0x03E0) >> 5 - if y == 29 { - // coarse Y = 0 - y = 0 - // switch vertical nametable - ppu.v ^= 0x0800 - } else if y == 31 { - // coarse Y = 0, nametable not switched - y = 0 - } else { - // increment coarse Y - y++ - } - // put coarse Y back into v - ppu.v = (ppu.v & 0xFC1F) | (y << 5) - } -} - -func (ppu *PPU) copyX() { - // hori(v) = hori(t) - // v: .....F.. ...EDCBA = t: .....F.. ...EDCBA - ppu.v = (ppu.v & 0xFBE0) | (ppu.t & 0x041F) -} - -func (ppu *PPU) copyY() { - // vert(v) = vert(t) - // v: .IHGF.ED CBA..... = t: .IHGF.ED CBA..... - ppu.v = (ppu.v & 0x841F) | (ppu.t & 0x7BE0) -} - -func (ppu *PPU) nmiChange() { - nmi := ppu.nmiOutput && ppu.nmiOccurred - if nmi && !ppu.nmiPrevious { - // TODO: this fixes some games but the delay shouldn't have to be so - // long, so the timings are off somewhere - ppu.nmiDelay = 15 - } - ppu.nmiPrevious = nmi -} - -func (ppu *PPU) setVerticalBlank() { - ppu.front, ppu.back = ppu.back, ppu.front - ppu.nmiOccurred = true - ppu.nmiChange() -} - -func (ppu *PPU) clearVerticalBlank() { - ppu.nmiOccurred = false - ppu.nmiChange() -} - -func (ppu *PPU) fetchNameTableByte() { - v := ppu.v - address := 0x2000 | (v & 0x0FFF) - ppu.nameTableByte = ppu.Read(address) -} - -func (ppu *PPU) fetchAttributeTableByte() { - v := ppu.v - address := 0x23C0 | (v & 0x0C00) | ((v >> 4) & 0x38) | ((v >> 2) & 0x07) - shift := ((v >> 4) & 4) | (v & 2) - ppu.attributeTableByte = ((ppu.Read(address) >> shift) & 3) << 2 -} - -func (ppu *PPU) fetchLowTileByte() { - fineY := (ppu.v >> 12) & 7 - table := ppu.flagBackgroundTable - tile := ppu.nameTableByte - address := 0x1000*uint16(table) + uint16(tile)*16 + fineY - ppu.lowTileByte = ppu.Read(address) -} - -func (ppu *PPU) fetchHighTileByte() { - fineY := (ppu.v >> 12) & 7 - table := ppu.flagBackgroundTable - tile := ppu.nameTableByte - address := 0x1000*uint16(table) + uint16(tile)*16 + fineY - ppu.highTileByte = ppu.Read(address + 8) -} - -func (ppu *PPU) storeTileData() { - var data uint32 - for i := 0; i < 8; i++ { - a := ppu.attributeTableByte - p1 := (ppu.lowTileByte & 0x80) >> 7 - p2 := (ppu.highTileByte & 0x80) >> 6 - ppu.lowTileByte <<= 1 - ppu.highTileByte <<= 1 - data <<= 4 - data |= uint32(a | p1 | p2) - } - ppu.tileData |= uint64(data) -} - -func (ppu *PPU) fetchTileData() uint32 { - return uint32(ppu.tileData >> 32) -} - -func (ppu *PPU) backgroundPixel() byte { - if ppu.flagShowBackground == 0 { - return 0 - } - data := ppu.fetchTileData() >> ((7 - ppu.x) * 4) - return byte(data & 0x0F) -} - -func (ppu *PPU) spritePixel() (byte, byte) { - if ppu.flagShowSprites == 0 { - return 0, 0 - } - for i := 0; i < ppu.spriteCount; i++ { - offset := (ppu.Cycle - 1) - int(ppu.spritePositions[i]) - if offset < 0 || offset > 7 { - continue - } - offset = 7 - offset - color := byte((ppu.spritePatterns[i] >> byte(offset*4)) & 0x0F) - if color%4 == 0 { - continue - } - return byte(i), color - } - return 0, 0 -} - -func (ppu *PPU) renderPixel() { - x := ppu.Cycle - 1 - y := ppu.ScanLine - background := ppu.backgroundPixel() - i, sprite := ppu.spritePixel() - if x < 8 && ppu.flagShowLeftBackground == 0 { - background = 0 - } - if x < 8 && ppu.flagShowLeftSprites == 0 { - sprite = 0 - } - b := background%4 != 0 - s := sprite%4 != 0 - var color byte - if !b && !s { - color = 0 - } else if !b && s { - color = sprite | 0x10 - } else if b && !s { - color = background - } else { - if ppu.spriteIndexes[i] == 0 && x < 255 { - ppu.flagSpriteZeroHit = 1 - } - if ppu.spritePriorities[i] == 0 { - color = sprite | 0x10 - } else { - color = background - } - } - c := Palette[ppu.readPalette(uint16(color))%64] - ppu.back.SetRGBA(x, y, c) -} - -func (ppu *PPU) fetchSpritePattern(i, row int) uint32 { - tile := ppu.oamData[i*4+1] - attributes := ppu.oamData[i*4+2] - var address uint16 - if ppu.flagSpriteSize == 0 { - if attributes&0x80 == 0x80 { - row = 7 - row - } - table := ppu.flagSpriteTable - address = 0x1000*uint16(table) + uint16(tile)*16 + uint16(row) - } else { - if attributes&0x80 == 0x80 { - row = 15 - row - } - table := tile & 1 - tile &= 0xFE - if row > 7 { - tile++ - row -= 8 - } - address = 0x1000*uint16(table) + uint16(tile)*16 + uint16(row) - } - a := (attributes & 3) << 2 - lowTileByte := ppu.Read(address) - highTileByte := ppu.Read(address + 8) - var data uint32 - for i := 0; i < 8; i++ { - var p1, p2 byte - if attributes&0x40 == 0x40 { - p1 = (lowTileByte & 1) << 0 - p2 = (highTileByte & 1) << 1 - lowTileByte >>= 1 - highTileByte >>= 1 - } else { - p1 = (lowTileByte & 0x80) >> 7 - p2 = (highTileByte & 0x80) >> 6 - lowTileByte <<= 1 - highTileByte <<= 1 - } - data <<= 4 - data |= uint32(a | p1 | p2) - } - return data -} - -func (ppu *PPU) evaluateSprites() { - var h int - if ppu.flagSpriteSize == 0 { - h = 8 - } else { - h = 16 - } - count := 0 - for i := 0; i < 64; i++ { - y := ppu.oamData[i*4+0] - a := ppu.oamData[i*4+2] - x := ppu.oamData[i*4+3] - row := ppu.ScanLine - int(y) - if row < 0 || row >= h { - continue - } - if count < 8 { - ppu.spritePatterns[count] = ppu.fetchSpritePattern(i, row) - ppu.spritePositions[count] = x - ppu.spritePriorities[count] = (a >> 5) & 1 - ppu.spriteIndexes[count] = byte(i) - } - count++ - } - if count > 8 { - count = 8 - ppu.flagSpriteOverflow = 1 - } - ppu.spriteCount = count -} - -// tick updates Cycle, ScanLine and Frame counters -func (ppu *PPU) tick() { - if ppu.nmiDelay > 0 { - ppu.nmiDelay-- - if ppu.nmiDelay == 0 && ppu.nmiOutput && ppu.nmiOccurred { - ppu.console.CPU.triggerNMI() - } - } - - if ppu.flagShowBackground != 0 || ppu.flagShowSprites != 0 { - if ppu.f == 1 && ppu.ScanLine == 261 && ppu.Cycle == 339 { - ppu.Cycle = 0 - ppu.ScanLine = 0 - ppu.Frame++ - ppu.f ^= 1 - return - } - } - ppu.Cycle++ - if ppu.Cycle > 340 { - ppu.Cycle = 0 - ppu.ScanLine++ - if ppu.ScanLine > 261 { - ppu.ScanLine = 0 - ppu.Frame++ - ppu.f ^= 1 - } - } -} - -// Step executes a single PPU cycle -func (ppu *PPU) Step() { - ppu.tick() - - renderingEnabled := ppu.flagShowBackground != 0 || ppu.flagShowSprites != 0 - preLine := ppu.ScanLine == 261 - visibleLine := ppu.ScanLine < 240 - // postLine := ppu.ScanLine == 240 - renderLine := preLine || visibleLine - preFetchCycle := ppu.Cycle >= 321 && ppu.Cycle <= 336 - visibleCycle := ppu.Cycle >= 1 && ppu.Cycle <= 256 - fetchCycle := preFetchCycle || visibleCycle - - // background logic - if renderingEnabled { - if visibleLine && visibleCycle { - ppu.renderPixel() - } - if renderLine && fetchCycle { - ppu.tileData <<= 4 - switch ppu.Cycle % 8 { - case 1: - ppu.fetchNameTableByte() - case 3: - ppu.fetchAttributeTableByte() - case 5: - ppu.fetchLowTileByte() - case 7: - ppu.fetchHighTileByte() - case 0: - ppu.storeTileData() - } - } - if preLine && ppu.Cycle >= 280 && ppu.Cycle <= 304 { - ppu.copyY() - } - if renderLine { - if fetchCycle && ppu.Cycle%8 == 0 { - ppu.incrementX() - } - if ppu.Cycle == 256 { - ppu.incrementY() - } - if ppu.Cycle == 257 { - ppu.copyX() - } - } - } - - // sprite logic - if renderingEnabled { - if ppu.Cycle == 257 { - if visibleLine { - ppu.evaluateSprites() - } else { - ppu.spriteCount = 0 - } - } - } - - // vblank logic - if ppu.ScanLine == 241 && ppu.Cycle == 1 { - ppu.setVerticalBlank() - } - if preLine && ppu.Cycle == 1 { - ppu.clearVerticalBlank() - ppu.flagSpriteZeroHit = 0 - ppu.flagSpriteOverflow = 0 - } -} diff --git a/emulator/util.go b/emulator/util.go deleted file mode 100644 index 7cadf500..00000000 --- a/emulator/util.go +++ /dev/null @@ -1,117 +0,0 @@ -// credit to https://github.com/fogleman/nes -package emulator - -import ( - "encoding/binary" - "fmt" - "image" - "image/color" - "image/draw" - "image/gif" - "image/png" - "os" - "path" - - "github.com/giongto35/cloud-game/emulator/nes" -) - -func combineButtons(a, b [8]bool) [8]bool { - var result [8]bool - for i := 0; i < 8; i++ { - result[i] = a[i] || b[i] - } - return result -} - -func copyImage(src image.Image) *image.RGBA { - dst := image.NewRGBA(src.Bounds()) - draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src) - return dst -} - -func loadPNG(path string) (image.Image, error) { - file, err := os.Open(path) - if err != nil { - return nil, err - } - defer file.Close() - return png.Decode(file) -} - -func savePNG(path string, im image.Image) error { - file, err := os.Create(path) - if err != nil { - return err - } - defer file.Close() - return png.Encode(file, im) -} - -func saveGIF(path string, frames []image.Image) error { - var palette []color.Color - for _, c := range nes.Palette { - palette = append(palette, c) - } - g := gif.GIF{} - for i, src := range frames { - if i%3 != 0 { - continue - } - dst := image.NewPaletted(src.Bounds(), palette) - draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src) - g.Image = append(g.Image, dst) - g.Delay = append(g.Delay, 5) - } - file, err := os.Create(path) - if err != nil { - return err - } - defer file.Close() - return gif.EncodeAll(file, &g) -} - -func screenshot(im image.Image) { - for i := 0; i < 1000; i++ { - path := fmt.Sprintf("%03d.png", i) - if _, err := os.Stat(path); os.IsNotExist(err) { - savePNG(path, im) - return - } - } -} - -func animation(frames []image.Image) { - for i := 0; i < 1000; i++ { - path := fmt.Sprintf("%03d.gif", i) - if _, err := os.Stat(path); os.IsNotExist(err) { - saveGIF(path, frames) - return - } - } -} - -func writeSRAM(filename string, sram []byte) error { - dir, _ := path.Split(filename) - if err := os.MkdirAll(dir, 0755); err != nil { - return err - } - file, err := os.Create(filename) - if err != nil { - return err - } - defer file.Close() - return binary.Write(file, binary.LittleEndian, sram) -} - -func readSRAM(filename string) ([]byte, error) { - file, err := os.Open(filename) - if err != nil { - return nil, err - } - defer file.Close() - sram := make([]byte, 0x2000) - if err := binary.Read(file, binary.LittleEndian, sram); err != nil { - return nil, err - } - return sram, nil -} diff --git a/libretro/nanoarch/nanoarch.go b/libretro/nanoarch/nanoarch.go index ebffc5fb..cf6fec90 100644 --- a/libretro/nanoarch/nanoarch.go +++ b/libretro/nanoarch/nanoarch.go @@ -12,8 +12,6 @@ import ( "reflect" "sync" "unsafe" - - "github.com/giongto35/cloud-game/emulator" ) /* @@ -97,7 +95,6 @@ const ( ) type CloudEmulator interface { - SetView(view *emulator.GameView) Start(path string) SaveGame(saveExtraFunc func() error) error LoadGame() error diff --git a/worker/room/room.go b/worker/room/room.go index 178fd9e5..dc7c388d 100644 --- a/worker/room/room.go +++ b/worker/room/room.go @@ -116,9 +116,9 @@ func NewRoom(roomID string, gameName string, onlineStorage *storage.Client) *Roo // create director func getEmulator(emuName string, roomID string, imageChannel chan<- *image.RGBA, audioChannel chan<- float32, inputChannel <-chan int) emulator.CloudEmulator { - if emuName == "nes" { - return emulator.NewDirector(roomID, imageChannel, audioChannel, inputChannel) - } + //if emuName == "nes" { + //return emulator.NewDirector(roomID, imageChannel, audioChannel, inputChannel) + //} nanoarch.Init(emuName, roomID, imageChannel, audioChannel, inputChannel) return nanoarch.NAEmulator @@ -170,7 +170,7 @@ func (r *Room) startWebRTCSession(peerconnection *webrtc.WebRTC, playerIndex int // the first 10 bits belong to player 1 // the next 10 belongs to player 2 ... // We standardize and put it to inputChannel (20 bits) - input = input << ((uint(playerIndex) - 1) * emulator.NumKeys) + input = input << ((uint(playerIndex) - 1) * config.NumKeys) select { case r.inputChannel <- input: default: