diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..f831c0a1 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: giongto35 +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.gitignore b/.gitignore index 14eb3f94..730df839 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,9 @@ key.json run_prod_docker.sh prod/ turnserver.conf + +### Ignore Goland files +.idea/ + +### Ignore build artifact directory +./build diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..847849c0 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +dep: + go mod download + go mod vendor + go mod tidy + +build: dep + go build -o build/cloudretro ./cmd + +run: build + # Run coordinator first + ./build/cloudretro -overlordhost overlord & + # Wait till overlord finish initialized + # Run a worker connecting to overload + ./build/cloudretro -overlordhost ws://localhost:8000/wso + +run-docker: + docker build . -t cloud-game-local + docker stop cloud-game-local + docker rm cloud-game-local + # Overlord and worker should be run separately. Local is for demo purpose + docker run --privileged -v $PWD/games:/cloud-game/games -d --name cloud-game-local -p 8000:8000 -p 9000:9000 cloud-game-local bash -c "cmd -overlordhost ws://localhost:8000/wso & cmd -overlordhost overlord" + +build-vendor: + go build -o build/cloudretro -mod=vendor ./cmd + +#run with vendor so it is faster +run-fast: build-vendor + ./build/cloudretro -overlordhost overlord & + ./build/cloudretro -overlordhost ws://localhost:8000/wso diff --git a/cmd/cmd b/cmd/cmd deleted file mode 100755 index 0eaa2397..00000000 Binary files a/cmd/cmd and /dev/null differ diff --git a/cmd/main.go b/cmd/main.go index a8ac70ab..2cf819cf 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -20,8 +20,6 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" ) -const gamePath = "games" - // Time allowed to write a message to the peer. var upgrader = websocket.Upgrader{} @@ -49,7 +47,7 @@ func initilizeOverlord() { // initializeWorker setup a worker func initializeWorker() { - worker := worker.NewHandler(*config.OverlordHost, gamePath) + worker := worker.NewHandler(*config.OverlordHost) defer func() { log.Println("Close worker") @@ -103,6 +101,7 @@ func main() { if *config.IsMonitor { go monitor() } + // There are two server mode // Overlord is coordinator. If the OvelordHost Param is `overlord`, we spawn a new host as Overlord. // else we spawn new server as normal server connecting to OverlordHost. diff --git a/config/config.go b/config/config.go index 0d57bf52..a450af55 100644 --- a/config/config.go +++ b/config/config.go @@ -7,6 +7,8 @@ import ( const defaultoverlord = "ws://localhost:9000/wso" const DefaultSTUNTURN = `[{"urls":"stun:stun-turn.webgame2d.com:3478"},{"urls":"turn:stun-turn.webgame2d.com:3478","username":"root","credential":"root"}]` +const CODEC_VP8 = "VP8" +const CODEC_H264 = "H264" var IsDebug = flag.Bool("debug", false, "Is game running in debug mode?") var OverlordHost = flag.String("overlordhost", defaultoverlord, "Specify the path for overlord. If the flag is `overlord`, the server will be run as overlord") @@ -15,7 +17,64 @@ var IsMonitor = flag.Bool("monitor", false, "Turn on monitor") var FrontendSTUNTURN = flag.String("stunturn", DefaultSTUNTURN, "Frontend STUN TURN servers") var Mode = flag.String("mode", "dev", "Environment") var IsRetro = flag.Bool("isretro", true, "Is retro") +var StunTurnTemplate = `[{"urls":"stun:stun.l.google.com:19302"},{"urls":"stun:%s:3478"},{"urls":"turn:%s:3478","username":"root","credential":"root"}]` var WSWait = 20 * time.Second var MatchWorkerRandom = false var ProdEnv = "prod" + +const NumKeys = 10 + +var Codec = CODEC_H264 + +//var Codec = CODEC_VP8 + +var FileTypeToEmulator = map[string]string{ + "gba": "gba", + "cue": "pcsx", + "zip": "mame", + "nes": "nes", + "smc": "snes", + "sfc": "snes", + "swc": "snes", + "fig": "snes", + "bs": "snes", +} + +// There is no good way to determine main width and height of the emulator. +// When game run, frame width and height can scale abnormally. +type EmulatorMeta struct { + Path string + Width int + Height int + AudioSampleRate int + Fps int +} + +var EmulatorConfig = map[string]EmulatorMeta{ + "gba": EmulatorMeta{ + Path: "libretro/cores/mgba_libretro.so", + Width: 240, + Height: 160, + }, + "pcsx": EmulatorMeta{ + Path: "libretro/cores/mednafen_psx_libretro.so", + Width: 350, + Height: 240, + }, + "nes": EmulatorMeta{ + Path: "libretro/cores/nestopia_libretro.so", + Width: 256, + Height: 240, + }, + "snes": EmulatorMeta{ + Path: "libretro/cores/mednafen_snes_libretro.so", + Width: 256, + Height: 224, + }, + "mame": EmulatorMeta{ + Path: "libretro/cores/mame2016_libretro.so", + Width: 0, + Height: 0, + }, +} diff --git a/cws/cws.go b/cws/cws.go index 47e3dc78..9b1dd50b 100644 --- a/cws/cws.go +++ b/cws/cws.go @@ -3,6 +3,7 @@ package cws import ( "encoding/json" "log" + "runtime/debug" "sync" "time" @@ -96,6 +97,7 @@ func (c *Client) Receive(id string, f func(response WSPacket) (request WSPacket) defer func() { if err := recover(); err != nil { log.Println("Recovered from err ", err) + log.Println(debug.Stack()) } }() 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-gb.png b/document/img/landing-page-gb.png index 3c2d860c..24ba8d87 100644 Binary files a/document/img/landing-page-gb.png and b/document/img/landing-page-gb.png differ diff --git a/document/img/landing-page-ps-hm.png b/document/img/landing-page-ps-hm.png new file mode 100644 index 00000000..3ccab8d4 Binary files /dev/null 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 new file mode 100644 index 00000000..3e351475 Binary files /dev/null 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 24ee020b..00000000 --- a/emulator/director.go +++ /dev/null @@ -1,165 +0,0 @@ -package emulator - -import ( - "image" - "log" - "time" - - "github.com/giongto35/cloud-game/emulator/nes" - "github.com/giongto35/cloud-game/util" - // "github.com/gordonklaus/portaudio" -) - -// 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) Meta { - // portaudio.Initialize() - // defer portaudio.Terminate() - - // audio := NewAudio() - // audio.Start() - // d.audio = audio - log.Println("Start game: ", path) - - d.gamePath = path - return Meta{ - 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 3525483a..00000000 --- a/emulator/gameview.go +++ /dev/null @@ -1,196 +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 = 8 - -// Audio consts -const ( - //SampleRate = 16000 - SampleRate = 48000 - //SampleRate = 32768 - Channels = 2 - TimeFrame = 40 - AppAudio = 1 -) - -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) UpdateInput(keysInBinary int) { -//for i := 0; i < NumKeys*2; i++ { -//b := ((keysInBinary & 1) == 1) -//view.keyPressed[i] = (view.keyPressed[i] && b) || b -//keysInBinary = keysInBinary >> 1 -//} -//} - -// 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*2; i++ { - b := ((keysInBinary & 1) == 1) - view.keyPressed[i] = (view.keyPressed[i] && b) || b - keysInBinary = keysInBinary >> 1 - } - } -} - -// 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/type.go b/emulator/type.go index 9aef2ba5..03a418c3 100644 --- a/emulator/type.go +++ b/emulator/type.go @@ -1,9 +1,11 @@ package emulator +import "github.com/giongto35/cloud-game/config" + // CloudEmulator is the interface of cloud emulator. Currently NES emulator and RetroArch implements this in codebase type CloudEmulator interface { // LoadMeta returns meta data of emulator. Refer below - LoadMeta(path string) Meta + LoadMeta(path string) config.EmulatorMeta // Start is called after LoadGame Start() // SaveGame save game state, saveExtraFunc is callback to do extra step. Ex: save to google cloud @@ -15,11 +17,3 @@ type CloudEmulator interface { // Close will be called when the game is done Close() } - -// Meta is metadata of game -type Meta struct { - AudioSampleRate int - Fps int - Width int - Height int -} 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/encoder/type.go b/encoder/type.go new file mode 100644 index 00000000..ec93a31a --- /dev/null +++ b/encoder/type.go @@ -0,0 +1,9 @@ +package encoder + +import "image" + +type Encoder interface { + GetInputChan() chan *image.RGBA + GetOutputChan() chan []byte + Stop() +} diff --git a/games/Pokemon - Fire Red Version (U) (V1.1).gba b/games/Pokemon - Fire Red Version (U) (V1.1).gba new file mode 100644 index 00000000..21cf7b0a Binary files /dev/null and b/games/Pokemon - Fire Red Version (U) (V1.1).gba differ diff --git a/go.mod b/go.mod index 4c5aadd9..4f800452 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,14 @@ module github.com/giongto35/cloud-game go 1.12 require ( - cloud.google.com/go v0.39.0 + cloud.google.com/go v0.43.0 + github.com/gen2brain/x264-go v0.0.0-20180306035800-58f586137654 + github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 // indirect github.com/gofrs/uuid v3.2.0+incompatible github.com/gorilla/websocket v1.4.0 - github.com/pion/webrtc/v2 v2.0.23 - github.com/prometheus/client_golang v0.9.3 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/pion/webrtc/v2 v2.1.2 + github.com/prometheus/client_golang v1.1.0 gopkg.in/hraban/opus.v2 v2.0.0-20180426093920-0f2e0b4fc6cd ) diff --git a/go.sum b/go.sum index ce481876..e53d1ed0 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,28 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.39.0 h1:UgQP9na6OTfp4dsAiz/eFpFA1C6tPdH5wiRdi19tuMw= -cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.43.0 h1:banaiRPAM8kUVYneOSkhgcDsLzEvL25FinuiSZaH/2w= +cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gen2brain/x264-go v0.0.0-20180306035800-58f586137654 h1:RNpogAT5Qz69YLIu6+92q5sSw61PjjhDj4upH2el5pk= +github.com/gen2brain/x264-go v0.0.0-20180306035800-58f586137654/go.mod h1:17kvfYQKi9/QHiKPeqmJW0YuDPZEgy72tSBVmweSyiE= +github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw= +github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -29,29 +33,35 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS0VQKc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gortc/turn v0.7.1/go.mod h1:3FZ+LvCZKCKu6YYgwuYPqEi3FqCtdjfSFnFqVQNwfjk= -github.com/gortc/turn v0.7.3 h1:CE72C79erbcsfa6L/QDhKztcl2kDq1UK20ImrJWDt/w= -github.com/gortc/turn v0.7.3/go.mod h1:gvguwaGAFyv5/9KrcW9MkCgHALYD+e99mSM7pSCYYho= -github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -62,131 +72,169 @@ github.com/marten-seemann/qtls v0.2.3 h1:0yWJ43C62LsZt08vuQJDK1uC1czUc3FJeCLPoNA github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pion/datachannel v1.4.3 h1:tqS6YiqqAiFCxGGhvn1K7fHEzemK9Aov025dE/isGFo= -github.com/pion/datachannel v1.4.3/go.mod h1:SpMJbuu8v+qbA94m6lWQwSdCf8JKQvgmdSHDNtcbe+w= -github.com/pion/dtls v1.3.5 h1:mBioifvh6JSE9pD4FtJh5WoizygoqkOJNJyS5Ns+y1U= -github.com/pion/dtls v1.3.5/go.mod h1:CjlPLfQdsTg3G4AEXjJp8FY5bRweBlxHrgoFrN+fQsk= -github.com/pion/ice v0.4.0 h1:BdTXHTjzdsJHGi9yMFnj9ffgr+Kg2oHVv1qk4B0mQ8A= -github.com/pion/ice v0.4.0/go.mod h1:/gw3aFmD/pBG8UM3TcEHs6HuaOEMSd/v1As3TodE7Ss= -github.com/pion/logging v0.2.1 h1:LwASkBKZ+2ysGJ+jLv1E/9H1ge0k1nTfi1X+5zirkDk= +github.com/pion/datachannel v1.4.5 h1:paz18kYAetpTdK8tlMAtDY+Ayxrv5fndZ5XPZwiZHrU= +github.com/pion/datachannel v1.4.5/go.mod h1:SpMJbuu8v+qbA94m6lWQwSdCf8JKQvgmdSHDNtcbe+w= +github.com/pion/dtls v1.5.0 h1:GtN3DJ0Fv5wC/Y04uOXT1zPeGA4C3HrsP1aAKsIBiU8= +github.com/pion/dtls v1.5.0/go.mod h1:CjlPLfQdsTg3G4AEXjJp8FY5bRweBlxHrgoFrN+fQsk= +github.com/pion/ice v0.5.7 h1:1ZhyuFkwo0Nup7cRplz72ulghFXnZSpocD5YVJP90yg= +github.com/pion/ice v0.5.7/go.mod h1:FSTDLP+ian3PtxRjervtyDP2AOqt2c6cvfebZ7dwLnI= github.com/pion/logging v0.2.1/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= -github.com/pion/mdns v0.0.2 h1:T22Gg4dSuYVYsZ21oRFh9z7twzAm27+5PEKiABbjCvM= -github.com/pion/mdns v0.0.2/go.mod h1:VrN3wefVgtfL8QgpEblPUC46ag1reLIfpqekCnKunLE= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/mdns v0.0.3 h1:DxdOYd0pgwLKiDlIIxfU0qdG5iWh1Xn6CsS9vc6cMAY= +github.com/pion/mdns v0.0.3/go.mod h1:VrN3wefVgtfL8QgpEblPUC46ag1reLIfpqekCnKunLE= github.com/pion/quic v0.1.1 h1:D951FV+TOqI9A0rTF7tHx0Loooqz+nyzjEyj8o3PuMA= github.com/pion/quic v0.1.1/go.mod h1:zEU51v7ru8Mp4AUBJvj6psrSth5eEFNnVQK5K48oV3k= -github.com/pion/rtcp v1.2.0 h1:rT2FptW5YHIern+4XlbGYnnsT26XGxurnkNLnzhtDXg= -github.com/pion/rtcp v1.2.0/go.mod h1:a5dj2d6BKIKHl43EnAOIrCczcjESrtPuMgfmL6/K6QM= -github.com/pion/rtp v1.1.2 h1:ERNugzYHW9F2ldpwoARbeFGKRoq1REe5Jxdjvm/rOx8= -github.com/pion/rtp v1.1.2/go.mod h1:/l4cvcKd0D3u9JLs2xSVI95YkfXW87a3br3nqmVtSlE= -github.com/pion/sctp v1.6.3 h1:SC4vKOjcddK8tXiTNj05a+0/GyPpCmuNfeBA/rzNFqs= +github.com/pion/rtcp v1.2.1 h1:S3yG4KpYAiSmBVqKAfgRa5JdwBNj4zK3RLUa8JYdhak= +github.com/pion/rtcp v1.2.1/go.mod h1:a5dj2d6BKIKHl43EnAOIrCczcjESrtPuMgfmL6/K6QM= +github.com/pion/rtp v1.1.3 h1:GTYSTsSLF5vH+UqShGYQEBdoYasWjTTC9UeYglnUO+o= +github.com/pion/rtp v1.1.3/go.mod h1:/l4cvcKd0D3u9JLs2xSVI95YkfXW87a3br3nqmVtSlE= github.com/pion/sctp v1.6.3/go.mod h1:cCqpLdYvgEUdl715+qbWtgT439CuQrAgy8BZTp0aEfA= -github.com/pion/sdp/v2 v2.2.0 h1:JiixCEU8g6LbSsh1Bg5SOk0TPnJrn2HBOA1yJ+mRYhI= -github.com/pion/sdp/v2 v2.2.0/go.mod h1:idSlWxhfWQDtTy9J05cgxpHBu/POwXN2VDRGYxT/EjU= -github.com/pion/srtp v1.2.4 h1:wwGKC5ewuBukkZ+i+pZ8aO33+t6z2y/XRiYtyP0Xpv0= -github.com/pion/srtp v1.2.4/go.mod h1:52qiP0g3FVMG/5NL6Ko8Vr2qirevKH+ukYbNS/4EX40= -github.com/pion/stun v0.3.0/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M= +github.com/pion/sctp v1.6.4 h1:edUNxTabSErLWOdeUUAxds8gwx5kGnFam4zL5DWpILk= +github.com/pion/sctp v1.6.4/go.mod h1:cCqpLdYvgEUdl715+qbWtgT439CuQrAgy8BZTp0aEfA= +github.com/pion/sdp/v2 v2.3.0 h1:5EhwPh1xKWYYjjvMuubHoMLy6M0B9U26Hh7q3f7vEGk= +github.com/pion/sdp/v2 v2.3.0/go.mod h1:idSlWxhfWQDtTy9J05cgxpHBu/POwXN2VDRGYxT/EjU= +github.com/pion/srtp v1.2.6 h1:mHQuAMh0P67R7/j1F260u3O+fbRWLyjKLRPZYYvODFM= +github.com/pion/srtp v1.2.6/go.mod h1:rd8imc5htjfs99XiEoOjLMEOcVjME63UHx9Ek9IGst0= github.com/pion/stun v0.3.1 h1:d09JJzOmOS8ZzIp8NppCMgrxGZpJ4Ix8qirfNYyI3BA= github.com/pion/stun v0.3.1/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M= github.com/pion/transport v0.6.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE= -github.com/pion/transport v0.7.0 h1:EsXN8TglHMlKZMo4ZGqwK6QgXBu0WYg7wfGMWIXsS+w= github.com/pion/transport v0.7.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE= -github.com/pion/transport v0.8.0 h1:YHZnWBBrBuMqkuvMFUHeAETXS+LgfwW1IsVd2K2cyW8= -github.com/pion/transport v0.8.0/go.mod h1:nAmRRnn+ArVtsoNuwktvAD+jrjSD7pA+H3iRmZwdUno= -github.com/pion/turn v1.1.4 h1:yGxcasBvge4idNjxjowePn8oW43C4v70bXroBBKLyKY= -github.com/pion/turn v1.1.4/go.mod h1:2O2GFDGO6+hJ5gsyExDhoNHtVcacPB1NOyc81gkq0WA= -github.com/pion/turnc v0.0.6 h1:FHsmwYvdJ8mhT1/ZtWWer9L0unEb7AyRgrymfWy6mEY= -github.com/pion/turnc v0.0.6/go.mod h1:4MSFv5i0v3MRkDLdo5eF9cD/xJtj1pxSphHNnxKL2W8= -github.com/pion/webrtc/v2 v2.0.23 h1:v/tDKsP4zB6Sj+Wx861fLsaNmbwWbxacciHUhetH288= -github.com/pion/webrtc/v2 v2.0.23/go.mod h1:AgremGibyNcHWIEkDbXt4ujKzKBO3tMuoYXybVRa8zo= +github.com/pion/transport v0.8.6 h1:xHQq2mxAjB+UrFs90aUBaXwlmIACfQAZnOiVAX3uqMw= +github.com/pion/transport v0.8.6/go.mod h1:nAmRRnn+ArVtsoNuwktvAD+jrjSD7pA+H3iRmZwdUno= +github.com/pion/turn v1.3.3 h1:jO8bYTgUZ7ls6BCVa5lIQhxu56UFc5afX1A50vfxFio= +github.com/pion/turn v1.3.3/go.mod h1:zGPB7YYB/HTE9MWn0Sbznz8NtyfeVeanZ834cG/MXu0= +github.com/pion/webrtc/v2 v2.1.2 h1:5c4g01cFyCa/yLNnWt8hc6HR+GyrH0sDPAMgKW/y0OU= +github.com/pion/webrtc/v2 v2.1.2/go.mod h1:6+ovIHxDUZVgCVGP3JTEAyiy9Aa45q8u7/FebVhCy4c= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5 h1:bselrhR0Or1vomJZC8ZIjWtbDmn9OYFLX5Ik9alpJpE= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190403144856-b630fd6fe46b/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b h1:lkjdUzSyJ5P1+eal9fxXX9Xg2BTfswsonKUse48C0uE= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -google.golang.org/api v0.5.0 h1:lj9SyhMzyoa38fgFF0oO2T6pjs5IzkLPKfVtxpyCRMM= -google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8 h1:x913Lq/RebkvUmRSdQ8MNb0GZKn+SR1ESfoetcQSeak= -google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610 h1:Ygq9/SRJX9+dU0WCIICM8RkWvDw03lvB77hrhJnpxfU= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -200,3 +248,5 @@ gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/h264encoder/encoder.go b/h264encoder/encoder.go new file mode 100644 index 00000000..a4d86015 --- /dev/null +++ b/h264encoder/encoder.go @@ -0,0 +1,130 @@ +package h264encoder + +import ( + "bytes" + "image" + "log" + + "github.com/gen2brain/x264-go" + "github.com/giongto35/cloud-game/encoder" +) + +const chanSize = 2 + +// H264Encoder yuvI420 image to vp8 video +type H264Encoder struct { + Output chan []byte // frame + Input chan *image.RGBA // yuvI420 + + buf *bytes.Buffer + enc *x264.Encoder + + IsRunning bool + Done bool + // C + width int + height int + fps int +} + +// NewH264Encoder create h264 encoder +func NewH264Encoder(width, height, fps int) (encoder.Encoder, error) { + v := &H264Encoder{ + Output: make(chan []byte, 5*chanSize), + Input: make(chan *image.RGBA, chanSize), + + IsRunning: true, + Done: false, + + buf: bytes.NewBuffer(make([]byte, 0)), + width: width, + height: height, + fps: fps, + } + + if err := v.init(); err != nil { + return nil, err + } + + return v, nil +} + +func (v *H264Encoder) init() error { + v.IsRunning = true + + opts := &x264.Options{ + Width: v.width, + Height: v.height, + FrameRate: v.fps, + Tune: "zerolatency", + Preset: "veryfast", + Profile: "baseline", + //LogLevel: x264.LogDebug, + } + + enc, err := x264.NewEncoder(v.buf, opts) + if err != nil { + panic(err) + } + v.enc = enc + + go v.startLooping() + return nil +} + +func (v *H264Encoder) startLooping() { + defer func() { + if r := recover(); r != nil { + log.Println("Warn: Recovered panic in encoding ", r) + } + + if v.Done == true { + // The first time we see IsRunning set to false, we release and return + v.release() + return + } + }() + + for img := range v.Input { + if v.Done == true { + // The first time we see IsRunning set to false, we release and return + v.release() + return + } + + v.enc.Encode(img) + v.Output <- v.buf.Bytes() + v.buf.Reset() + } +} + +// Release release memory and stop loop +func (v *H264Encoder) release() { + if v.IsRunning { + v.IsRunning = false + log.Println("Releasing encoder") + // TODO: Bug here, after close it will signal + close(v.Output) + err := v.enc.Close() + if err != nil { + log.Println("Failed to close H264 encoder") + } + } + // TODO: Can we merge IsRunning and Done together +} + +// GetInputChan returns input channel +func (v *H264Encoder) GetInputChan() chan *image.RGBA { + return v.Input +} + +// GetInputChan returns output channel +func (v *H264Encoder) GetOutputChan() chan []byte { + return v.Output +} + +// GetDoneChan returns done channel +func (v *H264Encoder) Stop() { + v.Done = true + close(v.Input) +} diff --git a/libretro/cores/mednafen_psx_hw_libretro.so b/libretro/cores/mednafen_psx_hw_libretro.so new file mode 100755 index 00000000..3d55ac19 Binary files /dev/null and b/libretro/cores/mednafen_psx_hw_libretro.so differ diff --git a/libretro/cores/mednafen_psx_libretro.so b/libretro/cores/mednafen_psx_libretro.so new file mode 100755 index 00000000..cc37c5d3 Binary files /dev/null and b/libretro/cores/mednafen_psx_libretro.so differ diff --git a/libretro/cores/mednafen_snes_libretro.so b/libretro/cores/mednafen_snes_libretro.so new file mode 100755 index 00000000..b6f59d4a Binary files /dev/null and b/libretro/cores/mednafen_snes_libretro.so differ diff --git a/libretro/nanoarch/naemulator.go b/libretro/nanoarch/naemulator.go index aac900d4..185d3ebf 100644 --- a/libretro/nanoarch/naemulator.go +++ b/libretro/nanoarch/naemulator.go @@ -5,7 +5,7 @@ import ( "log" "time" - emulator "github.com/giongto35/cloud-game/emulator" + "github.com/giongto35/cloud-game/config" "github.com/giongto35/cloud-game/util" ) @@ -50,34 +50,30 @@ type naEmulator struct { imageChannel chan<- *image.RGBA audioChannel chan<- float32 inputChannel <-chan int - corePath string - gamePath string - roomID string + meta config.EmulatorMeta + gamePath string + roomID string gameName string isSavingLoading bool keys []bool done chan struct{} - meta emulator.Meta } var NAEmulator *naEmulator // TODO: Load from config -var emulatorCorePath = map[string]string{ - "gba": "libretro/cores/mgba_libretro.so", - //"pcsx": "libretro/cores/mednafen_psx_libretro.so", - //"pcsx": "libretro/cores/mednafen_psx_hw_libretro.so", - "pcsx": "libretro/cores/pcsx_rearmed_libretro.so", - "arcade": "libretro/cores/fbalpha2012_neogeo_libretro.so", - "mame": "libretro/cores/mame2016_libretro.so", -} +var emulatorCorePath = map[string]string{} // NAEmulator implements CloudEmulator interface based on NanoArch(golang RetroArch) func NewNAEmulator(etype string, roomID string, imageChannel chan<- *image.RGBA, audioChannel chan<- float32, inputChannel <-chan int) *naEmulator { + meta := config.EmulatorConfig[etype] + ewidth = meta.Width + eheight = meta.Height + return &naEmulator{ - corePath: emulatorCorePath[etype], + meta: meta, imageChannel: imageChannel, audioChannel: audioChannel, inputChannel: inputChannel, @@ -97,8 +93,12 @@ func (na *naEmulator) listenInput() { // input from javascript follows bitmap. Ex: 00110101 // we decode the bitmap and send to channel for inpBitmap := range NAEmulator.inputChannel { - for k := 0; k < len(na.keys); k++ { - key := bindRetroKeys[k] + for k := 0; k < len(bindRetroKeys); k++ { + key, ok := bindRetroKeys[k] + if ok == false { + continue + } + if (inpBitmap & 1) == 1 { na.keys[key] = true } else { @@ -109,8 +109,8 @@ func (na *naEmulator) listenInput() { } } -func (na *naEmulator) LoadMeta(path string) emulator.Meta { - coreLoad(na.corePath) +func (na *naEmulator) LoadMeta(path string) config.EmulatorMeta { + coreLoad(na.meta.Path) coreLoadGame(path) na.gamePath = path @@ -119,10 +119,11 @@ func (na *naEmulator) LoadMeta(path string) emulator.Meta { func (na *naEmulator) Start() { na.playGame(na.gamePath) - ticker := time.NewTicker(time.Second / 60) + for range ticker.C { select { + // Slow response here case <-na.done: nanoarchShutdown() log.Println("Closed Director") diff --git a/libretro/nanoarch/nanoarch.go b/libretro/nanoarch/nanoarch.go index ea075e2f..cf6fec90 100644 --- a/libretro/nanoarch/nanoarch.go +++ b/libretro/nanoarch/nanoarch.go @@ -7,14 +7,11 @@ import ( "image" "image/color" "log" - "math" "os" "os/user" "reflect" "sync" "unsafe" - - "github.com/giongto35/cloud-game/emulator" ) /* @@ -60,7 +57,6 @@ var mu sync.Mutex var video struct { program uint32 vao uint32 - texID uint32 pitch uint32 pixFmt uint32 pixType uint32 @@ -71,24 +67,34 @@ var scale = 3.0 const bufSize = 1024 * 4 -const joypadNumKeys = C.RETRO_DEVICE_ID_JOYPAD_R3 +const joypadNumKeys = C.RETRO_DEVICE_ID_JOYPAD_R3 + 1 -var joy [joypadNumKeys + 1]bool +var joy [joypadNumKeys]bool var ewidth, eheight int var bindRetroKeys = map[int]int{ 0: C.RETRO_DEVICE_ID_JOYPAD_A, 1: C.RETRO_DEVICE_ID_JOYPAD_B, - 2: C.RETRO_DEVICE_ID_JOYPAD_SELECT, - 3: C.RETRO_DEVICE_ID_JOYPAD_START, - 4: C.RETRO_DEVICE_ID_JOYPAD_UP, - 5: C.RETRO_DEVICE_ID_JOYPAD_DOWN, - 6: C.RETRO_DEVICE_ID_JOYPAD_LEFT, - 7: C.RETRO_DEVICE_ID_JOYPAD_RIGHT, + 2: C.RETRO_DEVICE_ID_JOYPAD_X, + 3: C.RETRO_DEVICE_ID_JOYPAD_Y, + 4: C.RETRO_DEVICE_ID_JOYPAD_SELECT, + 5: C.RETRO_DEVICE_ID_JOYPAD_START, + 6: C.RETRO_DEVICE_ID_JOYPAD_UP, + 7: C.RETRO_DEVICE_ID_JOYPAD_DOWN, + 8: C.RETRO_DEVICE_ID_JOYPAD_LEFT, + 9: C.RETRO_DEVICE_ID_JOYPAD_RIGHT, } +const ( + // BIT_FORMAT_SHORT_5_5_5_1 has 5 bits R, 5 bits G, 5 bits B, 1 bit alpha + BIT_FORMAT_SHORT_5_5_5_1 = iota + // BIT_FORMAT_INT_8_8_8_8_REV has 8 bits R, 8 bits G, 8 bits B, 8 bit alpha + BIT_FORMAT_INT_8_8_8_8_REV + // BIT_FORMAT_SHORT_5_6_5 has 5 bits R, 6 bits G, 5 bits + BIT_FORMAT_SHORT_5_6_5 +) + type CloudEmulator interface { - SetView(view *emulator.GameView) Start(path string) SaveGame(saveExtraFunc func() error) error LoadGame() error @@ -111,58 +117,77 @@ func resizeToAspect(ratio float64, sw float64, sh float64) (dw float64, dh float return } -func videoConfigure(geom *C.struct_retro_game_geometry) (int, int) { - - nwidth, nheight := resizeToAspect(float64(geom.aspect_ratio), float64(geom.base_width), float64(geom.base_height)) - - fmt.Println("media config", nwidth, nheight, geom.base_width, geom.base_height, geom.aspect_ratio, video.bpp, scale) - - if video.texID == 0 { - fmt.Println("Failed to create the video texture") - } - - video.pitch = uint32(geom.base_width) * video.bpp - return int(math.Round(nwidth)), int(math.Round(nheight)) -} - //export coreVideoRefresh func coreVideoRefresh(data unsafe.Pointer, width C.unsigned, height C.unsigned, pitch C.size_t) { - if uint32(pitch) != video.pitch { - video.pitch = uint32(pitch) - } + bytesPerRow := int(uint32(pitch) / video.bpp) if data != nil { - NAEmulator.imageChannel <- toImageRGBA(data) + NAEmulator.imageChannel <- toImageRGBA(data, bytesPerRow, int(width), int(height)) } } // toImageRGBA convert nanoarch 2d array to image.RGBA -func toImageRGBA(data unsafe.Pointer) *image.RGBA { +func toImageRGBA(data unsafe.Pointer, bytesPerRow int, inputWidth, inputHeight int) *image.RGBA { // Convert unsafe Pointer to bytes array var bytes []byte - // TODO: Investigate this - // seems like there is a padding of slice. - // If the resolution is 240 * 160. I have to convert to 256 * 160 slice. - // If the resolution is 320 * 240. I can keep it to 320 * 240. - // I'm making assumption that the slice is packed and it has padding to fill 64 - var w = 0 - for w < ewidth { - w += 64 - } - - sh := (*reflect.SliceHeader)(unsafe.Pointer(&bytes)) - sh.Data = uintptr(data) - sh.Len = w * eheight * 2 - sh.Cap = w * eheight * 2 - - seek := 0 // Convert bytes array to image // TODO: Reduce overhead of copying to bytes array by accessing unsafe.Pointer directly + sh := (*reflect.SliceHeader)(unsafe.Pointer(&bytes)) + sh.Data = uintptr(data) + sh.Len = bytesPerRow * inputHeight * 4 + sh.Cap = bytesPerRow * inputHeight * 4 + + if video.pixFmt == BIT_FORMAT_SHORT_5_6_5 { + return to565Image(data, bytes, bytesPerRow, inputWidth, inputHeight) + } else if video.pixFmt == BIT_FORMAT_INT_8_8_8_8_REV { + return to8888Image(data, bytes, bytesPerRow, inputWidth, inputHeight) + } + return nil +} + +func to8888Image(data unsafe.Pointer, bytes []byte, bytesPerRow int, inputWidth, inputHeight int) *image.RGBA { + seek := 0 + + // scaleWidth and scaleHeight is the scale + scaleWidth := float64(ewidth) / float64(inputWidth) + scaleHeight := float64(eheight) / float64(inputHeight) + image := image.NewRGBA(image.Rect(0, 0, ewidth, eheight)) - for y := 0; y < eheight; y++ { - for x := 0; x < w; x++ { - if x < ewidth { + for y := 0; y < inputHeight; y++ { + for x := 0; x < bytesPerRow; x++ { + xx := int(float64(x) * scaleWidth) + yy := int(float64(y) * scaleHeight) + if xx < ewidth { + b8 := bytes[seek] + g8 := bytes[seek+1] + r8 := bytes[seek+2] + a8 := bytes[seek+3] + + image.Set(xx, yy, color.RGBA{byte(r8), byte(g8), byte(b8), byte(a8)}) + } + + seek += 4 + } + } + + // TODO: Resize Image + return image +} + +func to565Image(data unsafe.Pointer, bytes []byte, bytesPerRow int, inputWidth, inputHeight int) *image.RGBA { + seek := 0 + + // scaleWidth and scaleHeight is the scale + scaleWidth := float64(ewidth) / float64(inputWidth) + scaleHeight := float64(eheight) / float64(inputHeight) + + image := image.NewRGBA(image.Rect(0, 0, ewidth, eheight)) + for y := 0; y < inputHeight; y++ { + for x := 0; x < bytesPerRow; x++ { + xx := int(float64(x) * scaleWidth) + yy := int(float64(y) * scaleHeight) + if xx < ewidth { var bi int bi = (int)(bytes[seek]) + ((int)(bytes[seek+1]) << 8) b5 := bi & 0x1F @@ -173,20 +198,22 @@ func toImageRGBA(data unsafe.Pointer) *image.RGBA { g8 := (g6*255 + 31) / 63 r8 := (r5*255 + 15) / 31 - image.Set(x, y, color.RGBA{byte(r8), byte(g8), byte(b8), 255}) + image.Set(int(float64(xx)*scaleWidth), int(float64(yy)*scaleHeight), color.RGBA{byte(r8), byte(g8), byte(b8), 255}) } + seek += 2 } } + // TODO: Resize Image return image } //export coreInputPoll func coreInputPoll() { - for i := range NAEmulator.keys { - joy[i] = NAEmulator.keys[i] - } + //for i := range NAEmulator.keys { + //joy[i] = NAEmulator.keys[i] + //} } //export coreInputState @@ -195,7 +222,7 @@ func coreInputState(port C.unsigned, device C.unsigned, index C.unsigned, id C.u return 0 } - if id < 255 && joy[id] { + if id < 255 && NAEmulator.keys[id] { return 1 } return 0 @@ -214,7 +241,10 @@ func audioWrite2(buf unsafe.Pointer, frames C.size_t) C.size_t { for i := 0; i < numFrames; i += 1 { s := float32(pcm[i]) - NAEmulator.audioChannel <- s + select { + case NAEmulator.audioChannel <- s: + default: + } } return 2 * frames @@ -261,8 +291,11 @@ func coreEnvironment(cmd C.unsigned, data unsafe.Pointer) C.bool { if *format > C.RETRO_PIXEL_FORMAT_RGB565 { return false } - return true + return videoSetPixelFormat(*format) case C.RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY: + path := (**C.char)(data) + *path = C.CString("./libretro/system") + return true case C.RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY: path := (**C.char)(data) *path = C.CString(".") @@ -410,12 +443,9 @@ func coreLoadGame(filename string) { C.bridge_retro_get_system_av_info(retroGetSystemAVInfo, &avi) - ewidth, eheight = videoConfigure(&avi.geometry) // Append the library name to the window title. NAEmulator.meta.AudioSampleRate = int(avi.timing.sample_rate) NAEmulator.meta.Fps = int(avi.timing.fps) - NAEmulator.meta.Width = ewidth - NAEmulator.meta.Height = eheight } // serializeSize returns the amount of data the implementation requires to serialize @@ -455,3 +485,25 @@ func nanoarchShutdown() { func nanoarchRun() { C.bridge_retro_run(retroRun) } + +func videoSetPixelFormat(format uint32) C.bool { + switch format { + case C.RETRO_PIXEL_FORMAT_0RGB1555: + video.pixFmt = BIT_FORMAT_SHORT_5_5_5_1 + video.bpp = 2 + break + case C.RETRO_PIXEL_FORMAT_XRGB8888: + video.pixFmt = BIT_FORMAT_INT_8_8_8_8_REV + video.bpp = 4 + break + case C.RETRO_PIXEL_FORMAT_RGB565: + video.pixFmt = BIT_FORMAT_SHORT_5_6_5 + video.bpp = 2 + break + default: + log.Fatalf("Unknown pixel type %v", format) + } + + fmt.Printf("Video pixel: %v %v %v %v %v", video, format, C.RETRO_PIXEL_FORMAT_0RGB1555, C.RETRO_PIXEL_FORMAT_XRGB8888, C.RETRO_PIXEL_FORMAT_RGB565) + return true +} diff --git a/libretro/system/scph5500.bin b/libretro/system/scph5500.bin new file mode 100644 index 00000000..47428391 Binary files /dev/null and b/libretro/system/scph5500.bin differ diff --git a/libretro/system/scph5501.bin b/libretro/system/scph5501.bin new file mode 100644 index 00000000..9870cc36 Binary files /dev/null and b/libretro/system/scph5501.bin differ diff --git a/libretro/system/scph5502.bin b/libretro/system/scph5502.bin new file mode 100644 index 00000000..ee5725d5 Binary files /dev/null and b/libretro/system/scph5502.bin differ diff --git a/overlord/gamelist/games.go b/overlord/gamelist/games.go deleted file mode 100644 index 8cc714b8..00000000 --- a/overlord/gamelist/games.go +++ /dev/null @@ -1,32 +0,0 @@ -package gamelist - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" -) - -// getGameList returns list of games stored in games -// TODO: change to class -func GetGameList(gamePath string) []string { - var games []string - filepath.Walk(gamePath, func(path string, info os.FileInfo, err error) error { - if info != nil && !info.IsDir() { - // Remove prefix to obtain file names - path = path[len(gamePath)+1:] - // Add to games list - games = append(games, path) - } - return nil - }) - - return games -} - -// GetEncodedGameList returns game list in encoded wspacket format -func GetEncodedGameList(gamePath string) string { - encodedList, _ := json.Marshal(GetGameList(gamePath)) - fmt.Println(encodedList) - return string(encodedList) -} diff --git a/overlord/handlers.go b/overlord/handlers.go index 12542a0e..95da71b2 100644 --- a/overlord/handlers.go +++ b/overlord/handlers.go @@ -13,8 +13,8 @@ import ( "github.com/giongto35/cloud-game/config" "github.com/giongto35/cloud-game/cws" - "github.com/giongto35/cloud-game/overlord/gamelist" "github.com/giongto35/cloud-game/util" + "github.com/giongto35/cloud-game/util/gamelist" "github.com/gofrs/uuid" "github.com/gorilla/websocket" ) @@ -25,6 +25,7 @@ const ( ) type Server struct { + // roomToServer map roomID to workerID roomToServer map[string]string // workerClients are the map serverID to worker Client workerClients map[string]*WorkerClient @@ -91,7 +92,7 @@ func (o *Server) WSO(w http.ResponseWriter, r *http.Request) { return } } - client := NewWorkerClient(c, serverID, address) + client := NewWorkerClient(c, serverID, address, fmt.Sprintf(config.StunTurnTemplate, address, address)) o.workerClients[serverID] = client defer o.cleanConnection(client, serverID) @@ -160,8 +161,8 @@ func (o *Server) WS(w http.ResponseWriter, r *http.Request) { wssession.RouteBrowser() wssession.BrowserClient.Send(cws.WSPacket{ - ID: "gamelist", - Data: gamelist.GetEncodedGameList(gamePath), + ID: "init", + Data: createInitPackage(o.workerClients[serverID].StunTurnServer), }, nil) // If peerconnection is done (client.Done is signalled), we close peerconnection @@ -272,6 +273,8 @@ func getLatencyMapFromBrowser(workerClients map[string]*WorkerClient, client *Br return latencyMap } +// cleanConnection is called when a worker is disconnected +// connection from worker (client) to server is also closed func (o *Server) cleanConnection(client *WorkerClient, serverID string) { log.Println("Unregister server from overlord") // Remove serverID from servers @@ -285,3 +288,16 @@ func (o *Server) cleanConnection(client *WorkerClient, serverID string) { client.Close() } + +// createInitPackage returns serverhost + game list in encoded wspacket format +// This package will be sent to initialize +func createInitPackage(stunturn string) string { + var gameName []string + for _, game := range gamelist.GameList { + gameName = append(gameName, game.Name) + } + + initPackage := append([]string{stunturn}, gameName...) + encodedList, _ := json.Marshal(initPackage) + return string(encodedList) +} diff --git a/overlord/worker.go b/overlord/worker.go index 4d474c90..249faca8 100644 --- a/overlord/worker.go +++ b/overlord/worker.go @@ -9,9 +9,10 @@ import ( type WorkerClient struct { *cws.Client - ServerID string - Address string - IsAvailable bool + ServerID string + Address string + StunTurnServer string + IsAvailable bool } // RouteWorker are all routes server received from worker @@ -42,11 +43,12 @@ func (o *Server) RouteWorker(workerClient *WorkerClient) { } // NewWorkerClient returns a client connecting to worker. This connection exchanges information between workers and server -func NewWorkerClient(c *websocket.Conn, serverID string, address string) *WorkerClient { +func NewWorkerClient(c *websocket.Conn, serverID string, address string, stunturn string) *WorkerClient { return &WorkerClient{ - Client: cws.NewClient(c), - ServerID: serverID, - Address: address, - IsAvailable: true, + Client: cws.NewClient(c), + ServerID: serverID, + Address: address, + StunTurnServer: stunturn, + IsAvailable: true, } } diff --git a/run_local.sh b/run_local.sh deleted file mode 100755 index 4211633c..00000000 --- a/run_local.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -go build -o klog ./cmd -# Run coordinator first -./klog -overlordhost overlord & -# Wait till overlord finish initialized -# Run a worker connecting to overlord -./klog -overlordhost ws://localhost:8000/wso -# NOTE: Overlord and worker should be run separately. Local is for demo purpose diff --git a/run_local_docker.sh b/run_local_docker.sh deleted file mode 100755 index d2629538..00000000 --- a/run_local_docker.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -docker build . -t cloud-game-local -docker stop cloud-game-local -docker rm cloud-game-local -# Overlord and worker should be run separately. Local is for demo purpose -docker run --privileged -v $PWD/games:/cloud-game/games -d --name cloud-game-local -p 8000:8000 -p 9000:9000 cloud-game-local bash -c "cmd -overlordhost ws://localhost:8000/wso & cmd -overlordhost overlord" diff --git a/static/css/main.css b/static/css/main.css index 76b89660..863fde0a 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -209,13 +209,13 @@ body { } #btn-load { - top: 30px; + top: 20px; left: 435px; background-color: #f5a54b; } #btn-save { - top: 70px; + top: 60px; left: 435px; background-color: #b844ee; } @@ -236,23 +236,20 @@ body { width: 55px; } - #btn-select { - top: 0; - left: 50%; - transform: translateX(-50%); - -webkit-transform: translateX(-50%); - -moz-transform: translateX(-50%); - background-color: #6fa8eb; + background-color: #F7EDE3; + top: 100px; + left: 425px; + border-radius: 25px 0 0 25px; + width: 55px; } #btn-start { - top: 50%; - left: 0; - transform: translateY(-50%); - -webkit-transform: translateY(-50%); - -moz-transform: translateY(-50%); - background-color: #43be85; + background-color: #F7EDE3; + top: 100px; + left: 490px; + border-radius: 0px 25px 25px 0px; + width: 55px; } #btn-a { @@ -274,6 +271,25 @@ body { background-color: #d4b95e; } +#btn-x { + top: 0; + left: 50%; + transform: translateX(-50%); + -webkit-transform: translateX(-50%); + -moz-transform: translateX(-50%); + background-color: #6fa8eb; +} + + +#btn-y { + top: 50%; + left: 0; + transform: translateY(-50%); + -webkit-transform: translateY(-50%); + -moz-transform: translateY(-50%); + background-color: #43be85; +} + #lights-holder { display: block; @@ -392,7 +408,7 @@ body { height: 240px; /* background-color: gray; */ - background-image: url('/static/img/screen_background1.gif'); + background-image: url('/static/img/screen_background5.png'); background-size: cover; } @@ -402,7 +418,7 @@ body { width: 100%; height: 36px; - background-color: lightgreen; + background-color: #FFCF9E; opacity: 0.75; top: 50%; @@ -463,6 +479,7 @@ body { text-overflow: ellipsis; white-space: pre; text-align: center; + color: white; } .menu-item div .pick { diff --git a/static/game.html b/static/game.html index d5d2cf26..5ebe9741 100644 --- a/static/game.html +++ b/static/game.html @@ -33,8 +33,8 @@ There is still audio because current audio flow is not from media but it is manually encoded (technical webRTC challenge). Later, when we can integrate audio to media, we can face the issue with mute again . https://developers.google.com/web/updates/2017/09/autoplay-policy-changes --> - - + +