Remove old nes emulator (#78)

* Remove old NES emulator, using only libretro now

* Update landing page
This commit is contained in:
giongto35 2019-09-04 02:21:46 +08:00 committed by GitHub
parent e0b9fb413e
commit a41b54651f
28 changed files with 12 additions and 4527 deletions

5
README.md vendored
View file

@ -17,7 +17,7 @@ You can try hosting your own service following the instruction in the next sessi
Screenshot | Screenshot
:-------------------------:|:-------------------------:
![screenshot](document/img/landing-page-ps-hm.png)|![screenshot](document/img/landing-page-ps-x4.png)
![screenshot](document/img/landing-page-gb.png)|![screenshot](document/img/landing-page.gif)
![screenshot](document/img/landing-page-gb.png)|![screenshot](document/img/landing-page-front.png)
## Feature
1. Cloud gaming: Game logic is hosted on a remote server. User doesn't have to install or setup anything. Images and audio are streamed to user in the most optimal way.
@ -63,8 +63,9 @@ And run
* *Pion* Webrtc team for the incredible Golang Webrtc library and their supports https://github.com/pion/webrtc/.
* *Nanoarch* Golang RetroArch https://github.com/libretro/go-nanoarch and https://retroarch.com.
* *fogleman* for the awesome NES emulator https://github.com/fogleman/nes.
* *gen2brain* for the h264 go encoder https://github.com/gen2brain/x264-go
* *poi5305* for the video encoding https://github.com/poi5305/go-yuv2webRTC.
* *fogleman* for the NES emulator https://github.com/fogleman/nes.
* And last but not least, my longtime friend Tri as the co-author.
## Contributor

View file

@ -23,6 +23,8 @@ var WSWait = 20 * time.Second
var MatchWorkerRandom = false
var ProdEnv = "prod"
const NumKeys = 10
var Codec = CODEC_H264
//var Codec = CODEC_VP8
@ -61,8 +63,9 @@ var EmulatorConfig = map[string]EmulatorMeta{
Height: 240,
},
"nes": EmulatorMeta{
Width: 0,
Height: 0,
Path: "libretro/cores/nestopia_libretro.so",
Width: 256,
Height: 240,
},
"snes": EmulatorMeta{
Path: "libretro/cores/mednafen_snes_libretro.so",

BIN
document/img/landing-page-front.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 995 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Before After
Before After

View file

@ -1,159 +0,0 @@
package emulator
import (
"image"
"log"
"time"
"github.com/giongto35/cloud-game/config"
"github.com/giongto35/cloud-game/emulator/nes"
"github.com/giongto35/cloud-game/util"
)
// Director is the nes emulator
type Director struct {
// audio *Audio
view *GameView
timestamp float64
imageChannel chan<- *image.RGBA
audioChannel chan<- float32
inputChannel <-chan int
Done chan struct{}
gamePath string
roomID string
}
const fps = 300
// NewDirector returns a new director
func NewDirector(roomID string, imageChannel chan<- *image.RGBA, audioChannel chan<- float32, inputChannel <-chan int) CloudEmulator {
// TODO: return image channel from where it write
director := Director{}
director.Done = make(chan struct{}, 1)
director.audioChannel = audioChannel
director.imageChannel = imageChannel
director.inputChannel = inputChannel
director.roomID = roomID
return &director
}
// SetView ...
func (d *Director) SetView(view *GameView) {
if d.view != nil {
d.view.Exit()
}
d.view = view
if d.view != nil {
d.view.Enter()
}
d.timestamp = float64(time.Now().Nanosecond()) / float64(time.Second)
}
//func (d *Director) UpdateInput(input int) {
//d.view.UpdateInput(input)
//}
func (d *Director) LoadMeta(path string) config.EmulatorMeta {
log.Println("Start game: ", path)
d.gamePath = path
return config.EmulatorMeta{
AudioSampleRate: 48000,
Fps: 300,
Width: 256,
Height: 240,
}
}
// Start ...
func (d *Director) Start() {
// portaudio.Initialize()
// defer portaudio.Terminate()
// audio := NewAudio()
// audio.Start()
// d.audio = audio
log.Println("Start game: ", d.gamePath)
d.playGame(d.gamePath)
d.run()
}
// step ...
func (d *Director) step() {
timestamp := float64(time.Now().Nanosecond()) / float64(time.Second)
dt := timestamp - d.timestamp
d.timestamp = timestamp
if d.view != nil {
d.view.Update(timestamp, dt)
}
}
// run ...
func (d *Director) run() {
c := time.Tick(time.Second / fps)
L:
for range c {
// for {
// quit game
// TODO: How to not using select because it will slow down
select {
// if there is event from close channel => the game is ended
//case input := <-d.inputChannel:
//d.UpdateInput(input)
case <-d.Done:
log.Println("Closing Director")
break L
default:
}
d.step()
}
d.SetView(nil)
log.Println("Closed Director")
}
// PalyGame starts a game given a rom path
func (d *Director) playGame(path string) {
console, err := nes.NewConsole(path)
if err != nil {
log.Println("Err: Cannot load path, Got:", err)
}
// Set GameView as current view
d.SetView(NewGameView(console, path, d.roomID, d.imageChannel, d.audioChannel, d.inputChannel))
}
// SaveGame creates save events and doing extra step for load
func (d *Director) SaveGame(saveExtraFunc func() error) error {
if d.roomID != "" {
d.view.Save(saveExtraFunc)
return nil
}
return nil
}
// LoadGame creates load events and doing extra step for load
func (d *Director) LoadGame() error {
if d.roomID != "" {
d.view.Load()
return nil
}
return nil
}
// GetHashPath return the full path to hash file
func (d *Director) GetHashPath() string {
return util.GetSavePath(d.roomID)
}
func (d *Director) GetSampleRate() uint {
return SampleRate
}
// Close
func (d *Director) Close() {
close(d.Done)
}

View file

@ -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,
}

View file

@ -1,206 +0,0 @@
// credit to https://github.com/fogleman/nes
package emulator
import (
"image"
"github.com/giongto35/cloud-game/emulator/nes"
"github.com/giongto35/cloud-game/util"
)
// List key pressed
const (
a1 = iota
b1
select1
start1
up1
down1
left1
right1
a2
b2
select2
start2
up2
down2
left2
right2
)
const NumKeys = 10
// Audio consts
const (
//SampleRate = 16000
SampleRate = 48000
//SampleRate = 32768
Channels = 2
TimeFrame = 40
AppAudio = 1
)
var bindNESKeys = map[int]int{
0: nes.ButtonA,
1: nes.ButtonB,
2: -1,
3: -1,
4: nes.ButtonSelect,
5: nes.ButtonStart,
6: nes.ButtonUp,
7: nes.ButtonDown,
8: nes.ButtonLeft,
9: nes.ButtonRight,
}
type GameView struct {
console *nes.Console
title string
// saveFile is the filename gameview save to
saveFile string
// equivalent to the list key pressed const above
keyPressed [NumKeys * 2]bool
savingJob *job
loadingJob *job
imageChannel chan<- *image.RGBA
audioChannel chan<- float32
inputChannel <-chan int
}
type job struct {
path string
extraFunc func() error
}
func NewGameView(console *nes.Console, title, saveFile string, imageChannel chan<- *image.RGBA, audioChannel chan<- float32, inputChannel <-chan int) *GameView {
gameview := &GameView{
console: console,
title: title,
saveFile: saveFile,
keyPressed: [NumKeys * 2]bool{false},
imageChannel: imageChannel,
audioChannel: audioChannel,
inputChannel: inputChannel,
}
go gameview.ListenToInputChannel()
return gameview
}
// ListenToInputChannel listen from input channel streamm, which is exposed to WebRTC session
func (view *GameView) ListenToInputChannel() {
for keysInBinary := range view.inputChannel {
for i := 0; i < NumKeys; i++ {
key, ok := bindNESKeys[i]
isPressed := ((keysInBinary & 1) == 1)
keysInBinary = keysInBinary >> 1
if !ok || key == -1 {
continue
}
view.keyPressed[key] = (view.keyPressed[key] && isPressed) || isPressed
}
}
}
// Enter enter the game view.
func (view *GameView) Enter() {
view.console.SetAudioSampleRate(SampleRate)
view.console.SetAudioChannel(view.audioChannel)
// load state if the saveFile file existed in the server (Join the old room)
if err := view.console.LoadState(util.GetSavePath(view.saveFile)); err == nil {
return
} else {
view.console.Reset()
}
// load sram
cartridge := view.console.Cartridge
if cartridge.Battery != 0 {
if sram, err := readSRAM(util.GetSRAMPath(view.saveFile)); err == nil {
cartridge.SRAM = sram
}
}
}
// Exit ...
func (view *GameView) Exit() {
view.console.SetAudioChannel(nil)
view.console.SetAudioSampleRate(0)
// save sram
cartridge := view.console.Cartridge
if cartridge.Battery != 0 {
writeSRAM(util.GetSRAMPath(view.saveFile), cartridge.SRAM)
}
// close producer
close(view.imageChannel)
close(view.audioChannel)
}
// Update is called for every period of time, dt is the elapsed time from the last frame
func (view *GameView) Update(t, dt float64) {
if dt > 1 {
dt = 0
}
console := view.console
view.updateControllers()
view.UpdateEvents()
console.StepSeconds(dt)
// fps to set frame
view.imageChannel <- console.Buffer()
}
func (view *GameView) Save(extraSaveFunc func() error) {
// put saving event to queue, process in updateEvent
view.savingJob = &job{
path: util.GetSavePath(view.saveFile),
extraFunc: extraSaveFunc,
}
}
func (view *GameView) Load() {
// put saving event to queue, process in updateEvent
view.loadingJob = &job{
path: util.GetSavePath(view.saveFile),
extraFunc: nil,
}
}
func (view *GameView) UpdateEvents() {
// If there is saving event, save and discard the save event
if view.savingJob != nil {
view.console.SaveState(view.savingJob.path)
// Run extra function (online saving for example)
go view.savingJob.extraFunc()
view.savingJob = nil
}
// If there is loading event, save and discard the load event
if view.loadingJob != nil {
view.console.LoadState(view.loadingJob.path)
// Run extra function (online saving for example)
if view.loadingJob.extraFunc != nil {
go view.loadingJob.extraFunc()
}
view.loadingJob = nil
}
}
func (view *GameView) updateControllers() {
// Divide keyPressed to player 1 and player 2
// First 8 keys are player 1
var player1Keys [8]bool
copy(player1Keys[:], view.keyPressed[:8])
var player2Keys [8]bool
copy(player2Keys[:], view.keyPressed[8:])
view.console.Controller1.SetButtons(player1Keys)
view.console.Controller2.SetButtons(player2Keys)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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) {
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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))
}
}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -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))
}
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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}
}
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -12,8 +12,6 @@ import (
"reflect"
"sync"
"unsafe"
"github.com/giongto35/cloud-game/emulator"
)
/*
@ -97,7 +95,6 @@ const (
)
type CloudEmulator interface {
SetView(view *emulator.GameView)
Start(path string)
SaveGame(saveExtraFunc func() error) error
LoadGame() error

View file

@ -116,9 +116,9 @@ func NewRoom(roomID string, gameName string, onlineStorage *storage.Client) *Roo
// create director
func getEmulator(emuName string, roomID string, imageChannel chan<- *image.RGBA, audioChannel chan<- float32, inputChannel <-chan int) emulator.CloudEmulator {
if emuName == "nes" {
return emulator.NewDirector(roomID, imageChannel, audioChannel, inputChannel)
}
//if emuName == "nes" {
//return emulator.NewDirector(roomID, imageChannel, audioChannel, inputChannel)
//}
nanoarch.Init(emuName, roomID, imageChannel, audioChannel, inputChannel)
return nanoarch.NAEmulator
@ -170,7 +170,7 @@ func (r *Room) startWebRTCSession(peerconnection *webrtc.WebRTC, playerIndex int
// the first 10 bits belong to player 1
// the next 10 belongs to player 2 ...
// We standardize and put it to inputChannel (20 bits)
input = input << ((uint(playerIndex) - 1) * emulator.NumKeys)
input = input << ((uint(playerIndex) - 1) * config.NumKeys)
select {
case r.inputChannel <- input:
default: