Merge branch 'master' into gh-pages

This commit is contained in:
giongto35 2019-09-05 00:07:35 +08:00
commit 2533f6c3bf
1923 changed files with 613383 additions and 4950 deletions

12
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: giongto35
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

6
.gitignore vendored
View file

@ -51,3 +51,9 @@ key.json
run_prod_docker.sh
prod/
turnserver.conf
### Ignore Goland files
.idea/
### Ignore build artifact directory
./build

29
Makefile vendored Normal file
View file

@ -0,0 +1,29 @@
dep:
go mod download
go mod vendor
go mod tidy
build: dep
go build -o build/cloudretro ./cmd
run: build
# Run coordinator first
./build/cloudretro -overlordhost overlord &
# Wait till overlord finish initialized
# Run a worker connecting to overload
./build/cloudretro -overlordhost ws://localhost:8000/wso
run-docker:
docker build . -t cloud-game-local
docker stop cloud-game-local
docker rm cloud-game-local
# Overlord and worker should be run separately. Local is for demo purpose
docker run --privileged -v $PWD/games:/cloud-game/games -d --name cloud-game-local -p 8000:8000 -p 9000:9000 cloud-game-local bash -c "cmd -overlordhost ws://localhost:8000/wso & cmd -overlordhost overlord"
build-vendor:
go build -o build/cloudretro -mod=vendor ./cmd
#run with vendor so it is faster
run-fast: build-vendor
./build/cloudretro -overlordhost overlord &
./build/cloudretro -overlordhost ws://localhost:8000/wso

BIN
cmd/cmd vendored

Binary file not shown.

View file

@ -20,8 +20,6 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp"
)
const gamePath = "games"
// Time allowed to write a message to the peer.
var upgrader = websocket.Upgrader{}
@ -49,7 +47,7 @@ func initilizeOverlord() {
// initializeWorker setup a worker
func initializeWorker() {
worker := worker.NewHandler(*config.OverlordHost, gamePath)
worker := worker.NewHandler(*config.OverlordHost)
defer func() {
log.Println("Close worker")
@ -103,6 +101,7 @@ func main() {
if *config.IsMonitor {
go monitor()
}
// There are two server mode
// Overlord is coordinator. If the OvelordHost Param is `overlord`, we spawn a new host as Overlord.
// else we spawn new server as normal server connecting to OverlordHost.

View file

@ -7,6 +7,8 @@ import (
const defaultoverlord = "ws://localhost:9000/wso"
const DefaultSTUNTURN = `[{"urls":"stun:stun-turn.webgame2d.com:3478"},{"urls":"turn:stun-turn.webgame2d.com:3478","username":"root","credential":"root"}]`
const CODEC_VP8 = "VP8"
const CODEC_H264 = "H264"
var IsDebug = flag.Bool("debug", false, "Is game running in debug mode?")
var OverlordHost = flag.String("overlordhost", defaultoverlord, "Specify the path for overlord. If the flag is `overlord`, the server will be run as overlord")
@ -15,7 +17,64 @@ var IsMonitor = flag.Bool("monitor", false, "Turn on monitor")
var FrontendSTUNTURN = flag.String("stunturn", DefaultSTUNTURN, "Frontend STUN TURN servers")
var Mode = flag.String("mode", "dev", "Environment")
var IsRetro = flag.Bool("isretro", true, "Is retro")
var StunTurnTemplate = `[{"urls":"stun:stun.l.google.com:19302"},{"urls":"stun:%s:3478"},{"urls":"turn:%s:3478","username":"root","credential":"root"}]`
var WSWait = 20 * time.Second
var MatchWorkerRandom = false
var ProdEnv = "prod"
const NumKeys = 10
var Codec = CODEC_H264
//var Codec = CODEC_VP8
var FileTypeToEmulator = map[string]string{
"gba": "gba",
"cue": "pcsx",
"zip": "mame",
"nes": "nes",
"smc": "snes",
"sfc": "snes",
"swc": "snes",
"fig": "snes",
"bs": "snes",
}
// There is no good way to determine main width and height of the emulator.
// When game run, frame width and height can scale abnormally.
type EmulatorMeta struct {
Path string
Width int
Height int
AudioSampleRate int
Fps int
}
var EmulatorConfig = map[string]EmulatorMeta{
"gba": EmulatorMeta{
Path: "libretro/cores/mgba_libretro.so",
Width: 240,
Height: 160,
},
"pcsx": EmulatorMeta{
Path: "libretro/cores/mednafen_psx_libretro.so",
Width: 350,
Height: 240,
},
"nes": EmulatorMeta{
Path: "libretro/cores/nestopia_libretro.so",
Width: 256,
Height: 240,
},
"snes": EmulatorMeta{
Path: "libretro/cores/mednafen_snes_libretro.so",
Width: 256,
Height: 224,
},
"mame": EmulatorMeta{
Path: "libretro/cores/mame2016_libretro.so",
Width: 0,
Height: 0,
},
}

View file

@ -3,6 +3,7 @@ package cws
import (
"encoding/json"
"log"
"runtime/debug"
"sync"
"time"
@ -96,6 +97,7 @@ func (c *Client) Receive(id string, f func(response WSPacket) (request WSPacket)
defer func() {
if err := recover(); err != nil {
log.Println("Recovered from err ", err)
log.Println(debug.Stack())
}
}()

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 995 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 MiB

After

Width:  |  Height:  |  Size: 977 KiB

Before After
Before After

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View file

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

View file

@ -1,153 +0,0 @@
// credit to https://github.com/fogleman/nes
package emulator
import (
"bytes"
"image"
"image/color"
"image/draw"
"image/png"
"log"
"strings"
)
var fontMask image.Image
func init() {
im, err := png.Decode(bytes.NewBuffer(fontData))
if err != nil {
log.Fatalln(err)
}
size := im.Bounds().Size()
mask := image.NewRGBA(im.Bounds())
for y := 0; y < size.Y; y++ {
for x := 0; x < size.X; x++ {
r, _, _, _ := im.At(x, y).RGBA()
if r > 0 {
mask.Set(x, y, color.Opaque)
}
}
}
fontMask = mask
}
func WordWrap(text string, maxLength int) []string {
var rows []string
words := strings.Fields(text)
if len(words) == 0 {
return rows
}
row := words[0]
for _, word := range words[1:] {
newRow := row + " " + word
if len(newRow) <= maxLength {
row = newRow
} else {
rows = append(rows, row)
row = word
}
}
rows = append(rows, row)
return rows
}
func DrawCenteredText(dst draw.Image, text string, dx, dy int, c color.Color) {
rows := WordWrap(text, 15)
for i, row := range rows {
x := 128 - len(row)*8
y := 120 - len(rows)*12 + i*24
DrawText(dst, x+dx, y+dy, row, c)
}
}
func DrawCharacter(dst draw.Image, x, y int, ch byte, c color.Color) {
if ch < 32 || ch > 128 {
return
}
cx := int((ch-32)%16) * 16
cy := int((ch-32)/16) * 16
r := image.Rect(x, y, x+16, y+16)
src := &image.Uniform{c}
sp := image.Pt(cx, cy)
draw.DrawMask(dst, r, src, sp, fontMask, sp, draw.Over)
}
func DrawText(dst draw.Image, x, y int, text string, c color.Color) {
for i := range text {
DrawCharacter(dst, x, y, text[i], c)
x += 16
}
}
var fontData = []byte{
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x60,
0x02, 0x03, 0x00, 0x00, 0x00, 0x8F, 0x9F, 0x44, 0x1B, 0x00, 0x00, 0x00,
0x06, 0x50, 0x4C, 0x54, 0x45, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFE, 0x6A,
0x62, 0xC8, 0x2E, 0x00, 0x00, 0x00, 0x01, 0x62, 0x4B, 0x47, 0x44, 0x00,
0x88, 0x05, 0x1D, 0x48, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
0x00, 0x00, 0x0E, 0xC4, 0x00, 0x00, 0x0E, 0xC4, 0x01, 0x95, 0x2B, 0x0E,
0x1B, 0x00, 0x00, 0x02, 0xD2, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0xC5,
0x98, 0x49, 0x62, 0x23, 0x21, 0x0C, 0x45, 0xB5, 0xE1, 0x7E, 0xDA, 0xFC,
0xFB, 0x5F, 0xA5, 0x4B, 0xE8, 0x4B, 0x88, 0x72, 0x2A, 0x1D, 0x07, 0xB9,
0x9B, 0xC4, 0x2E, 0x8C, 0xE1, 0x19, 0x8D, 0x0C, 0x22, 0x56, 0x86, 0x5E,
0xFF, 0x98, 0x2F, 0x81, 0x78, 0x81, 0xB5, 0xC5, 0x77, 0xF2, 0x50, 0xE2,
0x9B, 0x63, 0x00, 0x90, 0x80, 0x71, 0xD5, 0xAD, 0xE6, 0x9F, 0xC5, 0x81,
0x90, 0xD9, 0x92, 0xF0, 0x2C, 0xF9, 0x6B, 0x4D, 0x00, 0x00, 0xEA, 0xFD,
0x31, 0x45, 0x40, 0x4C, 0xD2, 0xDE, 0x50, 0xC4, 0xBB, 0x4B, 0xD0, 0x02,
0xF0, 0xCF, 0x26, 0x82, 0xBA, 0xE2, 0xE0, 0xDF, 0x06, 0x00, 0x5E, 0xD8,
0x1D, 0xBB, 0x04, 0xE7, 0x00, 0x4E, 0xC5, 0x44, 0x98, 0xAD, 0xF6, 0x3F,
0x20, 0xF2, 0x17, 0x11, 0x96, 0x79, 0xBB, 0x00, 0xAE, 0x48, 0xA5, 0x11,
0xCB, 0x34, 0xAB, 0x19, 0xB1, 0x2C, 0x08, 0x69, 0x03, 0x24, 0x87, 0xD3,
0xB7, 0xEE, 0x48, 0xEA, 0xEE, 0xCA, 0x05, 0xA0, 0x7D, 0x80, 0xE7, 0x68,
0x79, 0x0C, 0x9F, 0x9F, 0xB4, 0xFE, 0x10, 0x90, 0x8E, 0x33, 0x03, 0xC9,
0x2A, 0xEE, 0x28, 0x70, 0xD7, 0x66, 0xBB, 0xBC, 0x2A, 0x5C, 0xDC, 0xE9,
0x8E, 0x01, 0x23, 0x03, 0x67, 0xD0, 0x89, 0x90, 0x41, 0x54, 0x82, 0x7C,
0x99, 0x70, 0x33, 0x23, 0x1A, 0x00, 0x48, 0x3B, 0x79, 0x0E, 0x63, 0x58,
0xB3, 0x1F, 0xE4, 0x0E, 0x70, 0xAF, 0x66, 0xA5, 0x17, 0x10, 0x8A, 0xA3,
0x00, 0xD1, 0x29, 0x95, 0x7B, 0xF3, 0xA3, 0x45, 0xEB, 0x02, 0xB8, 0x4D,
0xE7, 0x40, 0x95, 0x32, 0x6D, 0x57, 0xEE, 0x4B, 0xE0, 0xC6, 0xE0, 0x06,
0xC0, 0x15, 0x36, 0x9E, 0xB2, 0x62, 0x36, 0x37, 0xC5, 0x2D, 0xF3, 0x2E,
0x33, 0xA2, 0x26, 0x97, 0x63, 0xC0, 0xB4, 0x9E, 0xA5, 0x73, 0xBA, 0xED,
0x1C, 0x12, 0xEE, 0x6B, 0x7A, 0x02, 0x13, 0x6D, 0x00, 0x8A, 0x2B, 0x3B,
0xE4, 0x18, 0xF0, 0x46, 0x79, 0x23, 0xEA, 0xDF, 0x00, 0xF8, 0x54, 0xB5,
0x84, 0xB0, 0xFA, 0x12, 0xE7, 0x15, 0xF5, 0xF4, 0x36, 0xE5, 0xF4, 0x7A,
0x09, 0xB2, 0x39, 0xF6, 0x18, 0x80, 0x4C, 0xE2, 0xF6, 0xF4, 0x5A, 0xD5,
0xD7, 0x0A, 0xB6, 0xE8, 0x65, 0xBF, 0x67, 0x7F, 0x69, 0xEA, 0x53, 0x00,
0xA7, 0xC6, 0x0D, 0x8E, 0xAC, 0xE7, 0xEE, 0xEA, 0x13, 0x90, 0x71, 0x67,
0x22, 0xB6, 0x03, 0x80, 0x1C, 0x00, 0x44, 0x52, 0xA1, 0x32, 0x03, 0xB0,
0x12, 0x8A, 0x82, 0x0A, 0x6E, 0x02, 0x00, 0x0F, 0x22, 0x70, 0x9A, 0x54,
0x76, 0xB0, 0x30, 0x4C, 0x9C, 0x36, 0x40, 0xE9, 0xF8, 0x6A, 0xC6, 0x1B,
0x60, 0xA0, 0xA4, 0xFB, 0x2E, 0xC0, 0x80, 0xA6, 0xE2, 0x36, 0x57, 0xF6,
0x3D, 0x4F, 0xBA, 0xF2, 0xA0, 0x52, 0x07, 0x96, 0xD9, 0x19, 0x0B, 0x87,
0x80, 0xFF, 0x5F, 0xC0, 0xF4, 0x05, 0xA6, 0xAE, 0x3A, 0xC5, 0x4D, 0xB9,
0x53, 0x0C, 0x6E, 0x7F, 0x7D, 0x59, 0xEF, 0x02, 0x6C, 0x9D, 0xDD, 0x57,
0xB7, 0xB6, 0x19, 0xB8, 0x1E, 0xC4, 0xB9, 0xC4, 0xF8, 0xC7, 0x8F, 0x00,
0x64, 0x07, 0x0C, 0x37, 0xD5, 0x04, 0x8C, 0x72, 0x00, 0xE1, 0x36, 0xA0,
0x1B, 0xC0, 0x85, 0x25, 0x00, 0x1A, 0x89, 0x35, 0x96, 0xFE, 0x75, 0x08,
0x8B, 0x3E, 0x4D, 0x00, 0x33, 0xE1, 0x0C, 0xE9, 0x75, 0xC6, 0x0B, 0x37,
0xCD, 0xE5, 0xCE, 0x25, 0xE0, 0x10, 0xEC, 0x2B, 0xCC, 0x39, 0x80, 0x5B,
0x29, 0x0B, 0xD5, 0x1A, 0xBA, 0x73, 0xB1, 0x71, 0x41, 0x34, 0x96, 0xFF,
0x3C, 0x04, 0xB5, 0x03, 0x2E, 0x09, 0x36, 0x25, 0xFA, 0xF3, 0x96, 0xD2,
0xE8, 0xCA, 0x57, 0x89, 0x67, 0x13, 0xE0, 0x30, 0x92, 0xD1, 0x00, 0xC0,
0xC3, 0x11, 0x8E, 0xD1, 0x52, 0xF5, 0x95, 0x8E, 0xF4, 0xD5, 0x01, 0xBC,
0x1F, 0x50, 0x1F, 0x7C, 0xFB, 0x10, 0x80, 0x1B, 0x6D, 0xDF, 0xAE, 0xF8,
0xB6, 0x46, 0xD6, 0x2A, 0xAA, 0x37, 0x11, 0x34, 0x0E, 0xE6, 0xEC, 0xDF,
0x01, 0xE0, 0xE4, 0x06, 0x13, 0x57, 0xD6, 0x81, 0x78, 0x16, 0x11, 0x06,
0x3D, 0xD9, 0xF6, 0x1A, 0x31, 0xE2, 0x23, 0x00, 0xC4, 0xF1, 0x87, 0xAD,
0x09, 0xD0, 0x7F, 0x05, 0x58, 0x97, 0x0E, 0x3B, 0x20, 0x82, 0xAD, 0x1D,
0x60, 0x99, 0xE3, 0x66, 0xC6, 0x32, 0xED, 0xF1, 0x22, 0x4E, 0x00, 0xD2,
0x8C, 0xE7, 0x80, 0xE7, 0x78, 0xFD, 0xBA, 0xFE, 0x46, 0xC0, 0xFF, 0x02,
0xF0, 0x5D, 0x3F, 0xE6, 0xF6, 0x99, 0x60, 0xF1, 0x74, 0x7E, 0xF9, 0x28,
0x60, 0x9D, 0x01, 0xFC, 0xA2, 0x6A, 0x74, 0x00, 0xC2, 0x7C, 0xB6, 0x9C,
0x4A, 0x6C, 0x75, 0x96, 0x63, 0xC1, 0x5F, 0x74, 0xEF, 0x02, 0x58, 0xF7,
0x2F, 0xC7, 0x80, 0x0C, 0x5F, 0x8A, 0x84, 0xEA, 0xDA, 0xB6, 0xDE, 0x84,
0xCB, 0x22, 0x77, 0x35, 0x32, 0xD6, 0xF5, 0x61, 0x23, 0x40, 0x25, 0xAF,
0xFC, 0x36, 0x80, 0x75, 0x56, 0xD4, 0xE3, 0xBE, 0x48, 0xD9, 0xDD, 0x34,
0x00, 0x52, 0x89, 0xB9, 0xC3, 0xF2, 0x04, 0x7E, 0xA5, 0xAF, 0x10, 0xA1,
0xDC, 0x56, 0x7C, 0x02, 0xA0, 0x92, 0x8B, 0x06, 0xF7, 0xFF, 0xB2, 0xD2,
0x3A, 0xAF, 0x21, 0xFD, 0x88, 0xAC, 0xD5, 0x95, 0xF3, 0x0A, 0xB5, 0x0D,
0xF0, 0xEB, 0x7B, 0x83, 0x53, 0xC0, 0x1F, 0xEF, 0x0D, 0xA2, 0x4D, 0x77,
0x69, 0xB8, 0xB7, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
0x42, 0x60, 0x82,
}

View file

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

View file

@ -1,869 +0,0 @@
package nes
import (
"encoding/gob"
)
const frameCounterRate = CPUFrequency / 240.0
var lengthTable = []byte{
10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14,
12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30,
}
var dutyTable = [][]byte{
{0, 1, 0, 0, 0, 0, 0, 0},
{0, 1, 1, 0, 0, 0, 0, 0},
{0, 1, 1, 1, 1, 0, 0, 0},
{1, 0, 0, 1, 1, 1, 1, 1},
}
var triangleTable = []byte{
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
}
var noiseTable = []uint16{
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068,
}
var dmcTable = []byte{
214, 190, 170, 160, 143, 127, 113, 107, 95, 80, 71, 64, 53, 42, 36, 27,
}
var pulseTable [31]float32
var tndTable [203]float32
func init() {
for i := 0; i < 31; i++ {
pulseTable[i] = 95.52 / (8128.0/float32(i) + 100)
}
for i := 0; i < 203; i++ {
tndTable[i] = 163.67 / (24329.0/float32(i) + 100)
}
}
// APU
type APU struct {
console *Console
channel chan<- float32
sampleRate float64
pulse1 Pulse
pulse2 Pulse
triangle Triangle
noise Noise
dmc DMC
cycle uint64
framePeriod byte
frameValue byte
frameIRQ bool
filterChain FilterChain
}
func NewAPU(console *Console) *APU {
apu := APU{}
apu.console = console
apu.noise.shiftRegister = 1
apu.pulse1.channel = 1
apu.pulse2.channel = 2
apu.dmc.cpu = console.CPU
return &apu
}
func (apu *APU) Save(encoder *gob.Encoder) error {
encoder.Encode(apu.cycle)
encoder.Encode(apu.framePeriod)
encoder.Encode(apu.frameValue)
encoder.Encode(apu.frameIRQ)
apu.pulse1.Save(encoder)
apu.pulse2.Save(encoder)
apu.triangle.Save(encoder)
apu.noise.Save(encoder)
apu.dmc.Save(encoder)
return nil
}
func (apu *APU) Load(decoder *gob.Decoder) error {
decoder.Decode(&apu.cycle)
decoder.Decode(&apu.framePeriod)
decoder.Decode(&apu.frameValue)
decoder.Decode(&apu.frameIRQ)
apu.pulse1.Load(decoder)
apu.pulse2.Load(decoder)
apu.triangle.Load(decoder)
apu.noise.Load(decoder)
apu.dmc.Load(decoder)
return nil
}
func (apu *APU) Step() {
cycle1 := apu.cycle
apu.cycle++
cycle2 := apu.cycle
apu.stepTimer()
f1 := int(float64(cycle1) / frameCounterRate)
f2 := int(float64(cycle2) / frameCounterRate)
if f1 != f2 {
apu.stepFrameCounter()
}
s1 := int(float64(cycle1) / apu.sampleRate)
s2 := int(float64(cycle2) / apu.sampleRate)
if s1 != s2 {
apu.sendSample()
}
}
func (apu *APU) sendSample() {
output := apu.filterChain.Step(apu.output())
//stereo
select {
case apu.channel <- output:
default:
}
select {
case apu.channel <- output:
default:
}
}
func (apu *APU) output() float32 {
p1 := apu.pulse1.output()
p2 := apu.pulse2.output()
t := apu.triangle.output()
n := apu.noise.output()
d := apu.dmc.output()
pulseOut := pulseTable[p1+p2]
tndOut := tndTable[3*t+2*n+d]
return pulseOut + tndOut
}
// mode 0: mode 1: function
// --------- ----------- -----------------------------
// - - - f - - - - - IRQ (if bit 6 is clear)
// - l - l l - l - - Length counter and sweep
// e e e e e e e e - Envelope and linear counter
func (apu *APU) stepFrameCounter() {
switch apu.framePeriod {
case 4:
apu.frameValue = (apu.frameValue + 1) % 4
switch apu.frameValue {
case 0, 2:
apu.stepEnvelope()
case 1:
apu.stepEnvelope()
apu.stepSweep()
apu.stepLength()
case 3:
apu.stepEnvelope()
apu.stepSweep()
apu.stepLength()
apu.fireIRQ()
}
case 5:
apu.frameValue = (apu.frameValue + 1) % 5
switch apu.frameValue {
case 0, 2:
apu.stepEnvelope()
case 1, 3:
apu.stepEnvelope()
apu.stepSweep()
apu.stepLength()
}
}
}
func (apu *APU) stepTimer() {
if apu.cycle%2 == 0 {
apu.pulse1.stepTimer()
apu.pulse2.stepTimer()
apu.noise.stepTimer()
apu.dmc.stepTimer()
}
apu.triangle.stepTimer()
}
func (apu *APU) stepEnvelope() {
apu.pulse1.stepEnvelope()
apu.pulse2.stepEnvelope()
apu.triangle.stepCounter()
apu.noise.stepEnvelope()
}
func (apu *APU) stepSweep() {
apu.pulse1.stepSweep()
apu.pulse2.stepSweep()
}
func (apu *APU) stepLength() {
apu.pulse1.stepLength()
apu.pulse2.stepLength()
apu.triangle.stepLength()
apu.noise.stepLength()
}
func (apu *APU) fireIRQ() {
if apu.frameIRQ {
apu.console.CPU.triggerIRQ()
}
}
func (apu *APU) readRegister(address uint16) byte {
switch address {
case 0x4015:
return apu.readStatus()
// default:
// log.Fatalf("unhandled apu register read at address: 0x%04X", address)
}
return 0
}
func (apu *APU) writeRegister(address uint16, value byte) {
switch address {
case 0x4000:
apu.pulse1.writeControl(value)
case 0x4001:
apu.pulse1.writeSweep(value)
case 0x4002:
apu.pulse1.writeTimerLow(value)
case 0x4003:
apu.pulse1.writeTimerHigh(value)
case 0x4004:
apu.pulse2.writeControl(value)
case 0x4005:
apu.pulse2.writeSweep(value)
case 0x4006:
apu.pulse2.writeTimerLow(value)
case 0x4007:
apu.pulse2.writeTimerHigh(value)
case 0x4008:
apu.triangle.writeControl(value)
case 0x4009:
case 0x4010:
apu.dmc.writeControl(value)
case 0x4011:
apu.dmc.writeValue(value)
case 0x4012:
apu.dmc.writeAddress(value)
case 0x4013:
apu.dmc.writeLength(value)
case 0x400A:
apu.triangle.writeTimerLow(value)
case 0x400B:
apu.triangle.writeTimerHigh(value)
case 0x400C:
apu.noise.writeControl(value)
case 0x400D:
case 0x400E:
apu.noise.writePeriod(value)
case 0x400F:
apu.noise.writeLength(value)
case 0x4015:
apu.writeControl(value)
case 0x4017:
apu.writeFrameCounter(value)
// default:
// log.Fatalf("unhandled apu register write at address: 0x%04X", address)
}
}
func (apu *APU) readStatus() byte {
var result byte
if apu.pulse1.lengthValue > 0 {
result |= 1
}
if apu.pulse2.lengthValue > 0 {
result |= 2
}
if apu.triangle.lengthValue > 0 {
result |= 4
}
if apu.noise.lengthValue > 0 {
result |= 8
}
if apu.dmc.currentLength > 0 {
result |= 16
}
return result
}
func (apu *APU) writeControl(value byte) {
apu.pulse1.enabled = value&1 == 1
apu.pulse2.enabled = value&2 == 2
apu.triangle.enabled = value&4 == 4
apu.noise.enabled = value&8 == 8
apu.dmc.enabled = value&16 == 16
if !apu.pulse1.enabled {
apu.pulse1.lengthValue = 0
}
if !apu.pulse2.enabled {
apu.pulse2.lengthValue = 0
}
if !apu.triangle.enabled {
apu.triangle.lengthValue = 0
}
if !apu.noise.enabled {
apu.noise.lengthValue = 0
}
if !apu.dmc.enabled {
apu.dmc.currentLength = 0
} else {
if apu.dmc.currentLength == 0 {
apu.dmc.restart()
}
}
}
func (apu *APU) writeFrameCounter(value byte) {
apu.framePeriod = 4 + (value>>7)&1
apu.frameIRQ = (value>>6)&1 == 0
// apu.frameValue = 0
if apu.framePeriod == 5 {
apu.stepEnvelope()
apu.stepSweep()
apu.stepLength()
}
}
// Pulse
type Pulse struct {
enabled bool
channel byte
lengthEnabled bool
lengthValue byte
timerPeriod uint16
timerValue uint16
dutyMode byte
dutyValue byte
sweepReload bool
sweepEnabled bool
sweepNegate bool
sweepShift byte
sweepPeriod byte
sweepValue byte
envelopeEnabled bool
envelopeLoop bool
envelopeStart bool
envelopePeriod byte
envelopeValue byte
envelopeVolume byte
constantVolume byte
}
func (p *Pulse) Save(encoder *gob.Encoder) error {
encoder.Encode(p.enabled)
encoder.Encode(p.channel)
encoder.Encode(p.lengthEnabled)
encoder.Encode(p.lengthValue)
encoder.Encode(p.timerPeriod)
encoder.Encode(p.timerValue)
encoder.Encode(p.dutyMode)
encoder.Encode(p.dutyValue)
encoder.Encode(p.sweepReload)
encoder.Encode(p.sweepEnabled)
encoder.Encode(p.sweepNegate)
encoder.Encode(p.sweepShift)
encoder.Encode(p.sweepPeriod)
encoder.Encode(p.sweepValue)
encoder.Encode(p.envelopeEnabled)
encoder.Encode(p.envelopeLoop)
encoder.Encode(p.envelopeStart)
encoder.Encode(p.envelopePeriod)
encoder.Encode(p.envelopeValue)
encoder.Encode(p.envelopeVolume)
encoder.Encode(p.constantVolume)
return nil
}
func (p *Pulse) Load(decoder *gob.Decoder) error {
decoder.Decode(&p.enabled)
decoder.Decode(&p.channel)
decoder.Decode(&p.lengthEnabled)
decoder.Decode(&p.lengthValue)
decoder.Decode(&p.timerPeriod)
decoder.Decode(&p.timerValue)
decoder.Decode(&p.dutyMode)
decoder.Decode(&p.dutyValue)
decoder.Decode(&p.sweepReload)
decoder.Decode(&p.sweepEnabled)
decoder.Decode(&p.sweepNegate)
decoder.Decode(&p.sweepShift)
decoder.Decode(&p.sweepPeriod)
decoder.Decode(&p.sweepValue)
decoder.Decode(&p.envelopeEnabled)
decoder.Decode(&p.envelopeLoop)
decoder.Decode(&p.envelopeStart)
decoder.Decode(&p.envelopePeriod)
decoder.Decode(&p.envelopeValue)
decoder.Decode(&p.envelopeVolume)
decoder.Decode(&p.constantVolume)
return nil
}
func (p *Pulse) writeControl(value byte) {
p.dutyMode = (value >> 6) & 3
p.lengthEnabled = (value>>5)&1 == 0
p.envelopeLoop = (value>>5)&1 == 1
p.envelopeEnabled = (value>>4)&1 == 0
p.envelopePeriod = value & 15
p.constantVolume = value & 15
p.envelopeStart = true
}
func (p *Pulse) writeSweep(value byte) {
p.sweepEnabled = (value>>7)&1 == 1
p.sweepPeriod = (value>>4)&7 + 1
p.sweepNegate = (value>>3)&1 == 1
p.sweepShift = value & 7
p.sweepReload = true
}
func (p *Pulse) writeTimerLow(value byte) {
p.timerPeriod = (p.timerPeriod & 0xFF00) | uint16(value)
}
func (p *Pulse) writeTimerHigh(value byte) {
p.lengthValue = lengthTable[value>>3]
p.timerPeriod = (p.timerPeriod & 0x00FF) | (uint16(value&7) << 8)
p.envelopeStart = true
p.dutyValue = 0
}
func (p *Pulse) stepTimer() {
if p.timerValue == 0 {
p.timerValue = p.timerPeriod
p.dutyValue = (p.dutyValue + 1) % 8
} else {
p.timerValue--
}
}
func (p *Pulse) stepEnvelope() {
if p.envelopeStart {
p.envelopeVolume = 15
p.envelopeValue = p.envelopePeriod
p.envelopeStart = false
} else if p.envelopeValue > 0 {
p.envelopeValue--
} else {
if p.envelopeVolume > 0 {
p.envelopeVolume--
} else if p.envelopeLoop {
p.envelopeVolume = 15
}
p.envelopeValue = p.envelopePeriod
}
}
func (p *Pulse) stepSweep() {
if p.sweepReload {
if p.sweepEnabled && p.sweepValue == 0 {
p.sweep()
}
p.sweepValue = p.sweepPeriod
p.sweepReload = false
} else if p.sweepValue > 0 {
p.sweepValue--
} else {
if p.sweepEnabled {
p.sweep()
}
p.sweepValue = p.sweepPeriod
}
}
func (p *Pulse) stepLength() {
if p.lengthEnabled && p.lengthValue > 0 {
p.lengthValue--
}
}
func (p *Pulse) sweep() {
delta := p.timerPeriod >> p.sweepShift
if p.sweepNegate {
p.timerPeriod -= delta
if p.channel == 1 {
p.timerPeriod--
}
} else {
p.timerPeriod += delta
}
}
func (p *Pulse) output() byte {
if !p.enabled {
return 0
}
if p.lengthValue == 0 {
return 0
}
if dutyTable[p.dutyMode][p.dutyValue] == 0 {
return 0
}
if p.timerPeriod < 8 || p.timerPeriod > 0x7FF {
return 0
}
// if !p.sweepNegate && p.timerPeriod+(p.timerPeriod>>p.sweepShift) > 0x7FF {
// return 0
// }
if p.envelopeEnabled {
return p.envelopeVolume
} else {
return p.constantVolume
}
}
// Triangle
type Triangle struct {
enabled bool
lengthEnabled bool
lengthValue byte
timerPeriod uint16
timerValue uint16
dutyValue byte
counterPeriod byte
counterValue byte
counterReload bool
}
func (t *Triangle) Save(encoder *gob.Encoder) error {
encoder.Encode(t.enabled)
encoder.Encode(t.lengthEnabled)
encoder.Encode(t.lengthValue)
encoder.Encode(t.timerPeriod)
encoder.Encode(t.timerValue)
encoder.Encode(t.dutyValue)
encoder.Encode(t.counterPeriod)
encoder.Encode(t.counterValue)
encoder.Encode(t.counterReload)
return nil
}
func (t *Triangle) Load(decoder *gob.Decoder) error {
decoder.Decode(&t.enabled)
decoder.Decode(&t.lengthEnabled)
decoder.Decode(&t.lengthValue)
decoder.Decode(&t.timerPeriod)
decoder.Decode(&t.timerValue)
decoder.Decode(&t.dutyValue)
decoder.Decode(&t.counterPeriod)
decoder.Decode(&t.counterValue)
decoder.Decode(&t.counterReload)
return nil
}
func (t *Triangle) writeControl(value byte) {
t.lengthEnabled = (value>>7)&1 == 0
t.counterPeriod = value & 0x7F
}
func (t *Triangle) writeTimerLow(value byte) {
t.timerPeriod = (t.timerPeriod & 0xFF00) | uint16(value)
}
func (t *Triangle) writeTimerHigh(value byte) {
t.lengthValue = lengthTable[value>>3]
t.timerPeriod = (t.timerPeriod & 0x00FF) | (uint16(value&7) << 8)
t.timerValue = t.timerPeriod
t.counterReload = true
}
func (t *Triangle) stepTimer() {
if t.timerValue == 0 {
t.timerValue = t.timerPeriod
if t.lengthValue > 0 && t.counterValue > 0 {
t.dutyValue = (t.dutyValue + 1) % 32
}
} else {
t.timerValue--
}
}
func (t *Triangle) stepLength() {
if t.lengthEnabled && t.lengthValue > 0 {
t.lengthValue--
}
}
func (t *Triangle) stepCounter() {
if t.counterReload {
t.counterValue = t.counterPeriod
} else if t.counterValue > 0 {
t.counterValue--
}
if t.lengthEnabled {
t.counterReload = false
}
}
func (t *Triangle) output() byte {
if !t.enabled {
return 0
}
if t.lengthValue == 0 {
return 0
}
if t.counterValue == 0 {
return 0
}
return triangleTable[t.dutyValue]
}
// Noise
type Noise struct {
enabled bool
mode bool
shiftRegister uint16
lengthEnabled bool
lengthValue byte
timerPeriod uint16
timerValue uint16
envelopeEnabled bool
envelopeLoop bool
envelopeStart bool
envelopePeriod byte
envelopeValue byte
envelopeVolume byte
constantVolume byte
}
func (n *Noise) Save(encoder *gob.Encoder) error {
encoder.Encode(n.enabled)
encoder.Encode(n.mode)
encoder.Encode(n.shiftRegister)
encoder.Encode(n.lengthEnabled)
encoder.Encode(n.lengthValue)
encoder.Encode(n.timerPeriod)
encoder.Encode(n.timerValue)
encoder.Encode(n.envelopeEnabled)
encoder.Encode(n.envelopeLoop)
encoder.Encode(n.envelopeStart)
encoder.Encode(n.envelopePeriod)
encoder.Encode(n.envelopeValue)
encoder.Encode(n.envelopeVolume)
encoder.Encode(n.constantVolume)
return nil
}
func (n *Noise) Load(decoder *gob.Decoder) error {
decoder.Decode(&n.enabled)
decoder.Decode(&n.mode)
decoder.Decode(&n.shiftRegister)
decoder.Decode(&n.lengthEnabled)
decoder.Decode(&n.lengthValue)
decoder.Decode(&n.timerPeriod)
decoder.Decode(&n.timerValue)
decoder.Decode(&n.envelopeEnabled)
decoder.Decode(&n.envelopeLoop)
decoder.Decode(&n.envelopeStart)
decoder.Decode(&n.envelopePeriod)
decoder.Decode(&n.envelopeValue)
decoder.Decode(&n.envelopeVolume)
decoder.Decode(&n.constantVolume)
return nil
}
func (n *Noise) writeControl(value byte) {
n.lengthEnabled = (value>>5)&1 == 0
n.envelopeLoop = (value>>5)&1 == 1
n.envelopeEnabled = (value>>4)&1 == 0
n.envelopePeriod = value & 15
n.constantVolume = value & 15
n.envelopeStart = true
}
func (n *Noise) writePeriod(value byte) {
n.mode = value&0x80 == 0x80
n.timerPeriod = noiseTable[value&0x0F]
}
func (n *Noise) writeLength(value byte) {
n.lengthValue = lengthTable[value>>3]
n.envelopeStart = true
}
func (n *Noise) stepTimer() {
if n.timerValue == 0 {
n.timerValue = n.timerPeriod
var shift byte
if n.mode {
shift = 6
} else {
shift = 1
}
b1 := n.shiftRegister & 1
b2 := (n.shiftRegister >> shift) & 1
n.shiftRegister >>= 1
n.shiftRegister |= (b1 ^ b2) << 14
} else {
n.timerValue--
}
}
func (n *Noise) stepEnvelope() {
if n.envelopeStart {
n.envelopeVolume = 15
n.envelopeValue = n.envelopePeriod
n.envelopeStart = false
} else if n.envelopeValue > 0 {
n.envelopeValue--
} else {
if n.envelopeVolume > 0 {
n.envelopeVolume--
} else if n.envelopeLoop {
n.envelopeVolume = 15
}
n.envelopeValue = n.envelopePeriod
}
}
func (n *Noise) stepLength() {
if n.lengthEnabled && n.lengthValue > 0 {
n.lengthValue--
}
}
func (n *Noise) output() byte {
if !n.enabled {
return 0
}
if n.lengthValue == 0 {
return 0
}
if n.shiftRegister&1 == 1 {
return 0
}
if n.envelopeEnabled {
return n.envelopeVolume
} else {
return n.constantVolume
}
}
// DMC
type DMC struct {
cpu *CPU
enabled bool
value byte
sampleAddress uint16
sampleLength uint16
currentAddress uint16
currentLength uint16
shiftRegister byte
bitCount byte
tickPeriod byte
tickValue byte
loop bool
irq bool
}
func (d *DMC) Save(encoder *gob.Encoder) error {
encoder.Encode(d.enabled)
encoder.Encode(d.value)
encoder.Encode(d.sampleAddress)
encoder.Encode(d.sampleLength)
encoder.Encode(d.currentAddress)
encoder.Encode(d.currentLength)
encoder.Encode(d.shiftRegister)
encoder.Encode(d.bitCount)
encoder.Encode(d.tickPeriod)
encoder.Encode(d.tickValue)
encoder.Encode(d.loop)
encoder.Encode(d.irq)
return nil
}
func (d *DMC) Load(decoder *gob.Decoder) error {
decoder.Decode(&d.enabled)
decoder.Decode(&d.value)
decoder.Decode(&d.sampleAddress)
decoder.Decode(&d.sampleLength)
decoder.Decode(&d.currentAddress)
decoder.Decode(&d.currentLength)
decoder.Decode(&d.shiftRegister)
decoder.Decode(&d.bitCount)
decoder.Decode(&d.tickPeriod)
decoder.Decode(&d.tickValue)
decoder.Decode(&d.loop)
decoder.Decode(&d.irq)
return nil
}
func (d *DMC) writeControl(value byte) {
d.irq = value&0x80 == 0x80
d.loop = value&0x40 == 0x40
d.tickPeriod = dmcTable[value&0x0F]
}
func (d *DMC) writeValue(value byte) {
d.value = value & 0x7F
}
func (d *DMC) writeAddress(value byte) {
// Sample address = %11AAAAAA.AA000000
d.sampleAddress = 0xC000 | (uint16(value) << 6)
}
func (d *DMC) writeLength(value byte) {
// Sample length = %0000LLLL.LLLL0001
d.sampleLength = (uint16(value) << 4) | 1
}
func (d *DMC) restart() {
d.currentAddress = d.sampleAddress
d.currentLength = d.sampleLength
}
func (d *DMC) stepTimer() {
if !d.enabled {
return
}
d.stepReader()
if d.tickValue == 0 {
d.tickValue = d.tickPeriod
d.stepShifter()
} else {
d.tickValue--
}
}
func (d *DMC) stepReader() {
if d.currentLength > 0 && d.bitCount == 0 {
d.cpu.stall += 4
d.shiftRegister = d.cpu.Read(d.currentAddress)
d.bitCount = 8
d.currentAddress++
if d.currentAddress == 0 {
d.currentAddress = 0x8000
}
d.currentLength--
if d.currentLength == 0 && d.loop {
d.restart()
}
}
}
func (d *DMC) stepShifter() {
if d.bitCount == 0 {
return
}
if d.shiftRegister&1 == 1 {
if d.value <= 125 {
d.value += 2
}
} else {
if d.value >= 2 {
d.value -= 2
}
}
d.shiftRegister >>= 1
d.bitCount--
}
func (d *DMC) output() byte {
return d.value
}

View file

@ -1,33 +0,0 @@
package nes
import "encoding/gob"
type Cartridge struct {
PRG []byte // PRG-ROM banks
CHR []byte // CHR-ROM banks
SRAM []byte // Save RAM
Mapper byte // mapper type
Mirror byte // mirroring mode
Battery byte // battery present
}
func NewCartridge(prg, chr []byte, mapper, mirror, battery byte) *Cartridge {
sram := make([]byte, 0x2000)
return &Cartridge{prg, chr, sram, mapper, mirror, battery}
}
func (cartridge *Cartridge) Save(encoder *gob.Encoder) error {
encoder.Encode(cartridge.PRG)
encoder.Encode(cartridge.CHR)
encoder.Encode(cartridge.SRAM)
encoder.Encode(cartridge.Mirror)
return nil
}
func (cartridge *Cartridge) Load(decoder *gob.Decoder) error {
decoder.Decode(&cartridge.PRG)
decoder.Decode(&cartridge.CHR)
decoder.Decode(&cartridge.SRAM)
decoder.Decode(&cartridge.Mirror)
return nil
}

View file

@ -1,156 +0,0 @@
package nes
import (
"encoding/gob"
"image"
"image/color"
"os"
"path"
)
type Console struct {
CPU *CPU
APU *APU
PPU *PPU
Cartridge *Cartridge
Controller1 *Controller
Controller2 *Controller
Mapper Mapper
RAM []byte
}
func NewConsole(path string) (*Console, error) {
cartridge, err := LoadNESFile(path)
if err != nil {
return nil, err
}
ram := make([]byte, 2048)
controller1 := NewController()
controller2 := NewController()
console := Console{
nil, nil, nil, cartridge, controller1, controller2, nil, ram}
mapper, err := NewMapper(&console)
if err != nil {
return nil, err
}
console.Mapper = mapper
console.CPU = NewCPU(&console)
console.APU = NewAPU(&console)
console.PPU = NewPPU(&console)
return &console, nil
}
func (console *Console) Reset() {
console.CPU.Reset()
}
func (console *Console) Step() int {
cpuCycles := console.CPU.Step()
ppuCycles := cpuCycles * 3
for i := 0; i < ppuCycles; i++ {
console.PPU.Step()
console.Mapper.Step()
}
for i := 0; i < cpuCycles; i++ {
console.APU.Step()
}
return cpuCycles
}
func (console *Console) StepFrame() int {
cpuCycles := 0
frame := console.PPU.Frame
for frame == console.PPU.Frame {
cpuCycles += console.Step()
}
return cpuCycles
}
func (console *Console) StepSeconds(seconds float64) {
cycles := int(CPUFrequency * seconds)
for cycles > 0 {
cycles -= console.Step()
}
}
func (console *Console) Buffer() *image.RGBA {
return console.PPU.front
}
func (console *Console) BackgroundColor() color.RGBA {
return Palette[console.PPU.readPalette(0)%64]
}
func (console *Console) SetButtons1(buttons [8]bool) {
console.Controller1.SetButtons(buttons)
}
func (console *Console) SetButtons2(buttons [8]bool) {
console.Controller2.SetButtons(buttons)
}
func (console *Console) SetAudioChannel(channel chan<- float32) {
console.APU.channel = channel
}
func (console *Console) SetAudioSampleRate(sampleRate float64) {
if sampleRate != 0 {
// Convert samples per second to cpu steps per sample
console.APU.sampleRate = CPUFrequency / sampleRate
// Initialize filters
console.APU.filterChain = FilterChain{
HighPassFilter(float32(sampleRate), 90),
HighPassFilter(float32(sampleRate), 440),
LowPassFilter(float32(sampleRate), 14000),
}
} else {
console.APU.filterChain = nil
}
}
func (console *Console) SaveState(filename string) error {
dir, _ := path.Split(filename)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
encoder := gob.NewEncoder(file)
return console.Save(encoder)
}
func (console *Console) Save(encoder *gob.Encoder) error {
encoder.Encode(console.RAM)
console.CPU.Save(encoder)
console.APU.Save(encoder)
console.PPU.Save(encoder)
console.Cartridge.Save(encoder)
console.Mapper.Save(encoder)
return encoder.Encode(true)
}
func (console *Console) LoadState(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
decoder := gob.NewDecoder(file)
return console.Load(decoder)
}
func (console *Console) Load(decoder *gob.Decoder) error {
decoder.Decode(&console.RAM)
console.CPU.Load(decoder)
console.APU.Load(decoder)
console.PPU.Load(decoder)
console.Cartridge.Load(decoder)
console.Mapper.Load(decoder)
var dummy bool
if err := decoder.Decode(&dummy); err != nil {
return err
}
return nil
}

View file

@ -1,45 +0,0 @@
package nes
const (
ButtonA = iota
ButtonB
ButtonSelect
ButtonStart
ButtonUp
ButtonDown
ButtonLeft
ButtonRight
)
type Controller struct {
buttons [8]bool
index byte
strobe byte
}
func NewController() *Controller {
return &Controller{}
}
func (c *Controller) SetButtons(buttons [8]bool) {
c.buttons = buttons
}
func (c *Controller) Read() byte {
value := byte(0)
if c.index < 8 && c.buttons[c.index] {
value = 1
}
c.index++
if c.strobe&1 == 1 {
c.index = 0
}
return value
}
func (c *Controller) Write(value byte) {
c.strobe = value
if c.strobe&1 == 1 {
c.index = 0
}
}

View file

@ -1,975 +0,0 @@
package nes
import (
"encoding/gob"
"fmt"
)
const CPUFrequency = 1789773
// interrupt types
const (
_ = iota
interruptNone
interruptNMI
interruptIRQ
)
// addressing modes
const (
_ = iota
modeAbsolute
modeAbsoluteX
modeAbsoluteY
modeAccumulator
modeImmediate
modeImplied
modeIndexedIndirect
modeIndirect
modeIndirectIndexed
modeRelative
modeZeroPage
modeZeroPageX
modeZeroPageY
)
// instructionModes indicates the addressing mode for each instruction
var instructionModes = [256]byte{
6, 7, 6, 7, 11, 11, 11, 11, 6, 5, 4, 5, 1, 1, 1, 1,
10, 9, 6, 9, 12, 12, 12, 12, 6, 3, 6, 3, 2, 2, 2, 2,
1, 7, 6, 7, 11, 11, 11, 11, 6, 5, 4, 5, 1, 1, 1, 1,
10, 9, 6, 9, 12, 12, 12, 12, 6, 3, 6, 3, 2, 2, 2, 2,
6, 7, 6, 7, 11, 11, 11, 11, 6, 5, 4, 5, 1, 1, 1, 1,
10, 9, 6, 9, 12, 12, 12, 12, 6, 3, 6, 3, 2, 2, 2, 2,
6, 7, 6, 7, 11, 11, 11, 11, 6, 5, 4, 5, 8, 1, 1, 1,
10, 9, 6, 9, 12, 12, 12, 12, 6, 3, 6, 3, 2, 2, 2, 2,
5, 7, 5, 7, 11, 11, 11, 11, 6, 5, 6, 5, 1, 1, 1, 1,
10, 9, 6, 9, 12, 12, 13, 13, 6, 3, 6, 3, 2, 2, 3, 3,
5, 7, 5, 7, 11, 11, 11, 11, 6, 5, 6, 5, 1, 1, 1, 1,
10, 9, 6, 9, 12, 12, 13, 13, 6, 3, 6, 3, 2, 2, 3, 3,
5, 7, 5, 7, 11, 11, 11, 11, 6, 5, 6, 5, 1, 1, 1, 1,
10, 9, 6, 9, 12, 12, 12, 12, 6, 3, 6, 3, 2, 2, 2, 2,
5, 7, 5, 7, 11, 11, 11, 11, 6, 5, 6, 5, 1, 1, 1, 1,
10, 9, 6, 9, 12, 12, 12, 12, 6, 3, 6, 3, 2, 2, 2, 2,
}
// instructionSizes indicates the size of each instruction in bytes
var instructionSizes = [256]byte{
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
3, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
1, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
1, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 0, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 0, 3, 0, 0,
2, 2, 2, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
}
// instructionCycles indicates the number of cycles used by each instruction,
// not including conditional cycles
var instructionCycles = [256]byte{
7, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 4, 4, 6, 6,
2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 4, 4, 6, 6,
2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
6, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 3, 4, 6, 6,
2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 5, 4, 6, 6,
2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4,
2, 6, 2, 6, 4, 4, 4, 4, 2, 5, 2, 5, 5, 5, 5, 5,
2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4,
2, 5, 2, 5, 4, 4, 4, 4, 2, 4, 2, 4, 4, 4, 4, 4,
2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6,
2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6,
2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
}
// instructionPageCycles indicates the number of cycles used by each
// instruction when a page is crossed
var instructionPageCycles = [256]byte{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0,
}
// instructionNames indicates the name of each instruction
var instructionNames = [256]string{
"BRK", "ORA", "KIL", "SLO", "NOP", "ORA", "ASL", "SLO",
"PHP", "ORA", "ASL", "ANC", "NOP", "ORA", "ASL", "SLO",
"BPL", "ORA", "KIL", "SLO", "NOP", "ORA", "ASL", "SLO",
"CLC", "ORA", "NOP", "SLO", "NOP", "ORA", "ASL", "SLO",
"JSR", "AND", "KIL", "RLA", "BIT", "AND", "ROL", "RLA",
"PLP", "AND", "ROL", "ANC", "BIT", "AND", "ROL", "RLA",
"BMI", "AND", "KIL", "RLA", "NOP", "AND", "ROL", "RLA",
"SEC", "AND", "NOP", "RLA", "NOP", "AND", "ROL", "RLA",
"RTI", "EOR", "KIL", "SRE", "NOP", "EOR", "LSR", "SRE",
"PHA", "EOR", "LSR", "ALR", "JMP", "EOR", "LSR", "SRE",
"BVC", "EOR", "KIL", "SRE", "NOP", "EOR", "LSR", "SRE",
"CLI", "EOR", "NOP", "SRE", "NOP", "EOR", "LSR", "SRE",
"RTS", "ADC", "KIL", "RRA", "NOP", "ADC", "ROR", "RRA",
"PLA", "ADC", "ROR", "ARR", "JMP", "ADC", "ROR", "RRA",
"BVS", "ADC", "KIL", "RRA", "NOP", "ADC", "ROR", "RRA",
"SEI", "ADC", "NOP", "RRA", "NOP", "ADC", "ROR", "RRA",
"NOP", "STA", "NOP", "SAX", "STY", "STA", "STX", "SAX",
"DEY", "NOP", "TXA", "XAA", "STY", "STA", "STX", "SAX",
"BCC", "STA", "KIL", "AHX", "STY", "STA", "STX", "SAX",
"TYA", "STA", "TXS", "TAS", "SHY", "STA", "SHX", "AHX",
"LDY", "LDA", "LDX", "LAX", "LDY", "LDA", "LDX", "LAX",
"TAY", "LDA", "TAX", "LAX", "LDY", "LDA", "LDX", "LAX",
"BCS", "LDA", "KIL", "LAX", "LDY", "LDA", "LDX", "LAX",
"CLV", "LDA", "TSX", "LAS", "LDY", "LDA", "LDX", "LAX",
"CPY", "CMP", "NOP", "DCP", "CPY", "CMP", "DEC", "DCP",
"INY", "CMP", "DEX", "AXS", "CPY", "CMP", "DEC", "DCP",
"BNE", "CMP", "KIL", "DCP", "NOP", "CMP", "DEC", "DCP",
"CLD", "CMP", "NOP", "DCP", "NOP", "CMP", "DEC", "DCP",
"CPX", "SBC", "NOP", "ISC", "CPX", "SBC", "INC", "ISC",
"INX", "SBC", "NOP", "SBC", "CPX", "SBC", "INC", "ISC",
"BEQ", "SBC", "KIL", "ISC", "NOP", "SBC", "INC", "ISC",
"SED", "SBC", "NOP", "ISC", "NOP", "SBC", "INC", "ISC",
}
type CPU struct {
Memory // memory interface
Cycles uint64 // number of cycles
PC uint16 // program counter
SP byte // stack pointer
A byte // accumulator
X byte // x register
Y byte // y register
C byte // carry flag
Z byte // zero flag
I byte // interrupt disable flag
D byte // decimal mode flag
B byte // break command flag
U byte // unused flag
V byte // overflow flag
N byte // negative flag
interrupt byte // interrupt type to perform
stall int // number of cycles to stall
table [256]func(*stepInfo)
}
func NewCPU(console *Console) *CPU {
cpu := CPU{Memory: NewCPUMemory(console)}
cpu.createTable()
cpu.Reset()
return &cpu
}
// createTable builds a function table for each instruction
func (c *CPU) createTable() {
c.table = [256]func(*stepInfo){
c.brk, c.ora, c.kil, c.slo, c.nop, c.ora, c.asl, c.slo,
c.php, c.ora, c.asl, c.anc, c.nop, c.ora, c.asl, c.slo,
c.bpl, c.ora, c.kil, c.slo, c.nop, c.ora, c.asl, c.slo,
c.clc, c.ora, c.nop, c.slo, c.nop, c.ora, c.asl, c.slo,
c.jsr, c.and, c.kil, c.rla, c.bit, c.and, c.rol, c.rla,
c.plp, c.and, c.rol, c.anc, c.bit, c.and, c.rol, c.rla,
c.bmi, c.and, c.kil, c.rla, c.nop, c.and, c.rol, c.rla,
c.sec, c.and, c.nop, c.rla, c.nop, c.and, c.rol, c.rla,
c.rti, c.eor, c.kil, c.sre, c.nop, c.eor, c.lsr, c.sre,
c.pha, c.eor, c.lsr, c.alr, c.jmp, c.eor, c.lsr, c.sre,
c.bvc, c.eor, c.kil, c.sre, c.nop, c.eor, c.lsr, c.sre,
c.cli, c.eor, c.nop, c.sre, c.nop, c.eor, c.lsr, c.sre,
c.rts, c.adc, c.kil, c.rra, c.nop, c.adc, c.ror, c.rra,
c.pla, c.adc, c.ror, c.arr, c.jmp, c.adc, c.ror, c.rra,
c.bvs, c.adc, c.kil, c.rra, c.nop, c.adc, c.ror, c.rra,
c.sei, c.adc, c.nop, c.rra, c.nop, c.adc, c.ror, c.rra,
c.nop, c.sta, c.nop, c.sax, c.sty, c.sta, c.stx, c.sax,
c.dey, c.nop, c.txa, c.xaa, c.sty, c.sta, c.stx, c.sax,
c.bcc, c.sta, c.kil, c.ahx, c.sty, c.sta, c.stx, c.sax,
c.tya, c.sta, c.txs, c.tas, c.shy, c.sta, c.shx, c.ahx,
c.ldy, c.lda, c.ldx, c.lax, c.ldy, c.lda, c.ldx, c.lax,
c.tay, c.lda, c.tax, c.lax, c.ldy, c.lda, c.ldx, c.lax,
c.bcs, c.lda, c.kil, c.lax, c.ldy, c.lda, c.ldx, c.lax,
c.clv, c.lda, c.tsx, c.las, c.ldy, c.lda, c.ldx, c.lax,
c.cpy, c.cmp, c.nop, c.dcp, c.cpy, c.cmp, c.dec, c.dcp,
c.iny, c.cmp, c.dex, c.axs, c.cpy, c.cmp, c.dec, c.dcp,
c.bne, c.cmp, c.kil, c.dcp, c.nop, c.cmp, c.dec, c.dcp,
c.cld, c.cmp, c.nop, c.dcp, c.nop, c.cmp, c.dec, c.dcp,
c.cpx, c.sbc, c.nop, c.isc, c.cpx, c.sbc, c.inc, c.isc,
c.inx, c.sbc, c.nop, c.sbc, c.cpx, c.sbc, c.inc, c.isc,
c.beq, c.sbc, c.kil, c.isc, c.nop, c.sbc, c.inc, c.isc,
c.sed, c.sbc, c.nop, c.isc, c.nop, c.sbc, c.inc, c.isc,
}
}
func (cpu *CPU) Save(encoder *gob.Encoder) error {
encoder.Encode(cpu.Cycles)
encoder.Encode(cpu.PC)
encoder.Encode(cpu.SP)
encoder.Encode(cpu.A)
encoder.Encode(cpu.X)
encoder.Encode(cpu.Y)
encoder.Encode(cpu.C)
encoder.Encode(cpu.Z)
encoder.Encode(cpu.I)
encoder.Encode(cpu.D)
encoder.Encode(cpu.B)
encoder.Encode(cpu.U)
encoder.Encode(cpu.V)
encoder.Encode(cpu.N)
encoder.Encode(cpu.interrupt)
encoder.Encode(cpu.stall)
return nil
}
func (cpu *CPU) Load(decoder *gob.Decoder) error {
decoder.Decode(&cpu.Cycles)
decoder.Decode(&cpu.PC)
decoder.Decode(&cpu.SP)
decoder.Decode(&cpu.A)
decoder.Decode(&cpu.X)
decoder.Decode(&cpu.Y)
decoder.Decode(&cpu.C)
decoder.Decode(&cpu.Z)
decoder.Decode(&cpu.I)
decoder.Decode(&cpu.D)
decoder.Decode(&cpu.B)
decoder.Decode(&cpu.U)
decoder.Decode(&cpu.V)
decoder.Decode(&cpu.N)
decoder.Decode(&cpu.interrupt)
decoder.Decode(&cpu.stall)
return nil
}
// Reset resets the CPU to its initial powerup state
func (cpu *CPU) Reset() {
cpu.PC = cpu.Read16(0xFFFC)
cpu.SP = 0xFD
cpu.SetFlags(0x24)
}
// PrintInstruction prints the current CPU state
func (cpu *CPU) PrintInstruction() {
opcode := cpu.Read(cpu.PC)
bytes := instructionSizes[opcode]
name := instructionNames[opcode]
w0 := fmt.Sprintf("%02X", cpu.Read(cpu.PC+0))
w1 := fmt.Sprintf("%02X", cpu.Read(cpu.PC+1))
w2 := fmt.Sprintf("%02X", cpu.Read(cpu.PC+2))
if bytes < 2 {
w1 = " "
}
if bytes < 3 {
w2 = " "
}
fmt.Printf(
"%4X %s %s %s %s %21s"+
"A:%02X X:%02X Y:%02X P:%02X SP:%02X CYC:%3d\n",
cpu.PC, w0, w1, w2, name, "",
cpu.A, cpu.X, cpu.Y, cpu.Flags(), cpu.SP, (cpu.Cycles*3)%341)
}
// pagesDiffer returns true if the two addresses reference different pages
func pagesDiffer(a, b uint16) bool {
return a&0xFF00 != b&0xFF00
}
// addBranchCycles adds a cycle for taking a branch and adds another cycle
// if the branch jumps to a new page
func (cpu *CPU) addBranchCycles(info *stepInfo) {
cpu.Cycles++
if pagesDiffer(info.pc, info.address) {
cpu.Cycles++
}
}
func (cpu *CPU) compare(a, b byte) {
cpu.setZN(a - b)
if a >= b {
cpu.C = 1
} else {
cpu.C = 0
}
}
// Read16 reads two bytes using Read to return a double-word value
func (cpu *CPU) Read16(address uint16) uint16 {
lo := uint16(cpu.Read(address))
hi := uint16(cpu.Read(address + 1))
return hi<<8 | lo
}
// read16bug emulates a 6502 bug that caused the low byte to wrap without
// incrementing the high byte
func (cpu *CPU) read16bug(address uint16) uint16 {
a := address
b := (a & 0xFF00) | uint16(byte(a)+1)
lo := cpu.Read(a)
hi := cpu.Read(b)
return uint16(hi)<<8 | uint16(lo)
}
// push pushes a byte onto the stack
func (cpu *CPU) push(value byte) {
cpu.Write(0x100|uint16(cpu.SP), value)
cpu.SP--
}
// pull pops a byte from the stack
func (cpu *CPU) pull() byte {
cpu.SP++
return cpu.Read(0x100 | uint16(cpu.SP))
}
// push16 pushes two bytes onto the stack
func (cpu *CPU) push16(value uint16) {
hi := byte(value >> 8)
lo := byte(value & 0xFF)
cpu.push(hi)
cpu.push(lo)
}
// pull16 pops two bytes from the stack
func (cpu *CPU) pull16() uint16 {
lo := uint16(cpu.pull())
hi := uint16(cpu.pull())
return hi<<8 | lo
}
// Flags returns the processor status flags
func (cpu *CPU) Flags() byte {
var flags byte
flags |= cpu.C << 0
flags |= cpu.Z << 1
flags |= cpu.I << 2
flags |= cpu.D << 3
flags |= cpu.B << 4
flags |= cpu.U << 5
flags |= cpu.V << 6
flags |= cpu.N << 7
return flags
}
// SetFlags sets the processor status flags
func (cpu *CPU) SetFlags(flags byte) {
cpu.C = (flags >> 0) & 1
cpu.Z = (flags >> 1) & 1
cpu.I = (flags >> 2) & 1
cpu.D = (flags >> 3) & 1
cpu.B = (flags >> 4) & 1
cpu.U = (flags >> 5) & 1
cpu.V = (flags >> 6) & 1
cpu.N = (flags >> 7) & 1
}
// setZ sets the zero flag if the argument is zero
func (cpu *CPU) setZ(value byte) {
if value == 0 {
cpu.Z = 1
} else {
cpu.Z = 0
}
}
// setN sets the negative flag if the argument is negative (high bit is set)
func (cpu *CPU) setN(value byte) {
if value&0x80 != 0 {
cpu.N = 1
} else {
cpu.N = 0
}
}
// setZN sets the zero flag and the negative flag
func (cpu *CPU) setZN(value byte) {
cpu.setZ(value)
cpu.setN(value)
}
// triggerNMI causes a non-maskable interrupt to occur on the next cycle
func (cpu *CPU) triggerNMI() {
cpu.interrupt = interruptNMI
}
// triggerIRQ causes an IRQ interrupt to occur on the next cycle
func (cpu *CPU) triggerIRQ() {
if cpu.I == 0 {
cpu.interrupt = interruptIRQ
}
}
// stepInfo contains information that the instruction functions use
type stepInfo struct {
address uint16
pc uint16
mode byte
}
// Step executes a single CPU instruction
func (cpu *CPU) Step() int {
if cpu.stall > 0 {
cpu.stall--
return 1
}
cycles := cpu.Cycles
switch cpu.interrupt {
case interruptNMI:
cpu.nmi()
case interruptIRQ:
cpu.irq()
}
cpu.interrupt = interruptNone
opcode := cpu.Read(cpu.PC)
mode := instructionModes[opcode]
var address uint16
var pageCrossed bool
switch mode {
case modeAbsolute:
address = cpu.Read16(cpu.PC + 1)
case modeAbsoluteX:
address = cpu.Read16(cpu.PC+1) + uint16(cpu.X)
pageCrossed = pagesDiffer(address-uint16(cpu.X), address)
case modeAbsoluteY:
address = cpu.Read16(cpu.PC+1) + uint16(cpu.Y)
pageCrossed = pagesDiffer(address-uint16(cpu.Y), address)
case modeAccumulator:
address = 0
case modeImmediate:
address = cpu.PC + 1
case modeImplied:
address = 0
case modeIndexedIndirect:
address = cpu.read16bug(uint16(cpu.Read(cpu.PC+1) + cpu.X))
case modeIndirect:
address = cpu.read16bug(cpu.Read16(cpu.PC + 1))
case modeIndirectIndexed:
address = cpu.read16bug(uint16(cpu.Read(cpu.PC+1))) + uint16(cpu.Y)
pageCrossed = pagesDiffer(address-uint16(cpu.Y), address)
case modeRelative:
offset := uint16(cpu.Read(cpu.PC + 1))
if offset < 0x80 {
address = cpu.PC + 2 + offset
} else {
address = cpu.PC + 2 + offset - 0x100
}
case modeZeroPage:
address = uint16(cpu.Read(cpu.PC + 1))
case modeZeroPageX:
address = uint16(cpu.Read(cpu.PC+1)+cpu.X) & 0xff
case modeZeroPageY:
address = uint16(cpu.Read(cpu.PC+1)+cpu.Y) & 0xff
}
cpu.PC += uint16(instructionSizes[opcode])
cpu.Cycles += uint64(instructionCycles[opcode])
if pageCrossed {
cpu.Cycles += uint64(instructionPageCycles[opcode])
}
info := &stepInfo{address, cpu.PC, mode}
cpu.table[opcode](info)
return int(cpu.Cycles - cycles)
}
// NMI - Non-Maskable Interrupt
func (cpu *CPU) nmi() {
cpu.push16(cpu.PC)
cpu.php(nil)
cpu.PC = cpu.Read16(0xFFFA)
cpu.I = 1
cpu.Cycles += 7
}
// IRQ - IRQ Interrupt
func (cpu *CPU) irq() {
cpu.push16(cpu.PC)
cpu.php(nil)
cpu.PC = cpu.Read16(0xFFFE)
cpu.I = 1
cpu.Cycles += 7
}
// ADC - Add with Carry
func (cpu *CPU) adc(info *stepInfo) {
a := cpu.A
b := cpu.Read(info.address)
c := cpu.C
cpu.A = a + b + c
cpu.setZN(cpu.A)
if int(a)+int(b)+int(c) > 0xFF {
cpu.C = 1
} else {
cpu.C = 0
}
if (a^b)&0x80 == 0 && (a^cpu.A)&0x80 != 0 {
cpu.V = 1
} else {
cpu.V = 0
}
}
// AND - Logical AND
func (cpu *CPU) and(info *stepInfo) {
cpu.A = cpu.A & cpu.Read(info.address)
cpu.setZN(cpu.A)
}
// ASL - Arithmetic Shift Left
func (cpu *CPU) asl(info *stepInfo) {
if info.mode == modeAccumulator {
cpu.C = (cpu.A >> 7) & 1
cpu.A <<= 1
cpu.setZN(cpu.A)
} else {
value := cpu.Read(info.address)
cpu.C = (value >> 7) & 1
value <<= 1
cpu.Write(info.address, value)
cpu.setZN(value)
}
}
// BCC - Branch if Carry Clear
func (cpu *CPU) bcc(info *stepInfo) {
if cpu.C == 0 {
cpu.PC = info.address
cpu.addBranchCycles(info)
}
}
// BCS - Branch if Carry Set
func (cpu *CPU) bcs(info *stepInfo) {
if cpu.C != 0 {
cpu.PC = info.address
cpu.addBranchCycles(info)
}
}
// BEQ - Branch if Equal
func (cpu *CPU) beq(info *stepInfo) {
if cpu.Z != 0 {
cpu.PC = info.address
cpu.addBranchCycles(info)
}
}
// BIT - Bit Test
func (cpu *CPU) bit(info *stepInfo) {
value := cpu.Read(info.address)
cpu.V = (value >> 6) & 1
cpu.setZ(value & cpu.A)
cpu.setN(value)
}
// BMI - Branch if Minus
func (cpu *CPU) bmi(info *stepInfo) {
if cpu.N != 0 {
cpu.PC = info.address
cpu.addBranchCycles(info)
}
}
// BNE - Branch if Not Equal
func (cpu *CPU) bne(info *stepInfo) {
if cpu.Z == 0 {
cpu.PC = info.address
cpu.addBranchCycles(info)
}
}
// BPL - Branch if Positive
func (cpu *CPU) bpl(info *stepInfo) {
if cpu.N == 0 {
cpu.PC = info.address
cpu.addBranchCycles(info)
}
}
// BRK - Force Interrupt
func (cpu *CPU) brk(info *stepInfo) {
cpu.push16(cpu.PC)
cpu.php(info)
cpu.sei(info)
cpu.PC = cpu.Read16(0xFFFE)
}
// BVC - Branch if Overflow Clear
func (cpu *CPU) bvc(info *stepInfo) {
if cpu.V == 0 {
cpu.PC = info.address
cpu.addBranchCycles(info)
}
}
// BVS - Branch if Overflow Set
func (cpu *CPU) bvs(info *stepInfo) {
if cpu.V != 0 {
cpu.PC = info.address
cpu.addBranchCycles(info)
}
}
// CLC - Clear Carry Flag
func (cpu *CPU) clc(info *stepInfo) {
cpu.C = 0
}
// CLD - Clear Decimal Mode
func (cpu *CPU) cld(info *stepInfo) {
cpu.D = 0
}
// CLI - Clear Interrupt Disable
func (cpu *CPU) cli(info *stepInfo) {
cpu.I = 0
}
// CLV - Clear Overflow Flag
func (cpu *CPU) clv(info *stepInfo) {
cpu.V = 0
}
// CMP - Compare
func (cpu *CPU) cmp(info *stepInfo) {
value := cpu.Read(info.address)
cpu.compare(cpu.A, value)
}
// CPX - Compare X Register
func (cpu *CPU) cpx(info *stepInfo) {
value := cpu.Read(info.address)
cpu.compare(cpu.X, value)
}
// CPY - Compare Y Register
func (cpu *CPU) cpy(info *stepInfo) {
value := cpu.Read(info.address)
cpu.compare(cpu.Y, value)
}
// DEC - Decrement Memory
func (cpu *CPU) dec(info *stepInfo) {
value := cpu.Read(info.address) - 1
cpu.Write(info.address, value)
cpu.setZN(value)
}
// DEX - Decrement X Register
func (cpu *CPU) dex(info *stepInfo) {
cpu.X--
cpu.setZN(cpu.X)
}
// DEY - Decrement Y Register
func (cpu *CPU) dey(info *stepInfo) {
cpu.Y--
cpu.setZN(cpu.Y)
}
// EOR - Exclusive OR
func (cpu *CPU) eor(info *stepInfo) {
cpu.A = cpu.A ^ cpu.Read(info.address)
cpu.setZN(cpu.A)
}
// INC - Increment Memory
func (cpu *CPU) inc(info *stepInfo) {
value := cpu.Read(info.address) + 1
cpu.Write(info.address, value)
cpu.setZN(value)
}
// INX - Increment X Register
func (cpu *CPU) inx(info *stepInfo) {
cpu.X++
cpu.setZN(cpu.X)
}
// INY - Increment Y Register
func (cpu *CPU) iny(info *stepInfo) {
cpu.Y++
cpu.setZN(cpu.Y)
}
// JMP - Jump
func (cpu *CPU) jmp(info *stepInfo) {
cpu.PC = info.address
}
// JSR - Jump to Subroutine
func (cpu *CPU) jsr(info *stepInfo) {
cpu.push16(cpu.PC - 1)
cpu.PC = info.address
}
// LDA - Load Accumulator
func (cpu *CPU) lda(info *stepInfo) {
cpu.A = cpu.Read(info.address)
cpu.setZN(cpu.A)
}
// LDX - Load X Register
func (cpu *CPU) ldx(info *stepInfo) {
cpu.X = cpu.Read(info.address)
cpu.setZN(cpu.X)
}
// LDY - Load Y Register
func (cpu *CPU) ldy(info *stepInfo) {
cpu.Y = cpu.Read(info.address)
cpu.setZN(cpu.Y)
}
// LSR - Logical Shift Right
func (cpu *CPU) lsr(info *stepInfo) {
if info.mode == modeAccumulator {
cpu.C = cpu.A & 1
cpu.A >>= 1
cpu.setZN(cpu.A)
} else {
value := cpu.Read(info.address)
cpu.C = value & 1
value >>= 1
cpu.Write(info.address, value)
cpu.setZN(value)
}
}
// NOP - No Operation
func (cpu *CPU) nop(info *stepInfo) {
}
// ORA - Logical Inclusive OR
func (cpu *CPU) ora(info *stepInfo) {
cpu.A = cpu.A | cpu.Read(info.address)
cpu.setZN(cpu.A)
}
// PHA - Push Accumulator
func (cpu *CPU) pha(info *stepInfo) {
cpu.push(cpu.A)
}
// PHP - Push Processor Status
func (cpu *CPU) php(info *stepInfo) {
cpu.push(cpu.Flags() | 0x10)
}
// PLA - Pull Accumulator
func (cpu *CPU) pla(info *stepInfo) {
cpu.A = cpu.pull()
cpu.setZN(cpu.A)
}
// PLP - Pull Processor Status
func (cpu *CPU) plp(info *stepInfo) {
cpu.SetFlags(cpu.pull()&0xEF | 0x20)
}
// ROL - Rotate Left
func (cpu *CPU) rol(info *stepInfo) {
if info.mode == modeAccumulator {
c := cpu.C
cpu.C = (cpu.A >> 7) & 1
cpu.A = (cpu.A << 1) | c
cpu.setZN(cpu.A)
} else {
c := cpu.C
value := cpu.Read(info.address)
cpu.C = (value >> 7) & 1
value = (value << 1) | c
cpu.Write(info.address, value)
cpu.setZN(value)
}
}
// ROR - Rotate Right
func (cpu *CPU) ror(info *stepInfo) {
if info.mode == modeAccumulator {
c := cpu.C
cpu.C = cpu.A & 1
cpu.A = (cpu.A >> 1) | (c << 7)
cpu.setZN(cpu.A)
} else {
c := cpu.C
value := cpu.Read(info.address)
cpu.C = value & 1
value = (value >> 1) | (c << 7)
cpu.Write(info.address, value)
cpu.setZN(value)
}
}
// RTI - Return from Interrupt
func (cpu *CPU) rti(info *stepInfo) {
cpu.SetFlags(cpu.pull()&0xEF | 0x20)
cpu.PC = cpu.pull16()
}
// RTS - Return from Subroutine
func (cpu *CPU) rts(info *stepInfo) {
cpu.PC = cpu.pull16() + 1
}
// SBC - Subtract with Carry
func (cpu *CPU) sbc(info *stepInfo) {
a := cpu.A
b := cpu.Read(info.address)
c := cpu.C
cpu.A = a - b - (1 - c)
cpu.setZN(cpu.A)
if int(a)-int(b)-int(1-c) >= 0 {
cpu.C = 1
} else {
cpu.C = 0
}
if (a^b)&0x80 != 0 && (a^cpu.A)&0x80 != 0 {
cpu.V = 1
} else {
cpu.V = 0
}
}
// SEC - Set Carry Flag
func (cpu *CPU) sec(info *stepInfo) {
cpu.C = 1
}
// SED - Set Decimal Flag
func (cpu *CPU) sed(info *stepInfo) {
cpu.D = 1
}
// SEI - Set Interrupt Disable
func (cpu *CPU) sei(info *stepInfo) {
cpu.I = 1
}
// STA - Store Accumulator
func (cpu *CPU) sta(info *stepInfo) {
cpu.Write(info.address, cpu.A)
}
// STX - Store X Register
func (cpu *CPU) stx(info *stepInfo) {
cpu.Write(info.address, cpu.X)
}
// STY - Store Y Register
func (cpu *CPU) sty(info *stepInfo) {
cpu.Write(info.address, cpu.Y)
}
// TAX - Transfer Accumulator to X
func (cpu *CPU) tax(info *stepInfo) {
cpu.X = cpu.A
cpu.setZN(cpu.X)
}
// TAY - Transfer Accumulator to Y
func (cpu *CPU) tay(info *stepInfo) {
cpu.Y = cpu.A
cpu.setZN(cpu.Y)
}
// TSX - Transfer Stack Pointer to X
func (cpu *CPU) tsx(info *stepInfo) {
cpu.X = cpu.SP
cpu.setZN(cpu.X)
}
// TXA - Transfer X to Accumulator
func (cpu *CPU) txa(info *stepInfo) {
cpu.A = cpu.X
cpu.setZN(cpu.A)
}
// TXS - Transfer X to Stack Pointer
func (cpu *CPU) txs(info *stepInfo) {
cpu.SP = cpu.X
}
// TYA - Transfer Y to Accumulator
func (cpu *CPU) tya(info *stepInfo) {
cpu.A = cpu.Y
cpu.setZN(cpu.A)
}
// illegal opcodes below
func (cpu *CPU) ahx(info *stepInfo) {
}
func (cpu *CPU) alr(info *stepInfo) {
}
func (cpu *CPU) anc(info *stepInfo) {
}
func (cpu *CPU) arr(info *stepInfo) {
}
func (cpu *CPU) axs(info *stepInfo) {
}
func (cpu *CPU) dcp(info *stepInfo) {
}
func (cpu *CPU) isc(info *stepInfo) {
}
func (cpu *CPU) kil(info *stepInfo) {
}
func (cpu *CPU) las(info *stepInfo) {
}
func (cpu *CPU) lax(info *stepInfo) {
}
func (cpu *CPU) rla(info *stepInfo) {
}
func (cpu *CPU) rra(info *stepInfo) {
}
func (cpu *CPU) sax(info *stepInfo) {
}
func (cpu *CPU) shx(info *stepInfo) {
}
func (cpu *CPU) shy(info *stepInfo) {
}
func (cpu *CPU) slo(info *stepInfo) {
}
func (cpu *CPU) sre(info *stepInfo) {
}
func (cpu *CPU) tas(info *stepInfo) {
}
func (cpu *CPU) xaa(info *stepInfo) {
}

View file

@ -1,57 +0,0 @@
package nes
import "math"
type Filter interface {
Step(x float32) float32
}
// First order filters are defined by the following parameters.
// y[n] = B0*x[n] + B1*x[n-1] - A1*y[n-1]
type FirstOrderFilter struct {
B0 float32
B1 float32
A1 float32
prevX float32
prevY float32
}
func (f *FirstOrderFilter) Step(x float32) float32 {
y := f.B0*x + f.B1*f.prevX - f.A1*f.prevY
f.prevY = y
f.prevX = x
return y
}
// sampleRate: samples per second
// cutoffFreq: oscillations per second
func LowPassFilter(sampleRate float32, cutoffFreq float32) Filter {
c := sampleRate / math.Pi / cutoffFreq
a0i := 1 / (1 + c)
return &FirstOrderFilter{
B0: a0i,
B1: a0i,
A1: (1 - c) * a0i,
}
}
func HighPassFilter(sampleRate float32, cutoffFreq float32) Filter {
c := sampleRate / math.Pi / cutoffFreq
a0i := 1 / (1 + c)
return &FirstOrderFilter{
B0: c * a0i,
B1: -c * a0i,
A1: (1 - c) * a0i,
}
}
type FilterChain []Filter
func (fc FilterChain) Step(x float32) float32 {
if fc != nil {
for i := range fc {
x = fc[i].Step(x)
}
}
return x
}

View file

@ -1,84 +0,0 @@
package nes
import (
"encoding/binary"
"errors"
"io"
"os"
)
const iNESFileMagic = 0x1a53454e
type iNESFileHeader struct {
Magic uint32 // iNES magic number
NumPRG byte // number of PRG-ROM banks (16KB each)
NumCHR byte // number of CHR-ROM banks (8KB each)
Control1 byte // control bits
Control2 byte // control bits
NumRAM byte // PRG-RAM size (x 8KB)
_ [7]byte // unused padding
}
// LoadNESFile reads an iNES file (.nes) and returns a Cartridge on success.
// http://wiki.nesdev.com/w/index.php/INES
// http://nesdev.com/NESDoc.pdf (page 28)
func LoadNESFile(path string) (*Cartridge, error) {
// open file
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
// read file header
header := iNESFileHeader{}
if err := binary.Read(file, binary.LittleEndian, &header); err != nil {
return nil, err
}
// verify header magic number
if header.Magic != iNESFileMagic {
return nil, errors.New("invalid .nes file")
}
// mapper type
mapper1 := header.Control1 >> 4
mapper2 := header.Control2 >> 4
mapper := mapper1 | mapper2<<4
// mirroring type
mirror1 := header.Control1 & 1
mirror2 := (header.Control1 >> 3) & 1
mirror := mirror1 | mirror2<<1
// battery-backed RAM
battery := (header.Control1 >> 1) & 1
// read trainer if present (unused)
if header.Control1&4 == 4 {
trainer := make([]byte, 512)
if _, err := io.ReadFull(file, trainer); err != nil {
return nil, err
}
}
// read prg-rom bank(s)
prg := make([]byte, int(header.NumPRG)*16384)
if _, err := io.ReadFull(file, prg); err != nil {
return nil, err
}
// read chr-rom bank(s)
chr := make([]byte, int(header.NumCHR)*8192)
if _, err := io.ReadFull(file, chr); err != nil {
return nil, err
}
// provide chr-rom/ram if not in file
if header.NumCHR == 0 {
chr = make([]byte, 8192)
}
// success
return NewCartridge(prg, chr, mapper, mirror, battery), nil
}

View file

@ -1,36 +0,0 @@
package nes
import (
"encoding/gob"
"fmt"
)
type Mapper interface {
Read(address uint16) byte
Write(address uint16, value byte)
Step()
Save(encoder *gob.Encoder) error
Load(decoder *gob.Decoder) error
}
func NewMapper(console *Console) (Mapper, error) {
cartridge := console.Cartridge
switch cartridge.Mapper {
case 0:
return NewMapper2(cartridge), nil
case 1:
return NewMapper1(cartridge), nil
case 2:
return NewMapper2(cartridge), nil
case 3:
return NewMapper3(cartridge), nil
case 4:
return NewMapper4(console, cartridge), nil
case 7:
return NewMapper7(cartridge), nil
case 225:
return NewMapper225(cartridge), nil
}
err := fmt.Errorf("unsupported mapper: %d", cartridge.Mapper)
return nil, err
}

View file

@ -1,205 +0,0 @@
package nes
import (
"encoding/gob"
"log"
)
type Mapper1 struct {
*Cartridge
shiftRegister byte
control byte
prgMode byte
chrMode byte
prgBank byte
chrBank0 byte
chrBank1 byte
prgOffsets [2]int
chrOffsets [2]int
}
func NewMapper1(cartridge *Cartridge) Mapper {
m := Mapper1{}
m.Cartridge = cartridge
m.shiftRegister = 0x10
m.prgOffsets[1] = m.prgBankOffset(-1)
return &m
}
func (m *Mapper1) Save(encoder *gob.Encoder) error {
encoder.Encode(m.shiftRegister)
encoder.Encode(m.control)
encoder.Encode(m.prgMode)
encoder.Encode(m.chrMode)
encoder.Encode(m.prgBank)
encoder.Encode(m.chrBank0)
encoder.Encode(m.chrBank1)
encoder.Encode(m.prgOffsets)
encoder.Encode(m.chrOffsets)
return nil
}
func (m *Mapper1) Load(decoder *gob.Decoder) error {
decoder.Decode(&m.shiftRegister)
decoder.Decode(&m.control)
decoder.Decode(&m.prgMode)
decoder.Decode(&m.chrMode)
decoder.Decode(&m.prgBank)
decoder.Decode(&m.chrBank0)
decoder.Decode(&m.chrBank1)
decoder.Decode(&m.prgOffsets)
decoder.Decode(&m.chrOffsets)
return nil
}
func (m *Mapper1) Step() {
}
func (m *Mapper1) Read(address uint16) byte {
switch {
case address < 0x2000:
bank := address / 0x1000
offset := address % 0x1000
return m.CHR[m.chrOffsets[bank]+int(offset)]
case address >= 0x8000:
address = address - 0x8000
bank := address / 0x4000
offset := address % 0x4000
return m.PRG[m.prgOffsets[bank]+int(offset)]
case address >= 0x6000:
return m.SRAM[int(address)-0x6000]
default:
log.Fatalf("unhandled mapper1 read at address: 0x%04X", address)
}
return 0
}
func (m *Mapper1) Write(address uint16, value byte) {
switch {
case address < 0x2000:
bank := address / 0x1000
offset := address % 0x1000
m.CHR[m.chrOffsets[bank]+int(offset)] = value
case address >= 0x8000:
m.loadRegister(address, value)
case address >= 0x6000:
m.SRAM[int(address)-0x6000] = value
default:
log.Fatalf("unhandled mapper1 write at address: 0x%04X", address)
}
}
func (m *Mapper1) loadRegister(address uint16, value byte) {
if value&0x80 == 0x80 {
m.shiftRegister = 0x10
m.writeControl(m.control | 0x0C)
} else {
complete := m.shiftRegister&1 == 1
m.shiftRegister >>= 1
m.shiftRegister |= (value & 1) << 4
if complete {
m.writeRegister(address, m.shiftRegister)
m.shiftRegister = 0x10
}
}
}
func (m *Mapper1) writeRegister(address uint16, value byte) {
switch {
case address <= 0x9FFF:
m.writeControl(value)
case address <= 0xBFFF:
m.writeCHRBank0(value)
case address <= 0xDFFF:
m.writeCHRBank1(value)
case address <= 0xFFFF:
m.writePRGBank(value)
}
}
// Control (internal, $8000-$9FFF)
func (m *Mapper1) writeControl(value byte) {
m.control = value
m.chrMode = (value >> 4) & 1
m.prgMode = (value >> 2) & 3
mirror := value & 3
switch mirror {
case 0:
m.Cartridge.Mirror = MirrorSingle0
case 1:
m.Cartridge.Mirror = MirrorSingle1
case 2:
m.Cartridge.Mirror = MirrorVertical
case 3:
m.Cartridge.Mirror = MirrorHorizontal
}
m.updateOffsets()
}
// CHR bank 0 (internal, $A000-$BFFF)
func (m *Mapper1) writeCHRBank0(value byte) {
m.chrBank0 = value
m.updateOffsets()
}
// CHR bank 1 (internal, $C000-$DFFF)
func (m *Mapper1) writeCHRBank1(value byte) {
m.chrBank1 = value
m.updateOffsets()
}
// PRG bank (internal, $E000-$FFFF)
func (m *Mapper1) writePRGBank(value byte) {
m.prgBank = value & 0x0F
m.updateOffsets()
}
func (m *Mapper1) prgBankOffset(index int) int {
if index >= 0x80 {
index -= 0x100
}
index %= len(m.PRG) / 0x4000
offset := index * 0x4000
if offset < 0 {
offset += len(m.PRG)
}
return offset
}
func (m *Mapper1) chrBankOffset(index int) int {
if index >= 0x80 {
index -= 0x100
}
index %= len(m.CHR) / 0x1000
offset := index * 0x1000
if offset < 0 {
offset += len(m.CHR)
}
return offset
}
// PRG ROM bank mode (0, 1: switch 32 KB at $8000, ignoring low bit of bank number;
// 2: fix first bank at $8000 and switch 16 KB bank at $C000;
// 3: fix last bank at $C000 and switch 16 KB bank at $8000)
// CHR ROM bank mode (0: switch 8 KB at a time; 1: switch two separate 4 KB banks)
func (m *Mapper1) updateOffsets() {
switch m.prgMode {
case 0, 1:
m.prgOffsets[0] = m.prgBankOffset(int(m.prgBank & 0xFE))
m.prgOffsets[1] = m.prgBankOffset(int(m.prgBank | 0x01))
case 2:
m.prgOffsets[0] = 0
m.prgOffsets[1] = m.prgBankOffset(int(m.prgBank))
case 3:
m.prgOffsets[0] = m.prgBankOffset(int(m.prgBank))
m.prgOffsets[1] = m.prgBankOffset(-1)
}
switch m.chrMode {
case 0:
m.chrOffsets[0] = m.chrBankOffset(int(m.chrBank0 & 0xFE))
m.chrOffsets[1] = m.chrBankOffset(int(m.chrBank0 | 0x01))
case 1:
m.chrOffsets[0] = m.chrBankOffset(int(m.chrBank0))
m.chrOffsets[1] = m.chrBankOffset(int(m.chrBank1))
}
}

View file

@ -1,70 +0,0 @@
package nes
import (
"encoding/gob"
"log"
)
type Mapper2 struct {
*Cartridge
prgBanks int
prgBank1 int
prgBank2 int
}
func NewMapper2(cartridge *Cartridge) Mapper {
prgBanks := len(cartridge.PRG) / 0x4000
prgBank1 := 0
prgBank2 := prgBanks - 1
return &Mapper2{cartridge, prgBanks, prgBank1, prgBank2}
}
func (m *Mapper2) Save(encoder *gob.Encoder) error {
encoder.Encode(m.prgBanks)
encoder.Encode(m.prgBank1)
encoder.Encode(m.prgBank2)
return nil
}
func (m *Mapper2) Load(decoder *gob.Decoder) error {
decoder.Decode(&m.prgBanks)
decoder.Decode(&m.prgBank1)
decoder.Decode(&m.prgBank2)
return nil
}
func (m *Mapper2) Step() {
}
func (m *Mapper2) Read(address uint16) byte {
switch {
case address < 0x2000:
return m.CHR[address]
case address >= 0xC000:
index := m.prgBank2*0x4000 + int(address-0xC000)
return m.PRG[index]
case address >= 0x8000:
index := m.prgBank1*0x4000 + int(address-0x8000)
return m.PRG[index]
case address >= 0x6000:
index := int(address) - 0x6000
return m.SRAM[index]
default:
log.Fatalf("unhandled mapper2 read at address: 0x%04X", address)
}
return 0
}
func (m *Mapper2) Write(address uint16, value byte) {
switch {
case address < 0x2000:
m.CHR[address] = value
case address >= 0x8000:
m.prgBank1 = int(value) % m.prgBanks
case address >= 0x6000:
index := int(address) - 0x6000
m.SRAM[index] = value
default:
log.Fatalf("unhandled mapper2 write at address: 0x%04X", address)
}
}

View file

@ -1,85 +0,0 @@
package nes
import (
"encoding/gob"
"log"
)
// https://github.com/asfdfdfd/fceux/blob/master/src/boards/225.cpp
// https://wiki.nesdev.com/w/index.php/INES_Mapper_225
type Mapper225 struct {
*Cartridge
chrBank int
prgBank1 int
prgBank2 int
}
func NewMapper225(cartridge *Cartridge) Mapper {
prgBanks := len(cartridge.PRG) / 0x4000
return &Mapper225{cartridge, 0, 0, prgBanks - 1}
}
func (m *Mapper225) Save(encoder *gob.Encoder) error {
encoder.Encode(m.chrBank)
encoder.Encode(m.prgBank1)
encoder.Encode(m.prgBank2)
return nil
}
func (m *Mapper225) Load(decoder *gob.Decoder) error {
decoder.Decode(&m.chrBank)
decoder.Decode(&m.prgBank1)
decoder.Decode(&m.prgBank2)
return nil
}
func (m *Mapper225) Step() {
}
func (m *Mapper225) Read(address uint16) byte {
switch {
case address < 0x2000:
index := m.chrBank*0x2000 + int(address)
return m.CHR[index]
case address >= 0xC000:
index := m.prgBank2*0x4000 + int(address-0xC000)
return m.PRG[index]
case address >= 0x8000:
index := m.prgBank1*0x4000 + int(address-0x8000)
return m.PRG[index]
case address >= 0x6000:
index := int(address) - 0x6000
return m.SRAM[index]
default:
log.Fatalf("unhandled Mapper225 read at address: 0x%04X", address)
}
return 0
}
func (m *Mapper225) Write(address uint16, value byte) {
if (address < 0x8000) {
return
}
A := int(address)
bank := (A >> 14) & 1
m.chrBank = (A & 0x3f) | (bank << 6)
prg := ((A >> 6) & 0x3f) | (bank << 6)
mode := (A >> 12) & 1;
if (mode == 1) {
m.prgBank1 = prg
m.prgBank2 = prg
} else {
m.prgBank1 = prg
m.prgBank2 = prg + 1
}
mirr := (A >> 13) & 1
if (mirr == 1) {
m.Cartridge.Mirror = MirrorHorizontal
} else {
m.Cartridge.Mirror = MirrorVertical
}
// fmt.Println(address, mirr, mode, prg)
}

View file

@ -1,70 +0,0 @@
package nes
import (
"encoding/gob"
"log"
)
type Mapper3 struct {
*Cartridge
chrBank int
prgBank1 int
prgBank2 int
}
func NewMapper3(cartridge *Cartridge) Mapper {
prgBanks := len(cartridge.PRG) / 0x4000
return &Mapper3{cartridge, 0, 0, prgBanks - 1}
}
func (m *Mapper3) Save(encoder *gob.Encoder) error {
encoder.Encode(m.chrBank)
encoder.Encode(m.prgBank1)
encoder.Encode(m.prgBank2)
return nil
}
func (m *Mapper3) Load(decoder *gob.Decoder) error {
decoder.Decode(&m.chrBank)
decoder.Decode(&m.prgBank1)
decoder.Decode(&m.prgBank2)
return nil
}
func (m *Mapper3) Step() {
}
func (m *Mapper3) Read(address uint16) byte {
switch {
case address < 0x2000:
index := m.chrBank*0x2000 + int(address)
return m.CHR[index]
case address >= 0xC000:
index := m.prgBank2*0x4000 + int(address-0xC000)
return m.PRG[index]
case address >= 0x8000:
index := m.prgBank1*0x4000 + int(address-0x8000)
return m.PRG[index]
case address >= 0x6000:
index := int(address) - 0x6000
return m.SRAM[index]
default:
log.Fatalf("unhandled mapper3 read at address: 0x%04X", address)
}
return 0
}
func (m *Mapper3) Write(address uint16, value byte) {
switch {
case address < 0x2000:
index := m.chrBank*0x2000 + int(address)
m.CHR[index] = value
case address >= 0x8000:
m.chrBank = int(value & 3)
case address >= 0x6000:
index := int(address) - 0x6000
m.SRAM[index] = value
default:
log.Fatalf("unhandled mapper3 write at address: 0x%04X", address)
}
}

View file

@ -1,234 +0,0 @@
package nes
import (
"encoding/gob"
"log"
)
type Mapper4 struct {
*Cartridge
console *Console
register byte
registers [8]byte
prgMode byte
chrMode byte
prgOffsets [4]int
chrOffsets [8]int
reload byte
counter byte
irqEnable bool
}
func NewMapper4(console *Console, cartridge *Cartridge) Mapper {
m := Mapper4{Cartridge: cartridge, console: console}
m.prgOffsets[0] = m.prgBankOffset(0)
m.prgOffsets[1] = m.prgBankOffset(1)
m.prgOffsets[2] = m.prgBankOffset(-2)
m.prgOffsets[3] = m.prgBankOffset(-1)
return &m
}
func (m *Mapper4) Save(encoder *gob.Encoder) error {
encoder.Encode(m.register)
encoder.Encode(m.registers)
encoder.Encode(m.prgMode)
encoder.Encode(m.chrMode)
encoder.Encode(m.prgOffsets)
encoder.Encode(m.chrOffsets)
encoder.Encode(m.reload)
encoder.Encode(m.counter)
encoder.Encode(m.irqEnable)
return nil
}
func (m *Mapper4) Load(decoder *gob.Decoder) error {
decoder.Decode(&m.register)
decoder.Decode(&m.registers)
decoder.Decode(&m.prgMode)
decoder.Decode(&m.chrMode)
decoder.Decode(&m.prgOffsets)
decoder.Decode(&m.chrOffsets)
decoder.Decode(&m.reload)
decoder.Decode(&m.counter)
decoder.Decode(&m.irqEnable)
return nil
}
func (m *Mapper4) Step() {
ppu := m.console.PPU
if ppu.Cycle != 280 { // TODO: this *should* be 260
return
}
if ppu.ScanLine > 239 && ppu.ScanLine < 261 {
return
}
if ppu.flagShowBackground == 0 && ppu.flagShowSprites == 0 {
return
}
m.HandleScanLine()
}
func (m *Mapper4) HandleScanLine() {
if m.counter == 0 {
m.counter = m.reload
} else {
m.counter--
if m.counter == 0 && m.irqEnable {
m.console.CPU.triggerIRQ()
}
}
}
func (m *Mapper4) Read(address uint16) byte {
switch {
case address < 0x2000:
bank := address / 0x0400
offset := address % 0x0400
return m.CHR[m.chrOffsets[bank]+int(offset)]
case address >= 0x8000:
address = address - 0x8000
bank := address / 0x2000
offset := address % 0x2000
return m.PRG[m.prgOffsets[bank]+int(offset)]
case address >= 0x6000:
return m.SRAM[int(address)-0x6000]
default:
log.Fatalf("unhandled mapper4 read at address: 0x%04X", address)
}
return 0
}
func (m *Mapper4) Write(address uint16, value byte) {
switch {
case address < 0x2000:
bank := address / 0x0400
offset := address % 0x0400
m.CHR[m.chrOffsets[bank]+int(offset)] = value
case address >= 0x8000:
m.writeRegister(address, value)
case address >= 0x6000:
m.SRAM[int(address)-0x6000] = value
default:
log.Fatalf("unhandled mapper4 write at address: 0x%04X", address)
}
}
func (m *Mapper4) writeRegister(address uint16, value byte) {
switch {
case address <= 0x9FFF && address%2 == 0:
m.writeBankSelect(value)
case address <= 0x9FFF && address%2 == 1:
m.writeBankData(value)
case address <= 0xBFFF && address%2 == 0:
m.writeMirror(value)
case address <= 0xBFFF && address%2 == 1:
m.writeProtect(value)
case address <= 0xDFFF && address%2 == 0:
m.writeIRQLatch(value)
case address <= 0xDFFF && address%2 == 1:
m.writeIRQReload(value)
case address <= 0xFFFF && address%2 == 0:
m.writeIRQDisable(value)
case address <= 0xFFFF && address%2 == 1:
m.writeIRQEnable(value)
}
}
func (m *Mapper4) writeBankSelect(value byte) {
m.prgMode = (value >> 6) & 1
m.chrMode = (value >> 7) & 1
m.register = value & 7
m.updateOffsets()
}
func (m *Mapper4) writeBankData(value byte) {
m.registers[m.register] = value
m.updateOffsets()
}
func (m *Mapper4) writeMirror(value byte) {
switch value & 1 {
case 0:
m.Cartridge.Mirror = MirrorVertical
case 1:
m.Cartridge.Mirror = MirrorHorizontal
}
}
func (m *Mapper4) writeProtect(value byte) {
}
func (m *Mapper4) writeIRQLatch(value byte) {
m.reload = value
}
func (m *Mapper4) writeIRQReload(value byte) {
m.counter = 0
}
func (m *Mapper4) writeIRQDisable(value byte) {
m.irqEnable = false
}
func (m *Mapper4) writeIRQEnable(value byte) {
m.irqEnable = true
}
func (m *Mapper4) prgBankOffset(index int) int {
if index >= 0x80 {
index -= 0x100
}
index %= len(m.PRG) / 0x2000
offset := index * 0x2000
if offset < 0 {
offset += len(m.PRG)
}
return offset
}
func (m *Mapper4) chrBankOffset(index int) int {
if index >= 0x80 {
index -= 0x100
}
index %= len(m.CHR) / 0x0400
offset := index * 0x0400
if offset < 0 {
offset += len(m.CHR)
}
return offset
}
func (m *Mapper4) updateOffsets() {
switch m.prgMode {
case 0:
m.prgOffsets[0] = m.prgBankOffset(int(m.registers[6]))
m.prgOffsets[1] = m.prgBankOffset(int(m.registers[7]))
m.prgOffsets[2] = m.prgBankOffset(-2)
m.prgOffsets[3] = m.prgBankOffset(-1)
case 1:
m.prgOffsets[0] = m.prgBankOffset(-2)
m.prgOffsets[1] = m.prgBankOffset(int(m.registers[7]))
m.prgOffsets[2] = m.prgBankOffset(int(m.registers[6]))
m.prgOffsets[3] = m.prgBankOffset(-1)
}
switch m.chrMode {
case 0:
m.chrOffsets[0] = m.chrBankOffset(int(m.registers[0] & 0xFE))
m.chrOffsets[1] = m.chrBankOffset(int(m.registers[0] | 0x01))
m.chrOffsets[2] = m.chrBankOffset(int(m.registers[1] & 0xFE))
m.chrOffsets[3] = m.chrBankOffset(int(m.registers[1] | 0x01))
m.chrOffsets[4] = m.chrBankOffset(int(m.registers[2]))
m.chrOffsets[5] = m.chrBankOffset(int(m.registers[3]))
m.chrOffsets[6] = m.chrBankOffset(int(m.registers[4]))
m.chrOffsets[7] = m.chrBankOffset(int(m.registers[5]))
case 1:
m.chrOffsets[0] = m.chrBankOffset(int(m.registers[2]))
m.chrOffsets[1] = m.chrBankOffset(int(m.registers[3]))
m.chrOffsets[2] = m.chrBankOffset(int(m.registers[4]))
m.chrOffsets[3] = m.chrBankOffset(int(m.registers[5]))
m.chrOffsets[4] = m.chrBankOffset(int(m.registers[0] & 0xFE))
m.chrOffsets[5] = m.chrBankOffset(int(m.registers[0] | 0x01))
m.chrOffsets[6] = m.chrBankOffset(int(m.registers[1] & 0xFE))
m.chrOffsets[7] = m.chrBankOffset(int(m.registers[1] | 0x01))
}
}

View file

@ -1,64 +0,0 @@
package nes
import (
"encoding/gob"
"log"
)
type Mapper7 struct {
*Cartridge
prgBank int
}
func NewMapper7(cartridge *Cartridge) Mapper {
return &Mapper7{cartridge, 0}
}
func (m *Mapper7) Save(encoder *gob.Encoder) error {
encoder.Encode(m.prgBank)
return nil
}
func (m *Mapper7) Load(decoder *gob.Decoder) error {
decoder.Decode(&m.prgBank)
return nil
}
func (m *Mapper7) Step() {
}
func (m *Mapper7) Read(address uint16) byte {
switch {
case address < 0x2000:
return m.CHR[address]
case address >= 0x8000:
index := m.prgBank*0x8000 + int(address-0x8000)
return m.PRG[index]
case address >= 0x6000:
index := int(address) - 0x6000
return m.SRAM[index]
default:
log.Fatalf("unhandled mapper7 read at address: 0x%04X", address)
}
return 0
}
func (m *Mapper7) Write(address uint16, value byte) {
switch {
case address < 0x2000:
m.CHR[address] = value
case address >= 0x8000:
m.prgBank = int(value & 7)
switch value & 0x10 {
case 0x00:
m.Cartridge.Mirror = MirrorSingle0
case 0x10:
m.Cartridge.Mirror = MirrorSingle1
}
case address >= 0x6000:
index := int(address) - 0x6000
m.SRAM[index] = value
default:
log.Fatalf("unhandled mapper7 write at address: 0x%04X", address)
}
}

View file

@ -1,134 +0,0 @@
package nes
import "log"
type Memory interface {
Read(address uint16) byte
Write(address uint16, value byte)
}
// CPU Memory Map
type cpuMemory struct {
console *Console
}
func NewCPUMemory(console *Console) Memory {
return &cpuMemory{console}
}
func (mem *cpuMemory) Read(address uint16) byte {
switch {
case address < 0x2000:
return mem.console.RAM[address%0x0800]
case address < 0x4000:
return mem.console.PPU.readRegister(0x2000 + address%8)
case address == 0x4014:
return mem.console.PPU.readRegister(address)
case address == 0x4015:
return mem.console.APU.readRegister(address)
case address == 0x4016:
return mem.console.Controller1.Read()
case address == 0x4017:
return mem.console.Controller2.Read()
case address < 0x6000:
// TODO: I/O registers
case address >= 0x6000:
return mem.console.Mapper.Read(address)
default:
log.Fatalf("unhandled cpu memory read at address: 0x%04X", address)
}
return 0
}
func (mem *cpuMemory) Write(address uint16, value byte) {
switch {
case address < 0x2000:
mem.console.RAM[address%0x0800] = value
case address < 0x4000:
mem.console.PPU.writeRegister(0x2000+address%8, value)
case address < 0x4014:
mem.console.APU.writeRegister(address, value)
case address == 0x4014:
mem.console.PPU.writeRegister(address, value)
case address == 0x4015:
mem.console.APU.writeRegister(address, value)
case address == 0x4016:
mem.console.Controller1.Write(value)
mem.console.Controller2.Write(value)
case address == 0x4017:
mem.console.APU.writeRegister(address, value)
case address < 0x6000:
// TODO: I/O registers
case address >= 0x6000:
mem.console.Mapper.Write(address, value)
default:
log.Fatalf("unhandled cpu memory write at address: 0x%04X", address)
}
}
// PPU Memory Map
type ppuMemory struct {
console *Console
}
func NewPPUMemory(console *Console) Memory {
return &ppuMemory{console}
}
func (mem *ppuMemory) Read(address uint16) byte {
address = address % 0x4000
switch {
case address < 0x2000:
return mem.console.Mapper.Read(address)
case address < 0x3F00:
mode := mem.console.Cartridge.Mirror
return mem.console.PPU.nameTableData[MirrorAddress(mode, address)%2048]
case address < 0x4000:
return mem.console.PPU.readPalette(address % 32)
default:
log.Fatalf("unhandled ppu memory read at address: 0x%04X", address)
}
return 0
}
func (mem *ppuMemory) Write(address uint16, value byte) {
address = address % 0x4000
switch {
case address < 0x2000:
mem.console.Mapper.Write(address, value)
case address < 0x3F00:
mode := mem.console.Cartridge.Mirror
mem.console.PPU.nameTableData[MirrorAddress(mode, address)%2048] = value
case address < 0x4000:
mem.console.PPU.writePalette(address%32, value)
default:
log.Fatalf("unhandled ppu memory write at address: 0x%04X", address)
}
}
// Mirroring Modes
const (
MirrorHorizontal = 0
MirrorVertical = 1
MirrorSingle0 = 2
MirrorSingle1 = 3
MirrorFour = 4
)
var MirrorLookup = [...][4]uint16{
{0, 0, 1, 1},
{0, 1, 0, 1},
{0, 0, 0, 0},
{1, 1, 1, 1},
{0, 1, 2, 3},
}
func MirrorAddress(mode byte, address uint16) uint16 {
address = (address - 0x2000) % 0x1000
table := address / 0x0400
offset := address % 0x0400
return 0x2000 + MirrorLookup[mode][table]*0x0400 + offset
}

View file

@ -1,24 +0,0 @@
package nes
import "image/color"
var Palette [64]color.RGBA
func init() {
colors := []uint32{
0x666666, 0x002A88, 0x1412A7, 0x3B00A4, 0x5C007E, 0x6E0040, 0x6C0600, 0x561D00,
0x333500, 0x0B4800, 0x005200, 0x004F08, 0x00404D, 0x000000, 0x000000, 0x000000,
0xADADAD, 0x155FD9, 0x4240FF, 0x7527FE, 0xA01ACC, 0xB71E7B, 0xB53120, 0x994E00,
0x6B6D00, 0x388700, 0x0C9300, 0x008F32, 0x007C8D, 0x000000, 0x000000, 0x000000,
0xFFFEFF, 0x64B0FF, 0x9290FF, 0xC676FF, 0xF36AFF, 0xFE6ECC, 0xFE8170, 0xEA9E22,
0xBCBE00, 0x88D800, 0x5CE430, 0x45E082, 0x48CDDE, 0x4F4F4F, 0x000000, 0x000000,
0xFFFEFF, 0xC0DFFF, 0xD3D2FF, 0xE8C8FF, 0xFBC2FF, 0xFEC4EA, 0xFECCC5, 0xF7D8A5,
0xE4E594, 0xCFEF96, 0xBDF4AB, 0xB3F3CC, 0xB5EBF2, 0xB8B8B8, 0x000000, 0x000000,
}
for i, c := range colors {
r := byte(c >> 16)
g := byte(c >> 8)
b := byte(c)
Palette[i] = color.RGBA{r, g, b, 0xFF}
}
}

View file

@ -1,740 +0,0 @@
package nes
import (
"encoding/gob"
"image"
)
type PPU struct {
Memory // memory interface
console *Console // reference to parent object
Cycle int // 0-340
ScanLine int // 0-261, 0-239=visible, 240=post, 241-260=vblank, 261=pre
Frame uint64 // frame counter
// storage variables
paletteData [32]byte
nameTableData [2048]byte
oamData [256]byte
front *image.RGBA
back *image.RGBA
// PPU registers
v uint16 // current vram address (15 bit)
t uint16 // temporary vram address (15 bit)
x byte // fine x scroll (3 bit)
w byte // write toggle (1 bit)
f byte // even/odd frame flag (1 bit)
register byte
// NMI flags
nmiOccurred bool
nmiOutput bool
nmiPrevious bool
nmiDelay byte
// background temporary variables
nameTableByte byte
attributeTableByte byte
lowTileByte byte
highTileByte byte
tileData uint64
// sprite temporary variables
spriteCount int
spritePatterns [8]uint32
spritePositions [8]byte
spritePriorities [8]byte
spriteIndexes [8]byte
// $2000 PPUCTRL
flagNameTable byte // 0: $2000; 1: $2400; 2: $2800; 3: $2C00
flagIncrement byte // 0: add 1; 1: add 32
flagSpriteTable byte // 0: $0000; 1: $1000; ignored in 8x16 mode
flagBackgroundTable byte // 0: $0000; 1: $1000
flagSpriteSize byte // 0: 8x8; 1: 8x16
flagMasterSlave byte // 0: read EXT; 1: write EXT
// $2001 PPUMASK
flagGrayscale byte // 0: color; 1: grayscale
flagShowLeftBackground byte // 0: hide; 1: show
flagShowLeftSprites byte // 0: hide; 1: show
flagShowBackground byte // 0: hide; 1: show
flagShowSprites byte // 0: hide; 1: show
flagRedTint byte // 0: normal; 1: emphasized
flagGreenTint byte // 0: normal; 1: emphasized
flagBlueTint byte // 0: normal; 1: emphasized
// $2002 PPUSTATUS
flagSpriteZeroHit byte
flagSpriteOverflow byte
// $2003 OAMADDR
oamAddress byte
// $2007 PPUDATA
bufferedData byte // for buffered reads
}
func NewPPU(console *Console) *PPU {
ppu := PPU{Memory: NewPPUMemory(console), console: console}
ppu.front = image.NewRGBA(image.Rect(0, 0, 256, 240))
ppu.back = image.NewRGBA(image.Rect(0, 0, 256, 240))
ppu.Reset()
return &ppu
}
func (ppu *PPU) Save(encoder *gob.Encoder) error {
encoder.Encode(ppu.Cycle)
encoder.Encode(ppu.ScanLine)
encoder.Encode(ppu.Frame)
encoder.Encode(ppu.paletteData)
encoder.Encode(ppu.nameTableData)
encoder.Encode(ppu.oamData)
encoder.Encode(ppu.v)
encoder.Encode(ppu.t)
encoder.Encode(ppu.x)
encoder.Encode(ppu.w)
encoder.Encode(ppu.f)
encoder.Encode(ppu.register)
encoder.Encode(ppu.nmiOccurred)
encoder.Encode(ppu.nmiOutput)
encoder.Encode(ppu.nmiPrevious)
encoder.Encode(ppu.nmiDelay)
encoder.Encode(ppu.nameTableByte)
encoder.Encode(ppu.attributeTableByte)
encoder.Encode(ppu.lowTileByte)
encoder.Encode(ppu.highTileByte)
encoder.Encode(ppu.tileData)
encoder.Encode(ppu.spriteCount)
encoder.Encode(ppu.spritePatterns)
encoder.Encode(ppu.spritePositions)
encoder.Encode(ppu.spritePriorities)
encoder.Encode(ppu.spriteIndexes)
encoder.Encode(ppu.flagNameTable)
encoder.Encode(ppu.flagIncrement)
encoder.Encode(ppu.flagSpriteTable)
encoder.Encode(ppu.flagBackgroundTable)
encoder.Encode(ppu.flagSpriteSize)
encoder.Encode(ppu.flagMasterSlave)
encoder.Encode(ppu.flagGrayscale)
encoder.Encode(ppu.flagShowLeftBackground)
encoder.Encode(ppu.flagShowLeftSprites)
encoder.Encode(ppu.flagShowBackground)
encoder.Encode(ppu.flagShowSprites)
encoder.Encode(ppu.flagRedTint)
encoder.Encode(ppu.flagGreenTint)
encoder.Encode(ppu.flagBlueTint)
encoder.Encode(ppu.flagSpriteZeroHit)
encoder.Encode(ppu.flagSpriteOverflow)
encoder.Encode(ppu.oamAddress)
encoder.Encode(ppu.bufferedData)
return nil
}
func (ppu *PPU) Load(decoder *gob.Decoder) error {
decoder.Decode(&ppu.Cycle)
decoder.Decode(&ppu.ScanLine)
decoder.Decode(&ppu.Frame)
decoder.Decode(&ppu.paletteData)
decoder.Decode(&ppu.nameTableData)
decoder.Decode(&ppu.oamData)
decoder.Decode(&ppu.v)
decoder.Decode(&ppu.t)
decoder.Decode(&ppu.x)
decoder.Decode(&ppu.w)
decoder.Decode(&ppu.f)
decoder.Decode(&ppu.register)
decoder.Decode(&ppu.nmiOccurred)
decoder.Decode(&ppu.nmiOutput)
decoder.Decode(&ppu.nmiPrevious)
decoder.Decode(&ppu.nmiDelay)
decoder.Decode(&ppu.nameTableByte)
decoder.Decode(&ppu.attributeTableByte)
decoder.Decode(&ppu.lowTileByte)
decoder.Decode(&ppu.highTileByte)
decoder.Decode(&ppu.tileData)
decoder.Decode(&ppu.spriteCount)
decoder.Decode(&ppu.spritePatterns)
decoder.Decode(&ppu.spritePositions)
decoder.Decode(&ppu.spritePriorities)
decoder.Decode(&ppu.spriteIndexes)
decoder.Decode(&ppu.flagNameTable)
decoder.Decode(&ppu.flagIncrement)
decoder.Decode(&ppu.flagSpriteTable)
decoder.Decode(&ppu.flagBackgroundTable)
decoder.Decode(&ppu.flagSpriteSize)
decoder.Decode(&ppu.flagMasterSlave)
decoder.Decode(&ppu.flagGrayscale)
decoder.Decode(&ppu.flagShowLeftBackground)
decoder.Decode(&ppu.flagShowLeftSprites)
decoder.Decode(&ppu.flagShowBackground)
decoder.Decode(&ppu.flagShowSprites)
decoder.Decode(&ppu.flagRedTint)
decoder.Decode(&ppu.flagGreenTint)
decoder.Decode(&ppu.flagBlueTint)
decoder.Decode(&ppu.flagSpriteZeroHit)
decoder.Decode(&ppu.flagSpriteOverflow)
decoder.Decode(&ppu.oamAddress)
decoder.Decode(&ppu.bufferedData)
return nil
}
func (ppu *PPU) Reset() {
ppu.Cycle = 340
ppu.ScanLine = 240
ppu.Frame = 0
ppu.writeControl(0)
ppu.writeMask(0)
ppu.writeOAMAddress(0)
}
func (ppu *PPU) readPalette(address uint16) byte {
if address >= 16 && address%4 == 0 {
address -= 16
}
return ppu.paletteData[address]
}
func (ppu *PPU) writePalette(address uint16, value byte) {
if address >= 16 && address%4 == 0 {
address -= 16
}
ppu.paletteData[address] = value
}
func (ppu *PPU) readRegister(address uint16) byte {
switch address {
case 0x2002:
return ppu.readStatus()
case 0x2004:
return ppu.readOAMData()
case 0x2007:
return ppu.readData()
}
return 0
}
func (ppu *PPU) writeRegister(address uint16, value byte) {
ppu.register = value
switch address {
case 0x2000:
ppu.writeControl(value)
case 0x2001:
ppu.writeMask(value)
case 0x2003:
ppu.writeOAMAddress(value)
case 0x2004:
ppu.writeOAMData(value)
case 0x2005:
ppu.writeScroll(value)
case 0x2006:
ppu.writeAddress(value)
case 0x2007:
ppu.writeData(value)
case 0x4014:
ppu.writeDMA(value)
}
}
// $2000: PPUCTRL
func (ppu *PPU) writeControl(value byte) {
ppu.flagNameTable = (value >> 0) & 3
ppu.flagIncrement = (value >> 2) & 1
ppu.flagSpriteTable = (value >> 3) & 1
ppu.flagBackgroundTable = (value >> 4) & 1
ppu.flagSpriteSize = (value >> 5) & 1
ppu.flagMasterSlave = (value >> 6) & 1
ppu.nmiOutput = (value>>7)&1 == 1
ppu.nmiChange()
// t: ....BA.. ........ = d: ......BA
ppu.t = (ppu.t & 0xF3FF) | ((uint16(value) & 0x03) << 10)
}
// $2001: PPUMASK
func (ppu *PPU) writeMask(value byte) {
ppu.flagGrayscale = (value >> 0) & 1
ppu.flagShowLeftBackground = (value >> 1) & 1
ppu.flagShowLeftSprites = (value >> 2) & 1
ppu.flagShowBackground = (value >> 3) & 1
ppu.flagShowSprites = (value >> 4) & 1
ppu.flagRedTint = (value >> 5) & 1
ppu.flagGreenTint = (value >> 6) & 1
ppu.flagBlueTint = (value >> 7) & 1
}
// $2002: PPUSTATUS
func (ppu *PPU) readStatus() byte {
result := ppu.register & 0x1F
result |= ppu.flagSpriteOverflow << 5
result |= ppu.flagSpriteZeroHit << 6
if ppu.nmiOccurred {
result |= 1 << 7
}
ppu.nmiOccurred = false
ppu.nmiChange()
// w: = 0
ppu.w = 0
return result
}
// $2003: OAMADDR
func (ppu *PPU) writeOAMAddress(value byte) {
ppu.oamAddress = value
}
// $2004: OAMDATA (read)
func (ppu *PPU) readOAMData() byte {
return ppu.oamData[ppu.oamAddress]
}
// $2004: OAMDATA (write)
func (ppu *PPU) writeOAMData(value byte) {
ppu.oamData[ppu.oamAddress] = value
ppu.oamAddress++
}
// $2005: PPUSCROLL
func (ppu *PPU) writeScroll(value byte) {
if ppu.w == 0 {
// t: ........ ...HGFED = d: HGFED...
// x: CBA = d: .....CBA
// w: = 1
ppu.t = (ppu.t & 0xFFE0) | (uint16(value) >> 3)
ppu.x = value & 0x07
ppu.w = 1
} else {
// t: .CBA..HG FED..... = d: HGFEDCBA
// w: = 0
ppu.t = (ppu.t & 0x8FFF) | ((uint16(value) & 0x07) << 12)
ppu.t = (ppu.t & 0xFC1F) | ((uint16(value) & 0xF8) << 2)
ppu.w = 0
}
}
// $2006: PPUADDR
func (ppu *PPU) writeAddress(value byte) {
if ppu.w == 0 {
// t: ..FEDCBA ........ = d: ..FEDCBA
// t: .X...... ........ = 0
// w: = 1
ppu.t = (ppu.t & 0x80FF) | ((uint16(value) & 0x3F) << 8)
ppu.w = 1
} else {
// t: ........ HGFEDCBA = d: HGFEDCBA
// v = t
// w: = 0
ppu.t = (ppu.t & 0xFF00) | uint16(value)
ppu.v = ppu.t
ppu.w = 0
}
}
// $2007: PPUDATA (read)
func (ppu *PPU) readData() byte {
value := ppu.Read(ppu.v)
// emulate buffered reads
if ppu.v%0x4000 < 0x3F00 {
buffered := ppu.bufferedData
ppu.bufferedData = value
value = buffered
} else {
ppu.bufferedData = ppu.Read(ppu.v - 0x1000)
}
// increment address
if ppu.flagIncrement == 0 {
ppu.v += 1
} else {
ppu.v += 32
}
return value
}
// $2007: PPUDATA (write)
func (ppu *PPU) writeData(value byte) {
ppu.Write(ppu.v, value)
if ppu.flagIncrement == 0 {
ppu.v += 1
} else {
ppu.v += 32
}
}
// $4014: OAMDMA
func (ppu *PPU) writeDMA(value byte) {
cpu := ppu.console.CPU
address := uint16(value) << 8
for i := 0; i < 256; i++ {
ppu.oamData[ppu.oamAddress] = cpu.Read(address)
ppu.oamAddress++
address++
}
cpu.stall += 513
if cpu.Cycles%2 == 1 {
cpu.stall++
}
}
// NTSC Timing Helper Functions
func (ppu *PPU) incrementX() {
// increment hori(v)
// if coarse X == 31
if ppu.v&0x001F == 31 {
// coarse X = 0
ppu.v &= 0xFFE0
// switch horizontal nametable
ppu.v ^= 0x0400
} else {
// increment coarse X
ppu.v++
}
}
func (ppu *PPU) incrementY() {
// increment vert(v)
// if fine Y < 7
if ppu.v&0x7000 != 0x7000 {
// increment fine Y
ppu.v += 0x1000
} else {
// fine Y = 0
ppu.v &= 0x8FFF
// let y = coarse Y
y := (ppu.v & 0x03E0) >> 5
if y == 29 {
// coarse Y = 0
y = 0
// switch vertical nametable
ppu.v ^= 0x0800
} else if y == 31 {
// coarse Y = 0, nametable not switched
y = 0
} else {
// increment coarse Y
y++
}
// put coarse Y back into v
ppu.v = (ppu.v & 0xFC1F) | (y << 5)
}
}
func (ppu *PPU) copyX() {
// hori(v) = hori(t)
// v: .....F.. ...EDCBA = t: .....F.. ...EDCBA
ppu.v = (ppu.v & 0xFBE0) | (ppu.t & 0x041F)
}
func (ppu *PPU) copyY() {
// vert(v) = vert(t)
// v: .IHGF.ED CBA..... = t: .IHGF.ED CBA.....
ppu.v = (ppu.v & 0x841F) | (ppu.t & 0x7BE0)
}
func (ppu *PPU) nmiChange() {
nmi := ppu.nmiOutput && ppu.nmiOccurred
if nmi && !ppu.nmiPrevious {
// TODO: this fixes some games but the delay shouldn't have to be so
// long, so the timings are off somewhere
ppu.nmiDelay = 15
}
ppu.nmiPrevious = nmi
}
func (ppu *PPU) setVerticalBlank() {
ppu.front, ppu.back = ppu.back, ppu.front
ppu.nmiOccurred = true
ppu.nmiChange()
}
func (ppu *PPU) clearVerticalBlank() {
ppu.nmiOccurred = false
ppu.nmiChange()
}
func (ppu *PPU) fetchNameTableByte() {
v := ppu.v
address := 0x2000 | (v & 0x0FFF)
ppu.nameTableByte = ppu.Read(address)
}
func (ppu *PPU) fetchAttributeTableByte() {
v := ppu.v
address := 0x23C0 | (v & 0x0C00) | ((v >> 4) & 0x38) | ((v >> 2) & 0x07)
shift := ((v >> 4) & 4) | (v & 2)
ppu.attributeTableByte = ((ppu.Read(address) >> shift) & 3) << 2
}
func (ppu *PPU) fetchLowTileByte() {
fineY := (ppu.v >> 12) & 7
table := ppu.flagBackgroundTable
tile := ppu.nameTableByte
address := 0x1000*uint16(table) + uint16(tile)*16 + fineY
ppu.lowTileByte = ppu.Read(address)
}
func (ppu *PPU) fetchHighTileByte() {
fineY := (ppu.v >> 12) & 7
table := ppu.flagBackgroundTable
tile := ppu.nameTableByte
address := 0x1000*uint16(table) + uint16(tile)*16 + fineY
ppu.highTileByte = ppu.Read(address + 8)
}
func (ppu *PPU) storeTileData() {
var data uint32
for i := 0; i < 8; i++ {
a := ppu.attributeTableByte
p1 := (ppu.lowTileByte & 0x80) >> 7
p2 := (ppu.highTileByte & 0x80) >> 6
ppu.lowTileByte <<= 1
ppu.highTileByte <<= 1
data <<= 4
data |= uint32(a | p1 | p2)
}
ppu.tileData |= uint64(data)
}
func (ppu *PPU) fetchTileData() uint32 {
return uint32(ppu.tileData >> 32)
}
func (ppu *PPU) backgroundPixel() byte {
if ppu.flagShowBackground == 0 {
return 0
}
data := ppu.fetchTileData() >> ((7 - ppu.x) * 4)
return byte(data & 0x0F)
}
func (ppu *PPU) spritePixel() (byte, byte) {
if ppu.flagShowSprites == 0 {
return 0, 0
}
for i := 0; i < ppu.spriteCount; i++ {
offset := (ppu.Cycle - 1) - int(ppu.spritePositions[i])
if offset < 0 || offset > 7 {
continue
}
offset = 7 - offset
color := byte((ppu.spritePatterns[i] >> byte(offset*4)) & 0x0F)
if color%4 == 0 {
continue
}
return byte(i), color
}
return 0, 0
}
func (ppu *PPU) renderPixel() {
x := ppu.Cycle - 1
y := ppu.ScanLine
background := ppu.backgroundPixel()
i, sprite := ppu.spritePixel()
if x < 8 && ppu.flagShowLeftBackground == 0 {
background = 0
}
if x < 8 && ppu.flagShowLeftSprites == 0 {
sprite = 0
}
b := background%4 != 0
s := sprite%4 != 0
var color byte
if !b && !s {
color = 0
} else if !b && s {
color = sprite | 0x10
} else if b && !s {
color = background
} else {
if ppu.spriteIndexes[i] == 0 && x < 255 {
ppu.flagSpriteZeroHit = 1
}
if ppu.spritePriorities[i] == 0 {
color = sprite | 0x10
} else {
color = background
}
}
c := Palette[ppu.readPalette(uint16(color))%64]
ppu.back.SetRGBA(x, y, c)
}
func (ppu *PPU) fetchSpritePattern(i, row int) uint32 {
tile := ppu.oamData[i*4+1]
attributes := ppu.oamData[i*4+2]
var address uint16
if ppu.flagSpriteSize == 0 {
if attributes&0x80 == 0x80 {
row = 7 - row
}
table := ppu.flagSpriteTable
address = 0x1000*uint16(table) + uint16(tile)*16 + uint16(row)
} else {
if attributes&0x80 == 0x80 {
row = 15 - row
}
table := tile & 1
tile &= 0xFE
if row > 7 {
tile++
row -= 8
}
address = 0x1000*uint16(table) + uint16(tile)*16 + uint16(row)
}
a := (attributes & 3) << 2
lowTileByte := ppu.Read(address)
highTileByte := ppu.Read(address + 8)
var data uint32
for i := 0; i < 8; i++ {
var p1, p2 byte
if attributes&0x40 == 0x40 {
p1 = (lowTileByte & 1) << 0
p2 = (highTileByte & 1) << 1
lowTileByte >>= 1
highTileByte >>= 1
} else {
p1 = (lowTileByte & 0x80) >> 7
p2 = (highTileByte & 0x80) >> 6
lowTileByte <<= 1
highTileByte <<= 1
}
data <<= 4
data |= uint32(a | p1 | p2)
}
return data
}
func (ppu *PPU) evaluateSprites() {
var h int
if ppu.flagSpriteSize == 0 {
h = 8
} else {
h = 16
}
count := 0
for i := 0; i < 64; i++ {
y := ppu.oamData[i*4+0]
a := ppu.oamData[i*4+2]
x := ppu.oamData[i*4+3]
row := ppu.ScanLine - int(y)
if row < 0 || row >= h {
continue
}
if count < 8 {
ppu.spritePatterns[count] = ppu.fetchSpritePattern(i, row)
ppu.spritePositions[count] = x
ppu.spritePriorities[count] = (a >> 5) & 1
ppu.spriteIndexes[count] = byte(i)
}
count++
}
if count > 8 {
count = 8
ppu.flagSpriteOverflow = 1
}
ppu.spriteCount = count
}
// tick updates Cycle, ScanLine and Frame counters
func (ppu *PPU) tick() {
if ppu.nmiDelay > 0 {
ppu.nmiDelay--
if ppu.nmiDelay == 0 && ppu.nmiOutput && ppu.nmiOccurred {
ppu.console.CPU.triggerNMI()
}
}
if ppu.flagShowBackground != 0 || ppu.flagShowSprites != 0 {
if ppu.f == 1 && ppu.ScanLine == 261 && ppu.Cycle == 339 {
ppu.Cycle = 0
ppu.ScanLine = 0
ppu.Frame++
ppu.f ^= 1
return
}
}
ppu.Cycle++
if ppu.Cycle > 340 {
ppu.Cycle = 0
ppu.ScanLine++
if ppu.ScanLine > 261 {
ppu.ScanLine = 0
ppu.Frame++
ppu.f ^= 1
}
}
}
// Step executes a single PPU cycle
func (ppu *PPU) Step() {
ppu.tick()
renderingEnabled := ppu.flagShowBackground != 0 || ppu.flagShowSprites != 0
preLine := ppu.ScanLine == 261
visibleLine := ppu.ScanLine < 240
// postLine := ppu.ScanLine == 240
renderLine := preLine || visibleLine
preFetchCycle := ppu.Cycle >= 321 && ppu.Cycle <= 336
visibleCycle := ppu.Cycle >= 1 && ppu.Cycle <= 256
fetchCycle := preFetchCycle || visibleCycle
// background logic
if renderingEnabled {
if visibleLine && visibleCycle {
ppu.renderPixel()
}
if renderLine && fetchCycle {
ppu.tileData <<= 4
switch ppu.Cycle % 8 {
case 1:
ppu.fetchNameTableByte()
case 3:
ppu.fetchAttributeTableByte()
case 5:
ppu.fetchLowTileByte()
case 7:
ppu.fetchHighTileByte()
case 0:
ppu.storeTileData()
}
}
if preLine && ppu.Cycle >= 280 && ppu.Cycle <= 304 {
ppu.copyY()
}
if renderLine {
if fetchCycle && ppu.Cycle%8 == 0 {
ppu.incrementX()
}
if ppu.Cycle == 256 {
ppu.incrementY()
}
if ppu.Cycle == 257 {
ppu.copyX()
}
}
}
// sprite logic
if renderingEnabled {
if ppu.Cycle == 257 {
if visibleLine {
ppu.evaluateSprites()
} else {
ppu.spriteCount = 0
}
}
}
// vblank logic
if ppu.ScanLine == 241 && ppu.Cycle == 1 {
ppu.setVerticalBlank()
}
if preLine && ppu.Cycle == 1 {
ppu.clearVerticalBlank()
ppu.flagSpriteZeroHit = 0
ppu.flagSpriteOverflow = 0
}
}

View file

@ -1,9 +1,11 @@
package emulator
import "github.com/giongto35/cloud-game/config"
// CloudEmulator is the interface of cloud emulator. Currently NES emulator and RetroArch implements this in codebase
type CloudEmulator interface {
// LoadMeta returns meta data of emulator. Refer below
LoadMeta(path string) Meta
LoadMeta(path string) config.EmulatorMeta
// Start is called after LoadGame
Start()
// SaveGame save game state, saveExtraFunc is callback to do extra step. Ex: save to google cloud
@ -15,11 +17,3 @@ type CloudEmulator interface {
// Close will be called when the game is done
Close()
}
// Meta is metadata of game
type Meta struct {
AudioSampleRate int
Fps int
Width int
Height int
}

View file

@ -1,117 +0,0 @@
// credit to https://github.com/fogleman/nes
package emulator
import (
"encoding/binary"
"fmt"
"image"
"image/color"
"image/draw"
"image/gif"
"image/png"
"os"
"path"
"github.com/giongto35/cloud-game/emulator/nes"
)
func combineButtons(a, b [8]bool) [8]bool {
var result [8]bool
for i := 0; i < 8; i++ {
result[i] = a[i] || b[i]
}
return result
}
func copyImage(src image.Image) *image.RGBA {
dst := image.NewRGBA(src.Bounds())
draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src)
return dst
}
func loadPNG(path string) (image.Image, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
return png.Decode(file)
}
func savePNG(path string, im image.Image) error {
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()
return png.Encode(file, im)
}
func saveGIF(path string, frames []image.Image) error {
var palette []color.Color
for _, c := range nes.Palette {
palette = append(palette, c)
}
g := gif.GIF{}
for i, src := range frames {
if i%3 != 0 {
continue
}
dst := image.NewPaletted(src.Bounds(), palette)
draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src)
g.Image = append(g.Image, dst)
g.Delay = append(g.Delay, 5)
}
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()
return gif.EncodeAll(file, &g)
}
func screenshot(im image.Image) {
for i := 0; i < 1000; i++ {
path := fmt.Sprintf("%03d.png", i)
if _, err := os.Stat(path); os.IsNotExist(err) {
savePNG(path, im)
return
}
}
}
func animation(frames []image.Image) {
for i := 0; i < 1000; i++ {
path := fmt.Sprintf("%03d.gif", i)
if _, err := os.Stat(path); os.IsNotExist(err) {
saveGIF(path, frames)
return
}
}
}
func writeSRAM(filename string, sram []byte) error {
dir, _ := path.Split(filename)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
return binary.Write(file, binary.LittleEndian, sram)
}
func readSRAM(filename string) ([]byte, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
sram := make([]byte, 0x2000)
if err := binary.Read(file, binary.LittleEndian, sram); err != nil {
return nil, err
}
return sram, nil
}

9
encoder/type.go Normal file
View file

@ -0,0 +1,9 @@
package encoder
import "image"
type Encoder interface {
GetInputChan() chan *image.RGBA
GetOutputChan() chan []byte
Stop()
}

Binary file not shown.

10
go.mod vendored
View file

@ -3,10 +3,14 @@ module github.com/giongto35/cloud-game
go 1.12
require (
cloud.google.com/go v0.39.0
cloud.google.com/go v0.43.0
github.com/gen2brain/x264-go v0.0.0-20180306035800-58f586137654
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 // indirect
github.com/gofrs/uuid v3.2.0+incompatible
github.com/gorilla/websocket v1.4.0
github.com/pion/webrtc/v2 v2.0.23
github.com/prometheus/client_golang v0.9.3
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/pion/webrtc/v2 v2.1.2
github.com/prometheus/client_golang v1.1.0
gopkg.in/hraban/opus.v2 v2.0.0-20180426093920-0f2e0b4fc6cd
)

188
go.sum vendored
View file

@ -1,24 +1,28 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.39.0 h1:UgQP9na6OTfp4dsAiz/eFpFA1C6tPdH5wiRdi19tuMw=
cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.43.0 h1:banaiRPAM8kUVYneOSkhgcDsLzEvL25FinuiSZaH/2w=
cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gen2brain/x264-go v0.0.0-20180306035800-58f586137654 h1:RNpogAT5Qz69YLIu6+92q5sSw61PjjhDj4upH2el5pk=
github.com/gen2brain/x264-go v0.0.0-20180306035800-58f586137654/go.mod h1:17kvfYQKi9/QHiKPeqmJW0YuDPZEgy72tSBVmweSyiE=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
@ -29,29 +33,35 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/googleapis/gax-go/v2 v2.0.4 h1:hU4mGcQI4DaAYW+IbTun+2qEZVFxK0ySjQLTbS0VQKc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gortc/turn v0.7.1/go.mod h1:3FZ+LvCZKCKu6YYgwuYPqEi3FqCtdjfSFnFqVQNwfjk=
github.com/gortc/turn v0.7.3 h1:CE72C79erbcsfa6L/QDhKztcl2kDq1UK20ImrJWDt/w=
github.com/gortc/turn v0.7.3/go.mod h1:gvguwaGAFyv5/9KrcW9MkCgHALYD+e99mSM7pSCYYho=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -62,131 +72,169 @@ github.com/marten-seemann/qtls v0.2.3 h1:0yWJ43C62LsZt08vuQJDK1uC1czUc3FJeCLPoNA
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pion/datachannel v1.4.3 h1:tqS6YiqqAiFCxGGhvn1K7fHEzemK9Aov025dE/isGFo=
github.com/pion/datachannel v1.4.3/go.mod h1:SpMJbuu8v+qbA94m6lWQwSdCf8JKQvgmdSHDNtcbe+w=
github.com/pion/dtls v1.3.5 h1:mBioifvh6JSE9pD4FtJh5WoizygoqkOJNJyS5Ns+y1U=
github.com/pion/dtls v1.3.5/go.mod h1:CjlPLfQdsTg3G4AEXjJp8FY5bRweBlxHrgoFrN+fQsk=
github.com/pion/ice v0.4.0 h1:BdTXHTjzdsJHGi9yMFnj9ffgr+Kg2oHVv1qk4B0mQ8A=
github.com/pion/ice v0.4.0/go.mod h1:/gw3aFmD/pBG8UM3TcEHs6HuaOEMSd/v1As3TodE7Ss=
github.com/pion/logging v0.2.1 h1:LwASkBKZ+2ysGJ+jLv1E/9H1ge0k1nTfi1X+5zirkDk=
github.com/pion/datachannel v1.4.5 h1:paz18kYAetpTdK8tlMAtDY+Ayxrv5fndZ5XPZwiZHrU=
github.com/pion/datachannel v1.4.5/go.mod h1:SpMJbuu8v+qbA94m6lWQwSdCf8JKQvgmdSHDNtcbe+w=
github.com/pion/dtls v1.5.0 h1:GtN3DJ0Fv5wC/Y04uOXT1zPeGA4C3HrsP1aAKsIBiU8=
github.com/pion/dtls v1.5.0/go.mod h1:CjlPLfQdsTg3G4AEXjJp8FY5bRweBlxHrgoFrN+fQsk=
github.com/pion/ice v0.5.7 h1:1ZhyuFkwo0Nup7cRplz72ulghFXnZSpocD5YVJP90yg=
github.com/pion/ice v0.5.7/go.mod h1:FSTDLP+ian3PtxRjervtyDP2AOqt2c6cvfebZ7dwLnI=
github.com/pion/logging v0.2.1/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.2 h1:T22Gg4dSuYVYsZ21oRFh9z7twzAm27+5PEKiABbjCvM=
github.com/pion/mdns v0.0.2/go.mod h1:VrN3wefVgtfL8QgpEblPUC46ag1reLIfpqekCnKunLE=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.3 h1:DxdOYd0pgwLKiDlIIxfU0qdG5iWh1Xn6CsS9vc6cMAY=
github.com/pion/mdns v0.0.3/go.mod h1:VrN3wefVgtfL8QgpEblPUC46ag1reLIfpqekCnKunLE=
github.com/pion/quic v0.1.1 h1:D951FV+TOqI9A0rTF7tHx0Loooqz+nyzjEyj8o3PuMA=
github.com/pion/quic v0.1.1/go.mod h1:zEU51v7ru8Mp4AUBJvj6psrSth5eEFNnVQK5K48oV3k=
github.com/pion/rtcp v1.2.0 h1:rT2FptW5YHIern+4XlbGYnnsT26XGxurnkNLnzhtDXg=
github.com/pion/rtcp v1.2.0/go.mod h1:a5dj2d6BKIKHl43EnAOIrCczcjESrtPuMgfmL6/K6QM=
github.com/pion/rtp v1.1.2 h1:ERNugzYHW9F2ldpwoARbeFGKRoq1REe5Jxdjvm/rOx8=
github.com/pion/rtp v1.1.2/go.mod h1:/l4cvcKd0D3u9JLs2xSVI95YkfXW87a3br3nqmVtSlE=
github.com/pion/sctp v1.6.3 h1:SC4vKOjcddK8tXiTNj05a+0/GyPpCmuNfeBA/rzNFqs=
github.com/pion/rtcp v1.2.1 h1:S3yG4KpYAiSmBVqKAfgRa5JdwBNj4zK3RLUa8JYdhak=
github.com/pion/rtcp v1.2.1/go.mod h1:a5dj2d6BKIKHl43EnAOIrCczcjESrtPuMgfmL6/K6QM=
github.com/pion/rtp v1.1.3 h1:GTYSTsSLF5vH+UqShGYQEBdoYasWjTTC9UeYglnUO+o=
github.com/pion/rtp v1.1.3/go.mod h1:/l4cvcKd0D3u9JLs2xSVI95YkfXW87a3br3nqmVtSlE=
github.com/pion/sctp v1.6.3/go.mod h1:cCqpLdYvgEUdl715+qbWtgT439CuQrAgy8BZTp0aEfA=
github.com/pion/sdp/v2 v2.2.0 h1:JiixCEU8g6LbSsh1Bg5SOk0TPnJrn2HBOA1yJ+mRYhI=
github.com/pion/sdp/v2 v2.2.0/go.mod h1:idSlWxhfWQDtTy9J05cgxpHBu/POwXN2VDRGYxT/EjU=
github.com/pion/srtp v1.2.4 h1:wwGKC5ewuBukkZ+i+pZ8aO33+t6z2y/XRiYtyP0Xpv0=
github.com/pion/srtp v1.2.4/go.mod h1:52qiP0g3FVMG/5NL6Ko8Vr2qirevKH+ukYbNS/4EX40=
github.com/pion/stun v0.3.0/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M=
github.com/pion/sctp v1.6.4 h1:edUNxTabSErLWOdeUUAxds8gwx5kGnFam4zL5DWpILk=
github.com/pion/sctp v1.6.4/go.mod h1:cCqpLdYvgEUdl715+qbWtgT439CuQrAgy8BZTp0aEfA=
github.com/pion/sdp/v2 v2.3.0 h1:5EhwPh1xKWYYjjvMuubHoMLy6M0B9U26Hh7q3f7vEGk=
github.com/pion/sdp/v2 v2.3.0/go.mod h1:idSlWxhfWQDtTy9J05cgxpHBu/POwXN2VDRGYxT/EjU=
github.com/pion/srtp v1.2.6 h1:mHQuAMh0P67R7/j1F260u3O+fbRWLyjKLRPZYYvODFM=
github.com/pion/srtp v1.2.6/go.mod h1:rd8imc5htjfs99XiEoOjLMEOcVjME63UHx9Ek9IGst0=
github.com/pion/stun v0.3.1 h1:d09JJzOmOS8ZzIp8NppCMgrxGZpJ4Ix8qirfNYyI3BA=
github.com/pion/stun v0.3.1/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M=
github.com/pion/transport v0.6.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE=
github.com/pion/transport v0.7.0 h1:EsXN8TglHMlKZMo4ZGqwK6QgXBu0WYg7wfGMWIXsS+w=
github.com/pion/transport v0.7.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE=
github.com/pion/transport v0.8.0 h1:YHZnWBBrBuMqkuvMFUHeAETXS+LgfwW1IsVd2K2cyW8=
github.com/pion/transport v0.8.0/go.mod h1:nAmRRnn+ArVtsoNuwktvAD+jrjSD7pA+H3iRmZwdUno=
github.com/pion/turn v1.1.4 h1:yGxcasBvge4idNjxjowePn8oW43C4v70bXroBBKLyKY=
github.com/pion/turn v1.1.4/go.mod h1:2O2GFDGO6+hJ5gsyExDhoNHtVcacPB1NOyc81gkq0WA=
github.com/pion/turnc v0.0.6 h1:FHsmwYvdJ8mhT1/ZtWWer9L0unEb7AyRgrymfWy6mEY=
github.com/pion/turnc v0.0.6/go.mod h1:4MSFv5i0v3MRkDLdo5eF9cD/xJtj1pxSphHNnxKL2W8=
github.com/pion/webrtc/v2 v2.0.23 h1:v/tDKsP4zB6Sj+Wx861fLsaNmbwWbxacciHUhetH288=
github.com/pion/webrtc/v2 v2.0.23/go.mod h1:AgremGibyNcHWIEkDbXt4ujKzKBO3tMuoYXybVRa8zo=
github.com/pion/transport v0.8.6 h1:xHQq2mxAjB+UrFs90aUBaXwlmIACfQAZnOiVAX3uqMw=
github.com/pion/transport v0.8.6/go.mod h1:nAmRRnn+ArVtsoNuwktvAD+jrjSD7pA+H3iRmZwdUno=
github.com/pion/turn v1.3.3 h1:jO8bYTgUZ7ls6BCVa5lIQhxu56UFc5afX1A50vfxFio=
github.com/pion/turn v1.3.3/go.mod h1:zGPB7YYB/HTE9MWn0Sbznz8NtyfeVeanZ834cG/MXu0=
github.com/pion/webrtc/v2 v2.1.2 h1:5c4g01cFyCa/yLNnWt8hc6HR+GyrH0sDPAMgKW/y0OU=
github.com/pion/webrtc/v2 v2.1.2/go.mod h1:6+ovIHxDUZVgCVGP3JTEAyiy9Aa45q8u7/FebVhCy4c=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE=
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5 h1:bselrhR0Or1vomJZC8ZIjWtbDmn9OYFLX5Ik9alpJpE=
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190403144856-b630fd6fe46b/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b h1:lkjdUzSyJ5P1+eal9fxXX9Xg2BTfswsonKUse48C0uE=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/api v0.5.0 h1:lj9SyhMzyoa38fgFF0oO2T6pjs5IzkLPKfVtxpyCRMM=
google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8 h1:x913Lq/RebkvUmRSdQ8MNb0GZKn+SR1ESfoetcQSeak=
google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610 h1:Ygq9/SRJX9+dU0WCIICM8RkWvDw03lvB77hrhJnpxfU=
google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -200,3 +248,5 @@ gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

130
h264encoder/encoder.go Normal file
View file

@ -0,0 +1,130 @@
package h264encoder
import (
"bytes"
"image"
"log"
"github.com/gen2brain/x264-go"
"github.com/giongto35/cloud-game/encoder"
)
const chanSize = 2
// H264Encoder yuvI420 image to vp8 video
type H264Encoder struct {
Output chan []byte // frame
Input chan *image.RGBA // yuvI420
buf *bytes.Buffer
enc *x264.Encoder
IsRunning bool
Done bool
// C
width int
height int
fps int
}
// NewH264Encoder create h264 encoder
func NewH264Encoder(width, height, fps int) (encoder.Encoder, error) {
v := &H264Encoder{
Output: make(chan []byte, 5*chanSize),
Input: make(chan *image.RGBA, chanSize),
IsRunning: true,
Done: false,
buf: bytes.NewBuffer(make([]byte, 0)),
width: width,
height: height,
fps: fps,
}
if err := v.init(); err != nil {
return nil, err
}
return v, nil
}
func (v *H264Encoder) init() error {
v.IsRunning = true
opts := &x264.Options{
Width: v.width,
Height: v.height,
FrameRate: v.fps,
Tune: "zerolatency",
Preset: "veryfast",
Profile: "baseline",
//LogLevel: x264.LogDebug,
}
enc, err := x264.NewEncoder(v.buf, opts)
if err != nil {
panic(err)
}
v.enc = enc
go v.startLooping()
return nil
}
func (v *H264Encoder) startLooping() {
defer func() {
if r := recover(); r != nil {
log.Println("Warn: Recovered panic in encoding ", r)
}
if v.Done == true {
// The first time we see IsRunning set to false, we release and return
v.release()
return
}
}()
for img := range v.Input {
if v.Done == true {
// The first time we see IsRunning set to false, we release and return
v.release()
return
}
v.enc.Encode(img)
v.Output <- v.buf.Bytes()
v.buf.Reset()
}
}
// Release release memory and stop loop
func (v *H264Encoder) release() {
if v.IsRunning {
v.IsRunning = false
log.Println("Releasing encoder")
// TODO: Bug here, after close it will signal
close(v.Output)
err := v.enc.Close()
if err != nil {
log.Println("Failed to close H264 encoder")
}
}
// TODO: Can we merge IsRunning and Done together
}
// GetInputChan returns input channel
func (v *H264Encoder) GetInputChan() chan *image.RGBA {
return v.Input
}
// GetInputChan returns output channel
func (v *H264Encoder) GetOutputChan() chan []byte {
return v.Output
}
// GetDoneChan returns done channel
func (v *H264Encoder) Stop() {
v.Done = true
close(v.Input)
}

BIN
libretro/cores/mednafen_psx_hw_libretro.so vendored Executable file

Binary file not shown.

BIN
libretro/cores/mednafen_psx_libretro.so vendored Executable file

Binary file not shown.

BIN
libretro/cores/mednafen_snes_libretro.so vendored Executable file

Binary file not shown.

View file

@ -5,7 +5,7 @@ import (
"log"
"time"
emulator "github.com/giongto35/cloud-game/emulator"
"github.com/giongto35/cloud-game/config"
"github.com/giongto35/cloud-game/util"
)
@ -50,34 +50,30 @@ type naEmulator struct {
imageChannel chan<- *image.RGBA
audioChannel chan<- float32
inputChannel <-chan int
corePath string
gamePath string
roomID string
meta config.EmulatorMeta
gamePath string
roomID string
gameName string
isSavingLoading bool
keys []bool
done chan struct{}
meta emulator.Meta
}
var NAEmulator *naEmulator
// TODO: Load from config
var emulatorCorePath = map[string]string{
"gba": "libretro/cores/mgba_libretro.so",
//"pcsx": "libretro/cores/mednafen_psx_libretro.so",
//"pcsx": "libretro/cores/mednafen_psx_hw_libretro.so",
"pcsx": "libretro/cores/pcsx_rearmed_libretro.so",
"arcade": "libretro/cores/fbalpha2012_neogeo_libretro.so",
"mame": "libretro/cores/mame2016_libretro.so",
}
var emulatorCorePath = map[string]string{}
// NAEmulator implements CloudEmulator interface based on NanoArch(golang RetroArch)
func NewNAEmulator(etype string, roomID string, imageChannel chan<- *image.RGBA, audioChannel chan<- float32, inputChannel <-chan int) *naEmulator {
meta := config.EmulatorConfig[etype]
ewidth = meta.Width
eheight = meta.Height
return &naEmulator{
corePath: emulatorCorePath[etype],
meta: meta,
imageChannel: imageChannel,
audioChannel: audioChannel,
inputChannel: inputChannel,
@ -97,8 +93,12 @@ func (na *naEmulator) listenInput() {
// input from javascript follows bitmap. Ex: 00110101
// we decode the bitmap and send to channel
for inpBitmap := range NAEmulator.inputChannel {
for k := 0; k < len(na.keys); k++ {
key := bindRetroKeys[k]
for k := 0; k < len(bindRetroKeys); k++ {
key, ok := bindRetroKeys[k]
if ok == false {
continue
}
if (inpBitmap & 1) == 1 {
na.keys[key] = true
} else {
@ -109,8 +109,8 @@ func (na *naEmulator) listenInput() {
}
}
func (na *naEmulator) LoadMeta(path string) emulator.Meta {
coreLoad(na.corePath)
func (na *naEmulator) LoadMeta(path string) config.EmulatorMeta {
coreLoad(na.meta.Path)
coreLoadGame(path)
na.gamePath = path
@ -119,10 +119,11 @@ func (na *naEmulator) LoadMeta(path string) emulator.Meta {
func (na *naEmulator) Start() {
na.playGame(na.gamePath)
ticker := time.NewTicker(time.Second / 60)
for range ticker.C {
select {
// Slow response here
case <-na.done:
nanoarchShutdown()
log.Println("Closed Director")

View file

@ -7,14 +7,11 @@ import (
"image"
"image/color"
"log"
"math"
"os"
"os/user"
"reflect"
"sync"
"unsafe"
"github.com/giongto35/cloud-game/emulator"
)
/*
@ -60,7 +57,6 @@ var mu sync.Mutex
var video struct {
program uint32
vao uint32
texID uint32
pitch uint32
pixFmt uint32
pixType uint32
@ -71,24 +67,34 @@ var scale = 3.0
const bufSize = 1024 * 4
const joypadNumKeys = C.RETRO_DEVICE_ID_JOYPAD_R3
const joypadNumKeys = C.RETRO_DEVICE_ID_JOYPAD_R3 + 1
var joy [joypadNumKeys + 1]bool
var joy [joypadNumKeys]bool
var ewidth, eheight int
var bindRetroKeys = map[int]int{
0: C.RETRO_DEVICE_ID_JOYPAD_A,
1: C.RETRO_DEVICE_ID_JOYPAD_B,
2: C.RETRO_DEVICE_ID_JOYPAD_SELECT,
3: C.RETRO_DEVICE_ID_JOYPAD_START,
4: C.RETRO_DEVICE_ID_JOYPAD_UP,
5: C.RETRO_DEVICE_ID_JOYPAD_DOWN,
6: C.RETRO_DEVICE_ID_JOYPAD_LEFT,
7: C.RETRO_DEVICE_ID_JOYPAD_RIGHT,
2: C.RETRO_DEVICE_ID_JOYPAD_X,
3: C.RETRO_DEVICE_ID_JOYPAD_Y,
4: C.RETRO_DEVICE_ID_JOYPAD_SELECT,
5: C.RETRO_DEVICE_ID_JOYPAD_START,
6: C.RETRO_DEVICE_ID_JOYPAD_UP,
7: C.RETRO_DEVICE_ID_JOYPAD_DOWN,
8: C.RETRO_DEVICE_ID_JOYPAD_LEFT,
9: C.RETRO_DEVICE_ID_JOYPAD_RIGHT,
}
const (
// BIT_FORMAT_SHORT_5_5_5_1 has 5 bits R, 5 bits G, 5 bits B, 1 bit alpha
BIT_FORMAT_SHORT_5_5_5_1 = iota
// BIT_FORMAT_INT_8_8_8_8_REV has 8 bits R, 8 bits G, 8 bits B, 8 bit alpha
BIT_FORMAT_INT_8_8_8_8_REV
// BIT_FORMAT_SHORT_5_6_5 has 5 bits R, 6 bits G, 5 bits
BIT_FORMAT_SHORT_5_6_5
)
type CloudEmulator interface {
SetView(view *emulator.GameView)
Start(path string)
SaveGame(saveExtraFunc func() error) error
LoadGame() error
@ -111,58 +117,77 @@ func resizeToAspect(ratio float64, sw float64, sh float64) (dw float64, dh float
return
}
func videoConfigure(geom *C.struct_retro_game_geometry) (int, int) {
nwidth, nheight := resizeToAspect(float64(geom.aspect_ratio), float64(geom.base_width), float64(geom.base_height))
fmt.Println("media config", nwidth, nheight, geom.base_width, geom.base_height, geom.aspect_ratio, video.bpp, scale)
if video.texID == 0 {
fmt.Println("Failed to create the video texture")
}
video.pitch = uint32(geom.base_width) * video.bpp
return int(math.Round(nwidth)), int(math.Round(nheight))
}
//export coreVideoRefresh
func coreVideoRefresh(data unsafe.Pointer, width C.unsigned, height C.unsigned, pitch C.size_t) {
if uint32(pitch) != video.pitch {
video.pitch = uint32(pitch)
}
bytesPerRow := int(uint32(pitch) / video.bpp)
if data != nil {
NAEmulator.imageChannel <- toImageRGBA(data)
NAEmulator.imageChannel <- toImageRGBA(data, bytesPerRow, int(width), int(height))
}
}
// toImageRGBA convert nanoarch 2d array to image.RGBA
func toImageRGBA(data unsafe.Pointer) *image.RGBA {
func toImageRGBA(data unsafe.Pointer, bytesPerRow int, inputWidth, inputHeight int) *image.RGBA {
// Convert unsafe Pointer to bytes array
var bytes []byte
// TODO: Investigate this
// seems like there is a padding of slice.
// If the resolution is 240 * 160. I have to convert to 256 * 160 slice.
// If the resolution is 320 * 240. I can keep it to 320 * 240.
// I'm making assumption that the slice is packed and it has padding to fill 64
var w = 0
for w < ewidth {
w += 64
}
sh := (*reflect.SliceHeader)(unsafe.Pointer(&bytes))
sh.Data = uintptr(data)
sh.Len = w * eheight * 2
sh.Cap = w * eheight * 2
seek := 0
// Convert bytes array to image
// TODO: Reduce overhead of copying to bytes array by accessing unsafe.Pointer directly
sh := (*reflect.SliceHeader)(unsafe.Pointer(&bytes))
sh.Data = uintptr(data)
sh.Len = bytesPerRow * inputHeight * 4
sh.Cap = bytesPerRow * inputHeight * 4
if video.pixFmt == BIT_FORMAT_SHORT_5_6_5 {
return to565Image(data, bytes, bytesPerRow, inputWidth, inputHeight)
} else if video.pixFmt == BIT_FORMAT_INT_8_8_8_8_REV {
return to8888Image(data, bytes, bytesPerRow, inputWidth, inputHeight)
}
return nil
}
func to8888Image(data unsafe.Pointer, bytes []byte, bytesPerRow int, inputWidth, inputHeight int) *image.RGBA {
seek := 0
// scaleWidth and scaleHeight is the scale
scaleWidth := float64(ewidth) / float64(inputWidth)
scaleHeight := float64(eheight) / float64(inputHeight)
image := image.NewRGBA(image.Rect(0, 0, ewidth, eheight))
for y := 0; y < eheight; y++ {
for x := 0; x < w; x++ {
if x < ewidth {
for y := 0; y < inputHeight; y++ {
for x := 0; x < bytesPerRow; x++ {
xx := int(float64(x) * scaleWidth)
yy := int(float64(y) * scaleHeight)
if xx < ewidth {
b8 := bytes[seek]
g8 := bytes[seek+1]
r8 := bytes[seek+2]
a8 := bytes[seek+3]
image.Set(xx, yy, color.RGBA{byte(r8), byte(g8), byte(b8), byte(a8)})
}
seek += 4
}
}
// TODO: Resize Image
return image
}
func to565Image(data unsafe.Pointer, bytes []byte, bytesPerRow int, inputWidth, inputHeight int) *image.RGBA {
seek := 0
// scaleWidth and scaleHeight is the scale
scaleWidth := float64(ewidth) / float64(inputWidth)
scaleHeight := float64(eheight) / float64(inputHeight)
image := image.NewRGBA(image.Rect(0, 0, ewidth, eheight))
for y := 0; y < inputHeight; y++ {
for x := 0; x < bytesPerRow; x++ {
xx := int(float64(x) * scaleWidth)
yy := int(float64(y) * scaleHeight)
if xx < ewidth {
var bi int
bi = (int)(bytes[seek]) + ((int)(bytes[seek+1]) << 8)
b5 := bi & 0x1F
@ -173,20 +198,22 @@ func toImageRGBA(data unsafe.Pointer) *image.RGBA {
g8 := (g6*255 + 31) / 63
r8 := (r5*255 + 15) / 31
image.Set(x, y, color.RGBA{byte(r8), byte(g8), byte(b8), 255})
image.Set(int(float64(xx)*scaleWidth), int(float64(yy)*scaleHeight), color.RGBA{byte(r8), byte(g8), byte(b8), 255})
}
seek += 2
}
}
// TODO: Resize Image
return image
}
//export coreInputPoll
func coreInputPoll() {
for i := range NAEmulator.keys {
joy[i] = NAEmulator.keys[i]
}
//for i := range NAEmulator.keys {
//joy[i] = NAEmulator.keys[i]
//}
}
//export coreInputState
@ -195,7 +222,7 @@ func coreInputState(port C.unsigned, device C.unsigned, index C.unsigned, id C.u
return 0
}
if id < 255 && joy[id] {
if id < 255 && NAEmulator.keys[id] {
return 1
}
return 0
@ -214,7 +241,10 @@ func audioWrite2(buf unsafe.Pointer, frames C.size_t) C.size_t {
for i := 0; i < numFrames; i += 1 {
s := float32(pcm[i])
NAEmulator.audioChannel <- s
select {
case NAEmulator.audioChannel <- s:
default:
}
}
return 2 * frames
@ -261,8 +291,11 @@ func coreEnvironment(cmd C.unsigned, data unsafe.Pointer) C.bool {
if *format > C.RETRO_PIXEL_FORMAT_RGB565 {
return false
}
return true
return videoSetPixelFormat(*format)
case C.RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
path := (**C.char)(data)
*path = C.CString("./libretro/system")
return true
case C.RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
path := (**C.char)(data)
*path = C.CString(".")
@ -410,12 +443,9 @@ func coreLoadGame(filename string) {
C.bridge_retro_get_system_av_info(retroGetSystemAVInfo, &avi)
ewidth, eheight = videoConfigure(&avi.geometry)
// Append the library name to the window title.
NAEmulator.meta.AudioSampleRate = int(avi.timing.sample_rate)
NAEmulator.meta.Fps = int(avi.timing.fps)
NAEmulator.meta.Width = ewidth
NAEmulator.meta.Height = eheight
}
// serializeSize returns the amount of data the implementation requires to serialize
@ -455,3 +485,25 @@ func nanoarchShutdown() {
func nanoarchRun() {
C.bridge_retro_run(retroRun)
}
func videoSetPixelFormat(format uint32) C.bool {
switch format {
case C.RETRO_PIXEL_FORMAT_0RGB1555:
video.pixFmt = BIT_FORMAT_SHORT_5_5_5_1
video.bpp = 2
break
case C.RETRO_PIXEL_FORMAT_XRGB8888:
video.pixFmt = BIT_FORMAT_INT_8_8_8_8_REV
video.bpp = 4
break
case C.RETRO_PIXEL_FORMAT_RGB565:
video.pixFmt = BIT_FORMAT_SHORT_5_6_5
video.bpp = 2
break
default:
log.Fatalf("Unknown pixel type %v", format)
}
fmt.Printf("Video pixel: %v %v %v %v %v", video, format, C.RETRO_PIXEL_FORMAT_0RGB1555, C.RETRO_PIXEL_FORMAT_XRGB8888, C.RETRO_PIXEL_FORMAT_RGB565)
return true
}

BIN
libretro/system/scph5500.bin vendored Normal file

Binary file not shown.

BIN
libretro/system/scph5501.bin vendored Normal file

Binary file not shown.

BIN
libretro/system/scph5502.bin vendored Normal file

Binary file not shown.

View file

@ -1,32 +0,0 @@
package gamelist
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
)
// getGameList returns list of games stored in games
// TODO: change to class
func GetGameList(gamePath string) []string {
var games []string
filepath.Walk(gamePath, func(path string, info os.FileInfo, err error) error {
if info != nil && !info.IsDir() {
// Remove prefix to obtain file names
path = path[len(gamePath)+1:]
// Add to games list
games = append(games, path)
}
return nil
})
return games
}
// GetEncodedGameList returns game list in encoded wspacket format
func GetEncodedGameList(gamePath string) string {
encodedList, _ := json.Marshal(GetGameList(gamePath))
fmt.Println(encodedList)
return string(encodedList)
}

View file

@ -13,8 +13,8 @@ import (
"github.com/giongto35/cloud-game/config"
"github.com/giongto35/cloud-game/cws"
"github.com/giongto35/cloud-game/overlord/gamelist"
"github.com/giongto35/cloud-game/util"
"github.com/giongto35/cloud-game/util/gamelist"
"github.com/gofrs/uuid"
"github.com/gorilla/websocket"
)
@ -25,6 +25,7 @@ const (
)
type Server struct {
// roomToServer map roomID to workerID
roomToServer map[string]string
// workerClients are the map serverID to worker Client
workerClients map[string]*WorkerClient
@ -91,7 +92,7 @@ func (o *Server) WSO(w http.ResponseWriter, r *http.Request) {
return
}
}
client := NewWorkerClient(c, serverID, address)
client := NewWorkerClient(c, serverID, address, fmt.Sprintf(config.StunTurnTemplate, address, address))
o.workerClients[serverID] = client
defer o.cleanConnection(client, serverID)
@ -160,8 +161,8 @@ func (o *Server) WS(w http.ResponseWriter, r *http.Request) {
wssession.RouteBrowser()
wssession.BrowserClient.Send(cws.WSPacket{
ID: "gamelist",
Data: gamelist.GetEncodedGameList(gamePath),
ID: "init",
Data: createInitPackage(o.workerClients[serverID].StunTurnServer),
}, nil)
// If peerconnection is done (client.Done is signalled), we close peerconnection
@ -272,6 +273,8 @@ func getLatencyMapFromBrowser(workerClients map[string]*WorkerClient, client *Br
return latencyMap
}
// cleanConnection is called when a worker is disconnected
// connection from worker (client) to server is also closed
func (o *Server) cleanConnection(client *WorkerClient, serverID string) {
log.Println("Unregister server from overlord")
// Remove serverID from servers
@ -285,3 +288,16 @@ func (o *Server) cleanConnection(client *WorkerClient, serverID string) {
client.Close()
}
// createInitPackage returns serverhost + game list in encoded wspacket format
// This package will be sent to initialize
func createInitPackage(stunturn string) string {
var gameName []string
for _, game := range gamelist.GameList {
gameName = append(gameName, game.Name)
}
initPackage := append([]string{stunturn}, gameName...)
encodedList, _ := json.Marshal(initPackage)
return string(encodedList)
}

View file

@ -9,9 +9,10 @@ import (
type WorkerClient struct {
*cws.Client
ServerID string
Address string
IsAvailable bool
ServerID string
Address string
StunTurnServer string
IsAvailable bool
}
// RouteWorker are all routes server received from worker
@ -42,11 +43,12 @@ func (o *Server) RouteWorker(workerClient *WorkerClient) {
}
// NewWorkerClient returns a client connecting to worker. This connection exchanges information between workers and server
func NewWorkerClient(c *websocket.Conn, serverID string, address string) *WorkerClient {
func NewWorkerClient(c *websocket.Conn, serverID string, address string, stunturn string) *WorkerClient {
return &WorkerClient{
Client: cws.NewClient(c),
ServerID: serverID,
Address: address,
IsAvailable: true,
Client: cws.NewClient(c),
ServerID: serverID,
Address: address,
StunTurnServer: stunturn,
IsAvailable: true,
}
}

8
run_local.sh vendored
View file

@ -1,8 +0,0 @@
#!/bin/bash
go build -o klog ./cmd
# Run coordinator first
./klog -overlordhost overlord &
# Wait till overlord finish initialized
# Run a worker connecting to overlord
./klog -overlordhost ws://localhost:8000/wso
# NOTE: Overlord and worker should be run separately. Local is for demo purpose

6
run_local_docker.sh vendored
View file

@ -1,6 +0,0 @@
#!/bin/bash
docker build . -t cloud-game-local
docker stop cloud-game-local
docker rm cloud-game-local
# Overlord and worker should be run separately. Local is for demo purpose
docker run --privileged -v $PWD/games:/cloud-game/games -d --name cloud-game-local -p 8000:8000 -p 9000:9000 cloud-game-local bash -c "cmd -overlordhost ws://localhost:8000/wso & cmd -overlordhost overlord"

51
static/css/main.css vendored
View file

@ -209,13 +209,13 @@ body {
}
#btn-load {
top: 30px;
top: 20px;
left: 435px;
background-color: #f5a54b;
}
#btn-save {
top: 70px;
top: 60px;
left: 435px;
background-color: #b844ee;
}
@ -236,23 +236,20 @@ body {
width: 55px;
}
#btn-select {
top: 0;
left: 50%;
transform: translateX(-50%);
-webkit-transform: translateX(-50%);
-moz-transform: translateX(-50%);
background-color: #6fa8eb;
background-color: #F7EDE3;
top: 100px;
left: 425px;
border-radius: 25px 0 0 25px;
width: 55px;
}
#btn-start {
top: 50%;
left: 0;
transform: translateY(-50%);
-webkit-transform: translateY(-50%);
-moz-transform: translateY(-50%);
background-color: #43be85;
background-color: #F7EDE3;
top: 100px;
left: 490px;
border-radius: 0px 25px 25px 0px;
width: 55px;
}
#btn-a {
@ -274,6 +271,25 @@ body {
background-color: #d4b95e;
}
#btn-x {
top: 0;
left: 50%;
transform: translateX(-50%);
-webkit-transform: translateX(-50%);
-moz-transform: translateX(-50%);
background-color: #6fa8eb;
}
#btn-y {
top: 50%;
left: 0;
transform: translateY(-50%);
-webkit-transform: translateY(-50%);
-moz-transform: translateY(-50%);
background-color: #43be85;
}
#lights-holder {
display: block;
@ -392,7 +408,7 @@ body {
height: 240px;
/* background-color: gray; */
background-image: url('/static/img/screen_background1.gif');
background-image: url('/static/img/screen_background5.png');
background-size: cover;
}
@ -402,7 +418,7 @@ body {
width: 100%;
height: 36px;
background-color: lightgreen;
background-color: #FFCF9E;
opacity: 0.75;
top: 50%;
@ -463,6 +479,7 @@ body {
text-overflow: ellipsis;
white-space: pre;
text-align: center;
color: white;
}
.menu-item div .pick {

21
static/game.html vendored
View file

@ -33,8 +33,8 @@
There is still audio because current audio flow is not from media but it is manually encoded (technical webRTC challenge). Later, when we can integrate audio to media, we can face the issue with mute again .
https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
-->
<!--<video id="game-screen" autoplay=true muted poster="/static/img/screen_loading.gif"></video>-->
<video id="game-screen" autoplay=true poster="/static/img/screen_loading.gif"></video>
<video id="game-screen" muted playinfullscreen="false" playsinline poster="/static/img/screen_loading.gif"></video>
<!--<video id="game-screen" autoplay=true poster="/static/img/screen_loading.gif"></video>-->
<div id="menu-screen">
<div id="menu-container">
@ -49,13 +49,15 @@
<div id="btn-save" unselectable="on" class="btn big unselectable" value="save">save</div>
<div id="btn-join" unselectable="on" class="btn big unselectable" value="join">play</div>
<div id="btn-quit" unselectable="on" class="btn big unselectable" value="quit">quit</div>
<div id="btn-select" unselectable="on" class="btn big unselectable" value="select">Select</div>
<div id="btn-start" unselectable="on" class="btn big unselectable" value="start">Start</div>
<div id="color-button-holder">
<div id="btn-select" unselectable="on" class="btn unselectable" value="select">Select</div>
<div id="btn-start" unselectable="on" class="btn unselectable" value="start">Start</div>
<div id="btn-a" unselectable="on" class="btn unselectable" value="a">A</div>
<div id="btn-b" unselectable="on" class="btn unselectable" value="b">B</div>
<div id="btn-x" unselectable="on" class="btn unselectable" value="x">X</div>
<div id="btn-y" unselectable="on" class="btn unselectable" value="y">Y</div>
</div>
<div id="lights-holder">
@ -95,9 +97,18 @@
<script src="/static/js/gesture_keyboard.js?1"></script>
<script src="/static/js/gesture_touch.js?1"></script>
<script src="/static/js/gesture_joystick.js?1"></script>
<script src="/static/js/ws.js?4"></script>
<script src="/static/js/ws.js?5"></script>
<script src="/static/js/init.js?1"></script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-145078282-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-145078282-1');
</script>
</body>

BIN
static/img/screen_background4.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

BIN
static/img/screen_background5.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View file

@ -37,14 +37,14 @@ function reloadGameMenu() {
// sort gameList first
gameList.sort(function (a, b) {
return a.name > b.name ? 1 : -1;
return a > b ? 1 : -1;
});
// generate html
var listbox = $("#menu-container");
listbox.html('');
gameList.forEach(function (game) {
listbox.append(`<div class="menu-item unselectable" unselectable="on"><div><span>${game.name}</span></div></div>`);
listbox.append(`<div class="menu-item unselectable" unselectable="on"><div><span>${game}</span></div></div>`);
});
}
@ -88,7 +88,7 @@ function pickGame(idx) {
$(`.menu-item:eq(${idx}) span`).addClass("pick");
gameIdx = idx;
log(`> [Pick] game ${gameIdx + 1}/${gameList.length} - ${gameList[gameIdx].name}`);
log(`> [Pick] game ${gameIdx + 1}/${gameList.length} - ${gameList[gameIdx]}`);
}
@ -129,9 +129,9 @@ function sendKeyState() {
console.log(`Key state string: ${bits} ==> ${data}`);
// send packed keystate
var arrBuf = new Uint8Array(1);
arrBuf[0] = data;
var arrBuf = new Uint8Array(2);
arrBuf[0] = data & ((1 << 8) - 1);
arrBuf[1] = data >> 8;
inputChannel.send(arrBuf);
unchangePacket--;
@ -193,6 +193,8 @@ function doButtonUp(name) {
case "join":
case "a":
case "b":
case "x":
case "y":
case "start":
case "select":
startGame();

View file

@ -10,8 +10,10 @@ const KEYBOARD_MAP = {
90: "a", // z
88: "b", // x
67: "start", // c
86: "select", // v
67: "x", // c
86: "y", // v
13: "start", // start
16: "select", // select
// non-game
81: "quit", // q

4
static/js/global.js vendored
View file

@ -3,7 +3,7 @@
*/
const DEBUG = false;
const KEY_BIT = ["a", "b", "select", "start", "up", "down", "left", "right"];
const KEY_BIT = ["a", "b", "x", "y", "select", "start", "up", "down", "left", "right"];
const INPUT_FPS = 100;
const INPUT_STATE_PACKET = 1;
const PINGPONGPS = 5;
@ -29,6 +29,8 @@ let keyState = {
// control
a: false,
b: false,
x: false,
y: false,
start: false,
select: false,

68
static/js/ws.js vendored
View file

@ -24,14 +24,19 @@ conn.onmessage = e => {
d = JSON.parse(e.data);
switch (d["id"]) {
case "gamelist":
// parse files list to gamelist
files = JSON.parse(d["data"]);
case "init":
// TODO: Read from struct
// init package has 2 part [stunturn, gamelist]
// The first element is stunturn address
// The rest are list of game
data = JSON.parse(d["data"]);
stunturn = data[0]
startWebRTC(stunturn);
data.shift()
gameList = [];
files.forEach(file => {
var file = file
var name = file.substr(0, file.indexOf('.'));
gameList.push({file: file, name: name});
data.forEach(name => {
gameList.push(name);
});
log("Received game list");
@ -56,7 +61,7 @@ conn.onmessage = e => {
// TODO: Calc time
break;
case "start":
roomID = d["room_id"];
roomID = d["room_id"];
log(`Got start with room id: ${roomID}`);
popup("Started! You can share you game!")
saveRoomID(roomID);
@ -102,8 +107,8 @@ conn.onmessage = e => {
console.log(latenciesMap)
conn.send(JSON.stringify({"id": "checkLatency", "data": JSON.stringify(latenciesMap), "packet_id": latencyPacketID}));
startWebRTC();
}
//startWebRTC();
}
}
xmlHttp.onload = () => {
cntResp++;
@ -116,8 +121,8 @@ conn.onmessage = e => {
//conn.send(JSON.stringify({"id": "checkLatency", "data": latenciesMap, "packet_id": latencyPacketID}));
conn.send(JSON.stringify({"id": "checkLatency", "data": JSON.stringify(latenciesMap), "packet_id": latencyPacketID}));
startWebRTC();
}
//startWebRTC();
}
}
xmlHttp.send( null );
}
@ -141,14 +146,10 @@ function sendPing() {
conn.send(JSON.stringify({"id": "heartbeat", "data": Date.now().toString()}));
}
function startWebRTC() {
function startWebRTC(iceservers) {
log(`received stunturn from worker ${iceservers}`)
// webrtc
var iceservers = [];
if (STUNTURN == "") {
iceservers = defaultICE
} else {
iceservers = JSON.parse(STUNTURN);
}
iceservers = JSON.parse(iceservers);
pc = new RTCPeerConnection({iceServers: iceservers });
// input channel, ordered + reliable, id 0
@ -197,18 +198,6 @@ function startWebRTC() {
// video channel
pc.ontrack = function (event) {
stream.addTrack(event.track);
var promise = document.getElementById("game-screen").play();
if (promise !== undefined) {
promise.then(_ => {
console.log("Media can autoplay")
}).catch(error => {
// Usually error happens when we autoplay unmuted video, browser requires manual play.
// We already muted video and use separate audio encoding so it's fine now
console.log("Media Failed to autoplay")
console.log(error)
// TODO: Consider workaround
});
}
}
@ -261,14 +250,27 @@ function startGame() {
popup("Game is not ready yet. Please wait");
return false;
}
var promise = document.getElementById("game-screen").play();
if (promise !== undefined) {
promise.then(_ => {
console.log("Media can autoplay")
}).catch(error => {
// Usually error happens when we autoplay unmuted video, browser requires manual play.
// We already muted video and use separate audio encoding so it's fine now
console.log("Media Failed to autoplay")
console.log(error)
// TODO: Consider workaround
});
}
if (screenState != "menu") {
return false;
}
log("Starting game screen");
screenState = "game";
// conn.send(JSON.stringify({"id": "start", "data": gameList[gameIdx].file, "room_id": $("#room-txt").val(), "player_index": parseInt(playerIndex.value, 10)}));
conn.send(JSON.stringify({"id": "start", "data": gameList[gameIdx].file, "room_id": roomID != null ? roomID : '', "player_index": 1}));
conn.send(JSON.stringify({"id": "start", "data": gameList[gameIdx], "room_id": roomID != null ? roomID : '', "player_index": 1}));
// clear menu screen
stopGameInputTimer();

70
util/gamelist/games.go Normal file
View file

@ -0,0 +1,70 @@
package gamelist
import (
"os"
"path/filepath"
"strings"
"github.com/giongto35/cloud-game/config"
)
type GameInfo struct {
Name string
Path string
Type string
}
const gamePath = "games"
var GameList []GameInfo
func init() {
GameList = getAllGames(gamePath)
}
// GetGameInfoFromName returns game info from a gameName
func GetGameInfoFromName(name string) GameInfo {
for _, game := range GameList {
if game.Name == name {
return game
}
}
return GameInfo{}
}
// getAllGames returns list of games stored in games. This call should be called when server start (package init)
// TODO: Maybe later we need to make realtime update without server restart
func getAllGames(gamePath string) []GameInfo {
var games []GameInfo
filepath.Walk(gamePath, func(path string, info os.FileInfo, err error) error {
if info != nil && !info.IsDir() && isValidGameType(path) {
// Add to games list
gameInfo := getGameInfo(path)
games = append(games, gameInfo)
}
return nil
})
return games
}
// isValidGameType check if a game is valid for cloud retro based on extension
func isValidGameType(gamePath string) bool {
ext := filepath.Ext(gamePath)[1:]
_, ok := config.FileTypeToEmulator[ext]
return ok
}
// getGameInfo returns game info from a path
func getGameInfo(path string) GameInfo {
// Remove prefix to obtain file names
fileName := filepath.Base(path)
ext := filepath.Ext(fileName)
return GameInfo{
Name: strings.TrimSuffix(fileName, ext),
Type: ext[1:],
Path: path,
}
}

202
vendor/cloud.google.com/go/LICENSE generated vendored Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,513 @@
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package metadata provides access to Google Compute Engine (GCE)
// metadata and API service accounts.
//
// This package is a wrapper around the GCE metadata service,
// as documented at https://developers.google.com/compute/docs/metadata.
package metadata // import "cloud.google.com/go/compute/metadata"
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"runtime"
"strings"
"sync"
"time"
)
const (
// metadataIP is the documented metadata server IP address.
metadataIP = "169.254.169.254"
// metadataHostEnv is the environment variable specifying the
// GCE metadata hostname. If empty, the default value of
// metadataIP ("169.254.169.254") is used instead.
// This is variable name is not defined by any spec, as far as
// I know; it was made up for the Go package.
metadataHostEnv = "GCE_METADATA_HOST"
userAgent = "gcloud-golang/0.1"
)
type cachedValue struct {
k string
trim bool
mu sync.Mutex
v string
}
var (
projID = &cachedValue{k: "project/project-id", trim: true}
projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
instID = &cachedValue{k: "instance/id", trim: true}
)
var (
defaultClient = &Client{hc: &http.Client{
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: 2 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
ResponseHeaderTimeout: 2 * time.Second,
},
}}
subscribeClient = &Client{hc: &http.Client{
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: 2 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
},
}}
)
// NotDefinedError is returned when requested metadata is not defined.
//
// The underlying string is the suffix after "/computeMetadata/v1/".
//
// This error is not returned if the value is defined to be the empty
// string.
type NotDefinedError string
func (suffix NotDefinedError) Error() string {
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
}
func (c *cachedValue) get(cl *Client) (v string, err error) {
defer c.mu.Unlock()
c.mu.Lock()
if c.v != "" {
return c.v, nil
}
if c.trim {
v, err = cl.getTrimmed(c.k)
} else {
v, err = cl.Get(c.k)
}
if err == nil {
c.v = v
}
return
}
var (
onGCEOnce sync.Once
onGCE bool
)
// OnGCE reports whether this process is running on Google Compute Engine.
func OnGCE() bool {
onGCEOnce.Do(initOnGCE)
return onGCE
}
func initOnGCE() {
onGCE = testOnGCE()
}
func testOnGCE() bool {
// The user explicitly said they're on GCE, so trust them.
if os.Getenv(metadataHostEnv) != "" {
return true
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
resc := make(chan bool, 2)
// Try two strategies in parallel.
// See https://github.com/googleapis/google-cloud-go/issues/194
go func() {
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
req.Header.Set("User-Agent", userAgent)
res, err := defaultClient.hc.Do(req.WithContext(ctx))
if err != nil {
resc <- false
return
}
defer res.Body.Close()
resc <- res.Header.Get("Metadata-Flavor") == "Google"
}()
go func() {
addrs, err := net.LookupHost("metadata.google.internal")
if err != nil || len(addrs) == 0 {
resc <- false
return
}
resc <- strsContains(addrs, metadataIP)
}()
tryHarder := systemInfoSuggestsGCE()
if tryHarder {
res := <-resc
if res {
// The first strategy succeeded, so let's use it.
return true
}
// Wait for either the DNS or metadata server probe to
// contradict the other one and say we are running on
// GCE. Give it a lot of time to do so, since the system
// info already suggests we're running on a GCE BIOS.
timer := time.NewTimer(5 * time.Second)
defer timer.Stop()
select {
case res = <-resc:
return res
case <-timer.C:
// Too slow. Who knows what this system is.
return false
}
}
// There's no hint from the system info that we're running on
// GCE, so use the first probe's result as truth, whether it's
// true or false. The goal here is to optimize for speed for
// users who are NOT running on GCE. We can't assume that
// either a DNS lookup or an HTTP request to a blackholed IP
// address is fast. Worst case this should return when the
// metaClient's Transport.ResponseHeaderTimeout or
// Transport.Dial.Timeout fires (in two seconds).
return <-resc
}
// systemInfoSuggestsGCE reports whether the local system (without
// doing network requests) suggests that we're running on GCE. If this
// returns true, testOnGCE tries a bit harder to reach its metadata
// server.
func systemInfoSuggestsGCE() bool {
if runtime.GOOS != "linux" {
// We don't have any non-Linux clues available, at least yet.
return false
}
slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name")
name := strings.TrimSpace(string(slurp))
return name == "Google" || name == "Google Compute Engine"
}
// Subscribe calls Client.Subscribe on a client designed for subscribing (one with no
// ResponseHeaderTimeout).
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
return subscribeClient.Subscribe(suffix, fn)
}
// Get calls Client.Get on the default client.
func Get(suffix string) (string, error) { return defaultClient.Get(suffix) }
// ProjectID returns the current instance's project ID string.
func ProjectID() (string, error) { return defaultClient.ProjectID() }
// NumericProjectID returns the current instance's numeric project ID.
func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() }
// InternalIP returns the instance's primary internal IP address.
func InternalIP() (string, error) { return defaultClient.InternalIP() }
// ExternalIP returns the instance's primary external (public) IP address.
func ExternalIP() (string, error) { return defaultClient.ExternalIP() }
// Hostname returns the instance's hostname. This will be of the form
// "<instanceID>.c.<projID>.internal".
func Hostname() (string, error) { return defaultClient.Hostname() }
// InstanceTags returns the list of user-defined instance tags,
// assigned when initially creating a GCE instance.
func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() }
// InstanceID returns the current VM's numeric instance ID.
func InstanceID() (string, error) { return defaultClient.InstanceID() }
// InstanceName returns the current VM's instance ID string.
func InstanceName() (string, error) { return defaultClient.InstanceName() }
// Zone returns the current VM's zone, such as "us-central1-b".
func Zone() (string, error) { return defaultClient.Zone() }
// InstanceAttributes calls Client.InstanceAttributes on the default client.
func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() }
// ProjectAttributes calls Client.ProjectAttributes on the default client.
func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() }
// InstanceAttributeValue calls Client.InstanceAttributeValue on the default client.
func InstanceAttributeValue(attr string) (string, error) {
return defaultClient.InstanceAttributeValue(attr)
}
// ProjectAttributeValue calls Client.ProjectAttributeValue on the default client.
func ProjectAttributeValue(attr string) (string, error) {
return defaultClient.ProjectAttributeValue(attr)
}
// Scopes calls Client.Scopes on the default client.
func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) }
func strsContains(ss []string, s string) bool {
for _, v := range ss {
if v == s {
return true
}
}
return false
}
// A Client provides metadata.
type Client struct {
hc *http.Client
}
// NewClient returns a Client that can be used to fetch metadata. All HTTP requests
// will use the given http.Client instead of the default client.
func NewClient(c *http.Client) *Client {
return &Client{hc: c}
}
// getETag returns a value from the metadata service as well as the associated ETag.
// This func is otherwise equivalent to Get.
func (c *Client) getETag(suffix string) (value, etag string, err error) {
// Using a fixed IP makes it very difficult to spoof the metadata service in
// a container, which is an important use-case for local testing of cloud
// deployments. To enable spoofing of the metadata service, the environment
// variable GCE_METADATA_HOST is first inspected to decide where metadata
// requests shall go.
host := os.Getenv(metadataHostEnv)
if host == "" {
// Using 169.254.169.254 instead of "metadata" here because Go
// binaries built with the "netgo" tag and without cgo won't
// know the search suffix for "metadata" is
// ".google.internal", and this IP address is documented as
// being stable anyway.
host = metadataIP
}
u := "http://" + host + "/computeMetadata/v1/" + suffix
req, _ := http.NewRequest("GET", u, nil)
req.Header.Set("Metadata-Flavor", "Google")
req.Header.Set("User-Agent", userAgent)
res, err := c.hc.Do(req)
if err != nil {
return "", "", err
}
defer res.Body.Close()
if res.StatusCode == http.StatusNotFound {
return "", "", NotDefinedError(suffix)
}
all, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", err
}
if res.StatusCode != 200 {
return "", "", &Error{Code: res.StatusCode, Message: string(all)}
}
return string(all), res.Header.Get("Etag"), nil
}
// Get returns a value from the metadata service.
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
//
// If the GCE_METADATA_HOST environment variable is not defined, a default of
// 169.254.169.254 will be used instead.
//
// If the requested metadata is not defined, the returned error will
// be of type NotDefinedError.
func (c *Client) Get(suffix string) (string, error) {
val, _, err := c.getETag(suffix)
return val, err
}
func (c *Client) getTrimmed(suffix string) (s string, err error) {
s, err = c.Get(suffix)
s = strings.TrimSpace(s)
return
}
func (c *Client) lines(suffix string) ([]string, error) {
j, err := c.Get(suffix)
if err != nil {
return nil, err
}
s := strings.Split(strings.TrimSpace(j), "\n")
for i := range s {
s[i] = strings.TrimSpace(s[i])
}
return s, nil
}
// ProjectID returns the current instance's project ID string.
func (c *Client) ProjectID() (string, error) { return projID.get(c) }
// NumericProjectID returns the current instance's numeric project ID.
func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) }
// InstanceID returns the current VM's numeric instance ID.
func (c *Client) InstanceID() (string, error) { return instID.get(c) }
// InternalIP returns the instance's primary internal IP address.
func (c *Client) InternalIP() (string, error) {
return c.getTrimmed("instance/network-interfaces/0/ip")
}
// ExternalIP returns the instance's primary external (public) IP address.
func (c *Client) ExternalIP() (string, error) {
return c.getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
}
// Hostname returns the instance's hostname. This will be of the form
// "<instanceID>.c.<projID>.internal".
func (c *Client) Hostname() (string, error) {
return c.getTrimmed("instance/hostname")
}
// InstanceTags returns the list of user-defined instance tags,
// assigned when initially creating a GCE instance.
func (c *Client) InstanceTags() ([]string, error) {
var s []string
j, err := c.Get("instance/tags")
if err != nil {
return nil, err
}
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
return nil, err
}
return s, nil
}
// InstanceName returns the current VM's instance ID string.
func (c *Client) InstanceName() (string, error) {
host, err := c.Hostname()
if err != nil {
return "", err
}
return strings.Split(host, ".")[0], nil
}
// Zone returns the current VM's zone, such as "us-central1-b".
func (c *Client) Zone() (string, error) {
zone, err := c.getTrimmed("instance/zone")
// zone is of the form "projects/<projNum>/zones/<zoneName>".
if err != nil {
return "", err
}
return zone[strings.LastIndex(zone, "/")+1:], nil
}
// InstanceAttributes returns the list of user-defined attributes,
// assigned when initially creating a GCE VM instance. The value of an
// attribute can be obtained with InstanceAttributeValue.
func (c *Client) InstanceAttributes() ([]string, error) { return c.lines("instance/attributes/") }
// ProjectAttributes returns the list of user-defined attributes
// applying to the project as a whole, not just this VM. The value of
// an attribute can be obtained with ProjectAttributeValue.
func (c *Client) ProjectAttributes() ([]string, error) { return c.lines("project/attributes/") }
// InstanceAttributeValue returns the value of the provided VM
// instance attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// InstanceAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func (c *Client) InstanceAttributeValue(attr string) (string, error) {
return c.Get("instance/attributes/" + attr)
}
// ProjectAttributeValue returns the value of the provided
// project attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// ProjectAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func (c *Client) ProjectAttributeValue(attr string) (string, error) {
return c.Get("project/attributes/" + attr)
}
// Scopes returns the service account scopes for the given account.
// The account may be empty or the string "default" to use the instance's
// main account.
func (c *Client) Scopes(serviceAccount string) ([]string, error) {
if serviceAccount == "" {
serviceAccount = "default"
}
return c.lines("instance/service-accounts/" + serviceAccount + "/scopes")
}
// Subscribe subscribes to a value from the metadata service.
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
// The suffix may contain query parameters.
//
// Subscribe calls fn with the latest metadata value indicated by the provided
// suffix. If the metadata value is deleted, fn is called with the empty string
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
// is deleted. Subscribe returns the error value returned from the last call to
// fn, which may be nil when ok == false.
func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error {
const failedSubscribeSleep = time.Second * 5
// First check to see if the metadata value exists at all.
val, lastETag, err := c.getETag(suffix)
if err != nil {
return err
}
if err := fn(val, true); err != nil {
return err
}
ok := true
if strings.ContainsRune(suffix, '?') {
suffix += "&wait_for_change=true&last_etag="
} else {
suffix += "?wait_for_change=true&last_etag="
}
for {
val, etag, err := c.getETag(suffix + url.QueryEscape(lastETag))
if err != nil {
if _, deleted := err.(NotDefinedError); !deleted {
time.Sleep(failedSubscribeSleep)
continue // Retry on other errors.
}
ok = false
}
lastETag = etag
if err := fn(val, ok); err != nil || !ok {
return err
}
}
}
// Error contains an error response from the server.
type Error struct {
// Code is the HTTP response status code.
Code int
// Message is the server response message.
Message string
}
func (e *Error) Error() string {
return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message)
}

315
vendor/cloud.google.com/go/iam/iam.go generated Normal file
View file

@ -0,0 +1,315 @@
// Copyright 2016 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package iam supports the resource-specific operations of Google Cloud
// IAM (Identity and Access Management) for the Google Cloud Libraries.
// See https://cloud.google.com/iam for more about IAM.
//
// Users of the Google Cloud Libraries will typically not use this package
// directly. Instead they will begin with some resource that supports IAM, like
// a pubsub topic, and call its IAM method to get a Handle for that resource.
package iam
import (
"context"
"fmt"
"time"
gax "github.com/googleapis/gax-go/v2"
pb "google.golang.org/genproto/googleapis/iam/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
)
// client abstracts the IAMPolicy API to allow multiple implementations.
type client interface {
Get(ctx context.Context, resource string) (*pb.Policy, error)
Set(ctx context.Context, resource string, p *pb.Policy) error
Test(ctx context.Context, resource string, perms []string) ([]string, error)
}
// grpcClient implements client for the standard gRPC-based IAMPolicy service.
type grpcClient struct {
c pb.IAMPolicyClient
}
var withRetry = gax.WithRetry(func() gax.Retryer {
return gax.OnCodes([]codes.Code{
codes.DeadlineExceeded,
codes.Unavailable,
}, gax.Backoff{
Initial: 100 * time.Millisecond,
Max: 60 * time.Second,
Multiplier: 1.3,
})
})
func (g *grpcClient) Get(ctx context.Context, resource string) (*pb.Policy, error) {
var proto *pb.Policy
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
ctx = insertMetadata(ctx, md)
err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
var err error
proto, err = g.c.GetIamPolicy(ctx, &pb.GetIamPolicyRequest{Resource: resource})
return err
}, withRetry)
if err != nil {
return nil, err
}
return proto, nil
}
func (g *grpcClient) Set(ctx context.Context, resource string, p *pb.Policy) error {
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
ctx = insertMetadata(ctx, md)
return gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
_, err := g.c.SetIamPolicy(ctx, &pb.SetIamPolicyRequest{
Resource: resource,
Policy: p,
})
return err
}, withRetry)
}
func (g *grpcClient) Test(ctx context.Context, resource string, perms []string) ([]string, error) {
var res *pb.TestIamPermissionsResponse
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
ctx = insertMetadata(ctx, md)
err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
var err error
res, err = g.c.TestIamPermissions(ctx, &pb.TestIamPermissionsRequest{
Resource: resource,
Permissions: perms,
})
return err
}, withRetry)
if err != nil {
return nil, err
}
return res.Permissions, nil
}
// A Handle provides IAM operations for a resource.
type Handle struct {
c client
resource string
}
// InternalNewHandle is for use by the Google Cloud Libraries only.
//
// InternalNewHandle returns a Handle for resource.
// The conn parameter refers to a server that must support the IAMPolicy service.
func InternalNewHandle(conn *grpc.ClientConn, resource string) *Handle {
return InternalNewHandleGRPCClient(pb.NewIAMPolicyClient(conn), resource)
}
// InternalNewHandleGRPCClient is for use by the Google Cloud Libraries only.
//
// InternalNewHandleClient returns a Handle for resource using the given
// grpc service that implements IAM as a mixin
func InternalNewHandleGRPCClient(c pb.IAMPolicyClient, resource string) *Handle {
return InternalNewHandleClient(&grpcClient{c: c}, resource)
}
// InternalNewHandleClient is for use by the Google Cloud Libraries only.
//
// InternalNewHandleClient returns a Handle for resource using the given
// client implementation.
func InternalNewHandleClient(c client, resource string) *Handle {
return &Handle{
c: c,
resource: resource,
}
}
// Policy retrieves the IAM policy for the resource.
func (h *Handle) Policy(ctx context.Context) (*Policy, error) {
proto, err := h.c.Get(ctx, h.resource)
if err != nil {
return nil, err
}
return &Policy{InternalProto: proto}, nil
}
// SetPolicy replaces the resource's current policy with the supplied Policy.
//
// If policy was created from a prior call to Get, then the modification will
// only succeed if the policy has not changed since the Get.
func (h *Handle) SetPolicy(ctx context.Context, policy *Policy) error {
return h.c.Set(ctx, h.resource, policy.InternalProto)
}
// TestPermissions returns the subset of permissions that the caller has on the resource.
func (h *Handle) TestPermissions(ctx context.Context, permissions []string) ([]string, error) {
return h.c.Test(ctx, h.resource, permissions)
}
// A RoleName is a name representing a collection of permissions.
type RoleName string
// Common role names.
const (
Owner RoleName = "roles/owner"
Editor RoleName = "roles/editor"
Viewer RoleName = "roles/viewer"
)
const (
// AllUsers is a special member that denotes all users, even unauthenticated ones.
AllUsers = "allUsers"
// AllAuthenticatedUsers is a special member that denotes all authenticated users.
AllAuthenticatedUsers = "allAuthenticatedUsers"
)
// A Policy is a list of Bindings representing roles
// granted to members.
//
// The zero Policy is a valid policy with no bindings.
type Policy struct {
// TODO(jba): when type aliases are available, put Policy into an internal package
// and provide an exported alias here.
// This field is exported for use by the Google Cloud Libraries only.
// It may become unexported in a future release.
InternalProto *pb.Policy
}
// Members returns the list of members with the supplied role.
// The return value should not be modified. Use Add and Remove
// to modify the members of a role.
func (p *Policy) Members(r RoleName) []string {
b := p.binding(r)
if b == nil {
return nil
}
return b.Members
}
// HasRole reports whether member has role r.
func (p *Policy) HasRole(member string, r RoleName) bool {
return memberIndex(member, p.binding(r)) >= 0
}
// Add adds member member to role r if it is not already present.
// A new binding is created if there is no binding for the role.
func (p *Policy) Add(member string, r RoleName) {
b := p.binding(r)
if b == nil {
if p.InternalProto == nil {
p.InternalProto = &pb.Policy{}
}
p.InternalProto.Bindings = append(p.InternalProto.Bindings, &pb.Binding{
Role: string(r),
Members: []string{member},
})
return
}
if memberIndex(member, b) < 0 {
b.Members = append(b.Members, member)
return
}
}
// Remove removes member from role r if it is present.
func (p *Policy) Remove(member string, r RoleName) {
bi := p.bindingIndex(r)
if bi < 0 {
return
}
bindings := p.InternalProto.Bindings
b := bindings[bi]
mi := memberIndex(member, b)
if mi < 0 {
return
}
// Order doesn't matter for bindings or members, so to remove, move the last item
// into the removed spot and shrink the slice.
if len(b.Members) == 1 {
// Remove binding.
last := len(bindings) - 1
bindings[bi] = bindings[last]
bindings[last] = nil
p.InternalProto.Bindings = bindings[:last]
return
}
// Remove member.
// TODO(jba): worry about multiple copies of m?
last := len(b.Members) - 1
b.Members[mi] = b.Members[last]
b.Members[last] = ""
b.Members = b.Members[:last]
}
// Roles returns the names of all the roles that appear in the Policy.
func (p *Policy) Roles() []RoleName {
if p.InternalProto == nil {
return nil
}
var rns []RoleName
for _, b := range p.InternalProto.Bindings {
rns = append(rns, RoleName(b.Role))
}
return rns
}
// binding returns the Binding for the suppied role, or nil if there isn't one.
func (p *Policy) binding(r RoleName) *pb.Binding {
i := p.bindingIndex(r)
if i < 0 {
return nil
}
return p.InternalProto.Bindings[i]
}
func (p *Policy) bindingIndex(r RoleName) int {
if p.InternalProto == nil {
return -1
}
for i, b := range p.InternalProto.Bindings {
if b.Role == string(r) {
return i
}
}
return -1
}
// memberIndex returns the index of m in b's Members, or -1 if not found.
func memberIndex(m string, b *pb.Binding) int {
if b == nil {
return -1
}
for i, mm := range b.Members {
if mm == m {
return i
}
}
return -1
}
// insertMetadata inserts metadata into the given context
func insertMetadata(ctx context.Context, mds ...metadata.MD) context.Context {
out, _ := metadata.FromOutgoingContext(ctx)
out = out.Copy()
for _, md := range mds {
for k, v := range md {
out[k] = append(out[k], v...)
}
}
return metadata.NewOutgoingContext(ctx, out)
}

View file

@ -0,0 +1,54 @@
// Copyright 2017 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package internal
import (
"fmt"
"google.golang.org/api/googleapi"
"google.golang.org/grpc/status"
)
// Annotate prepends msg to the error message in err, attempting
// to preserve other information in err, like an error code.
//
// Annotate panics if err is nil.
//
// Annotate knows about these error types:
// - "google.golang.org/grpc/status".Status
// - "google.golang.org/api/googleapi".Error
// If the error is not one of these types, Annotate behaves
// like
// fmt.Errorf("%s: %v", msg, err)
func Annotate(err error, msg string) error {
if err == nil {
panic("Annotate called with nil")
}
if s, ok := status.FromError(err); ok {
p := s.Proto()
p.Message = msg + ": " + p.Message
return status.ErrorProto(p)
}
if g, ok := err.(*googleapi.Error); ok {
g.Message = msg + ": " + g.Message
return g
}
return fmt.Errorf("%s: %v", msg, err)
}
// Annotatef uses format and args to format a string, then calls Annotate.
func Annotatef(err error, format string, args ...interface{}) error {
return Annotate(err, fmt.Sprintf(format, args...))
}

View file

@ -0,0 +1,108 @@
// Copyright 2016 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package optional provides versions of primitive types that can
// be nil. These are useful in methods that update some of an API object's
// fields.
package optional
import (
"fmt"
"strings"
"time"
)
type (
// Bool is either a bool or nil.
Bool interface{}
// String is either a string or nil.
String interface{}
// Int is either an int or nil.
Int interface{}
// Uint is either a uint or nil.
Uint interface{}
// Float64 is either a float64 or nil.
Float64 interface{}
// Duration is either a time.Duration or nil.
Duration interface{}
)
// ToBool returns its argument as a bool.
// It panics if its argument is nil or not a bool.
func ToBool(v Bool) bool {
x, ok := v.(bool)
if !ok {
doPanic("Bool", v)
}
return x
}
// ToString returns its argument as a string.
// It panics if its argument is nil or not a string.
func ToString(v String) string {
x, ok := v.(string)
if !ok {
doPanic("String", v)
}
return x
}
// ToInt returns its argument as an int.
// It panics if its argument is nil or not an int.
func ToInt(v Int) int {
x, ok := v.(int)
if !ok {
doPanic("Int", v)
}
return x
}
// ToUint returns its argument as a uint.
// It panics if its argument is nil or not a uint.
func ToUint(v Uint) uint {
x, ok := v.(uint)
if !ok {
doPanic("Uint", v)
}
return x
}
// ToFloat64 returns its argument as a float64.
// It panics if its argument is nil or not a float64.
func ToFloat64(v Float64) float64 {
x, ok := v.(float64)
if !ok {
doPanic("Float64", v)
}
return x
}
// ToDuration returns its argument as a time.Duration.
// It panics if its argument is nil or not a time.Duration.
func ToDuration(v Duration) time.Duration {
x, ok := v.(time.Duration)
if !ok {
doPanic("Duration", v)
}
return x
}
func doPanic(capType string, v interface{}) {
panic(fmt.Sprintf("optional.%s value should be %s, got %T", capType, strings.ToLower(capType), v))
}

View file

@ -0,0 +1,54 @@
// Copyright 2016 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package internal
import (
"context"
"time"
gax "github.com/googleapis/gax-go/v2"
)
// Retry calls the supplied function f repeatedly according to the provided
// backoff parameters. It returns when one of the following occurs:
// When f's first return value is true, Retry immediately returns with f's second
// return value.
// When the provided context is done, Retry returns with an error that
// includes both ctx.Error() and the last error returned by f.
func Retry(ctx context.Context, bo gax.Backoff, f func() (stop bool, err error)) error {
return retry(ctx, bo, f, gax.Sleep)
}
func retry(ctx context.Context, bo gax.Backoff, f func() (stop bool, err error),
sleep func(context.Context, time.Duration) error) error {
var lastErr error
for {
stop, err := f()
if stop {
return err
}
// Remember the last "real" error from f.
if err != nil && err != context.Canceled && err != context.DeadlineExceeded {
lastErr = err
}
p := bo.Pause()
if cerr := sleep(ctx, p); cerr != nil {
if lastErr != nil {
return Annotatef(lastErr, "retry failed with %v; last error", cerr)
}
return cerr
}
}
}

View file

@ -0,0 +1,109 @@
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package trace
import (
"context"
"fmt"
"go.opencensus.io/trace"
"google.golang.org/api/googleapi"
"google.golang.org/genproto/googleapis/rpc/code"
"google.golang.org/grpc/status"
)
// StartSpan adds a span to the trace with the given name.
func StartSpan(ctx context.Context, name string) context.Context {
ctx, _ = trace.StartSpan(ctx, name)
return ctx
}
// EndSpan ends a span with the given error.
func EndSpan(ctx context.Context, err error) {
span := trace.FromContext(ctx)
if err != nil {
span.SetStatus(toStatus(err))
}
span.End()
}
// toStatus interrogates an error and converts it to an appropriate
// OpenCensus status.
func toStatus(err error) trace.Status {
if err2, ok := err.(*googleapi.Error); ok {
return trace.Status{Code: httpStatusCodeToOCCode(err2.Code), Message: err2.Message}
} else if s, ok := status.FromError(err); ok {
return trace.Status{Code: int32(s.Code()), Message: s.Message()}
} else {
return trace.Status{Code: int32(code.Code_UNKNOWN), Message: err.Error()}
}
}
// TODO(deklerk): switch to using OpenCensus function when it becomes available.
// Reference: https://github.com/googleapis/googleapis/blob/26b634d2724ac5dd30ae0b0cbfb01f07f2e4050e/google/rpc/code.proto
func httpStatusCodeToOCCode(httpStatusCode int) int32 {
switch httpStatusCode {
case 200:
return int32(code.Code_OK)
case 499:
return int32(code.Code_CANCELLED)
case 500:
return int32(code.Code_UNKNOWN) // Could also be Code_INTERNAL, Code_DATA_LOSS
case 400:
return int32(code.Code_INVALID_ARGUMENT) // Could also be Code_OUT_OF_RANGE
case 504:
return int32(code.Code_DEADLINE_EXCEEDED)
case 404:
return int32(code.Code_NOT_FOUND)
case 409:
return int32(code.Code_ALREADY_EXISTS) // Could also be Code_ABORTED
case 403:
return int32(code.Code_PERMISSION_DENIED)
case 401:
return int32(code.Code_UNAUTHENTICATED)
case 429:
return int32(code.Code_RESOURCE_EXHAUSTED)
case 501:
return int32(code.Code_UNIMPLEMENTED)
case 503:
return int32(code.Code_UNAVAILABLE)
default:
return int32(code.Code_UNKNOWN)
}
}
// TODO: (odeke-em): perhaps just pass around spans due to the cost
// incurred from using trace.FromContext(ctx) yet we could avoid
// throwing away the work done by ctx, span := trace.StartSpan.
func TracePrintf(ctx context.Context, attrMap map[string]interface{}, format string, args ...interface{}) {
var attrs []trace.Attribute
for k, v := range attrMap {
var a trace.Attribute
switch v := v.(type) {
case string:
a = trace.StringAttribute(k, v)
case bool:
a = trace.BoolAttribute(k, v)
case int:
a = trace.Int64Attribute(k, int64(v))
case int64:
a = trace.Int64Attribute(k, v)
default:
a = trace.StringAttribute(k, fmt.Sprintf("%#v", v))
}
attrs = append(attrs, a)
}
trace.FromContext(ctx).Annotatef(attrs, format, args...)
}

View file

@ -0,0 +1,6 @@
#!/bin/bash
today=$(date +%Y%m%d)
sed -i -r -e 's/const Repo = "([0-9]{8})"/const Repo = "'$today'"/' $GOFILE

View file

@ -0,0 +1,71 @@
// Copyright 2016 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:generate ./update_version.sh
// Package version contains version information for Google Cloud Client
// Libraries for Go, as reported in request headers.
package version
import (
"runtime"
"strings"
"unicode"
)
// Repo is the current version of the client libraries in this
// repo. It should be a date in YYYYMMDD format.
const Repo = "20180226"
// Go returns the Go runtime version. The returned string
// has no whitespace.
func Go() string {
return goVersion
}
var goVersion = goVer(runtime.Version())
const develPrefix = "devel +"
func goVer(s string) string {
if strings.HasPrefix(s, develPrefix) {
s = s[len(develPrefix):]
if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
s = s[:p]
}
return s
}
if strings.HasPrefix(s, "go1") {
s = s[2:]
var prerelease string
if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
s, prerelease = s[:p], s[p:]
}
if strings.HasSuffix(s, ".") {
s += "0"
} else if strings.Count(s, ".") < 2 {
s += ".0"
}
if prerelease != "" {
s += "-" + prerelease
}
return s
}
return ""
}
func notSemverRune(r rune) bool {
return !strings.ContainsRune("0123456789.", r)
}

32
vendor/cloud.google.com/go/storage/README.md generated vendored Normal file
View file

@ -0,0 +1,32 @@
## Cloud Storage [![GoDoc](https://godoc.org/cloud.google.com/go/storage?status.svg)](https://godoc.org/cloud.google.com/go/storage)
- [About Cloud Storage](https://cloud.google.com/storage/)
- [API documentation](https://cloud.google.com/storage/docs)
- [Go client documentation](https://godoc.org/cloud.google.com/go/storage)
- [Complete sample programs](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/storage)
### Example Usage
First create a `storage.Client` to use throughout your application:
[snip]:# (storage-1)
```go
client, err := storage.NewClient(ctx)
if err != nil {
log.Fatal(err)
}
```
[snip]:# (storage-2)
```go
// Read the object1 from bucket.
rc, err := client.Bucket("bucket").Object("object1").NewReader(ctx)
if err != nil {
log.Fatal(err)
}
defer rc.Close()
body, err := ioutil.ReadAll(rc)
if err != nil {
log.Fatal(err)
}
```

335
vendor/cloud.google.com/go/storage/acl.go generated Normal file
View file

@ -0,0 +1,335 @@
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package storage
import (
"context"
"net/http"
"reflect"
"cloud.google.com/go/internal/trace"
"google.golang.org/api/googleapi"
raw "google.golang.org/api/storage/v1"
)
// ACLRole is the level of access to grant.
type ACLRole string
const (
RoleOwner ACLRole = "OWNER"
RoleReader ACLRole = "READER"
RoleWriter ACLRole = "WRITER"
)
// ACLEntity refers to a user or group.
// They are sometimes referred to as grantees.
//
// It could be in the form of:
// "user-<userId>", "user-<email>", "group-<groupId>", "group-<email>",
// "domain-<domain>" and "project-team-<projectId>".
//
// Or one of the predefined constants: AllUsers, AllAuthenticatedUsers.
type ACLEntity string
const (
AllUsers ACLEntity = "allUsers"
AllAuthenticatedUsers ACLEntity = "allAuthenticatedUsers"
)
// ACLRule represents a grant for a role to an entity (user, group or team) for a
// Google Cloud Storage object or bucket.
type ACLRule struct {
Entity ACLEntity
EntityID string
Role ACLRole
Domain string
Email string
ProjectTeam *ProjectTeam
}
// ProjectTeam is the project team associated with the entity, if any.
type ProjectTeam struct {
ProjectNumber string
Team string
}
// ACLHandle provides operations on an access control list for a Google Cloud Storage bucket or object.
type ACLHandle struct {
c *Client
bucket string
object string
isDefault bool
userProject string // for requester-pays buckets
}
// Delete permanently deletes the ACL entry for the given entity.
func (a *ACLHandle) Delete(ctx context.Context, entity ACLEntity) (err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.ACL.Delete")
defer func() { trace.EndSpan(ctx, err) }()
if a.object != "" {
return a.objectDelete(ctx, entity)
}
if a.isDefault {
return a.bucketDefaultDelete(ctx, entity)
}
return a.bucketDelete(ctx, entity)
}
// Set sets the role for the given entity.
func (a *ACLHandle) Set(ctx context.Context, entity ACLEntity, role ACLRole) (err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.ACL.Set")
defer func() { trace.EndSpan(ctx, err) }()
if a.object != "" {
return a.objectSet(ctx, entity, role, false)
}
if a.isDefault {
return a.objectSet(ctx, entity, role, true)
}
return a.bucketSet(ctx, entity, role)
}
// List retrieves ACL entries.
func (a *ACLHandle) List(ctx context.Context) (rules []ACLRule, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.ACL.List")
defer func() { trace.EndSpan(ctx, err) }()
if a.object != "" {
return a.objectList(ctx)
}
if a.isDefault {
return a.bucketDefaultList(ctx)
}
return a.bucketList(ctx)
}
func (a *ACLHandle) bucketDefaultList(ctx context.Context) ([]ACLRule, error) {
var acls *raw.ObjectAccessControls
var err error
err = runWithRetry(ctx, func() error {
req := a.c.raw.DefaultObjectAccessControls.List(a.bucket)
a.configureCall(ctx, req)
acls, err = req.Do()
return err
})
if err != nil {
return nil, err
}
return toObjectACLRules(acls.Items), nil
}
func (a *ACLHandle) bucketDefaultDelete(ctx context.Context, entity ACLEntity) error {
return runWithRetry(ctx, func() error {
req := a.c.raw.DefaultObjectAccessControls.Delete(a.bucket, string(entity))
a.configureCall(ctx, req)
return req.Do()
})
}
func (a *ACLHandle) bucketList(ctx context.Context) ([]ACLRule, error) {
var acls *raw.BucketAccessControls
var err error
err = runWithRetry(ctx, func() error {
req := a.c.raw.BucketAccessControls.List(a.bucket)
a.configureCall(ctx, req)
acls, err = req.Do()
return err
})
if err != nil {
return nil, err
}
return toBucketACLRules(acls.Items), nil
}
func (a *ACLHandle) bucketSet(ctx context.Context, entity ACLEntity, role ACLRole) error {
acl := &raw.BucketAccessControl{
Bucket: a.bucket,
Entity: string(entity),
Role: string(role),
}
err := runWithRetry(ctx, func() error {
req := a.c.raw.BucketAccessControls.Update(a.bucket, string(entity), acl)
a.configureCall(ctx, req)
_, err := req.Do()
return err
})
if err != nil {
return err
}
return nil
}
func (a *ACLHandle) bucketDelete(ctx context.Context, entity ACLEntity) error {
return runWithRetry(ctx, func() error {
req := a.c.raw.BucketAccessControls.Delete(a.bucket, string(entity))
a.configureCall(ctx, req)
return req.Do()
})
}
func (a *ACLHandle) objectList(ctx context.Context) ([]ACLRule, error) {
var acls *raw.ObjectAccessControls
var err error
err = runWithRetry(ctx, func() error {
req := a.c.raw.ObjectAccessControls.List(a.bucket, a.object)
a.configureCall(ctx, req)
acls, err = req.Do()
return err
})
if err != nil {
return nil, err
}
return toObjectACLRules(acls.Items), nil
}
func (a *ACLHandle) objectSet(ctx context.Context, entity ACLEntity, role ACLRole, isBucketDefault bool) error {
type setRequest interface {
Do(opts ...googleapi.CallOption) (*raw.ObjectAccessControl, error)
Header() http.Header
}
acl := &raw.ObjectAccessControl{
Bucket: a.bucket,
Entity: string(entity),
Role: string(role),
}
var req setRequest
if isBucketDefault {
req = a.c.raw.DefaultObjectAccessControls.Update(a.bucket, string(entity), acl)
} else {
req = a.c.raw.ObjectAccessControls.Update(a.bucket, a.object, string(entity), acl)
}
a.configureCall(ctx, req)
return runWithRetry(ctx, func() error {
_, err := req.Do()
return err
})
}
func (a *ACLHandle) objectDelete(ctx context.Context, entity ACLEntity) error {
return runWithRetry(ctx, func() error {
req := a.c.raw.ObjectAccessControls.Delete(a.bucket, a.object, string(entity))
a.configureCall(ctx, req)
return req.Do()
})
}
func (a *ACLHandle) configureCall(ctx context.Context, call interface{ Header() http.Header }) {
vc := reflect.ValueOf(call)
vc.MethodByName("Context").Call([]reflect.Value{reflect.ValueOf(ctx)})
if a.userProject != "" {
vc.MethodByName("UserProject").Call([]reflect.Value{reflect.ValueOf(a.userProject)})
}
setClientHeader(call.Header())
}
func toObjectACLRules(items []*raw.ObjectAccessControl) []ACLRule {
var rs []ACLRule
for _, item := range items {
rs = append(rs, toObjectACLRule(item))
}
return rs
}
func toBucketACLRules(items []*raw.BucketAccessControl) []ACLRule {
var rs []ACLRule
for _, item := range items {
rs = append(rs, toBucketACLRule(item))
}
return rs
}
func toObjectACLRule(a *raw.ObjectAccessControl) ACLRule {
return ACLRule{
Entity: ACLEntity(a.Entity),
EntityID: a.EntityId,
Role: ACLRole(a.Role),
Domain: a.Domain,
Email: a.Email,
ProjectTeam: toObjectProjectTeam(a.ProjectTeam),
}
}
func toBucketACLRule(a *raw.BucketAccessControl) ACLRule {
return ACLRule{
Entity: ACLEntity(a.Entity),
EntityID: a.EntityId,
Role: ACLRole(a.Role),
Domain: a.Domain,
Email: a.Email,
ProjectTeam: toBucketProjectTeam(a.ProjectTeam),
}
}
func toRawObjectACL(rules []ACLRule) []*raw.ObjectAccessControl {
if len(rules) == 0 {
return nil
}
r := make([]*raw.ObjectAccessControl, 0, len(rules))
for _, rule := range rules {
r = append(r, rule.toRawObjectAccessControl("")) // bucket name unnecessary
}
return r
}
func toRawBucketACL(rules []ACLRule) []*raw.BucketAccessControl {
if len(rules) == 0 {
return nil
}
r := make([]*raw.BucketAccessControl, 0, len(rules))
for _, rule := range rules {
r = append(r, rule.toRawBucketAccessControl("")) // bucket name unnecessary
}
return r
}
func (r ACLRule) toRawBucketAccessControl(bucket string) *raw.BucketAccessControl {
return &raw.BucketAccessControl{
Bucket: bucket,
Entity: string(r.Entity),
Role: string(r.Role),
// The other fields are not settable.
}
}
func (r ACLRule) toRawObjectAccessControl(bucket string) *raw.ObjectAccessControl {
return &raw.ObjectAccessControl{
Bucket: bucket,
Entity: string(r.Entity),
Role: string(r.Role),
// The other fields are not settable.
}
}
func toBucketProjectTeam(p *raw.BucketAccessControlProjectTeam) *ProjectTeam {
if p == nil {
return nil
}
return &ProjectTeam{
ProjectNumber: p.ProjectNumber,
Team: p.Team,
}
}
func toObjectProjectTeam(p *raw.ObjectAccessControlProjectTeam) *ProjectTeam {
if p == nil {
return nil
}
return &ProjectTeam{
ProjectNumber: p.ProjectNumber,
Team: p.Team,
}
}

File diff suppressed because it is too large Load diff

228
vendor/cloud.google.com/go/storage/copy.go generated Normal file
View file

@ -0,0 +1,228 @@
// Copyright 2016 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package storage
import (
"context"
"errors"
"fmt"
"cloud.google.com/go/internal/trace"
raw "google.golang.org/api/storage/v1"
)
// CopierFrom creates a Copier that can copy src to dst.
// You can immediately call Run on the returned Copier, or
// you can configure it first.
//
// For Requester Pays buckets, the user project of dst is billed, unless it is empty,
// in which case the user project of src is billed.
func (dst *ObjectHandle) CopierFrom(src *ObjectHandle) *Copier {
return &Copier{dst: dst, src: src}
}
// A Copier copies a source object to a destination.
type Copier struct {
// ObjectAttrs are optional attributes to set on the destination object.
// Any attributes must be initialized before any calls on the Copier. Nil
// or zero-valued attributes are ignored.
ObjectAttrs
// RewriteToken can be set before calling Run to resume a copy
// operation. After Run returns a non-nil error, RewriteToken will
// have been updated to contain the value needed to resume the copy.
RewriteToken string
// ProgressFunc can be used to monitor the progress of a multi-RPC copy
// operation. If ProgressFunc is not nil and copying requires multiple
// calls to the underlying service (see
// https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite), then
// ProgressFunc will be invoked after each call with the number of bytes of
// content copied so far and the total size in bytes of the source object.
//
// ProgressFunc is intended to make upload progress available to the
// application. For example, the implementation of ProgressFunc may update
// a progress bar in the application's UI, or log the result of
// float64(copiedBytes)/float64(totalBytes).
//
// ProgressFunc should return quickly without blocking.
ProgressFunc func(copiedBytes, totalBytes uint64)
// The Cloud KMS key, in the form projects/P/locations/L/keyRings/R/cryptoKeys/K,
// that will be used to encrypt the object. Overrides the object's KMSKeyName, if
// any.
//
// Providing both a DestinationKMSKeyName and a customer-supplied encryption key
// (via ObjectHandle.Key) on the destination object will result in an error when
// Run is called.
DestinationKMSKeyName string
dst, src *ObjectHandle
}
// Run performs the copy.
func (c *Copier) Run(ctx context.Context) (attrs *ObjectAttrs, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Copier.Run")
defer func() { trace.EndSpan(ctx, err) }()
if err := c.src.validate(); err != nil {
return nil, err
}
if err := c.dst.validate(); err != nil {
return nil, err
}
if c.DestinationKMSKeyName != "" && c.dst.encryptionKey != nil {
return nil, errors.New("storage: cannot use DestinationKMSKeyName with a customer-supplied encryption key")
}
// Convert destination attributes to raw form, omitting the bucket.
// If the bucket is included but name or content-type aren't, the service
// returns a 400 with "Required" as the only message. Omitting the bucket
// does not cause any problems.
rawObject := c.ObjectAttrs.toRawObject("")
for {
res, err := c.callRewrite(ctx, rawObject)
if err != nil {
return nil, err
}
if c.ProgressFunc != nil {
c.ProgressFunc(uint64(res.TotalBytesRewritten), uint64(res.ObjectSize))
}
if res.Done { // Finished successfully.
return newObject(res.Resource), nil
}
}
}
func (c *Copier) callRewrite(ctx context.Context, rawObj *raw.Object) (*raw.RewriteResponse, error) {
call := c.dst.c.raw.Objects.Rewrite(c.src.bucket, c.src.object, c.dst.bucket, c.dst.object, rawObj)
call.Context(ctx).Projection("full")
if c.RewriteToken != "" {
call.RewriteToken(c.RewriteToken)
}
if c.DestinationKMSKeyName != "" {
call.DestinationKmsKeyName(c.DestinationKMSKeyName)
}
if c.PredefinedACL != "" {
call.DestinationPredefinedAcl(c.PredefinedACL)
}
if err := applyConds("Copy destination", c.dst.gen, c.dst.conds, call); err != nil {
return nil, err
}
if c.dst.userProject != "" {
call.UserProject(c.dst.userProject)
} else if c.src.userProject != "" {
call.UserProject(c.src.userProject)
}
if err := applySourceConds(c.src.gen, c.src.conds, call); err != nil {
return nil, err
}
if err := setEncryptionHeaders(call.Header(), c.dst.encryptionKey, false); err != nil {
return nil, err
}
if err := setEncryptionHeaders(call.Header(), c.src.encryptionKey, true); err != nil {
return nil, err
}
var res *raw.RewriteResponse
var err error
setClientHeader(call.Header())
err = runWithRetry(ctx, func() error { res, err = call.Do(); return err })
if err != nil {
return nil, err
}
c.RewriteToken = res.RewriteToken
return res, nil
}
// ComposerFrom creates a Composer that can compose srcs into dst.
// You can immediately call Run on the returned Composer, or you can
// configure it first.
//
// The encryption key for the destination object will be used to decrypt all
// source objects and encrypt the destination object. It is an error
// to specify an encryption key for any of the source objects.
func (dst *ObjectHandle) ComposerFrom(srcs ...*ObjectHandle) *Composer {
return &Composer{dst: dst, srcs: srcs}
}
// A Composer composes source objects into a destination object.
//
// For Requester Pays buckets, the user project of dst is billed.
type Composer struct {
// ObjectAttrs are optional attributes to set on the destination object.
// Any attributes must be initialized before any calls on the Composer. Nil
// or zero-valued attributes are ignored.
ObjectAttrs
dst *ObjectHandle
srcs []*ObjectHandle
}
// Run performs the compose operation.
func (c *Composer) Run(ctx context.Context) (attrs *ObjectAttrs, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Composer.Run")
defer func() { trace.EndSpan(ctx, err) }()
if err := c.dst.validate(); err != nil {
return nil, err
}
if len(c.srcs) == 0 {
return nil, errors.New("storage: at least one source object must be specified")
}
req := &raw.ComposeRequest{}
// Compose requires a non-empty Destination, so we always set it,
// even if the caller-provided ObjectAttrs is the zero value.
req.Destination = c.ObjectAttrs.toRawObject(c.dst.bucket)
for _, src := range c.srcs {
if err := src.validate(); err != nil {
return nil, err
}
if src.bucket != c.dst.bucket {
return nil, fmt.Errorf("storage: all source objects must be in bucket %q, found %q", c.dst.bucket, src.bucket)
}
if src.encryptionKey != nil {
return nil, fmt.Errorf("storage: compose source %s.%s must not have encryption key", src.bucket, src.object)
}
srcObj := &raw.ComposeRequestSourceObjects{
Name: src.object,
}
if err := applyConds("ComposeFrom source", src.gen, src.conds, composeSourceObj{srcObj}); err != nil {
return nil, err
}
req.SourceObjects = append(req.SourceObjects, srcObj)
}
call := c.dst.c.raw.Objects.Compose(c.dst.bucket, c.dst.object, req).Context(ctx)
if err := applyConds("ComposeFrom destination", c.dst.gen, c.dst.conds, call); err != nil {
return nil, err
}
if c.dst.userProject != "" {
call.UserProject(c.dst.userProject)
}
if c.PredefinedACL != "" {
call.DestinationPredefinedAcl(c.PredefinedACL)
}
if err := setEncryptionHeaders(call.Header(), c.dst.encryptionKey, false); err != nil {
return nil, err
}
var obj *raw.Object
setClientHeader(call.Header())
err = runWithRetry(ctx, func() error { obj, err = call.Do(); return err })
if err != nil {
return nil, err
}
return newObject(obj), nil
}

176
vendor/cloud.google.com/go/storage/doc.go generated Normal file
View file

@ -0,0 +1,176 @@
// Copyright 2016 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
Package storage provides an easy way to work with Google Cloud Storage.
Google Cloud Storage stores data in named objects, which are grouped into buckets.
More information about Google Cloud Storage is available at
https://cloud.google.com/storage/docs.
See https://godoc.org/cloud.google.com/go for authentication, timeouts,
connection pooling and similar aspects of this package.
All of the methods of this package use exponential backoff to retry calls that fail
with certain errors, as described in
https://cloud.google.com/storage/docs/exponential-backoff. Retrying continues
indefinitely unless the controlling context is canceled or the client is closed. See
context.WithTimeout and context.WithCancel.
Creating a Client
To start working with this package, create a client:
ctx := context.Background()
client, err := storage.NewClient(ctx)
if err != nil {
// TODO: Handle error.
}
The client will use your default application credentials.
If you only wish to access public data, you can create
an unauthenticated client with
client, err := storage.NewClient(ctx, option.WithoutAuthentication())
Buckets
A Google Cloud Storage bucket is a collection of objects. To work with a
bucket, make a bucket handle:
bkt := client.Bucket(bucketName)
A handle is a reference to a bucket. You can have a handle even if the
bucket doesn't exist yet. To create a bucket in Google Cloud Storage,
call Create on the handle:
if err := bkt.Create(ctx, projectID, nil); err != nil {
// TODO: Handle error.
}
Note that although buckets are associated with projects, bucket names are
global across all projects.
Each bucket has associated metadata, represented in this package by
BucketAttrs. The third argument to BucketHandle.Create allows you to set
the initial BucketAttrs of a bucket. To retrieve a bucket's attributes, use
Attrs:
attrs, err := bkt.Attrs(ctx)
if err != nil {
// TODO: Handle error.
}
fmt.Printf("bucket %s, created at %s, is located in %s with storage class %s\n",
attrs.Name, attrs.Created, attrs.Location, attrs.StorageClass)
Objects
An object holds arbitrary data as a sequence of bytes, like a file. You
refer to objects using a handle, just as with buckets, but unlike buckets
you don't explicitly create an object. Instead, the first time you write
to an object it will be created. You can use the standard Go io.Reader
and io.Writer interfaces to read and write object data:
obj := bkt.Object("data")
// Write something to obj.
// w implements io.Writer.
w := obj.NewWriter(ctx)
// Write some text to obj. This will either create the object or overwrite whatever is there already.
if _, err := fmt.Fprintf(w, "This object contains text.\n"); err != nil {
// TODO: Handle error.
}
// Close, just like writing a file.
if err := w.Close(); err != nil {
// TODO: Handle error.
}
// Read it back.
r, err := obj.NewReader(ctx)
if err != nil {
// TODO: Handle error.
}
defer r.Close()
if _, err := io.Copy(os.Stdout, r); err != nil {
// TODO: Handle error.
}
// Prints "This object contains text."
Objects also have attributes, which you can fetch with Attrs:
objAttrs, err := obj.Attrs(ctx)
if err != nil {
// TODO: Handle error.
}
fmt.Printf("object %s has size %d and can be read using %s\n",
objAttrs.Name, objAttrs.Size, objAttrs.MediaLink)
ACLs
Both objects and buckets have ACLs (Access Control Lists). An ACL is a list of
ACLRules, each of which specifies the role of a user, group or project. ACLs
are suitable for fine-grained control, but you may prefer using IAM to control
access at the project level (see
https://cloud.google.com/storage/docs/access-control/iam).
To list the ACLs of a bucket or object, obtain an ACLHandle and call its List method:
acls, err := obj.ACL().List(ctx)
if err != nil {
// TODO: Handle error.
}
for _, rule := range acls {
fmt.Printf("%s has role %s\n", rule.Entity, rule.Role)
}
You can also set and delete ACLs.
Conditions
Every object has a generation and a metageneration. The generation changes
whenever the content changes, and the metageneration changes whenever the
metadata changes. Conditions let you check these values before an operation;
the operation only executes if the conditions match. You can use conditions to
prevent race conditions in read-modify-write operations.
For example, say you've read an object's metadata into objAttrs. Now
you want to write to that object, but only if its contents haven't changed
since you read it. Here is how to express that:
w = obj.If(storage.Conditions{GenerationMatch: objAttrs.Generation}).NewWriter(ctx)
// Proceed with writing as above.
Signed URLs
You can obtain a URL that lets anyone read or write an object for a limited time.
You don't need to create a client to do this. See the documentation of
SignedURL for details.
url, err := storage.SignedURL(bucketName, "shared-object", opts)
if err != nil {
// TODO: Handle error.
}
fmt.Println(url)
Errors
Errors returned by this client are often of the type [`googleapi.Error`](https://godoc.org/google.golang.org/api/googleapi#Error).
These errors can be introspected for more information by type asserting to the richer `googleapi.Error` type. For example:
if e, ok := err.(*googleapi.Error); ok {
if e.Code == 409 { ... }
}
*/
package storage // import "cloud.google.com/go/storage"

View file

@ -0,0 +1,32 @@
// Copyright 2017 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build go1.10
package storage
import "google.golang.org/api/googleapi"
func shouldRetry(err error) bool {
switch e := err.(type) {
case *googleapi.Error:
// Retry on 429 and 5xx, according to
// https://cloud.google.com/storage/docs/exponential-backoff.
return e.Code == 429 || (e.Code >= 500 && e.Code < 600)
case interface{ Temporary() bool }:
return e.Temporary()
default:
return false
}
}

130
vendor/cloud.google.com/go/storage/iam.go generated Normal file
View file

@ -0,0 +1,130 @@
// Copyright 2017 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package storage
import (
"context"
"cloud.google.com/go/iam"
"cloud.google.com/go/internal/trace"
raw "google.golang.org/api/storage/v1"
iampb "google.golang.org/genproto/googleapis/iam/v1"
)
// IAM provides access to IAM access control for the bucket.
func (b *BucketHandle) IAM() *iam.Handle {
return iam.InternalNewHandleClient(&iamClient{
raw: b.c.raw,
userProject: b.userProject,
}, b.name)
}
// iamClient implements the iam.client interface.
type iamClient struct {
raw *raw.Service
userProject string
}
func (c *iamClient) Get(ctx context.Context, resource string) (p *iampb.Policy, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.IAM.Get")
defer func() { trace.EndSpan(ctx, err) }()
call := c.raw.Buckets.GetIamPolicy(resource)
setClientHeader(call.Header())
if c.userProject != "" {
call.UserProject(c.userProject)
}
var rp *raw.Policy
err = runWithRetry(ctx, func() error {
rp, err = call.Context(ctx).Do()
return err
})
if err != nil {
return nil, err
}
return iamFromStoragePolicy(rp), nil
}
func (c *iamClient) Set(ctx context.Context, resource string, p *iampb.Policy) (err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.IAM.Set")
defer func() { trace.EndSpan(ctx, err) }()
rp := iamToStoragePolicy(p)
call := c.raw.Buckets.SetIamPolicy(resource, rp)
setClientHeader(call.Header())
if c.userProject != "" {
call.UserProject(c.userProject)
}
return runWithRetry(ctx, func() error {
_, err := call.Context(ctx).Do()
return err
})
}
func (c *iamClient) Test(ctx context.Context, resource string, perms []string) (permissions []string, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.IAM.Test")
defer func() { trace.EndSpan(ctx, err) }()
call := c.raw.Buckets.TestIamPermissions(resource, perms)
setClientHeader(call.Header())
if c.userProject != "" {
call.UserProject(c.userProject)
}
var res *raw.TestIamPermissionsResponse
err = runWithRetry(ctx, func() error {
res, err = call.Context(ctx).Do()
return err
})
if err != nil {
return nil, err
}
return res.Permissions, nil
}
func iamToStoragePolicy(ip *iampb.Policy) *raw.Policy {
return &raw.Policy{
Bindings: iamToStorageBindings(ip.Bindings),
Etag: string(ip.Etag),
}
}
func iamToStorageBindings(ibs []*iampb.Binding) []*raw.PolicyBindings {
var rbs []*raw.PolicyBindings
for _, ib := range ibs {
rbs = append(rbs, &raw.PolicyBindings{
Role: ib.Role,
Members: ib.Members,
})
}
return rbs
}
func iamFromStoragePolicy(rp *raw.Policy) *iampb.Policy {
return &iampb.Policy{
Bindings: iamFromStorageBindings(rp.Bindings),
Etag: []byte(rp.Etag),
}
}
func iamFromStorageBindings(rbs []*raw.PolicyBindings) []*iampb.Binding {
var ibs []*iampb.Binding
for _, rb := range rbs {
ibs = append(ibs, &iampb.Binding{
Role: rb.Role,
Members: rb.Members,
})
}
return ibs
}

View file

@ -0,0 +1,37 @@
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package storage
import (
"context"
"cloud.google.com/go/internal"
gax "github.com/googleapis/gax-go/v2"
)
// runWithRetry calls the function until it returns nil or a non-retryable error, or
// the context is done.
func runWithRetry(ctx context.Context, call func() error) error {
return internal.Retry(ctx, gax.Backoff{}, func() (stop bool, err error) {
err = call()
if err == nil {
return true, nil
}
if shouldRetry(err) {
return false, nil
}
return true, err
})
}

View file

@ -0,0 +1,42 @@
// Copyright 2017 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !go1.10
package storage
import (
"net/url"
"strings"
"google.golang.org/api/googleapi"
)
func shouldRetry(err error) bool {
switch e := err.(type) {
case *googleapi.Error:
// Retry on 429 and 5xx, according to
// https://cloud.google.com/storage/docs/exponential-backoff.
return e.Code == 429 || (e.Code >= 500 && e.Code < 600)
case *url.Error:
// Retry on REFUSED_STREAM.
// Unfortunately the error type is unexported, so we resort to string
// matching.
return strings.Contains(e.Error(), "REFUSED_STREAM")
case interface{ Temporary() bool }:
return e.Temporary()
default:
return false
}
}

View file

@ -0,0 +1,188 @@
// Copyright 2017 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package storage
import (
"context"
"errors"
"fmt"
"regexp"
"cloud.google.com/go/internal/trace"
raw "google.golang.org/api/storage/v1"
)
// A Notification describes how to send Cloud PubSub messages when certain
// events occur in a bucket.
type Notification struct {
//The ID of the notification.
ID string
// The ID of the topic to which this subscription publishes.
TopicID string
// The ID of the project to which the topic belongs.
TopicProjectID string
// Only send notifications about listed event types. If empty, send notifications
// for all event types.
// See https://cloud.google.com/storage/docs/pubsub-notifications#events.
EventTypes []string
// If present, only apply this notification configuration to object names that
// begin with this prefix.
ObjectNamePrefix string
// An optional list of additional attributes to attach to each Cloud PubSub
// message published for this notification subscription.
CustomAttributes map[string]string
// The contents of the message payload.
// See https://cloud.google.com/storage/docs/pubsub-notifications#payload.
PayloadFormat string
}
// Values for Notification.PayloadFormat.
const (
// Send no payload with notification messages.
NoPayload = "NONE"
// Send object metadata as JSON with notification messages.
JSONPayload = "JSON_API_V1"
)
// Values for Notification.EventTypes.
const (
// Event that occurs when an object is successfully created.
ObjectFinalizeEvent = "OBJECT_FINALIZE"
// Event that occurs when the metadata of an existing object changes.
ObjectMetadataUpdateEvent = "OBJECT_METADATA_UPDATE"
// Event that occurs when an object is permanently deleted.
ObjectDeleteEvent = "OBJECT_DELETE"
// Event that occurs when the live version of an object becomes an
// archived version.
ObjectArchiveEvent = "OBJECT_ARCHIVE"
)
func toNotification(rn *raw.Notification) *Notification {
n := &Notification{
ID: rn.Id,
EventTypes: rn.EventTypes,
ObjectNamePrefix: rn.ObjectNamePrefix,
CustomAttributes: rn.CustomAttributes,
PayloadFormat: rn.PayloadFormat,
}
n.TopicProjectID, n.TopicID = parseNotificationTopic(rn.Topic)
return n
}
var topicRE = regexp.MustCompile("^//pubsub.googleapis.com/projects/([^/]+)/topics/([^/]+)")
// parseNotificationTopic extracts the project and topic IDs from from the full
// resource name returned by the service. If the name is malformed, it returns
// "?" for both IDs.
func parseNotificationTopic(nt string) (projectID, topicID string) {
matches := topicRE.FindStringSubmatch(nt)
if matches == nil {
return "?", "?"
}
return matches[1], matches[2]
}
func toRawNotification(n *Notification) *raw.Notification {
return &raw.Notification{
Id: n.ID,
Topic: fmt.Sprintf("//pubsub.googleapis.com/projects/%s/topics/%s",
n.TopicProjectID, n.TopicID),
EventTypes: n.EventTypes,
ObjectNamePrefix: n.ObjectNamePrefix,
CustomAttributes: n.CustomAttributes,
PayloadFormat: string(n.PayloadFormat),
}
}
// AddNotification adds a notification to b. You must set n's TopicProjectID, TopicID
// and PayloadFormat, and must not set its ID. The other fields are all optional. The
// returned Notification's ID can be used to refer to it.
func (b *BucketHandle) AddNotification(ctx context.Context, n *Notification) (ret *Notification, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.AddNotification")
defer func() { trace.EndSpan(ctx, err) }()
if n.ID != "" {
return nil, errors.New("storage: AddNotification: ID must not be set")
}
if n.TopicProjectID == "" {
return nil, errors.New("storage: AddNotification: missing TopicProjectID")
}
if n.TopicID == "" {
return nil, errors.New("storage: AddNotification: missing TopicID")
}
call := b.c.raw.Notifications.Insert(b.name, toRawNotification(n))
setClientHeader(call.Header())
if b.userProject != "" {
call.UserProject(b.userProject)
}
rn, err := call.Context(ctx).Do()
if err != nil {
return nil, err
}
return toNotification(rn), nil
}
// Notifications returns all the Notifications configured for this bucket, as a map
// indexed by notification ID.
func (b *BucketHandle) Notifications(ctx context.Context) (n map[string]*Notification, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Notifications")
defer func() { trace.EndSpan(ctx, err) }()
call := b.c.raw.Notifications.List(b.name)
setClientHeader(call.Header())
if b.userProject != "" {
call.UserProject(b.userProject)
}
var res *raw.Notifications
err = runWithRetry(ctx, func() error {
res, err = call.Context(ctx).Do()
return err
})
if err != nil {
return nil, err
}
return notificationsToMap(res.Items), nil
}
func notificationsToMap(rns []*raw.Notification) map[string]*Notification {
m := map[string]*Notification{}
for _, rn := range rns {
m[rn.Id] = toNotification(rn)
}
return m
}
// DeleteNotification deletes the notification with the given ID.
func (b *BucketHandle) DeleteNotification(ctx context.Context, id string) (err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.DeleteNotification")
defer func() { trace.EndSpan(ctx, err) }()
call := b.c.raw.Notifications.Delete(b.name, id)
setClientHeader(call.Header())
if b.userProject != "" {
call.UserProject(b.userProject)
}
return call.Context(ctx).Do()
}

View file

@ -0,0 +1,385 @@
// Copyright 2016 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package storage
import (
"context"
"errors"
"fmt"
"hash/crc32"
"io"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"strconv"
"strings"
"time"
"cloud.google.com/go/internal/trace"
"google.golang.org/api/googleapi"
)
var crc32cTable = crc32.MakeTable(crc32.Castagnoli)
// ReaderObjectAttrs are attributes about the object being read. These are populated
// during the New call. This struct only holds a subset of object attributes: to
// get the full set of attributes, use ObjectHandle.Attrs.
//
// Each field is read-only.
type ReaderObjectAttrs struct {
// Size is the length of the object's content.
Size int64
// ContentType is the MIME type of the object's content.
ContentType string
// ContentEncoding is the encoding of the object's content.
ContentEncoding string
// CacheControl specifies whether and for how long browser and Internet
// caches are allowed to cache your objects.
CacheControl string
// LastModified is the time that the object was last modified.
LastModified time.Time
// Generation is the generation number of the object's content.
Generation int64
// Metageneration is the version of the metadata for this object at
// this generation. This field is used for preconditions and for
// detecting changes in metadata. A metageneration number is only
// meaningful in the context of a particular generation of a
// particular object.
Metageneration int64
}
// NewReader creates a new Reader to read the contents of the
// object.
// ErrObjectNotExist will be returned if the object is not found.
//
// The caller must call Close on the returned Reader when done reading.
func (o *ObjectHandle) NewReader(ctx context.Context) (*Reader, error) {
return o.NewRangeReader(ctx, 0, -1)
}
// NewRangeReader reads part of an object, reading at most length bytes
// starting at the given offset. If length is negative, the object is read
// until the end.
func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64) (r *Reader, err error) {
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Object.NewRangeReader")
defer func() { trace.EndSpan(ctx, err) }()
if err := o.validate(); err != nil {
return nil, err
}
if offset < 0 {
return nil, fmt.Errorf("storage: invalid offset %d < 0", offset)
}
if o.conds != nil {
if err := o.conds.validate("NewRangeReader"); err != nil {
return nil, err
}
}
u := &url.URL{
Scheme: "https",
Host: "storage.googleapis.com",
Path: fmt.Sprintf("/%s/%s", o.bucket, o.object),
}
verb := "GET"
if length == 0 {
verb = "HEAD"
}
req, err := http.NewRequest(verb, u.String(), nil)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if o.userProject != "" {
req.Header.Set("X-Goog-User-Project", o.userProject)
}
if o.readCompressed {
req.Header.Set("Accept-Encoding", "gzip")
}
if err := setEncryptionHeaders(req.Header, o.encryptionKey, false); err != nil {
return nil, err
}
gen := o.gen
// Define a function that initiates a Read with offset and length, assuming we
// have already read seen bytes.
reopen := func(seen int64) (*http.Response, error) {
start := offset + seen
if length < 0 && start > 0 {
req.Header.Set("Range", fmt.Sprintf("bytes=%d-", start))
} else if length > 0 {
// The end character isn't affected by how many bytes we've seen.
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", start, offset+length-1))
}
// We wait to assign conditions here because the generation number can change in between reopen() runs.
req.URL.RawQuery = conditionsQuery(gen, o.conds)
var res *http.Response
err = runWithRetry(ctx, func() error {
res, err = o.c.hc.Do(req)
if err != nil {
return err
}
if res.StatusCode == http.StatusNotFound {
res.Body.Close()
return ErrObjectNotExist
}
if res.StatusCode < 200 || res.StatusCode > 299 {
body, _ := ioutil.ReadAll(res.Body)
res.Body.Close()
return &googleapi.Error{
Code: res.StatusCode,
Header: res.Header,
Body: string(body),
}
}
if start > 0 && length != 0 && res.StatusCode != http.StatusPartialContent {
res.Body.Close()
return errors.New("storage: partial request not satisfied")
}
// If a generation hasn't been specified, and this is the first response we get, let's record the
// generation. In future requests we'll use this generation as a precondition to avoid data races.
if gen < 0 && res.Header.Get("X-Goog-Generation") != "" {
gen64, err := strconv.ParseInt(res.Header.Get("X-Goog-Generation"), 10, 64)
if err != nil {
return err
}
gen = gen64
}
return nil
})
if err != nil {
return nil, err
}
return res, nil
}
res, err := reopen(0)
if err != nil {
return nil, err
}
var (
size int64 // total size of object, even if a range was requested.
checkCRC bool
crc uint32
)
if res.StatusCode == http.StatusPartialContent {
cr := strings.TrimSpace(res.Header.Get("Content-Range"))
if !strings.HasPrefix(cr, "bytes ") || !strings.Contains(cr, "/") {
return nil, fmt.Errorf("storage: invalid Content-Range %q", cr)
}
size, err = strconv.ParseInt(cr[strings.LastIndex(cr, "/")+1:], 10, 64)
if err != nil {
return nil, fmt.Errorf("storage: invalid Content-Range %q", cr)
}
} else {
size = res.ContentLength
// Check the CRC iff all of the following hold:
// - We asked for content (length != 0).
// - We got all the content (status != PartialContent).
// - The server sent a CRC header.
// - The Go http stack did not uncompress the file.
// - We were not served compressed data that was uncompressed on download.
// The problem with the last two cases is that the CRC will not match -- GCS
// computes it on the compressed contents, but we compute it on the
// uncompressed contents.
if length != 0 && !res.Uncompressed && !uncompressedByServer(res) {
crc, checkCRC = parseCRC32c(res)
}
}
remain := res.ContentLength
body := res.Body
if length == 0 {
remain = 0
body.Close()
body = emptyBody
}
var metaGen int64
if res.Header.Get("X-Goog-Generation") != "" {
metaGen, err = strconv.ParseInt(res.Header.Get("X-Goog-Metageneration"), 10, 64)
if err != nil {
return nil, err
}
}
var lm time.Time
if res.Header.Get("Last-Modified") != "" {
lm, err = http.ParseTime(res.Header.Get("Last-Modified"))
if err != nil {
return nil, err
}
}
attrs := ReaderObjectAttrs{
Size: size,
ContentType: res.Header.Get("Content-Type"),
ContentEncoding: res.Header.Get("Content-Encoding"),
CacheControl: res.Header.Get("Cache-Control"),
LastModified: lm,
Generation: gen,
Metageneration: metaGen,
}
return &Reader{
Attrs: attrs,
body: body,
size: size,
remain: remain,
wantCRC: crc,
checkCRC: checkCRC,
reopen: reopen,
}, nil
}
func uncompressedByServer(res *http.Response) bool {
// If the data is stored as gzip but is not encoded as gzip, then it
// was uncompressed by the server.
return res.Header.Get("X-Goog-Stored-Content-Encoding") == "gzip" &&
res.Header.Get("Content-Encoding") != "gzip"
}
func parseCRC32c(res *http.Response) (uint32, bool) {
const prefix = "crc32c="
for _, spec := range res.Header["X-Goog-Hash"] {
if strings.HasPrefix(spec, prefix) {
c, err := decodeUint32(spec[len(prefix):])
if err == nil {
return c, true
}
}
}
return 0, false
}
var emptyBody = ioutil.NopCloser(strings.NewReader(""))
// Reader reads a Cloud Storage object.
// It implements io.Reader.
//
// Typically, a Reader computes the CRC of the downloaded content and compares it to
// the stored CRC, returning an error from Read if there is a mismatch. This integrity check
// is skipped if transcoding occurs. See https://cloud.google.com/storage/docs/transcoding.
type Reader struct {
Attrs ReaderObjectAttrs
body io.ReadCloser
seen, remain, size int64
checkCRC bool // should we check the CRC?
wantCRC uint32 // the CRC32c value the server sent in the header
gotCRC uint32 // running crc
reopen func(seen int64) (*http.Response, error)
}
// Close closes the Reader. It must be called when done reading.
func (r *Reader) Close() error {
return r.body.Close()
}
func (r *Reader) Read(p []byte) (int, error) {
n, err := r.readWithRetry(p)
if r.remain != -1 {
r.remain -= int64(n)
}
if r.checkCRC {
r.gotCRC = crc32.Update(r.gotCRC, crc32cTable, p[:n])
// Check CRC here. It would be natural to check it in Close, but
// everybody defers Close on the assumption that it doesn't return
// anything worth looking at.
if err == io.EOF {
if r.gotCRC != r.wantCRC {
return n, fmt.Errorf("storage: bad CRC on read: got %d, want %d",
r.gotCRC, r.wantCRC)
}
}
}
return n, err
}
func (r *Reader) readWithRetry(p []byte) (int, error) {
n := 0
for len(p[n:]) > 0 {
m, err := r.body.Read(p[n:])
n += m
r.seen += int64(m)
if !shouldRetryRead(err) {
return n, err
}
// Read failed, but we will try again. Send a ranged read request that takes
// into account the number of bytes we've already seen.
res, err := r.reopen(r.seen)
if err != nil {
// reopen already retries
return n, err
}
r.body.Close()
r.body = res.Body
}
return n, nil
}
func shouldRetryRead(err error) bool {
if err == nil {
return false
}
return strings.HasSuffix(err.Error(), "INTERNAL_ERROR") && strings.Contains(reflect.TypeOf(err).String(), "http2")
}
// Size returns the size of the object in bytes.
// The returned value is always the same and is not affected by
// calls to Read or Close.
//
// Deprecated: use Reader.Attrs.Size.
func (r *Reader) Size() int64 {
return r.Attrs.Size
}
// Remain returns the number of bytes left to read, or -1 if unknown.
func (r *Reader) Remain() int64 {
return r.remain
}
// ContentType returns the content type of the object.
//
// Deprecated: use Reader.Attrs.ContentType.
func (r *Reader) ContentType() string {
return r.Attrs.ContentType
}
// ContentEncoding returns the content encoding of the object.
//
// Deprecated: use Reader.Attrs.ContentEncoding.
func (r *Reader) ContentEncoding() string {
return r.Attrs.ContentEncoding
}
// CacheControl returns the cache control of the object.
//
// Deprecated: use Reader.Attrs.CacheControl.
func (r *Reader) CacheControl() string {
return r.Attrs.CacheControl
}
// LastModified returns the value of the Last-Modified header.
//
// Deprecated: use Reader.Attrs.LastModified.
func (r *Reader) LastModified() (time.Time, error) {
return r.Attrs.LastModified, nil
}

File diff suppressed because it is too large Load diff

30067
vendor/cloud.google.com/go/storage/storage.replay generated vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,261 @@
// Copyright 2014 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package storage
import (
"context"
"encoding/base64"
"errors"
"fmt"
"io"
"sync"
"unicode/utf8"
"google.golang.org/api/googleapi"
raw "google.golang.org/api/storage/v1"
)
// A Writer writes a Cloud Storage object.
type Writer struct {
// ObjectAttrs are optional attributes to set on the object. Any attributes
// must be initialized before the first Write call. Nil or zero-valued
// attributes are ignored.
ObjectAttrs
// SendCRC specifies whether to transmit a CRC32C field. It should be set
// to true in addition to setting the Writer's CRC32C field, because zero
// is a valid CRC and normally a zero would not be transmitted.
// If a CRC32C is sent, and the data written does not match the checksum,
// the write will be rejected.
SendCRC32C bool
// ChunkSize controls the maximum number of bytes of the object that the
// Writer will attempt to send to the server in a single request. Objects
// smaller than the size will be sent in a single request, while larger
// objects will be split over multiple requests. The size will be rounded up
// to the nearest multiple of 256K. If zero, chunking will be disabled and
// the object will be uploaded in a single request.
//
// ChunkSize will default to a reasonable value. If you perform many concurrent
// writes of small objects, you may wish set ChunkSize to a value that matches
// your objects' sizes to avoid consuming large amounts of memory.
//
// ChunkSize must be set before the first Write call.
ChunkSize int
// ProgressFunc can be used to monitor the progress of a large write.
// operation. If ProgressFunc is not nil and writing requires multiple
// calls to the underlying service (see
// https://cloud.google.com/storage/docs/json_api/v1/how-tos/resumable-upload),
// then ProgressFunc will be invoked after each call with the number of bytes of
// content copied so far.
//
// ProgressFunc should return quickly without blocking.
ProgressFunc func(int64)
ctx context.Context
o *ObjectHandle
opened bool
pw *io.PipeWriter
donec chan struct{} // closed after err and obj are set.
obj *ObjectAttrs
mu sync.Mutex
err error
}
func (w *Writer) open() error {
attrs := w.ObjectAttrs
// Check the developer didn't change the object Name (this is unfortunate, but
// we don't want to store an object under the wrong name).
if attrs.Name != w.o.object {
return fmt.Errorf("storage: Writer.Name %q does not match object name %q", attrs.Name, w.o.object)
}
if !utf8.ValidString(attrs.Name) {
return fmt.Errorf("storage: object name %q is not valid UTF-8", attrs.Name)
}
if attrs.KMSKeyName != "" && w.o.encryptionKey != nil {
return errors.New("storage: cannot use KMSKeyName with a customer-supplied encryption key")
}
pr, pw := io.Pipe()
w.pw = pw
w.opened = true
go w.monitorCancel()
if w.ChunkSize < 0 {
return errors.New("storage: Writer.ChunkSize must be non-negative")
}
mediaOpts := []googleapi.MediaOption{
googleapi.ChunkSize(w.ChunkSize),
}
if c := attrs.ContentType; c != "" {
mediaOpts = append(mediaOpts, googleapi.ContentType(c))
}
go func() {
defer close(w.donec)
rawObj := attrs.toRawObject(w.o.bucket)
if w.SendCRC32C {
rawObj.Crc32c = encodeUint32(attrs.CRC32C)
}
if w.MD5 != nil {
rawObj.Md5Hash = base64.StdEncoding.EncodeToString(w.MD5)
}
call := w.o.c.raw.Objects.Insert(w.o.bucket, rawObj).
Media(pr, mediaOpts...).
Projection("full").
Context(w.ctx)
if w.ProgressFunc != nil {
call.ProgressUpdater(func(n, _ int64) { w.ProgressFunc(n) })
}
if attrs.KMSKeyName != "" {
call.KmsKeyName(attrs.KMSKeyName)
}
if attrs.PredefinedACL != "" {
call.PredefinedAcl(attrs.PredefinedACL)
}
if err := setEncryptionHeaders(call.Header(), w.o.encryptionKey, false); err != nil {
w.mu.Lock()
w.err = err
w.mu.Unlock()
pr.CloseWithError(err)
return
}
var resp *raw.Object
err := applyConds("NewWriter", w.o.gen, w.o.conds, call)
if err == nil {
if w.o.userProject != "" {
call.UserProject(w.o.userProject)
}
setClientHeader(call.Header())
// If the chunk size is zero, then no chunking is done on the Reader,
// which means we cannot retry: the first call will read the data, and if
// it fails, there is no way to re-read.
if w.ChunkSize == 0 {
resp, err = call.Do()
} else {
// We will only retry here if the initial POST, which obtains a URI for
// the resumable upload, fails with a retryable error. The upload itself
// has its own retry logic.
err = runWithRetry(w.ctx, func() error {
var err2 error
resp, err2 = call.Do()
return err2
})
}
}
if err != nil {
w.mu.Lock()
w.err = err
w.mu.Unlock()
pr.CloseWithError(err)
return
}
w.obj = newObject(resp)
}()
return nil
}
// Write appends to w. It implements the io.Writer interface.
//
// Since writes happen asynchronously, Write may return a nil
// error even though the write failed (or will fail). Always
// use the error returned from Writer.Close to determine if
// the upload was successful.
func (w *Writer) Write(p []byte) (n int, err error) {
w.mu.Lock()
werr := w.err
w.mu.Unlock()
if werr != nil {
return 0, werr
}
if !w.opened {
if err := w.open(); err != nil {
return 0, err
}
}
n, err = w.pw.Write(p)
if err != nil {
w.mu.Lock()
werr := w.err
w.mu.Unlock()
// Preserve existing functionality that when context is canceled, Write will return
// context.Canceled instead of "io: read/write on closed pipe". This hides the
// pipe implementation detail from users and makes Write seem as though it's an RPC.
if werr == context.Canceled || werr == context.DeadlineExceeded {
return n, werr
}
}
return n, err
}
// Close completes the write operation and flushes any buffered data.
// If Close doesn't return an error, metadata about the written object
// can be retrieved by calling Attrs.
func (w *Writer) Close() error {
if !w.opened {
if err := w.open(); err != nil {
return err
}
}
// Closing either the read or write causes the entire pipe to close.
if err := w.pw.Close(); err != nil {
return err
}
<-w.donec
w.mu.Lock()
defer w.mu.Unlock()
return w.err
}
// monitorCancel is intended to be used as a background goroutine. It monitors the
// the context, and when it observes that the context has been canceled, it manually
// closes things that do not take a context.
func (w *Writer) monitorCancel() {
select {
case <-w.ctx.Done():
w.mu.Lock()
werr := w.ctx.Err()
w.err = werr
w.mu.Unlock()
// Closing either the read or write causes the entire pipe to close.
w.CloseWithError(werr)
case <-w.donec:
}
}
// CloseWithError aborts the write operation with the provided error.
// CloseWithError always returns nil.
//
// Deprecated: cancel the context passed to NewWriter instead.
func (w *Writer) CloseWithError(err error) error {
if !w.opened {
return nil
}
return w.pw.CloseWithError(err)
}
// Attrs returns metadata about a successfully-written object.
// It's only valid to call it after Close returns nil.
func (w *Writer) Attrs() *ObjectAttrs {
return w.obj
}

20
vendor/github.com/beorn7/perks/LICENSE generated vendored Normal file
View file

@ -0,0 +1,20 @@
Copyright (C) 2013 Blake Mizerany
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

2388
vendor/github.com/beorn7/perks/quantile/exampledata.txt generated vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,316 @@
// Package quantile computes approximate quantiles over an unbounded data
// stream within low memory and CPU bounds.
//
// A small amount of accuracy is traded to achieve the above properties.
//
// Multiple streams can be merged before calling Query to generate a single set
// of results. This is meaningful when the streams represent the same type of
// data. See Merge and Samples.
//
// For more detailed information about the algorithm used, see:
//
// Effective Computation of Biased Quantiles over Data Streams
//
// http://www.cs.rutgers.edu/~muthu/bquant.pdf
package quantile
import (
"math"
"sort"
)
// Sample holds an observed value and meta information for compression. JSON
// tags have been added for convenience.
type Sample struct {
Value float64 `json:",string"`
Width float64 `json:",string"`
Delta float64 `json:",string"`
}
// Samples represents a slice of samples. It implements sort.Interface.
type Samples []Sample
func (a Samples) Len() int { return len(a) }
func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value }
func (a Samples) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type invariant func(s *stream, r float64) float64
// NewLowBiased returns an initialized Stream for low-biased quantiles
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
// error guarantees can still be given even for the lower ranks of the data
// distribution.
//
// The provided epsilon is a relative error, i.e. the true quantile of a value
// returned by a query is guaranteed to be within (1±Epsilon)*Quantile.
//
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
// properties.
func NewLowBiased(epsilon float64) *Stream {
ƒ := func(s *stream, r float64) float64 {
return 2 * epsilon * r
}
return newStream(ƒ)
}
// NewHighBiased returns an initialized Stream for high-biased quantiles
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
// error guarantees can still be given even for the higher ranks of the data
// distribution.
//
// The provided epsilon is a relative error, i.e. the true quantile of a value
// returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile).
//
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
// properties.
func NewHighBiased(epsilon float64) *Stream {
ƒ := func(s *stream, r float64) float64 {
return 2 * epsilon * (s.n - r)
}
return newStream(ƒ)
}
// NewTargeted returns an initialized Stream concerned with a particular set of
// quantile values that are supplied a priori. Knowing these a priori reduces
// space and computation time. The targets map maps the desired quantiles to
// their absolute errors, i.e. the true quantile of a value returned by a query
// is guaranteed to be within (Quantile±Epsilon).
//
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
func NewTargeted(targetMap map[float64]float64) *Stream {
// Convert map to slice to avoid slow iterations on a map.
// ƒ is called on the hot path, so converting the map to a slice
// beforehand results in significant CPU savings.
targets := targetMapToSlice(targetMap)
ƒ := func(s *stream, r float64) float64 {
var m = math.MaxFloat64
var f float64
for _, t := range targets {
if t.quantile*s.n <= r {
f = (2 * t.epsilon * r) / t.quantile
} else {
f = (2 * t.epsilon * (s.n - r)) / (1 - t.quantile)
}
if f < m {
m = f
}
}
return m
}
return newStream(ƒ)
}
type target struct {
quantile float64
epsilon float64
}
func targetMapToSlice(targetMap map[float64]float64) []target {
targets := make([]target, 0, len(targetMap))
for quantile, epsilon := range targetMap {
t := target{
quantile: quantile,
epsilon: epsilon,
}
targets = append(targets, t)
}
return targets
}
// Stream computes quantiles for a stream of float64s. It is not thread-safe by
// design. Take care when using across multiple goroutines.
type Stream struct {
*stream
b Samples
sorted bool
}
func newStream(ƒ invariant) *Stream {
x := &stream{ƒ: ƒ}
return &Stream{x, make(Samples, 0, 500), true}
}
// Insert inserts v into the stream.
func (s *Stream) Insert(v float64) {
s.insert(Sample{Value: v, Width: 1})
}
func (s *Stream) insert(sample Sample) {
s.b = append(s.b, sample)
s.sorted = false
if len(s.b) == cap(s.b) {
s.flush()
}
}
// Query returns the computed qth percentiles value. If s was created with
// NewTargeted, and q is not in the set of quantiles provided a priori, Query
// will return an unspecified result.
func (s *Stream) Query(q float64) float64 {
if !s.flushed() {
// Fast path when there hasn't been enough data for a flush;
// this also yields better accuracy for small sets of data.
l := len(s.b)
if l == 0 {
return 0
}
i := int(math.Ceil(float64(l) * q))
if i > 0 {
i -= 1
}
s.maybeSort()
return s.b[i].Value
}
s.flush()
return s.stream.query(q)
}
// Merge merges samples into the underlying streams samples. This is handy when
// merging multiple streams from separate threads, database shards, etc.
//
// ATTENTION: This method is broken and does not yield correct results. The
// underlying algorithm is not capable of merging streams correctly.
func (s *Stream) Merge(samples Samples) {
sort.Sort(samples)
s.stream.merge(samples)
}
// Reset reinitializes and clears the list reusing the samples buffer memory.
func (s *Stream) Reset() {
s.stream.reset()
s.b = s.b[:0]
}
// Samples returns stream samples held by s.
func (s *Stream) Samples() Samples {
if !s.flushed() {
return s.b
}
s.flush()
return s.stream.samples()
}
// Count returns the total number of samples observed in the stream
// since initialization.
func (s *Stream) Count() int {
return len(s.b) + s.stream.count()
}
func (s *Stream) flush() {
s.maybeSort()
s.stream.merge(s.b)
s.b = s.b[:0]
}
func (s *Stream) maybeSort() {
if !s.sorted {
s.sorted = true
sort.Sort(s.b)
}
}
func (s *Stream) flushed() bool {
return len(s.stream.l) > 0
}
type stream struct {
n float64
l []Sample
ƒ invariant
}
func (s *stream) reset() {
s.l = s.l[:0]
s.n = 0
}
func (s *stream) insert(v float64) {
s.merge(Samples{{v, 1, 0}})
}
func (s *stream) merge(samples Samples) {
// TODO(beorn7): This tries to merge not only individual samples, but
// whole summaries. The paper doesn't mention merging summaries at
// all. Unittests show that the merging is inaccurate. Find out how to
// do merges properly.
var r float64
i := 0
for _, sample := range samples {
for ; i < len(s.l); i++ {
c := s.l[i]
if c.Value > sample.Value {
// Insert at position i.
s.l = append(s.l, Sample{})
copy(s.l[i+1:], s.l[i:])
s.l[i] = Sample{
sample.Value,
sample.Width,
math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1),
// TODO(beorn7): How to calculate delta correctly?
}
i++
goto inserted
}
r += c.Width
}
s.l = append(s.l, Sample{sample.Value, sample.Width, 0})
i++
inserted:
s.n += sample.Width
r += sample.Width
}
s.compress()
}
func (s *stream) count() int {
return int(s.n)
}
func (s *stream) query(q float64) float64 {
t := math.Ceil(q * s.n)
t += math.Ceil(s.ƒ(s, t) / 2)
p := s.l[0]
var r float64
for _, c := range s.l[1:] {
r += p.Width
if r+c.Width+c.Delta > t {
return p.Value
}
p = c
}
return p.Value
}
func (s *stream) compress() {
if len(s.l) < 2 {
return
}
x := s.l[len(s.l)-1]
xi := len(s.l) - 1
r := s.n - 1 - x.Width
for i := len(s.l) - 2; i >= 0; i-- {
c := s.l[i]
if c.Width+x.Width+x.Delta <= s.ƒ(s, r) {
x.Width += c.Width
s.l[xi] = x
// Remove element at i.
copy(s.l[i:], s.l[i+1:])
s.l = s.l[:len(s.l)-1]
xi -= 1
} else {
x = c
xi = i
}
r -= c.Width
}
}
func (s *stream) samples() Samples {
samples := make(Samples, len(s.l))
copy(samples, s.l)
return samples
}

22
vendor/github.com/cheekybits/genny/LICENSE generated vendored Normal file
View file

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2014 cheekybits
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,2 @@
// Package generic contains the generic marker types.
package generic

View file

@ -0,0 +1,13 @@
package generic
// Type is the placeholder type that indicates a generic value.
// When genny is executed, variables of this type will be replaced with
// references to the specific types.
// var GenericType generic.Type
type Type interface{}
// Number is the placehoder type that indiccates a generic numerical value.
// When genny is executed, variables of this type will be replaced with
// references to the specific types.
// var GenericType generic.Number
type Number float64

22
vendor/github.com/gen2brain/x264-go/.appveyor.yml generated vendored Normal file
View file

@ -0,0 +1,22 @@
version: "{build}"
clone_depth: 1
clone_folder: c:\gopath\src\github.com\gen2brain\x264-go
environment:
GOPATH: c:\gopath
MSYS_PATH: c:\msys64
CGO_ENABLED: 1
GOARCH: 386
CC: i686-w64-mingw32-gcc
install:
- echo %GOPATH%
- echo %MSYS_PATH%
- set PATH=%GOPATH%\bin;c:\go\bin;%MSYS_PATH%\usr\bin;%MSYS_PATH%\mingw32\bin;%PATH%
- go version
- go env
build_script:
- bash -lc "cd /c/gopath/src/github.com/gen2brain/x264-go && go build"

12
vendor/github.com/gen2brain/x264-go/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,12 @@
language: go
go:
- 1.9.x
gobuild_args: "-tags extlib"
before_install:
- sudo apt-get install libx264-dev
script:
- go test -tags extlib -v ./

1
vendor/github.com/gen2brain/x264-go/AUTHORS generated vendored Normal file
View file

@ -0,0 +1 @@
Milan Nikolic <gen2brain@gmail.com>

340
vendor/github.com/gen2brain/x264-go/COPYING generated vendored Normal file
View file

@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

77
vendor/github.com/gen2brain/x264-go/README.md generated vendored Normal file
View file

@ -0,0 +1,77 @@
## x264-go
[![TravisCI Build Status](https://travis-ci.org/gen2brain/x264-go.svg?branch=master)](https://travis-ci.org/gen2brain/x264-go)
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/wfkqlac5ffwk5xgb?svg=true)](https://ci.appveyor.com/project/gen2brain/x264-go)
[![GoDoc](https://godoc.org/github.com/gen2brain/x264-go?status.svg)](https://godoc.org/github.com/gen2brain/x264-go)
[![Go Report Card](https://goreportcard.com/badge/github.com/gen2brain/x264-go?branch=master)](https://goreportcard.com/report/github.com/gen2brain/x264-go)
`x264-go` provides H.264/MPEG-4 AVC codec encoder based on [x264](https://www.videolan.org/developers/x264.html) library.
C source code is included in package. If you want to use external shared/static library (i.e. built with asm and/or OpenCL) use `-tags extlib`.
### Installation
go get -u github.com/gen2brain/x264-go
### Examples
See [screengrab](https://github.com/gen2brain/x264-go/blob/master/examples/screengrab/screengrab.go) example.
### Usage
```go
package main
import (
"bytes"
"image"
"image/color"
"image/draw"
"github.com/gen2brain/x264-go"
)
func main() {
buf := bytes.NewBuffer(make([]byte, 0))
opts := &x264.Options{
Width: 640,
Height: 480,
FrameRate: 25,
Tune: "zerolatency",
Preset: "veryfast",
Profile: "baseline",
LogLevel: x264.LogDebug,
}
enc, err := x264.NewEncoder(buf, opts)
if err != nil {
panic(err)
}
img := x264.NewYCbCr(image.Rect(0, 0, opts.Width, opts.Height))
draw.Draw(img, img.Bounds(), image.Black, image.ZP, draw.Src)
for i := 0; i < opts.Width/2; i++ {
img.Set(i, opts.Height/2, color.RGBA{255, 0, 0, 255})
err = enc.Encode(img)
if err != nil {
panic(err)
}
}
err = enc.Flush()
if err != nil {
panic(err)
}
err = enc.Close()
if err != nil {
panic(err)
}
}
```
## More
For AAC encoder see [aac-go](https://github.com/gen2brain/aac-go).

View file

@ -0,0 +1,212 @@
// Package x264 provides H.264/MPEG-4 AVC codec encoder based on [x264](https://www.videolan.org/developers/x264.html) library.
package x264
import "C"
import (
"fmt"
"image"
"io"
"github.com/gen2brain/x264-go/x264c"
)
// Logging constants.
const (
LogNone int32 = iota - 1
LogError
LogWarning
LogInfo
LogDebug
)
// Options represent encoding options.
type Options struct {
// Frame width.
Width int
// Frame height.
Height int
// Frame rate.
FrameRate int
// Tunings: film, animation, grain, stillimage, psnr, ssim, fastdecode, zerolatency.
Tune string
// Presets: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow, placebo.
Preset string
// Profiles: baseline, main, high, high10, high422, high444.
Profile string
// Log level.
LogLevel int32
}
// Encoder type.
type Encoder struct {
e *x264c.T
w io.Writer
img *YCbCr
opts *Options
csp int32
pts int64
nnals int32
nals []*x264c.Nal
}
// NewEncoder returns new x264 encoder.
func NewEncoder(w io.Writer, opts *Options) (e *Encoder, err error) {
e = &Encoder{}
e.w = w
e.pts = 0
e.opts = opts
e.csp = x264c.CspI420
e.nals = make([]*x264c.Nal, 3)
e.img = NewYCbCr(image.Rect(0, 0, e.opts.Width, e.opts.Height))
param := x264c.Param{}
if e.opts.Preset != "" && e.opts.Profile != "" {
ret := x264c.ParamDefaultPreset(&param, e.opts.Preset, e.opts.Tune)
if ret < 0 {
err = fmt.Errorf("x264: invalid preset/tune name")
return
}
} else {
x264c.ParamDefault(&param)
}
param.IWidth = int32(e.opts.Width)
param.IHeight = int32(e.opts.Height)
param.ICsp = e.csp
param.BVfrInput = 0
param.BRepeatHeaders = 1
param.BAnnexb = 1
param.ILogLevel = e.opts.LogLevel
if e.opts.FrameRate > 0 {
param.IFpsNum = uint32(e.opts.FrameRate)
param.IFpsDen = 1
param.IKeyintMax = int32(e.opts.FrameRate)
param.BIntraRefresh = 1
}
if e.opts.Profile != "" {
ret := x264c.ParamApplyProfile(&param, e.opts.Profile)
if ret < 0 {
err = fmt.Errorf("x264: invalid profile name")
return
}
}
e.e = x264c.EncoderOpen(&param)
if e.e == nil {
err = fmt.Errorf("x264: cannot open the encoder")
return
}
ret := x264c.EncoderHeaders(e.e, e.nals, &e.nnals)
if ret < 0 {
err = fmt.Errorf("x264: cannot encode headers")
return
}
if ret > 0 {
b := C.GoBytes(e.nals[0].PPayload, C.int(ret))
n, er := e.w.Write(b)
if er != nil {
err = er
return
}
if int(ret) != n {
err = fmt.Errorf("x264: error writing headers, size=%d, n=%d", ret, n)
}
}
return
}
// Encode encodes image.
func (e *Encoder) Encode(im image.Image) (err error) {
var picIn, picOut x264c.Picture
e.img.ToYCbCr(im)
ret := x264c.PictureAlloc(&picIn, e.csp, int32(e.opts.Width), int32(e.opts.Height))
if ret < 0 {
err = fmt.Errorf("x264: cannot allocate picture")
return
}
defer x264c.PictureClean(&picIn)
picIn.Img.Plane[0] = C.CBytes(e.img.Y)
picIn.Img.Plane[1] = C.CBytes(e.img.Cb)
picIn.Img.Plane[2] = C.CBytes(e.img.Cr)
picIn.IPts = e.pts
e.pts++
ret = x264c.EncoderEncode(e.e, e.nals, &e.nnals, &picIn, &picOut)
if ret < 0 {
err = fmt.Errorf("x264: cannot encode picture")
return
}
if ret > 0 {
b := C.GoBytes(e.nals[0].PPayload, C.int(ret))
n, er := e.w.Write(b)
if er != nil {
err = er
return
}
if int(ret) != n {
err = fmt.Errorf("x264: error writing payload, size=%d, n=%d", ret, n)
}
}
return
}
// Flush flushes encoder.
func (e *Encoder) Flush() (err error) {
var picOut x264c.Picture
for x264c.EncoderDelayedFrames(e.e) > 0 {
ret := x264c.EncoderEncode(e.e, e.nals, &e.nnals, nil, &picOut)
if ret < 0 {
err = fmt.Errorf("x264: cannot encode picture")
return
}
if ret > 0 {
b := C.GoBytes(e.nals[0].PPayload, C.int(ret))
n, er := e.w.Write(b)
if er != nil {
err = er
return
}
if int(ret) != n {
err = fmt.Errorf("x264: error writing payload, size=%d, n=%d", ret, n)
}
}
}
return
}
// Close closes encoder.
func (e *Encoder) Close() error {
x264c.EncoderClose(e.e)
return nil
}

View file

@ -0,0 +1,42 @@
/*****************************************************************************
* asm-offsets.c: check asm offsets for aarch64
*****************************************************************************
* Copyright (C) 2014-2017 x264 project
*
* Authors: Janne Grunau <janne-x264@jannau.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*
* This program is also available under a commercial proprietary license.
* For more information, contact us at licensing@x264.com.
*****************************************************************************/
#include "common/common.h"
#include "asm-offsets.h"
#define X264_CHECK_OFFSET(s, m, o) struct check_##s##_##m \
{ \
int m_##m[2 * (offsetof(s, m) == o) - 1]; \
}
X264_CHECK_OFFSET(x264_cabac_t, i_low, CABAC_I_LOW);
X264_CHECK_OFFSET(x264_cabac_t, i_range, CABAC_I_RANGE);
X264_CHECK_OFFSET(x264_cabac_t, i_queue, CABAC_I_QUEUE);
X264_CHECK_OFFSET(x264_cabac_t, i_bytes_outstanding, CABAC_I_BYTES_OUTSTANDING);
X264_CHECK_OFFSET(x264_cabac_t, p_start, CABAC_P_START);
X264_CHECK_OFFSET(x264_cabac_t, p, CABAC_P);
X264_CHECK_OFFSET(x264_cabac_t, p_end, CABAC_P_END);
X264_CHECK_OFFSET(x264_cabac_t, f8_bits_encoded, CABAC_F8_BITS_ENCODED);
X264_CHECK_OFFSET(x264_cabac_t, state, CABAC_STATE);

View file

@ -0,0 +1,39 @@
/*****************************************************************************
* asm-offsets.h: asm offsets for aarch64
*****************************************************************************
* Copyright (C) 2014-2017 x264 project
*
* Authors: Janne Grunau <janne-x264@jannau.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*
* This program is also available under a commercial proprietary license.
* For more information, contact us at licensing@x264.com.
*****************************************************************************/
#ifndef X264_AARCH64_ASM_OFFSETS_H
#define X264_AARCH64_ASM_OFFSETS_H
#define CABAC_I_LOW 0x00
#define CABAC_I_RANGE 0x04
#define CABAC_I_QUEUE 0x08
#define CABAC_I_BYTES_OUTSTANDING 0x0c
#define CABAC_P_START 0x10
#define CABAC_P 0x18
#define CABAC_P_END 0x20
#define CABAC_F8_BITS_ENCODED 0x30
#define CABAC_STATE 0x34
#endif

View file

@ -0,0 +1,67 @@
/*****************************************************************************
* dct.h: aarch64 transform and zigzag
*****************************************************************************
* Copyright (C) 2009-2017 x264 project
*
* Authors: David Conrad <lessen42@gmail.com>
* Janne Grunau <janne-x264@jannau.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*
* This program is also available under a commercial proprietary license.
* For more information, contact us at licensing@x264.com.
*****************************************************************************/
#ifndef X264_AARCH64_DCT_H
#define X264_AARCH64_DCT_H
void x264_dct4x4dc_neon( int16_t d[16] );
void x264_idct4x4dc_neon( int16_t d[16] );
void x264_sub4x4_dct_neon( int16_t dct[16], uint8_t *pix1, uint8_t *pix2 );
void x264_sub8x8_dct_neon( int16_t dct[4][16], uint8_t *pix1, uint8_t *pix2 );
void x264_sub16x16_dct_neon( int16_t dct[16][16], uint8_t *pix1, uint8_t *pix2 );
void x264_add4x4_idct_neon( uint8_t *p_dst, int16_t dct[16] );
void x264_add8x8_idct_neon( uint8_t *p_dst, int16_t dct[4][16] );
void x264_add16x16_idct_neon( uint8_t *p_dst, int16_t dct[16][16] );
void x264_add8x8_idct_dc_neon( uint8_t *p_dst, int16_t dct[4] );
void x264_add16x16_idct_dc_neon( uint8_t *p_dst, int16_t dct[16] );
void x264_sub8x8_dct_dc_neon( int16_t dct[4], uint8_t *pix1, uint8_t *pix2 );
void x264_sub8x16_dct_dc_neon( int16_t dct[8], uint8_t *pix1, uint8_t *pix2 );
void x264_sub8x8_dct8_neon( int16_t dct[64], uint8_t *pix1, uint8_t *pix2 );
void x264_sub16x16_dct8_neon( int16_t dct[4][64], uint8_t *pix1, uint8_t *pix2 );
void x264_add8x8_idct8_neon( uint8_t *p_dst, int16_t dct[64] );
void x264_add16x16_idct8_neon( uint8_t *p_dst, int16_t dct[4][64] );
void x264_zigzag_scan_4x4_frame_neon( int16_t level[16], int16_t dct[16] );
void x264_zigzag_scan_4x4_field_neon( int16_t level[16], int16_t dct[16] );
void x264_zigzag_scan_8x8_frame_neon( int16_t level[64], int16_t dct[64] );
void x264_zigzag_scan_8x8_field_neon( int16_t level[64], int16_t dct[64] );
int x264_zigzag_sub_4x4_field_neon( dctcoef level[16], const pixel *p_src, pixel *p_dst );
int x264_zigzag_sub_4x4ac_field_neon( dctcoef level[16], const pixel *p_src, pixel *p_dst, dctcoef *dc );
int x264_zigzag_sub_4x4_frame_neon( dctcoef level[16], const pixel *p_src, pixel *p_dst );
int x264_zigzag_sub_4x4ac_frame_neon( dctcoef level[16], const pixel *p_src, pixel *p_dst, dctcoef *dc );
int x264_zigzag_sub_8x8_field_neon( dctcoef level[16], const pixel *p_src, pixel *p_dst );
int x264_zigzag_sub_8x8_frame_neon( dctcoef level[16], const pixel *p_src, pixel *p_dst );
void x264_zigzag_interleave_8x8_cavlc_neon( dctcoef *dst, dctcoef *src, uint8_t *nnz );
#endif

View file

@ -0,0 +1,281 @@
/*****************************************************************************
* mc-c.c: aarch64 motion compensation
*****************************************************************************
* Copyright (C) 2009-2017 x264 project
*
* Authors: David Conrad <lessen42@gmail.com>
* Janne Grunau <janne-x264@jannau.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*
* This program is also available under a commercial proprietary license.
* For more information, contact us at licensing@x264.com.
*****************************************************************************/
#include "common/common.h"
#include "mc.h"
void x264_prefetch_ref_aarch64( uint8_t *, intptr_t, int );
void x264_prefetch_fenc_420_aarch64( uint8_t *, intptr_t, uint8_t *, intptr_t, int );
void x264_prefetch_fenc_422_aarch64( uint8_t *, intptr_t, uint8_t *, intptr_t, int );
void *x264_memcpy_aligned_neon( void *dst, const void *src, size_t n );
void x264_memzero_aligned_neon( void *dst, size_t n );
void x264_pixel_avg_16x16_neon( uint8_t *, intptr_t, uint8_t *, intptr_t, uint8_t *, intptr_t, int );
void x264_pixel_avg_16x8_neon ( uint8_t *, intptr_t, uint8_t *, intptr_t, uint8_t *, intptr_t, int );
void x264_pixel_avg_8x16_neon ( uint8_t *, intptr_t, uint8_t *, intptr_t, uint8_t *, intptr_t, int );
void x264_pixel_avg_8x8_neon ( uint8_t *, intptr_t, uint8_t *, intptr_t, uint8_t *, intptr_t, int );
void x264_pixel_avg_8x4_neon ( uint8_t *, intptr_t, uint8_t *, intptr_t, uint8_t *, intptr_t, int );
void x264_pixel_avg_4x16_neon ( uint8_t *, intptr_t, uint8_t *, intptr_t, uint8_t *, intptr_t, int );
void x264_pixel_avg_4x8_neon ( uint8_t *, intptr_t, uint8_t *, intptr_t, uint8_t *, intptr_t, int );
void x264_pixel_avg_4x4_neon ( uint8_t *, intptr_t, uint8_t *, intptr_t, uint8_t *, intptr_t, int );
void x264_pixel_avg_4x2_neon ( uint8_t *, intptr_t, uint8_t *, intptr_t, uint8_t *, intptr_t, int );
void x264_pixel_avg2_w4_neon ( uint8_t *, intptr_t, uint8_t *, intptr_t, uint8_t *, int );
void x264_pixel_avg2_w8_neon ( uint8_t *, intptr_t, uint8_t *, intptr_t, uint8_t *, int );
void x264_pixel_avg2_w16_neon( uint8_t *, intptr_t, uint8_t *, intptr_t, uint8_t *, int );
void x264_pixel_avg2_w20_neon( uint8_t *, intptr_t, uint8_t *, intptr_t, uint8_t *, int );
void x264_plane_copy_core_neon( pixel *dst, intptr_t i_dst,
pixel *src, intptr_t i_src, int w, int h );
void x264_plane_copy_swap_core_neon( pixel *dst, intptr_t i_dst,
pixel *src, intptr_t i_src, int w, int h );
void x264_plane_copy_deinterleave_neon( pixel *dstu, intptr_t i_dstu,
pixel *dstv, intptr_t i_dstv,
pixel *src, intptr_t i_src, int w, int h );
void x264_plane_copy_deinterleave_rgb_neon( pixel *dsta, intptr_t i_dsta,
pixel *dstb, intptr_t i_dstb,
pixel *dstc, intptr_t i_dstc,
pixel *src, intptr_t i_src, int pw, int w, int h );
void x264_plane_copy_interleave_core_neon( pixel *dst, intptr_t i_dst,
pixel *srcu, intptr_t i_srcu,
pixel *srcv, intptr_t i_srcv, int w, int h );
void x264_store_interleave_chroma_neon( pixel *dst, intptr_t i_dst, pixel *srcu, pixel *srcv, int height );
void x264_load_deinterleave_chroma_fdec_neon( pixel *dst, pixel *src, intptr_t i_src, int height );
void x264_load_deinterleave_chroma_fenc_neon( pixel *dst, pixel *src, intptr_t i_src, int height );
#define MC_WEIGHT(func)\
void x264_mc_weight_w20##func##_neon( uint8_t *, intptr_t, uint8_t *, intptr_t, const x264_weight_t *, int );\
void x264_mc_weight_w16##func##_neon( uint8_t *, intptr_t, uint8_t *, intptr_t, const x264_weight_t *, int );\
void x264_mc_weight_w8##func##_neon ( uint8_t *, intptr_t, uint8_t *, intptr_t, const x264_weight_t *, int );\
void x264_mc_weight_w4##func##_neon ( uint8_t *, intptr_t, uint8_t *, intptr_t, const x264_weight_t *, int );\
\
static void (* x264_mc##func##_wtab_neon[6])( uint8_t *, intptr_t, uint8_t *, intptr_t, const x264_weight_t *, int ) =\
{\
x264_mc_weight_w4##func##_neon,\
x264_mc_weight_w4##func##_neon,\
x264_mc_weight_w8##func##_neon,\
x264_mc_weight_w16##func##_neon,\
x264_mc_weight_w16##func##_neon,\
x264_mc_weight_w20##func##_neon,\
};
MC_WEIGHT()
MC_WEIGHT(_nodenom)
MC_WEIGHT(_offsetadd)
MC_WEIGHT(_offsetsub)
void x264_mc_copy_w4_neon ( uint8_t *, intptr_t, uint8_t *, intptr_t, int );
void x264_mc_copy_w8_neon ( uint8_t *, intptr_t, uint8_t *, intptr_t, int );
void x264_mc_copy_w16_neon( uint8_t *, intptr_t, uint8_t *, intptr_t, int );
void x264_mc_chroma_neon( uint8_t *, uint8_t *, intptr_t, uint8_t *, intptr_t, int, int, int, int );
void x264_integral_init4h_neon( uint16_t *, uint8_t *, intptr_t );
void x264_integral_init4v_neon( uint16_t *, uint16_t *, intptr_t );
void x264_integral_init8h_neon( uint16_t *, uint8_t *, intptr_t );
void x264_integral_init8v_neon( uint16_t *, intptr_t );
void x264_frame_init_lowres_core_neon( uint8_t *, uint8_t *, uint8_t *, uint8_t *, uint8_t *, intptr_t, intptr_t, int, int );
void x264_mbtree_propagate_cost_neon( int16_t *, uint16_t *, uint16_t *, uint16_t *, uint16_t *, float *, int );
void x264_mbtree_fix8_pack_neon( uint16_t *dst, float *src, int count );
void x264_mbtree_fix8_unpack_neon( float *dst, uint16_t *src, int count );
#if !HIGH_BIT_DEPTH
static void x264_weight_cache_neon( x264_t *h, x264_weight_t *w )
{
if( w->i_scale == 1<<w->i_denom )
{
if( w->i_offset < 0 )
{
w->weightfn = x264_mc_offsetsub_wtab_neon;
w->cachea[0] = -w->i_offset;
}
else
{
w->weightfn = x264_mc_offsetadd_wtab_neon;
w->cachea[0] = w->i_offset;
}
}
else if( !w->i_denom )
w->weightfn = x264_mc_nodenom_wtab_neon;
else
w->weightfn = x264_mc_wtab_neon;
}
static void (* const x264_pixel_avg_wtab_neon[6])( uint8_t *, intptr_t, uint8_t *, intptr_t, uint8_t *, int ) =
{
NULL,
x264_pixel_avg2_w4_neon,
x264_pixel_avg2_w8_neon,
x264_pixel_avg2_w16_neon, // no slower than w12, so no point in a separate function
x264_pixel_avg2_w16_neon,
x264_pixel_avg2_w20_neon,
};
static void (* const x264_mc_copy_wtab_neon[5])( uint8_t *, intptr_t, uint8_t *, intptr_t, int ) =
{
NULL,
x264_mc_copy_w4_neon,
x264_mc_copy_w8_neon,
NULL,
x264_mc_copy_w16_neon,
};
static void mc_luma_neon( uint8_t *dst, intptr_t i_dst_stride,
uint8_t *src[4], intptr_t i_src_stride,
int mvx, int mvy,
int i_width, int i_height, const x264_weight_t *weight )
{
int qpel_idx = ((mvy&3)<<2) + (mvx&3);
intptr_t offset = (mvy>>2)*i_src_stride + (mvx>>2);
uint8_t *src1 = src[x264_hpel_ref0[qpel_idx]] + offset;
if( (mvy&3) == 3 ) // explict if() to force conditional add
src1 += i_src_stride;
if( qpel_idx & 5 ) /* qpel interpolation needed */
{
uint8_t *src2 = src[x264_hpel_ref1[qpel_idx]] + offset + ((mvx&3) == 3);
x264_pixel_avg_wtab_neon[i_width>>2](
dst, i_dst_stride, src1, i_src_stride,
src2, i_height );
if( weight->weightfn )
weight->weightfn[i_width>>2]( dst, i_dst_stride, dst, i_dst_stride, weight, i_height );
}
else if( weight->weightfn )
weight->weightfn[i_width>>2]( dst, i_dst_stride, src1, i_src_stride, weight, i_height );
else
x264_mc_copy_wtab_neon[i_width>>2]( dst, i_dst_stride, src1, i_src_stride, i_height );
}
static uint8_t *get_ref_neon( uint8_t *dst, intptr_t *i_dst_stride,
uint8_t *src[4], intptr_t i_src_stride,
int mvx, int mvy,
int i_width, int i_height, const x264_weight_t *weight )
{
int qpel_idx = ((mvy&3)<<2) + (mvx&3);
intptr_t offset = (mvy>>2)*i_src_stride + (mvx>>2);
uint8_t *src1 = src[x264_hpel_ref0[qpel_idx]] + offset;
if( (mvy&3) == 3 ) // explict if() to force conditional add
src1 += i_src_stride;
if( qpel_idx & 5 ) /* qpel interpolation needed */
{
uint8_t *src2 = src[x264_hpel_ref1[qpel_idx]] + offset + ((mvx&3) == 3);
x264_pixel_avg_wtab_neon[i_width>>2](
dst, *i_dst_stride, src1, i_src_stride,
src2, i_height );
if( weight->weightfn )
weight->weightfn[i_width>>2]( dst, *i_dst_stride, dst, *i_dst_stride, weight, i_height );
return dst;
}
else if( weight->weightfn )
{
weight->weightfn[i_width>>2]( dst, *i_dst_stride, src1, i_src_stride, weight, i_height );
return dst;
}
else
{
*i_dst_stride = i_src_stride;
return src1;
}
}
void x264_hpel_filter_neon( uint8_t *dsth, uint8_t *dstv, uint8_t *dstc,
uint8_t *src, intptr_t stride, int width,
int height, int16_t *buf );
PLANE_COPY(16, neon)
PLANE_COPY_SWAP(16, neon)
PLANE_INTERLEAVE(neon)
#endif // !HIGH_BIT_DEPTH
PROPAGATE_LIST(neon)
void x264_mc_init_aarch64( int cpu, x264_mc_functions_t *pf )
{
#if !HIGH_BIT_DEPTH
if( cpu&X264_CPU_ARMV8 )
{
pf->prefetch_fenc_420 = x264_prefetch_fenc_420_aarch64;
pf->prefetch_fenc_422 = x264_prefetch_fenc_422_aarch64;
pf->prefetch_ref = x264_prefetch_ref_aarch64;
}
if( !(cpu&X264_CPU_NEON) )
return;
pf->copy_16x16_unaligned = x264_mc_copy_w16_neon;
pf->copy[PIXEL_16x16] = x264_mc_copy_w16_neon;
pf->copy[PIXEL_8x8] = x264_mc_copy_w8_neon;
pf->copy[PIXEL_4x4] = x264_mc_copy_w4_neon;
pf->plane_copy = x264_plane_copy_neon;
pf->plane_copy_swap = x264_plane_copy_swap_neon;
pf->plane_copy_deinterleave = x264_plane_copy_deinterleave_neon;
pf->plane_copy_deinterleave_rgb = x264_plane_copy_deinterleave_rgb_neon;
pf->plane_copy_interleave = x264_plane_copy_interleave_neon;
pf->load_deinterleave_chroma_fdec = x264_load_deinterleave_chroma_fdec_neon;
pf->load_deinterleave_chroma_fenc = x264_load_deinterleave_chroma_fenc_neon;
pf->store_interleave_chroma = x264_store_interleave_chroma_neon;
pf->avg[PIXEL_16x16] = x264_pixel_avg_16x16_neon;
pf->avg[PIXEL_16x8] = x264_pixel_avg_16x8_neon;
pf->avg[PIXEL_8x16] = x264_pixel_avg_8x16_neon;
pf->avg[PIXEL_8x8] = x264_pixel_avg_8x8_neon;
pf->avg[PIXEL_8x4] = x264_pixel_avg_8x4_neon;
pf->avg[PIXEL_4x16] = x264_pixel_avg_4x16_neon;
pf->avg[PIXEL_4x8] = x264_pixel_avg_4x8_neon;
pf->avg[PIXEL_4x4] = x264_pixel_avg_4x4_neon;
pf->avg[PIXEL_4x2] = x264_pixel_avg_4x2_neon;
pf->weight = x264_mc_wtab_neon;
pf->offsetadd = x264_mc_offsetadd_wtab_neon;
pf->offsetsub = x264_mc_offsetsub_wtab_neon;
pf->weight_cache = x264_weight_cache_neon;
pf->mc_chroma = x264_mc_chroma_neon;
pf->mc_luma = mc_luma_neon;
pf->get_ref = get_ref_neon;
pf->hpel_filter = x264_hpel_filter_neon;
pf->frame_init_lowres_core = x264_frame_init_lowres_core_neon;
pf->integral_init4h = x264_integral_init4h_neon;
pf->integral_init8h = x264_integral_init8h_neon;
pf->integral_init4v = x264_integral_init4v_neon;
pf->integral_init8v = x264_integral_init8v_neon;
pf->mbtree_propagate_cost = x264_mbtree_propagate_cost_neon;
pf->mbtree_propagate_list = x264_mbtree_propagate_list_neon;
pf->mbtree_fix8_pack = x264_mbtree_fix8_pack_neon;
pf->mbtree_fix8_unpack = x264_mbtree_fix8_unpack_neon;
pf->memcpy_aligned = x264_memcpy_aligned_neon;
pf->memzero_aligned = x264_memzero_aligned_neon;
#endif // !HIGH_BIT_DEPTH
}

View file

@ -0,0 +1,31 @@
/*****************************************************************************
* mc.h: aarch64 motion compensation
*****************************************************************************
* Copyright (C) 2014-2017 x264 project
*
* Authors: Janne Grunau <janne-x264@jannau.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111, USA.
*
* This program is also available under a commercial proprietary license.
* For more information, contact us at licensing@x264.com.
*****************************************************************************/
#ifndef X264_AARCH64_MC_H
#define X264_AARCH64_MC_H
void x264_mc_init_aarch64( int cpu, x264_mc_functions_t *pf );
#endif

Some files were not shown because too many files have changed in this diff Show more