mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-07-20 16:54:25 +00:00
Remove old nes emulator (#78)
* Remove old NES emulator, using only libretro now * Update landing page
This commit is contained in:
parent
e0b9fb413e
commit
a41b54651f
28 changed files with 12 additions and 4527 deletions
5
README.md
vendored
5
README.md
vendored
|
|
@ -17,7 +17,7 @@ You can try hosting your own service following the instruction in the next sessi
|
|||
Screenshot | Screenshot
|
||||
:-------------------------:|:-------------------------:
|
||||
|
|
||||
|
|
||||
|
|
||||
|
||||
## 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
|
||||
|
|
|
|||
|
|
@ -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
BIN
document/img/landing-page-front.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 995 KiB |
BIN
document/img/landing-page-ps-hm.png
vendored
BIN
document/img/landing-page-ps-hm.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.2 MiB |
BIN
document/img/landing-page-ps-x4.png
vendored
BIN
document/img/landing-page-ps-x4.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.1 MiB |
|
|
@ -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)
|
||||
}
|
||||
153
emulator/font.go
153
emulator/font.go
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
117
emulator/util.go
117
emulator/util.go
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue