Merge branch 'master' into gh-pages
12
.github/FUNDING.yml
vendored
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
After Width: | Height: | Size: 995 KiB |
BIN
document/img/landing-page-gb.png
vendored
|
Before Width: | Height: | Size: 1 MiB After Width: | Height: | Size: 977 KiB |
BIN
document/img/landing-page-ps-hm.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
document/img/landing-page-ps-x4.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
|
|
@ -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)
|
||||
}
|
||||
153
emulator/font.go
|
|
@ -1,153 +0,0 @@
|
|||
// credit to https://github.com/fogleman/nes
|
||||
package emulator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image/png"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var fontMask image.Image
|
||||
|
||||
func init() {
|
||||
im, err := png.Decode(bytes.NewBuffer(fontData))
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
size := im.Bounds().Size()
|
||||
mask := image.NewRGBA(im.Bounds())
|
||||
for y := 0; y < size.Y; y++ {
|
||||
for x := 0; x < size.X; x++ {
|
||||
r, _, _, _ := im.At(x, y).RGBA()
|
||||
if r > 0 {
|
||||
mask.Set(x, y, color.Opaque)
|
||||
}
|
||||
}
|
||||
}
|
||||
fontMask = mask
|
||||
}
|
||||
|
||||
func WordWrap(text string, maxLength int) []string {
|
||||
var rows []string
|
||||
words := strings.Fields(text)
|
||||
if len(words) == 0 {
|
||||
return rows
|
||||
}
|
||||
row := words[0]
|
||||
for _, word := range words[1:] {
|
||||
newRow := row + " " + word
|
||||
if len(newRow) <= maxLength {
|
||||
row = newRow
|
||||
} else {
|
||||
rows = append(rows, row)
|
||||
row = word
|
||||
}
|
||||
}
|
||||
rows = append(rows, row)
|
||||
return rows
|
||||
}
|
||||
|
||||
func DrawCenteredText(dst draw.Image, text string, dx, dy int, c color.Color) {
|
||||
rows := WordWrap(text, 15)
|
||||
for i, row := range rows {
|
||||
x := 128 - len(row)*8
|
||||
y := 120 - len(rows)*12 + i*24
|
||||
DrawText(dst, x+dx, y+dy, row, c)
|
||||
}
|
||||
}
|
||||
|
||||
func DrawCharacter(dst draw.Image, x, y int, ch byte, c color.Color) {
|
||||
if ch < 32 || ch > 128 {
|
||||
return
|
||||
}
|
||||
cx := int((ch-32)%16) * 16
|
||||
cy := int((ch-32)/16) * 16
|
||||
r := image.Rect(x, y, x+16, y+16)
|
||||
src := &image.Uniform{c}
|
||||
sp := image.Pt(cx, cy)
|
||||
draw.DrawMask(dst, r, src, sp, fontMask, sp, draw.Over)
|
||||
}
|
||||
|
||||
func DrawText(dst draw.Image, x, y int, text string, c color.Color) {
|
||||
for i := range text {
|
||||
DrawCharacter(dst, x, y, text[i], c)
|
||||
x += 16
|
||||
}
|
||||
}
|
||||
|
||||
var fontData = []byte{
|
||||
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D,
|
||||
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x60,
|
||||
0x02, 0x03, 0x00, 0x00, 0x00, 0x8F, 0x9F, 0x44, 0x1B, 0x00, 0x00, 0x00,
|
||||
0x06, 0x50, 0x4C, 0x54, 0x45, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFE, 0x6A,
|
||||
0x62, 0xC8, 0x2E, 0x00, 0x00, 0x00, 0x01, 0x62, 0x4B, 0x47, 0x44, 0x00,
|
||||
0x88, 0x05, 0x1D, 0x48, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
|
||||
0x00, 0x00, 0x0E, 0xC4, 0x00, 0x00, 0x0E, 0xC4, 0x01, 0x95, 0x2B, 0x0E,
|
||||
0x1B, 0x00, 0x00, 0x02, 0xD2, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0xC5,
|
||||
0x98, 0x49, 0x62, 0x23, 0x21, 0x0C, 0x45, 0xB5, 0xE1, 0x7E, 0xDA, 0xFC,
|
||||
0xFB, 0x5F, 0xA5, 0x4B, 0xE8, 0x4B, 0x88, 0x72, 0x2A, 0x1D, 0x07, 0xB9,
|
||||
0x9B, 0xC4, 0x2E, 0x8C, 0xE1, 0x19, 0x8D, 0x0C, 0x22, 0x56, 0x86, 0x5E,
|
||||
0xFF, 0x98, 0x2F, 0x81, 0x78, 0x81, 0xB5, 0xC5, 0x77, 0xF2, 0x50, 0xE2,
|
||||
0x9B, 0x63, 0x00, 0x90, 0x80, 0x71, 0xD5, 0xAD, 0xE6, 0x9F, 0xC5, 0x81,
|
||||
0x90, 0xD9, 0x92, 0xF0, 0x2C, 0xF9, 0x6B, 0x4D, 0x00, 0x00, 0xEA, 0xFD,
|
||||
0x31, 0x45, 0x40, 0x4C, 0xD2, 0xDE, 0x50, 0xC4, 0xBB, 0x4B, 0xD0, 0x02,
|
||||
0xF0, 0xCF, 0x26, 0x82, 0xBA, 0xE2, 0xE0, 0xDF, 0x06, 0x00, 0x5E, 0xD8,
|
||||
0x1D, 0xBB, 0x04, 0xE7, 0x00, 0x4E, 0xC5, 0x44, 0x98, 0xAD, 0xF6, 0x3F,
|
||||
0x20, 0xF2, 0x17, 0x11, 0x96, 0x79, 0xBB, 0x00, 0xAE, 0x48, 0xA5, 0x11,
|
||||
0xCB, 0x34, 0xAB, 0x19, 0xB1, 0x2C, 0x08, 0x69, 0x03, 0x24, 0x87, 0xD3,
|
||||
0xB7, 0xEE, 0x48, 0xEA, 0xEE, 0xCA, 0x05, 0xA0, 0x7D, 0x80, 0xE7, 0x68,
|
||||
0x79, 0x0C, 0x9F, 0x9F, 0xB4, 0xFE, 0x10, 0x90, 0x8E, 0x33, 0x03, 0xC9,
|
||||
0x2A, 0xEE, 0x28, 0x70, 0xD7, 0x66, 0xBB, 0xBC, 0x2A, 0x5C, 0xDC, 0xE9,
|
||||
0x8E, 0x01, 0x23, 0x03, 0x67, 0xD0, 0x89, 0x90, 0x41, 0x54, 0x82, 0x7C,
|
||||
0x99, 0x70, 0x33, 0x23, 0x1A, 0x00, 0x48, 0x3B, 0x79, 0x0E, 0x63, 0x58,
|
||||
0xB3, 0x1F, 0xE4, 0x0E, 0x70, 0xAF, 0x66, 0xA5, 0x17, 0x10, 0x8A, 0xA3,
|
||||
0x00, 0xD1, 0x29, 0x95, 0x7B, 0xF3, 0xA3, 0x45, 0xEB, 0x02, 0xB8, 0x4D,
|
||||
0xE7, 0x40, 0x95, 0x32, 0x6D, 0x57, 0xEE, 0x4B, 0xE0, 0xC6, 0xE0, 0x06,
|
||||
0xC0, 0x15, 0x36, 0x9E, 0xB2, 0x62, 0x36, 0x37, 0xC5, 0x2D, 0xF3, 0x2E,
|
||||
0x33, 0xA2, 0x26, 0x97, 0x63, 0xC0, 0xB4, 0x9E, 0xA5, 0x73, 0xBA, 0xED,
|
||||
0x1C, 0x12, 0xEE, 0x6B, 0x7A, 0x02, 0x13, 0x6D, 0x00, 0x8A, 0x2B, 0x3B,
|
||||
0xE4, 0x18, 0xF0, 0x46, 0x79, 0x23, 0xEA, 0xDF, 0x00, 0xF8, 0x54, 0xB5,
|
||||
0x84, 0xB0, 0xFA, 0x12, 0xE7, 0x15, 0xF5, 0xF4, 0x36, 0xE5, 0xF4, 0x7A,
|
||||
0x09, 0xB2, 0x39, 0xF6, 0x18, 0x80, 0x4C, 0xE2, 0xF6, 0xF4, 0x5A, 0xD5,
|
||||
0xD7, 0x0A, 0xB6, 0xE8, 0x65, 0xBF, 0x67, 0x7F, 0x69, 0xEA, 0x53, 0x00,
|
||||
0xA7, 0xC6, 0x0D, 0x8E, 0xAC, 0xE7, 0xEE, 0xEA, 0x13, 0x90, 0x71, 0x67,
|
||||
0x22, 0xB6, 0x03, 0x80, 0x1C, 0x00, 0x44, 0x52, 0xA1, 0x32, 0x03, 0xB0,
|
||||
0x12, 0x8A, 0x82, 0x0A, 0x6E, 0x02, 0x00, 0x0F, 0x22, 0x70, 0x9A, 0x54,
|
||||
0x76, 0xB0, 0x30, 0x4C, 0x9C, 0x36, 0x40, 0xE9, 0xF8, 0x6A, 0xC6, 0x1B,
|
||||
0x60, 0xA0, 0xA4, 0xFB, 0x2E, 0xC0, 0x80, 0xA6, 0xE2, 0x36, 0x57, 0xF6,
|
||||
0x3D, 0x4F, 0xBA, 0xF2, 0xA0, 0x52, 0x07, 0x96, 0xD9, 0x19, 0x0B, 0x87,
|
||||
0x80, 0xFF, 0x5F, 0xC0, 0xF4, 0x05, 0xA6, 0xAE, 0x3A, 0xC5, 0x4D, 0xB9,
|
||||
0x53, 0x0C, 0x6E, 0x7F, 0x7D, 0x59, 0xEF, 0x02, 0x6C, 0x9D, 0xDD, 0x57,
|
||||
0xB7, 0xB6, 0x19, 0xB8, 0x1E, 0xC4, 0xB9, 0xC4, 0xF8, 0xC7, 0x8F, 0x00,
|
||||
0x64, 0x07, 0x0C, 0x37, 0xD5, 0x04, 0x8C, 0x72, 0x00, 0xE1, 0x36, 0xA0,
|
||||
0x1B, 0xC0, 0x85, 0x25, 0x00, 0x1A, 0x89, 0x35, 0x96, 0xFE, 0x75, 0x08,
|
||||
0x8B, 0x3E, 0x4D, 0x00, 0x33, 0xE1, 0x0C, 0xE9, 0x75, 0xC6, 0x0B, 0x37,
|
||||
0xCD, 0xE5, 0xCE, 0x25, 0xE0, 0x10, 0xEC, 0x2B, 0xCC, 0x39, 0x80, 0x5B,
|
||||
0x29, 0x0B, 0xD5, 0x1A, 0xBA, 0x73, 0xB1, 0x71, 0x41, 0x34, 0x96, 0xFF,
|
||||
0x3C, 0x04, 0xB5, 0x03, 0x2E, 0x09, 0x36, 0x25, 0xFA, 0xF3, 0x96, 0xD2,
|
||||
0xE8, 0xCA, 0x57, 0x89, 0x67, 0x13, 0xE0, 0x30, 0x92, 0xD1, 0x00, 0xC0,
|
||||
0xC3, 0x11, 0x8E, 0xD1, 0x52, 0xF5, 0x95, 0x8E, 0xF4, 0xD5, 0x01, 0xBC,
|
||||
0x1F, 0x50, 0x1F, 0x7C, 0xFB, 0x10, 0x80, 0x1B, 0x6D, 0xDF, 0xAE, 0xF8,
|
||||
0xB6, 0x46, 0xD6, 0x2A, 0xAA, 0x37, 0x11, 0x34, 0x0E, 0xE6, 0xEC, 0xDF,
|
||||
0x01, 0xE0, 0xE4, 0x06, 0x13, 0x57, 0xD6, 0x81, 0x78, 0x16, 0x11, 0x06,
|
||||
0x3D, 0xD9, 0xF6, 0x1A, 0x31, 0xE2, 0x23, 0x00, 0xC4, 0xF1, 0x87, 0xAD,
|
||||
0x09, 0xD0, 0x7F, 0x05, 0x58, 0x97, 0x0E, 0x3B, 0x20, 0x82, 0xAD, 0x1D,
|
||||
0x60, 0x99, 0xE3, 0x66, 0xC6, 0x32, 0xED, 0xF1, 0x22, 0x4E, 0x00, 0xD2,
|
||||
0x8C, 0xE7, 0x80, 0xE7, 0x78, 0xFD, 0xBA, 0xFE, 0x46, 0xC0, 0xFF, 0x02,
|
||||
0xF0, 0x5D, 0x3F, 0xE6, 0xF6, 0x99, 0x60, 0xF1, 0x74, 0x7E, 0xF9, 0x28,
|
||||
0x60, 0x9D, 0x01, 0xFC, 0xA2, 0x6A, 0x74, 0x00, 0xC2, 0x7C, 0xB6, 0x9C,
|
||||
0x4A, 0x6C, 0x75, 0x96, 0x63, 0xC1, 0x5F, 0x74, 0xEF, 0x02, 0x58, 0xF7,
|
||||
0x2F, 0xC7, 0x80, 0x0C, 0x5F, 0x8A, 0x84, 0xEA, 0xDA, 0xB6, 0xDE, 0x84,
|
||||
0xCB, 0x22, 0x77, 0x35, 0x32, 0xD6, 0xF5, 0x61, 0x23, 0x40, 0x25, 0xAF,
|
||||
0xFC, 0x36, 0x80, 0x75, 0x56, 0xD4, 0xE3, 0xBE, 0x48, 0xD9, 0xDD, 0x34,
|
||||
0x00, 0x52, 0x89, 0xB9, 0xC3, 0xF2, 0x04, 0x7E, 0xA5, 0xAF, 0x10, 0xA1,
|
||||
0xDC, 0x56, 0x7C, 0x02, 0xA0, 0x92, 0x8B, 0x06, 0xF7, 0xFF, 0xB2, 0xD2,
|
||||
0x3A, 0xAF, 0x21, 0xFD, 0x88, 0xAC, 0xD5, 0x95, 0xF3, 0x0A, 0xB5, 0x0D,
|
||||
0xF0, 0xEB, 0x7B, 0x83, 0x53, 0xC0, 0x1F, 0xEF, 0x0D, 0xA2, 0x4D, 0x77,
|
||||
0x69, 0xB8, 0xB7, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
|
||||
0x42, 0x60, 0x82,
|
||||
}
|
||||
|
|
@ -1,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)
|
||||
}
|
||||
|
|
@ -1,869 +0,0 @@
|
|||
package nes
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
)
|
||||
|
||||
const frameCounterRate = CPUFrequency / 240.0
|
||||
|
||||
var lengthTable = []byte{
|
||||
10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14,
|
||||
12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30,
|
||||
}
|
||||
|
||||
var dutyTable = [][]byte{
|
||||
{0, 1, 0, 0, 0, 0, 0, 0},
|
||||
{0, 1, 1, 0, 0, 0, 0, 0},
|
||||
{0, 1, 1, 1, 1, 0, 0, 0},
|
||||
{1, 0, 0, 1, 1, 1, 1, 1},
|
||||
}
|
||||
|
||||
var triangleTable = []byte{
|
||||
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
}
|
||||
|
||||
var noiseTable = []uint16{
|
||||
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068,
|
||||
}
|
||||
|
||||
var dmcTable = []byte{
|
||||
214, 190, 170, 160, 143, 127, 113, 107, 95, 80, 71, 64, 53, 42, 36, 27,
|
||||
}
|
||||
|
||||
var pulseTable [31]float32
|
||||
var tndTable [203]float32
|
||||
|
||||
func init() {
|
||||
for i := 0; i < 31; i++ {
|
||||
pulseTable[i] = 95.52 / (8128.0/float32(i) + 100)
|
||||
}
|
||||
for i := 0; i < 203; i++ {
|
||||
tndTable[i] = 163.67 / (24329.0/float32(i) + 100)
|
||||
}
|
||||
}
|
||||
|
||||
// APU
|
||||
|
||||
type APU struct {
|
||||
console *Console
|
||||
channel chan<- float32
|
||||
sampleRate float64
|
||||
pulse1 Pulse
|
||||
pulse2 Pulse
|
||||
triangle Triangle
|
||||
noise Noise
|
||||
dmc DMC
|
||||
cycle uint64
|
||||
framePeriod byte
|
||||
frameValue byte
|
||||
frameIRQ bool
|
||||
filterChain FilterChain
|
||||
}
|
||||
|
||||
func NewAPU(console *Console) *APU {
|
||||
apu := APU{}
|
||||
apu.console = console
|
||||
apu.noise.shiftRegister = 1
|
||||
apu.pulse1.channel = 1
|
||||
apu.pulse2.channel = 2
|
||||
apu.dmc.cpu = console.CPU
|
||||
return &apu
|
||||
}
|
||||
|
||||
func (apu *APU) Save(encoder *gob.Encoder) error {
|
||||
encoder.Encode(apu.cycle)
|
||||
encoder.Encode(apu.framePeriod)
|
||||
encoder.Encode(apu.frameValue)
|
||||
encoder.Encode(apu.frameIRQ)
|
||||
apu.pulse1.Save(encoder)
|
||||
apu.pulse2.Save(encoder)
|
||||
apu.triangle.Save(encoder)
|
||||
apu.noise.Save(encoder)
|
||||
apu.dmc.Save(encoder)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (apu *APU) Load(decoder *gob.Decoder) error {
|
||||
decoder.Decode(&apu.cycle)
|
||||
decoder.Decode(&apu.framePeriod)
|
||||
decoder.Decode(&apu.frameValue)
|
||||
decoder.Decode(&apu.frameIRQ)
|
||||
apu.pulse1.Load(decoder)
|
||||
apu.pulse2.Load(decoder)
|
||||
apu.triangle.Load(decoder)
|
||||
apu.noise.Load(decoder)
|
||||
apu.dmc.Load(decoder)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (apu *APU) Step() {
|
||||
cycle1 := apu.cycle
|
||||
apu.cycle++
|
||||
cycle2 := apu.cycle
|
||||
apu.stepTimer()
|
||||
f1 := int(float64(cycle1) / frameCounterRate)
|
||||
f2 := int(float64(cycle2) / frameCounterRate)
|
||||
if f1 != f2 {
|
||||
apu.stepFrameCounter()
|
||||
}
|
||||
s1 := int(float64(cycle1) / apu.sampleRate)
|
||||
s2 := int(float64(cycle2) / apu.sampleRate)
|
||||
if s1 != s2 {
|
||||
apu.sendSample()
|
||||
}
|
||||
}
|
||||
|
||||
func (apu *APU) sendSample() {
|
||||
output := apu.filterChain.Step(apu.output())
|
||||
//stereo
|
||||
select {
|
||||
case apu.channel <- output:
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case apu.channel <- output:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (apu *APU) output() float32 {
|
||||
p1 := apu.pulse1.output()
|
||||
p2 := apu.pulse2.output()
|
||||
t := apu.triangle.output()
|
||||
n := apu.noise.output()
|
||||
d := apu.dmc.output()
|
||||
pulseOut := pulseTable[p1+p2]
|
||||
tndOut := tndTable[3*t+2*n+d]
|
||||
return pulseOut + tndOut
|
||||
}
|
||||
|
||||
// mode 0: mode 1: function
|
||||
// --------- ----------- -----------------------------
|
||||
// - - - f - - - - - IRQ (if bit 6 is clear)
|
||||
// - l - l l - l - - Length counter and sweep
|
||||
// e e e e e e e e - Envelope and linear counter
|
||||
func (apu *APU) stepFrameCounter() {
|
||||
switch apu.framePeriod {
|
||||
case 4:
|
||||
apu.frameValue = (apu.frameValue + 1) % 4
|
||||
switch apu.frameValue {
|
||||
case 0, 2:
|
||||
apu.stepEnvelope()
|
||||
case 1:
|
||||
apu.stepEnvelope()
|
||||
apu.stepSweep()
|
||||
apu.stepLength()
|
||||
case 3:
|
||||
apu.stepEnvelope()
|
||||
apu.stepSweep()
|
||||
apu.stepLength()
|
||||
apu.fireIRQ()
|
||||
}
|
||||
case 5:
|
||||
apu.frameValue = (apu.frameValue + 1) % 5
|
||||
switch apu.frameValue {
|
||||
case 0, 2:
|
||||
apu.stepEnvelope()
|
||||
case 1, 3:
|
||||
apu.stepEnvelope()
|
||||
apu.stepSweep()
|
||||
apu.stepLength()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (apu *APU) stepTimer() {
|
||||
if apu.cycle%2 == 0 {
|
||||
apu.pulse1.stepTimer()
|
||||
apu.pulse2.stepTimer()
|
||||
apu.noise.stepTimer()
|
||||
apu.dmc.stepTimer()
|
||||
}
|
||||
apu.triangle.stepTimer()
|
||||
}
|
||||
|
||||
func (apu *APU) stepEnvelope() {
|
||||
apu.pulse1.stepEnvelope()
|
||||
apu.pulse2.stepEnvelope()
|
||||
apu.triangle.stepCounter()
|
||||
apu.noise.stepEnvelope()
|
||||
}
|
||||
|
||||
func (apu *APU) stepSweep() {
|
||||
apu.pulse1.stepSweep()
|
||||
apu.pulse2.stepSweep()
|
||||
}
|
||||
|
||||
func (apu *APU) stepLength() {
|
||||
apu.pulse1.stepLength()
|
||||
apu.pulse2.stepLength()
|
||||
apu.triangle.stepLength()
|
||||
apu.noise.stepLength()
|
||||
}
|
||||
|
||||
func (apu *APU) fireIRQ() {
|
||||
if apu.frameIRQ {
|
||||
apu.console.CPU.triggerIRQ()
|
||||
}
|
||||
}
|
||||
|
||||
func (apu *APU) readRegister(address uint16) byte {
|
||||
switch address {
|
||||
case 0x4015:
|
||||
return apu.readStatus()
|
||||
// default:
|
||||
// log.Fatalf("unhandled apu register read at address: 0x%04X", address)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (apu *APU) writeRegister(address uint16, value byte) {
|
||||
switch address {
|
||||
case 0x4000:
|
||||
apu.pulse1.writeControl(value)
|
||||
case 0x4001:
|
||||
apu.pulse1.writeSweep(value)
|
||||
case 0x4002:
|
||||
apu.pulse1.writeTimerLow(value)
|
||||
case 0x4003:
|
||||
apu.pulse1.writeTimerHigh(value)
|
||||
case 0x4004:
|
||||
apu.pulse2.writeControl(value)
|
||||
case 0x4005:
|
||||
apu.pulse2.writeSweep(value)
|
||||
case 0x4006:
|
||||
apu.pulse2.writeTimerLow(value)
|
||||
case 0x4007:
|
||||
apu.pulse2.writeTimerHigh(value)
|
||||
case 0x4008:
|
||||
apu.triangle.writeControl(value)
|
||||
case 0x4009:
|
||||
case 0x4010:
|
||||
apu.dmc.writeControl(value)
|
||||
case 0x4011:
|
||||
apu.dmc.writeValue(value)
|
||||
case 0x4012:
|
||||
apu.dmc.writeAddress(value)
|
||||
case 0x4013:
|
||||
apu.dmc.writeLength(value)
|
||||
case 0x400A:
|
||||
apu.triangle.writeTimerLow(value)
|
||||
case 0x400B:
|
||||
apu.triangle.writeTimerHigh(value)
|
||||
case 0x400C:
|
||||
apu.noise.writeControl(value)
|
||||
case 0x400D:
|
||||
case 0x400E:
|
||||
apu.noise.writePeriod(value)
|
||||
case 0x400F:
|
||||
apu.noise.writeLength(value)
|
||||
case 0x4015:
|
||||
apu.writeControl(value)
|
||||
case 0x4017:
|
||||
apu.writeFrameCounter(value)
|
||||
// default:
|
||||
// log.Fatalf("unhandled apu register write at address: 0x%04X", address)
|
||||
}
|
||||
}
|
||||
|
||||
func (apu *APU) readStatus() byte {
|
||||
var result byte
|
||||
if apu.pulse1.lengthValue > 0 {
|
||||
result |= 1
|
||||
}
|
||||
if apu.pulse2.lengthValue > 0 {
|
||||
result |= 2
|
||||
}
|
||||
if apu.triangle.lengthValue > 0 {
|
||||
result |= 4
|
||||
}
|
||||
if apu.noise.lengthValue > 0 {
|
||||
result |= 8
|
||||
}
|
||||
if apu.dmc.currentLength > 0 {
|
||||
result |= 16
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (apu *APU) writeControl(value byte) {
|
||||
apu.pulse1.enabled = value&1 == 1
|
||||
apu.pulse2.enabled = value&2 == 2
|
||||
apu.triangle.enabled = value&4 == 4
|
||||
apu.noise.enabled = value&8 == 8
|
||||
apu.dmc.enabled = value&16 == 16
|
||||
if !apu.pulse1.enabled {
|
||||
apu.pulse1.lengthValue = 0
|
||||
}
|
||||
if !apu.pulse2.enabled {
|
||||
apu.pulse2.lengthValue = 0
|
||||
}
|
||||
if !apu.triangle.enabled {
|
||||
apu.triangle.lengthValue = 0
|
||||
}
|
||||
if !apu.noise.enabled {
|
||||
apu.noise.lengthValue = 0
|
||||
}
|
||||
if !apu.dmc.enabled {
|
||||
apu.dmc.currentLength = 0
|
||||
} else {
|
||||
if apu.dmc.currentLength == 0 {
|
||||
apu.dmc.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (apu *APU) writeFrameCounter(value byte) {
|
||||
apu.framePeriod = 4 + (value>>7)&1
|
||||
apu.frameIRQ = (value>>6)&1 == 0
|
||||
// apu.frameValue = 0
|
||||
if apu.framePeriod == 5 {
|
||||
apu.stepEnvelope()
|
||||
apu.stepSweep()
|
||||
apu.stepLength()
|
||||
}
|
||||
}
|
||||
|
||||
// Pulse
|
||||
|
||||
type Pulse struct {
|
||||
enabled bool
|
||||
channel byte
|
||||
lengthEnabled bool
|
||||
lengthValue byte
|
||||
timerPeriod uint16
|
||||
timerValue uint16
|
||||
dutyMode byte
|
||||
dutyValue byte
|
||||
sweepReload bool
|
||||
sweepEnabled bool
|
||||
sweepNegate bool
|
||||
sweepShift byte
|
||||
sweepPeriod byte
|
||||
sweepValue byte
|
||||
envelopeEnabled bool
|
||||
envelopeLoop bool
|
||||
envelopeStart bool
|
||||
envelopePeriod byte
|
||||
envelopeValue byte
|
||||
envelopeVolume byte
|
||||
constantVolume byte
|
||||
}
|
||||
|
||||
func (p *Pulse) Save(encoder *gob.Encoder) error {
|
||||
encoder.Encode(p.enabled)
|
||||
encoder.Encode(p.channel)
|
||||
encoder.Encode(p.lengthEnabled)
|
||||
encoder.Encode(p.lengthValue)
|
||||
encoder.Encode(p.timerPeriod)
|
||||
encoder.Encode(p.timerValue)
|
||||
encoder.Encode(p.dutyMode)
|
||||
encoder.Encode(p.dutyValue)
|
||||
encoder.Encode(p.sweepReload)
|
||||
encoder.Encode(p.sweepEnabled)
|
||||
encoder.Encode(p.sweepNegate)
|
||||
encoder.Encode(p.sweepShift)
|
||||
encoder.Encode(p.sweepPeriod)
|
||||
encoder.Encode(p.sweepValue)
|
||||
encoder.Encode(p.envelopeEnabled)
|
||||
encoder.Encode(p.envelopeLoop)
|
||||
encoder.Encode(p.envelopeStart)
|
||||
encoder.Encode(p.envelopePeriod)
|
||||
encoder.Encode(p.envelopeValue)
|
||||
encoder.Encode(p.envelopeVolume)
|
||||
encoder.Encode(p.constantVolume)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pulse) Load(decoder *gob.Decoder) error {
|
||||
decoder.Decode(&p.enabled)
|
||||
decoder.Decode(&p.channel)
|
||||
decoder.Decode(&p.lengthEnabled)
|
||||
decoder.Decode(&p.lengthValue)
|
||||
decoder.Decode(&p.timerPeriod)
|
||||
decoder.Decode(&p.timerValue)
|
||||
decoder.Decode(&p.dutyMode)
|
||||
decoder.Decode(&p.dutyValue)
|
||||
decoder.Decode(&p.sweepReload)
|
||||
decoder.Decode(&p.sweepEnabled)
|
||||
decoder.Decode(&p.sweepNegate)
|
||||
decoder.Decode(&p.sweepShift)
|
||||
decoder.Decode(&p.sweepPeriod)
|
||||
decoder.Decode(&p.sweepValue)
|
||||
decoder.Decode(&p.envelopeEnabled)
|
||||
decoder.Decode(&p.envelopeLoop)
|
||||
decoder.Decode(&p.envelopeStart)
|
||||
decoder.Decode(&p.envelopePeriod)
|
||||
decoder.Decode(&p.envelopeValue)
|
||||
decoder.Decode(&p.envelopeVolume)
|
||||
decoder.Decode(&p.constantVolume)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pulse) writeControl(value byte) {
|
||||
p.dutyMode = (value >> 6) & 3
|
||||
p.lengthEnabled = (value>>5)&1 == 0
|
||||
p.envelopeLoop = (value>>5)&1 == 1
|
||||
p.envelopeEnabled = (value>>4)&1 == 0
|
||||
p.envelopePeriod = value & 15
|
||||
p.constantVolume = value & 15
|
||||
p.envelopeStart = true
|
||||
}
|
||||
|
||||
func (p *Pulse) writeSweep(value byte) {
|
||||
p.sweepEnabled = (value>>7)&1 == 1
|
||||
p.sweepPeriod = (value>>4)&7 + 1
|
||||
p.sweepNegate = (value>>3)&1 == 1
|
||||
p.sweepShift = value & 7
|
||||
p.sweepReload = true
|
||||
}
|
||||
|
||||
func (p *Pulse) writeTimerLow(value byte) {
|
||||
p.timerPeriod = (p.timerPeriod & 0xFF00) | uint16(value)
|
||||
}
|
||||
|
||||
func (p *Pulse) writeTimerHigh(value byte) {
|
||||
p.lengthValue = lengthTable[value>>3]
|
||||
p.timerPeriod = (p.timerPeriod & 0x00FF) | (uint16(value&7) << 8)
|
||||
p.envelopeStart = true
|
||||
p.dutyValue = 0
|
||||
}
|
||||
|
||||
func (p *Pulse) stepTimer() {
|
||||
if p.timerValue == 0 {
|
||||
p.timerValue = p.timerPeriod
|
||||
p.dutyValue = (p.dutyValue + 1) % 8
|
||||
} else {
|
||||
p.timerValue--
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pulse) stepEnvelope() {
|
||||
if p.envelopeStart {
|
||||
p.envelopeVolume = 15
|
||||
p.envelopeValue = p.envelopePeriod
|
||||
p.envelopeStart = false
|
||||
} else if p.envelopeValue > 0 {
|
||||
p.envelopeValue--
|
||||
} else {
|
||||
if p.envelopeVolume > 0 {
|
||||
p.envelopeVolume--
|
||||
} else if p.envelopeLoop {
|
||||
p.envelopeVolume = 15
|
||||
}
|
||||
p.envelopeValue = p.envelopePeriod
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pulse) stepSweep() {
|
||||
if p.sweepReload {
|
||||
if p.sweepEnabled && p.sweepValue == 0 {
|
||||
p.sweep()
|
||||
}
|
||||
p.sweepValue = p.sweepPeriod
|
||||
p.sweepReload = false
|
||||
} else if p.sweepValue > 0 {
|
||||
p.sweepValue--
|
||||
} else {
|
||||
if p.sweepEnabled {
|
||||
p.sweep()
|
||||
}
|
||||
p.sweepValue = p.sweepPeriod
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pulse) stepLength() {
|
||||
if p.lengthEnabled && p.lengthValue > 0 {
|
||||
p.lengthValue--
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pulse) sweep() {
|
||||
delta := p.timerPeriod >> p.sweepShift
|
||||
if p.sweepNegate {
|
||||
p.timerPeriod -= delta
|
||||
if p.channel == 1 {
|
||||
p.timerPeriod--
|
||||
}
|
||||
} else {
|
||||
p.timerPeriod += delta
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pulse) output() byte {
|
||||
if !p.enabled {
|
||||
return 0
|
||||
}
|
||||
if p.lengthValue == 0 {
|
||||
return 0
|
||||
}
|
||||
if dutyTable[p.dutyMode][p.dutyValue] == 0 {
|
||||
return 0
|
||||
}
|
||||
if p.timerPeriod < 8 || p.timerPeriod > 0x7FF {
|
||||
return 0
|
||||
}
|
||||
// if !p.sweepNegate && p.timerPeriod+(p.timerPeriod>>p.sweepShift) > 0x7FF {
|
||||
// return 0
|
||||
// }
|
||||
if p.envelopeEnabled {
|
||||
return p.envelopeVolume
|
||||
} else {
|
||||
return p.constantVolume
|
||||
}
|
||||
}
|
||||
|
||||
// Triangle
|
||||
|
||||
type Triangle struct {
|
||||
enabled bool
|
||||
lengthEnabled bool
|
||||
lengthValue byte
|
||||
timerPeriod uint16
|
||||
timerValue uint16
|
||||
dutyValue byte
|
||||
counterPeriod byte
|
||||
counterValue byte
|
||||
counterReload bool
|
||||
}
|
||||
|
||||
func (t *Triangle) Save(encoder *gob.Encoder) error {
|
||||
encoder.Encode(t.enabled)
|
||||
encoder.Encode(t.lengthEnabled)
|
||||
encoder.Encode(t.lengthValue)
|
||||
encoder.Encode(t.timerPeriod)
|
||||
encoder.Encode(t.timerValue)
|
||||
encoder.Encode(t.dutyValue)
|
||||
encoder.Encode(t.counterPeriod)
|
||||
encoder.Encode(t.counterValue)
|
||||
encoder.Encode(t.counterReload)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Triangle) Load(decoder *gob.Decoder) error {
|
||||
decoder.Decode(&t.enabled)
|
||||
decoder.Decode(&t.lengthEnabled)
|
||||
decoder.Decode(&t.lengthValue)
|
||||
decoder.Decode(&t.timerPeriod)
|
||||
decoder.Decode(&t.timerValue)
|
||||
decoder.Decode(&t.dutyValue)
|
||||
decoder.Decode(&t.counterPeriod)
|
||||
decoder.Decode(&t.counterValue)
|
||||
decoder.Decode(&t.counterReload)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Triangle) writeControl(value byte) {
|
||||
t.lengthEnabled = (value>>7)&1 == 0
|
||||
t.counterPeriod = value & 0x7F
|
||||
}
|
||||
|
||||
func (t *Triangle) writeTimerLow(value byte) {
|
||||
t.timerPeriod = (t.timerPeriod & 0xFF00) | uint16(value)
|
||||
}
|
||||
|
||||
func (t *Triangle) writeTimerHigh(value byte) {
|
||||
t.lengthValue = lengthTable[value>>3]
|
||||
t.timerPeriod = (t.timerPeriod & 0x00FF) | (uint16(value&7) << 8)
|
||||
t.timerValue = t.timerPeriod
|
||||
t.counterReload = true
|
||||
}
|
||||
|
||||
func (t *Triangle) stepTimer() {
|
||||
if t.timerValue == 0 {
|
||||
t.timerValue = t.timerPeriod
|
||||
if t.lengthValue > 0 && t.counterValue > 0 {
|
||||
t.dutyValue = (t.dutyValue + 1) % 32
|
||||
}
|
||||
} else {
|
||||
t.timerValue--
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Triangle) stepLength() {
|
||||
if t.lengthEnabled && t.lengthValue > 0 {
|
||||
t.lengthValue--
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Triangle) stepCounter() {
|
||||
if t.counterReload {
|
||||
t.counterValue = t.counterPeriod
|
||||
} else if t.counterValue > 0 {
|
||||
t.counterValue--
|
||||
}
|
||||
if t.lengthEnabled {
|
||||
t.counterReload = false
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Triangle) output() byte {
|
||||
if !t.enabled {
|
||||
return 0
|
||||
}
|
||||
if t.lengthValue == 0 {
|
||||
return 0
|
||||
}
|
||||
if t.counterValue == 0 {
|
||||
return 0
|
||||
}
|
||||
return triangleTable[t.dutyValue]
|
||||
}
|
||||
|
||||
// Noise
|
||||
|
||||
type Noise struct {
|
||||
enabled bool
|
||||
mode bool
|
||||
shiftRegister uint16
|
||||
lengthEnabled bool
|
||||
lengthValue byte
|
||||
timerPeriod uint16
|
||||
timerValue uint16
|
||||
envelopeEnabled bool
|
||||
envelopeLoop bool
|
||||
envelopeStart bool
|
||||
envelopePeriod byte
|
||||
envelopeValue byte
|
||||
envelopeVolume byte
|
||||
constantVolume byte
|
||||
}
|
||||
|
||||
func (n *Noise) Save(encoder *gob.Encoder) error {
|
||||
encoder.Encode(n.enabled)
|
||||
encoder.Encode(n.mode)
|
||||
encoder.Encode(n.shiftRegister)
|
||||
encoder.Encode(n.lengthEnabled)
|
||||
encoder.Encode(n.lengthValue)
|
||||
encoder.Encode(n.timerPeriod)
|
||||
encoder.Encode(n.timerValue)
|
||||
encoder.Encode(n.envelopeEnabled)
|
||||
encoder.Encode(n.envelopeLoop)
|
||||
encoder.Encode(n.envelopeStart)
|
||||
encoder.Encode(n.envelopePeriod)
|
||||
encoder.Encode(n.envelopeValue)
|
||||
encoder.Encode(n.envelopeVolume)
|
||||
encoder.Encode(n.constantVolume)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Noise) Load(decoder *gob.Decoder) error {
|
||||
decoder.Decode(&n.enabled)
|
||||
decoder.Decode(&n.mode)
|
||||
decoder.Decode(&n.shiftRegister)
|
||||
decoder.Decode(&n.lengthEnabled)
|
||||
decoder.Decode(&n.lengthValue)
|
||||
decoder.Decode(&n.timerPeriod)
|
||||
decoder.Decode(&n.timerValue)
|
||||
decoder.Decode(&n.envelopeEnabled)
|
||||
decoder.Decode(&n.envelopeLoop)
|
||||
decoder.Decode(&n.envelopeStart)
|
||||
decoder.Decode(&n.envelopePeriod)
|
||||
decoder.Decode(&n.envelopeValue)
|
||||
decoder.Decode(&n.envelopeVolume)
|
||||
decoder.Decode(&n.constantVolume)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Noise) writeControl(value byte) {
|
||||
n.lengthEnabled = (value>>5)&1 == 0
|
||||
n.envelopeLoop = (value>>5)&1 == 1
|
||||
n.envelopeEnabled = (value>>4)&1 == 0
|
||||
n.envelopePeriod = value & 15
|
||||
n.constantVolume = value & 15
|
||||
n.envelopeStart = true
|
||||
}
|
||||
|
||||
func (n *Noise) writePeriod(value byte) {
|
||||
n.mode = value&0x80 == 0x80
|
||||
n.timerPeriod = noiseTable[value&0x0F]
|
||||
}
|
||||
|
||||
func (n *Noise) writeLength(value byte) {
|
||||
n.lengthValue = lengthTable[value>>3]
|
||||
n.envelopeStart = true
|
||||
}
|
||||
|
||||
func (n *Noise) stepTimer() {
|
||||
if n.timerValue == 0 {
|
||||
n.timerValue = n.timerPeriod
|
||||
var shift byte
|
||||
if n.mode {
|
||||
shift = 6
|
||||
} else {
|
||||
shift = 1
|
||||
}
|
||||
b1 := n.shiftRegister & 1
|
||||
b2 := (n.shiftRegister >> shift) & 1
|
||||
n.shiftRegister >>= 1
|
||||
n.shiftRegister |= (b1 ^ b2) << 14
|
||||
} else {
|
||||
n.timerValue--
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Noise) stepEnvelope() {
|
||||
if n.envelopeStart {
|
||||
n.envelopeVolume = 15
|
||||
n.envelopeValue = n.envelopePeriod
|
||||
n.envelopeStart = false
|
||||
} else if n.envelopeValue > 0 {
|
||||
n.envelopeValue--
|
||||
} else {
|
||||
if n.envelopeVolume > 0 {
|
||||
n.envelopeVolume--
|
||||
} else if n.envelopeLoop {
|
||||
n.envelopeVolume = 15
|
||||
}
|
||||
n.envelopeValue = n.envelopePeriod
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Noise) stepLength() {
|
||||
if n.lengthEnabled && n.lengthValue > 0 {
|
||||
n.lengthValue--
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Noise) output() byte {
|
||||
if !n.enabled {
|
||||
return 0
|
||||
}
|
||||
if n.lengthValue == 0 {
|
||||
return 0
|
||||
}
|
||||
if n.shiftRegister&1 == 1 {
|
||||
return 0
|
||||
}
|
||||
if n.envelopeEnabled {
|
||||
return n.envelopeVolume
|
||||
} else {
|
||||
return n.constantVolume
|
||||
}
|
||||
}
|
||||
|
||||
// DMC
|
||||
|
||||
type DMC struct {
|
||||
cpu *CPU
|
||||
enabled bool
|
||||
value byte
|
||||
sampleAddress uint16
|
||||
sampleLength uint16
|
||||
currentAddress uint16
|
||||
currentLength uint16
|
||||
shiftRegister byte
|
||||
bitCount byte
|
||||
tickPeriod byte
|
||||
tickValue byte
|
||||
loop bool
|
||||
irq bool
|
||||
}
|
||||
|
||||
func (d *DMC) Save(encoder *gob.Encoder) error {
|
||||
encoder.Encode(d.enabled)
|
||||
encoder.Encode(d.value)
|
||||
encoder.Encode(d.sampleAddress)
|
||||
encoder.Encode(d.sampleLength)
|
||||
encoder.Encode(d.currentAddress)
|
||||
encoder.Encode(d.currentLength)
|
||||
encoder.Encode(d.shiftRegister)
|
||||
encoder.Encode(d.bitCount)
|
||||
encoder.Encode(d.tickPeriod)
|
||||
encoder.Encode(d.tickValue)
|
||||
encoder.Encode(d.loop)
|
||||
encoder.Encode(d.irq)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DMC) Load(decoder *gob.Decoder) error {
|
||||
decoder.Decode(&d.enabled)
|
||||
decoder.Decode(&d.value)
|
||||
decoder.Decode(&d.sampleAddress)
|
||||
decoder.Decode(&d.sampleLength)
|
||||
decoder.Decode(&d.currentAddress)
|
||||
decoder.Decode(&d.currentLength)
|
||||
decoder.Decode(&d.shiftRegister)
|
||||
decoder.Decode(&d.bitCount)
|
||||
decoder.Decode(&d.tickPeriod)
|
||||
decoder.Decode(&d.tickValue)
|
||||
decoder.Decode(&d.loop)
|
||||
decoder.Decode(&d.irq)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DMC) writeControl(value byte) {
|
||||
d.irq = value&0x80 == 0x80
|
||||
d.loop = value&0x40 == 0x40
|
||||
d.tickPeriod = dmcTable[value&0x0F]
|
||||
}
|
||||
|
||||
func (d *DMC) writeValue(value byte) {
|
||||
d.value = value & 0x7F
|
||||
}
|
||||
|
||||
func (d *DMC) writeAddress(value byte) {
|
||||
// Sample address = %11AAAAAA.AA000000
|
||||
d.sampleAddress = 0xC000 | (uint16(value) << 6)
|
||||
}
|
||||
|
||||
func (d *DMC) writeLength(value byte) {
|
||||
// Sample length = %0000LLLL.LLLL0001
|
||||
d.sampleLength = (uint16(value) << 4) | 1
|
||||
}
|
||||
|
||||
func (d *DMC) restart() {
|
||||
d.currentAddress = d.sampleAddress
|
||||
d.currentLength = d.sampleLength
|
||||
}
|
||||
|
||||
func (d *DMC) stepTimer() {
|
||||
if !d.enabled {
|
||||
return
|
||||
}
|
||||
d.stepReader()
|
||||
if d.tickValue == 0 {
|
||||
d.tickValue = d.tickPeriod
|
||||
d.stepShifter()
|
||||
} else {
|
||||
d.tickValue--
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DMC) stepReader() {
|
||||
if d.currentLength > 0 && d.bitCount == 0 {
|
||||
d.cpu.stall += 4
|
||||
d.shiftRegister = d.cpu.Read(d.currentAddress)
|
||||
d.bitCount = 8
|
||||
d.currentAddress++
|
||||
if d.currentAddress == 0 {
|
||||
d.currentAddress = 0x8000
|
||||
}
|
||||
d.currentLength--
|
||||
if d.currentLength == 0 && d.loop {
|
||||
d.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DMC) stepShifter() {
|
||||
if d.bitCount == 0 {
|
||||
return
|
||||
}
|
||||
if d.shiftRegister&1 == 1 {
|
||||
if d.value <= 125 {
|
||||
d.value += 2
|
||||
}
|
||||
} else {
|
||||
if d.value >= 2 {
|
||||
d.value -= 2
|
||||
}
|
||||
}
|
||||
d.shiftRegister >>= 1
|
||||
d.bitCount--
|
||||
}
|
||||
|
||||
func (d *DMC) output() byte {
|
||||
return d.value
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
package nes
|
||||
|
||||
import "encoding/gob"
|
||||
|
||||
type Cartridge struct {
|
||||
PRG []byte // PRG-ROM banks
|
||||
CHR []byte // CHR-ROM banks
|
||||
SRAM []byte // Save RAM
|
||||
Mapper byte // mapper type
|
||||
Mirror byte // mirroring mode
|
||||
Battery byte // battery present
|
||||
}
|
||||
|
||||
func NewCartridge(prg, chr []byte, mapper, mirror, battery byte) *Cartridge {
|
||||
sram := make([]byte, 0x2000)
|
||||
return &Cartridge{prg, chr, sram, mapper, mirror, battery}
|
||||
}
|
||||
|
||||
func (cartridge *Cartridge) Save(encoder *gob.Encoder) error {
|
||||
encoder.Encode(cartridge.PRG)
|
||||
encoder.Encode(cartridge.CHR)
|
||||
encoder.Encode(cartridge.SRAM)
|
||||
encoder.Encode(cartridge.Mirror)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cartridge *Cartridge) Load(decoder *gob.Decoder) error {
|
||||
decoder.Decode(&cartridge.PRG)
|
||||
decoder.Decode(&cartridge.CHR)
|
||||
decoder.Decode(&cartridge.SRAM)
|
||||
decoder.Decode(&cartridge.Mirror)
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
package nes
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"image"
|
||||
"image/color"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
type Console struct {
|
||||
CPU *CPU
|
||||
APU *APU
|
||||
PPU *PPU
|
||||
Cartridge *Cartridge
|
||||
Controller1 *Controller
|
||||
Controller2 *Controller
|
||||
Mapper Mapper
|
||||
RAM []byte
|
||||
}
|
||||
|
||||
func NewConsole(path string) (*Console, error) {
|
||||
cartridge, err := LoadNESFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ram := make([]byte, 2048)
|
||||
controller1 := NewController()
|
||||
controller2 := NewController()
|
||||
console := Console{
|
||||
nil, nil, nil, cartridge, controller1, controller2, nil, ram}
|
||||
mapper, err := NewMapper(&console)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
console.Mapper = mapper
|
||||
console.CPU = NewCPU(&console)
|
||||
console.APU = NewAPU(&console)
|
||||
console.PPU = NewPPU(&console)
|
||||
return &console, nil
|
||||
}
|
||||
|
||||
func (console *Console) Reset() {
|
||||
console.CPU.Reset()
|
||||
}
|
||||
|
||||
func (console *Console) Step() int {
|
||||
cpuCycles := console.CPU.Step()
|
||||
ppuCycles := cpuCycles * 3
|
||||
for i := 0; i < ppuCycles; i++ {
|
||||
console.PPU.Step()
|
||||
console.Mapper.Step()
|
||||
}
|
||||
for i := 0; i < cpuCycles; i++ {
|
||||
console.APU.Step()
|
||||
}
|
||||
return cpuCycles
|
||||
}
|
||||
|
||||
func (console *Console) StepFrame() int {
|
||||
cpuCycles := 0
|
||||
frame := console.PPU.Frame
|
||||
for frame == console.PPU.Frame {
|
||||
cpuCycles += console.Step()
|
||||
}
|
||||
return cpuCycles
|
||||
}
|
||||
|
||||
func (console *Console) StepSeconds(seconds float64) {
|
||||
cycles := int(CPUFrequency * seconds)
|
||||
for cycles > 0 {
|
||||
cycles -= console.Step()
|
||||
}
|
||||
}
|
||||
|
||||
func (console *Console) Buffer() *image.RGBA {
|
||||
return console.PPU.front
|
||||
}
|
||||
|
||||
func (console *Console) BackgroundColor() color.RGBA {
|
||||
return Palette[console.PPU.readPalette(0)%64]
|
||||
}
|
||||
|
||||
func (console *Console) SetButtons1(buttons [8]bool) {
|
||||
console.Controller1.SetButtons(buttons)
|
||||
}
|
||||
|
||||
func (console *Console) SetButtons2(buttons [8]bool) {
|
||||
console.Controller2.SetButtons(buttons)
|
||||
}
|
||||
|
||||
func (console *Console) SetAudioChannel(channel chan<- float32) {
|
||||
console.APU.channel = channel
|
||||
}
|
||||
|
||||
func (console *Console) SetAudioSampleRate(sampleRate float64) {
|
||||
if sampleRate != 0 {
|
||||
// Convert samples per second to cpu steps per sample
|
||||
console.APU.sampleRate = CPUFrequency / sampleRate
|
||||
// Initialize filters
|
||||
console.APU.filterChain = FilterChain{
|
||||
HighPassFilter(float32(sampleRate), 90),
|
||||
HighPassFilter(float32(sampleRate), 440),
|
||||
LowPassFilter(float32(sampleRate), 14000),
|
||||
}
|
||||
} else {
|
||||
console.APU.filterChain = nil
|
||||
}
|
||||
}
|
||||
func (console *Console) SaveState(filename string) error {
|
||||
dir, _ := path.Split(filename)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
encoder := gob.NewEncoder(file)
|
||||
return console.Save(encoder)
|
||||
}
|
||||
|
||||
func (console *Console) Save(encoder *gob.Encoder) error {
|
||||
encoder.Encode(console.RAM)
|
||||
console.CPU.Save(encoder)
|
||||
console.APU.Save(encoder)
|
||||
console.PPU.Save(encoder)
|
||||
console.Cartridge.Save(encoder)
|
||||
console.Mapper.Save(encoder)
|
||||
return encoder.Encode(true)
|
||||
}
|
||||
|
||||
func (console *Console) LoadState(filename string) error {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
decoder := gob.NewDecoder(file)
|
||||
return console.Load(decoder)
|
||||
}
|
||||
|
||||
func (console *Console) Load(decoder *gob.Decoder) error {
|
||||
decoder.Decode(&console.RAM)
|
||||
console.CPU.Load(decoder)
|
||||
console.APU.Load(decoder)
|
||||
console.PPU.Load(decoder)
|
||||
console.Cartridge.Load(decoder)
|
||||
console.Mapper.Load(decoder)
|
||||
var dummy bool
|
||||
if err := decoder.Decode(&dummy); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
package nes
|
||||
|
||||
const (
|
||||
ButtonA = iota
|
||||
ButtonB
|
||||
ButtonSelect
|
||||
ButtonStart
|
||||
ButtonUp
|
||||
ButtonDown
|
||||
ButtonLeft
|
||||
ButtonRight
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
buttons [8]bool
|
||||
index byte
|
||||
strobe byte
|
||||
}
|
||||
|
||||
func NewController() *Controller {
|
||||
return &Controller{}
|
||||
}
|
||||
|
||||
func (c *Controller) SetButtons(buttons [8]bool) {
|
||||
c.buttons = buttons
|
||||
}
|
||||
|
||||
func (c *Controller) Read() byte {
|
||||
value := byte(0)
|
||||
if c.index < 8 && c.buttons[c.index] {
|
||||
value = 1
|
||||
}
|
||||
c.index++
|
||||
if c.strobe&1 == 1 {
|
||||
c.index = 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (c *Controller) Write(value byte) {
|
||||
c.strobe = value
|
||||
if c.strobe&1 == 1 {
|
||||
c.index = 0
|
||||
}
|
||||
}
|
||||
|
|
@ -1,975 +0,0 @@
|
|||
package nes
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const CPUFrequency = 1789773
|
||||
|
||||
// interrupt types
|
||||
const (
|
||||
_ = iota
|
||||
interruptNone
|
||||
interruptNMI
|
||||
interruptIRQ
|
||||
)
|
||||
|
||||
// addressing modes
|
||||
const (
|
||||
_ = iota
|
||||
modeAbsolute
|
||||
modeAbsoluteX
|
||||
modeAbsoluteY
|
||||
modeAccumulator
|
||||
modeImmediate
|
||||
modeImplied
|
||||
modeIndexedIndirect
|
||||
modeIndirect
|
||||
modeIndirectIndexed
|
||||
modeRelative
|
||||
modeZeroPage
|
||||
modeZeroPageX
|
||||
modeZeroPageY
|
||||
)
|
||||
|
||||
// instructionModes indicates the addressing mode for each instruction
|
||||
var instructionModes = [256]byte{
|
||||
6, 7, 6, 7, 11, 11, 11, 11, 6, 5, 4, 5, 1, 1, 1, 1,
|
||||
10, 9, 6, 9, 12, 12, 12, 12, 6, 3, 6, 3, 2, 2, 2, 2,
|
||||
1, 7, 6, 7, 11, 11, 11, 11, 6, 5, 4, 5, 1, 1, 1, 1,
|
||||
10, 9, 6, 9, 12, 12, 12, 12, 6, 3, 6, 3, 2, 2, 2, 2,
|
||||
6, 7, 6, 7, 11, 11, 11, 11, 6, 5, 4, 5, 1, 1, 1, 1,
|
||||
10, 9, 6, 9, 12, 12, 12, 12, 6, 3, 6, 3, 2, 2, 2, 2,
|
||||
6, 7, 6, 7, 11, 11, 11, 11, 6, 5, 4, 5, 8, 1, 1, 1,
|
||||
10, 9, 6, 9, 12, 12, 12, 12, 6, 3, 6, 3, 2, 2, 2, 2,
|
||||
5, 7, 5, 7, 11, 11, 11, 11, 6, 5, 6, 5, 1, 1, 1, 1,
|
||||
10, 9, 6, 9, 12, 12, 13, 13, 6, 3, 6, 3, 2, 2, 3, 3,
|
||||
5, 7, 5, 7, 11, 11, 11, 11, 6, 5, 6, 5, 1, 1, 1, 1,
|
||||
10, 9, 6, 9, 12, 12, 13, 13, 6, 3, 6, 3, 2, 2, 3, 3,
|
||||
5, 7, 5, 7, 11, 11, 11, 11, 6, 5, 6, 5, 1, 1, 1, 1,
|
||||
10, 9, 6, 9, 12, 12, 12, 12, 6, 3, 6, 3, 2, 2, 2, 2,
|
||||
5, 7, 5, 7, 11, 11, 11, 11, 6, 5, 6, 5, 1, 1, 1, 1,
|
||||
10, 9, 6, 9, 12, 12, 12, 12, 6, 3, 6, 3, 2, 2, 2, 2,
|
||||
}
|
||||
|
||||
// instructionSizes indicates the size of each instruction in bytes
|
||||
var instructionSizes = [256]byte{
|
||||
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
|
||||
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
|
||||
3, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
|
||||
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
|
||||
1, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
|
||||
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
|
||||
1, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
|
||||
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
|
||||
2, 2, 0, 0, 2, 2, 2, 0, 1, 0, 1, 0, 3, 3, 3, 0,
|
||||
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 0, 3, 0, 0,
|
||||
2, 2, 2, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
|
||||
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
|
||||
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
|
||||
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
|
||||
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0,
|
||||
2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0,
|
||||
}
|
||||
|
||||
// instructionCycles indicates the number of cycles used by each instruction,
|
||||
// not including conditional cycles
|
||||
var instructionCycles = [256]byte{
|
||||
7, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 4, 4, 6, 6,
|
||||
2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
|
||||
6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 4, 4, 6, 6,
|
||||
2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
|
||||
6, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 3, 4, 6, 6,
|
||||
2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
|
||||
6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 5, 4, 6, 6,
|
||||
2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
|
||||
2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4,
|
||||
2, 6, 2, 6, 4, 4, 4, 4, 2, 5, 2, 5, 5, 5, 5, 5,
|
||||
2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4,
|
||||
2, 5, 2, 5, 4, 4, 4, 4, 2, 4, 2, 4, 4, 4, 4, 4,
|
||||
2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6,
|
||||
2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
|
||||
2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6,
|
||||
2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
|
||||
}
|
||||
|
||||
// instructionPageCycles indicates the number of cycles used by each
|
||||
// instruction when a page is crossed
|
||||
var instructionPageCycles = [256]byte{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0,
|
||||
}
|
||||
|
||||
// instructionNames indicates the name of each instruction
|
||||
var instructionNames = [256]string{
|
||||
"BRK", "ORA", "KIL", "SLO", "NOP", "ORA", "ASL", "SLO",
|
||||
"PHP", "ORA", "ASL", "ANC", "NOP", "ORA", "ASL", "SLO",
|
||||
"BPL", "ORA", "KIL", "SLO", "NOP", "ORA", "ASL", "SLO",
|
||||
"CLC", "ORA", "NOP", "SLO", "NOP", "ORA", "ASL", "SLO",
|
||||
"JSR", "AND", "KIL", "RLA", "BIT", "AND", "ROL", "RLA",
|
||||
"PLP", "AND", "ROL", "ANC", "BIT", "AND", "ROL", "RLA",
|
||||
"BMI", "AND", "KIL", "RLA", "NOP", "AND", "ROL", "RLA",
|
||||
"SEC", "AND", "NOP", "RLA", "NOP", "AND", "ROL", "RLA",
|
||||
"RTI", "EOR", "KIL", "SRE", "NOP", "EOR", "LSR", "SRE",
|
||||
"PHA", "EOR", "LSR", "ALR", "JMP", "EOR", "LSR", "SRE",
|
||||
"BVC", "EOR", "KIL", "SRE", "NOP", "EOR", "LSR", "SRE",
|
||||
"CLI", "EOR", "NOP", "SRE", "NOP", "EOR", "LSR", "SRE",
|
||||
"RTS", "ADC", "KIL", "RRA", "NOP", "ADC", "ROR", "RRA",
|
||||
"PLA", "ADC", "ROR", "ARR", "JMP", "ADC", "ROR", "RRA",
|
||||
"BVS", "ADC", "KIL", "RRA", "NOP", "ADC", "ROR", "RRA",
|
||||
"SEI", "ADC", "NOP", "RRA", "NOP", "ADC", "ROR", "RRA",
|
||||
"NOP", "STA", "NOP", "SAX", "STY", "STA", "STX", "SAX",
|
||||
"DEY", "NOP", "TXA", "XAA", "STY", "STA", "STX", "SAX",
|
||||
"BCC", "STA", "KIL", "AHX", "STY", "STA", "STX", "SAX",
|
||||
"TYA", "STA", "TXS", "TAS", "SHY", "STA", "SHX", "AHX",
|
||||
"LDY", "LDA", "LDX", "LAX", "LDY", "LDA", "LDX", "LAX",
|
||||
"TAY", "LDA", "TAX", "LAX", "LDY", "LDA", "LDX", "LAX",
|
||||
"BCS", "LDA", "KIL", "LAX", "LDY", "LDA", "LDX", "LAX",
|
||||
"CLV", "LDA", "TSX", "LAS", "LDY", "LDA", "LDX", "LAX",
|
||||
"CPY", "CMP", "NOP", "DCP", "CPY", "CMP", "DEC", "DCP",
|
||||
"INY", "CMP", "DEX", "AXS", "CPY", "CMP", "DEC", "DCP",
|
||||
"BNE", "CMP", "KIL", "DCP", "NOP", "CMP", "DEC", "DCP",
|
||||
"CLD", "CMP", "NOP", "DCP", "NOP", "CMP", "DEC", "DCP",
|
||||
"CPX", "SBC", "NOP", "ISC", "CPX", "SBC", "INC", "ISC",
|
||||
"INX", "SBC", "NOP", "SBC", "CPX", "SBC", "INC", "ISC",
|
||||
"BEQ", "SBC", "KIL", "ISC", "NOP", "SBC", "INC", "ISC",
|
||||
"SED", "SBC", "NOP", "ISC", "NOP", "SBC", "INC", "ISC",
|
||||
}
|
||||
|
||||
type CPU struct {
|
||||
Memory // memory interface
|
||||
Cycles uint64 // number of cycles
|
||||
PC uint16 // program counter
|
||||
SP byte // stack pointer
|
||||
A byte // accumulator
|
||||
X byte // x register
|
||||
Y byte // y register
|
||||
C byte // carry flag
|
||||
Z byte // zero flag
|
||||
I byte // interrupt disable flag
|
||||
D byte // decimal mode flag
|
||||
B byte // break command flag
|
||||
U byte // unused flag
|
||||
V byte // overflow flag
|
||||
N byte // negative flag
|
||||
interrupt byte // interrupt type to perform
|
||||
stall int // number of cycles to stall
|
||||
table [256]func(*stepInfo)
|
||||
}
|
||||
|
||||
func NewCPU(console *Console) *CPU {
|
||||
cpu := CPU{Memory: NewCPUMemory(console)}
|
||||
cpu.createTable()
|
||||
cpu.Reset()
|
||||
return &cpu
|
||||
}
|
||||
|
||||
// createTable builds a function table for each instruction
|
||||
func (c *CPU) createTable() {
|
||||
c.table = [256]func(*stepInfo){
|
||||
c.brk, c.ora, c.kil, c.slo, c.nop, c.ora, c.asl, c.slo,
|
||||
c.php, c.ora, c.asl, c.anc, c.nop, c.ora, c.asl, c.slo,
|
||||
c.bpl, c.ora, c.kil, c.slo, c.nop, c.ora, c.asl, c.slo,
|
||||
c.clc, c.ora, c.nop, c.slo, c.nop, c.ora, c.asl, c.slo,
|
||||
c.jsr, c.and, c.kil, c.rla, c.bit, c.and, c.rol, c.rla,
|
||||
c.plp, c.and, c.rol, c.anc, c.bit, c.and, c.rol, c.rla,
|
||||
c.bmi, c.and, c.kil, c.rla, c.nop, c.and, c.rol, c.rla,
|
||||
c.sec, c.and, c.nop, c.rla, c.nop, c.and, c.rol, c.rla,
|
||||
c.rti, c.eor, c.kil, c.sre, c.nop, c.eor, c.lsr, c.sre,
|
||||
c.pha, c.eor, c.lsr, c.alr, c.jmp, c.eor, c.lsr, c.sre,
|
||||
c.bvc, c.eor, c.kil, c.sre, c.nop, c.eor, c.lsr, c.sre,
|
||||
c.cli, c.eor, c.nop, c.sre, c.nop, c.eor, c.lsr, c.sre,
|
||||
c.rts, c.adc, c.kil, c.rra, c.nop, c.adc, c.ror, c.rra,
|
||||
c.pla, c.adc, c.ror, c.arr, c.jmp, c.adc, c.ror, c.rra,
|
||||
c.bvs, c.adc, c.kil, c.rra, c.nop, c.adc, c.ror, c.rra,
|
||||
c.sei, c.adc, c.nop, c.rra, c.nop, c.adc, c.ror, c.rra,
|
||||
c.nop, c.sta, c.nop, c.sax, c.sty, c.sta, c.stx, c.sax,
|
||||
c.dey, c.nop, c.txa, c.xaa, c.sty, c.sta, c.stx, c.sax,
|
||||
c.bcc, c.sta, c.kil, c.ahx, c.sty, c.sta, c.stx, c.sax,
|
||||
c.tya, c.sta, c.txs, c.tas, c.shy, c.sta, c.shx, c.ahx,
|
||||
c.ldy, c.lda, c.ldx, c.lax, c.ldy, c.lda, c.ldx, c.lax,
|
||||
c.tay, c.lda, c.tax, c.lax, c.ldy, c.lda, c.ldx, c.lax,
|
||||
c.bcs, c.lda, c.kil, c.lax, c.ldy, c.lda, c.ldx, c.lax,
|
||||
c.clv, c.lda, c.tsx, c.las, c.ldy, c.lda, c.ldx, c.lax,
|
||||
c.cpy, c.cmp, c.nop, c.dcp, c.cpy, c.cmp, c.dec, c.dcp,
|
||||
c.iny, c.cmp, c.dex, c.axs, c.cpy, c.cmp, c.dec, c.dcp,
|
||||
c.bne, c.cmp, c.kil, c.dcp, c.nop, c.cmp, c.dec, c.dcp,
|
||||
c.cld, c.cmp, c.nop, c.dcp, c.nop, c.cmp, c.dec, c.dcp,
|
||||
c.cpx, c.sbc, c.nop, c.isc, c.cpx, c.sbc, c.inc, c.isc,
|
||||
c.inx, c.sbc, c.nop, c.sbc, c.cpx, c.sbc, c.inc, c.isc,
|
||||
c.beq, c.sbc, c.kil, c.isc, c.nop, c.sbc, c.inc, c.isc,
|
||||
c.sed, c.sbc, c.nop, c.isc, c.nop, c.sbc, c.inc, c.isc,
|
||||
}
|
||||
}
|
||||
|
||||
func (cpu *CPU) Save(encoder *gob.Encoder) error {
|
||||
encoder.Encode(cpu.Cycles)
|
||||
encoder.Encode(cpu.PC)
|
||||
encoder.Encode(cpu.SP)
|
||||
encoder.Encode(cpu.A)
|
||||
encoder.Encode(cpu.X)
|
||||
encoder.Encode(cpu.Y)
|
||||
encoder.Encode(cpu.C)
|
||||
encoder.Encode(cpu.Z)
|
||||
encoder.Encode(cpu.I)
|
||||
encoder.Encode(cpu.D)
|
||||
encoder.Encode(cpu.B)
|
||||
encoder.Encode(cpu.U)
|
||||
encoder.Encode(cpu.V)
|
||||
encoder.Encode(cpu.N)
|
||||
encoder.Encode(cpu.interrupt)
|
||||
encoder.Encode(cpu.stall)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cpu *CPU) Load(decoder *gob.Decoder) error {
|
||||
decoder.Decode(&cpu.Cycles)
|
||||
decoder.Decode(&cpu.PC)
|
||||
decoder.Decode(&cpu.SP)
|
||||
decoder.Decode(&cpu.A)
|
||||
decoder.Decode(&cpu.X)
|
||||
decoder.Decode(&cpu.Y)
|
||||
decoder.Decode(&cpu.C)
|
||||
decoder.Decode(&cpu.Z)
|
||||
decoder.Decode(&cpu.I)
|
||||
decoder.Decode(&cpu.D)
|
||||
decoder.Decode(&cpu.B)
|
||||
decoder.Decode(&cpu.U)
|
||||
decoder.Decode(&cpu.V)
|
||||
decoder.Decode(&cpu.N)
|
||||
decoder.Decode(&cpu.interrupt)
|
||||
decoder.Decode(&cpu.stall)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reset resets the CPU to its initial powerup state
|
||||
func (cpu *CPU) Reset() {
|
||||
cpu.PC = cpu.Read16(0xFFFC)
|
||||
cpu.SP = 0xFD
|
||||
cpu.SetFlags(0x24)
|
||||
}
|
||||
|
||||
// PrintInstruction prints the current CPU state
|
||||
func (cpu *CPU) PrintInstruction() {
|
||||
opcode := cpu.Read(cpu.PC)
|
||||
bytes := instructionSizes[opcode]
|
||||
name := instructionNames[opcode]
|
||||
w0 := fmt.Sprintf("%02X", cpu.Read(cpu.PC+0))
|
||||
w1 := fmt.Sprintf("%02X", cpu.Read(cpu.PC+1))
|
||||
w2 := fmt.Sprintf("%02X", cpu.Read(cpu.PC+2))
|
||||
if bytes < 2 {
|
||||
w1 = " "
|
||||
}
|
||||
if bytes < 3 {
|
||||
w2 = " "
|
||||
}
|
||||
fmt.Printf(
|
||||
"%4X %s %s %s %s %21s"+
|
||||
"A:%02X X:%02X Y:%02X P:%02X SP:%02X CYC:%3d\n",
|
||||
cpu.PC, w0, w1, w2, name, "",
|
||||
cpu.A, cpu.X, cpu.Y, cpu.Flags(), cpu.SP, (cpu.Cycles*3)%341)
|
||||
}
|
||||
|
||||
// pagesDiffer returns true if the two addresses reference different pages
|
||||
func pagesDiffer(a, b uint16) bool {
|
||||
return a&0xFF00 != b&0xFF00
|
||||
}
|
||||
|
||||
// addBranchCycles adds a cycle for taking a branch and adds another cycle
|
||||
// if the branch jumps to a new page
|
||||
func (cpu *CPU) addBranchCycles(info *stepInfo) {
|
||||
cpu.Cycles++
|
||||
if pagesDiffer(info.pc, info.address) {
|
||||
cpu.Cycles++
|
||||
}
|
||||
}
|
||||
|
||||
func (cpu *CPU) compare(a, b byte) {
|
||||
cpu.setZN(a - b)
|
||||
if a >= b {
|
||||
cpu.C = 1
|
||||
} else {
|
||||
cpu.C = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Read16 reads two bytes using Read to return a double-word value
|
||||
func (cpu *CPU) Read16(address uint16) uint16 {
|
||||
lo := uint16(cpu.Read(address))
|
||||
hi := uint16(cpu.Read(address + 1))
|
||||
return hi<<8 | lo
|
||||
}
|
||||
|
||||
// read16bug emulates a 6502 bug that caused the low byte to wrap without
|
||||
// incrementing the high byte
|
||||
func (cpu *CPU) read16bug(address uint16) uint16 {
|
||||
a := address
|
||||
b := (a & 0xFF00) | uint16(byte(a)+1)
|
||||
lo := cpu.Read(a)
|
||||
hi := cpu.Read(b)
|
||||
return uint16(hi)<<8 | uint16(lo)
|
||||
}
|
||||
|
||||
// push pushes a byte onto the stack
|
||||
func (cpu *CPU) push(value byte) {
|
||||
cpu.Write(0x100|uint16(cpu.SP), value)
|
||||
cpu.SP--
|
||||
}
|
||||
|
||||
// pull pops a byte from the stack
|
||||
func (cpu *CPU) pull() byte {
|
||||
cpu.SP++
|
||||
return cpu.Read(0x100 | uint16(cpu.SP))
|
||||
}
|
||||
|
||||
// push16 pushes two bytes onto the stack
|
||||
func (cpu *CPU) push16(value uint16) {
|
||||
hi := byte(value >> 8)
|
||||
lo := byte(value & 0xFF)
|
||||
cpu.push(hi)
|
||||
cpu.push(lo)
|
||||
}
|
||||
|
||||
// pull16 pops two bytes from the stack
|
||||
func (cpu *CPU) pull16() uint16 {
|
||||
lo := uint16(cpu.pull())
|
||||
hi := uint16(cpu.pull())
|
||||
return hi<<8 | lo
|
||||
}
|
||||
|
||||
// Flags returns the processor status flags
|
||||
func (cpu *CPU) Flags() byte {
|
||||
var flags byte
|
||||
flags |= cpu.C << 0
|
||||
flags |= cpu.Z << 1
|
||||
flags |= cpu.I << 2
|
||||
flags |= cpu.D << 3
|
||||
flags |= cpu.B << 4
|
||||
flags |= cpu.U << 5
|
||||
flags |= cpu.V << 6
|
||||
flags |= cpu.N << 7
|
||||
return flags
|
||||
}
|
||||
|
||||
// SetFlags sets the processor status flags
|
||||
func (cpu *CPU) SetFlags(flags byte) {
|
||||
cpu.C = (flags >> 0) & 1
|
||||
cpu.Z = (flags >> 1) & 1
|
||||
cpu.I = (flags >> 2) & 1
|
||||
cpu.D = (flags >> 3) & 1
|
||||
cpu.B = (flags >> 4) & 1
|
||||
cpu.U = (flags >> 5) & 1
|
||||
cpu.V = (flags >> 6) & 1
|
||||
cpu.N = (flags >> 7) & 1
|
||||
}
|
||||
|
||||
// setZ sets the zero flag if the argument is zero
|
||||
func (cpu *CPU) setZ(value byte) {
|
||||
if value == 0 {
|
||||
cpu.Z = 1
|
||||
} else {
|
||||
cpu.Z = 0
|
||||
}
|
||||
}
|
||||
|
||||
// setN sets the negative flag if the argument is negative (high bit is set)
|
||||
func (cpu *CPU) setN(value byte) {
|
||||
if value&0x80 != 0 {
|
||||
cpu.N = 1
|
||||
} else {
|
||||
cpu.N = 0
|
||||
}
|
||||
}
|
||||
|
||||
// setZN sets the zero flag and the negative flag
|
||||
func (cpu *CPU) setZN(value byte) {
|
||||
cpu.setZ(value)
|
||||
cpu.setN(value)
|
||||
}
|
||||
|
||||
// triggerNMI causes a non-maskable interrupt to occur on the next cycle
|
||||
func (cpu *CPU) triggerNMI() {
|
||||
cpu.interrupt = interruptNMI
|
||||
}
|
||||
|
||||
// triggerIRQ causes an IRQ interrupt to occur on the next cycle
|
||||
func (cpu *CPU) triggerIRQ() {
|
||||
if cpu.I == 0 {
|
||||
cpu.interrupt = interruptIRQ
|
||||
}
|
||||
}
|
||||
|
||||
// stepInfo contains information that the instruction functions use
|
||||
type stepInfo struct {
|
||||
address uint16
|
||||
pc uint16
|
||||
mode byte
|
||||
}
|
||||
|
||||
// Step executes a single CPU instruction
|
||||
func (cpu *CPU) Step() int {
|
||||
if cpu.stall > 0 {
|
||||
cpu.stall--
|
||||
return 1
|
||||
}
|
||||
|
||||
cycles := cpu.Cycles
|
||||
|
||||
switch cpu.interrupt {
|
||||
case interruptNMI:
|
||||
cpu.nmi()
|
||||
case interruptIRQ:
|
||||
cpu.irq()
|
||||
}
|
||||
cpu.interrupt = interruptNone
|
||||
|
||||
opcode := cpu.Read(cpu.PC)
|
||||
mode := instructionModes[opcode]
|
||||
|
||||
var address uint16
|
||||
var pageCrossed bool
|
||||
switch mode {
|
||||
case modeAbsolute:
|
||||
address = cpu.Read16(cpu.PC + 1)
|
||||
case modeAbsoluteX:
|
||||
address = cpu.Read16(cpu.PC+1) + uint16(cpu.X)
|
||||
pageCrossed = pagesDiffer(address-uint16(cpu.X), address)
|
||||
case modeAbsoluteY:
|
||||
address = cpu.Read16(cpu.PC+1) + uint16(cpu.Y)
|
||||
pageCrossed = pagesDiffer(address-uint16(cpu.Y), address)
|
||||
case modeAccumulator:
|
||||
address = 0
|
||||
case modeImmediate:
|
||||
address = cpu.PC + 1
|
||||
case modeImplied:
|
||||
address = 0
|
||||
case modeIndexedIndirect:
|
||||
address = cpu.read16bug(uint16(cpu.Read(cpu.PC+1) + cpu.X))
|
||||
case modeIndirect:
|
||||
address = cpu.read16bug(cpu.Read16(cpu.PC + 1))
|
||||
case modeIndirectIndexed:
|
||||
address = cpu.read16bug(uint16(cpu.Read(cpu.PC+1))) + uint16(cpu.Y)
|
||||
pageCrossed = pagesDiffer(address-uint16(cpu.Y), address)
|
||||
case modeRelative:
|
||||
offset := uint16(cpu.Read(cpu.PC + 1))
|
||||
if offset < 0x80 {
|
||||
address = cpu.PC + 2 + offset
|
||||
} else {
|
||||
address = cpu.PC + 2 + offset - 0x100
|
||||
}
|
||||
case modeZeroPage:
|
||||
address = uint16(cpu.Read(cpu.PC + 1))
|
||||
case modeZeroPageX:
|
||||
address = uint16(cpu.Read(cpu.PC+1)+cpu.X) & 0xff
|
||||
case modeZeroPageY:
|
||||
address = uint16(cpu.Read(cpu.PC+1)+cpu.Y) & 0xff
|
||||
}
|
||||
|
||||
cpu.PC += uint16(instructionSizes[opcode])
|
||||
cpu.Cycles += uint64(instructionCycles[opcode])
|
||||
if pageCrossed {
|
||||
cpu.Cycles += uint64(instructionPageCycles[opcode])
|
||||
}
|
||||
info := &stepInfo{address, cpu.PC, mode}
|
||||
cpu.table[opcode](info)
|
||||
|
||||
return int(cpu.Cycles - cycles)
|
||||
}
|
||||
|
||||
// NMI - Non-Maskable Interrupt
|
||||
func (cpu *CPU) nmi() {
|
||||
cpu.push16(cpu.PC)
|
||||
cpu.php(nil)
|
||||
cpu.PC = cpu.Read16(0xFFFA)
|
||||
cpu.I = 1
|
||||
cpu.Cycles += 7
|
||||
}
|
||||
|
||||
// IRQ - IRQ Interrupt
|
||||
func (cpu *CPU) irq() {
|
||||
cpu.push16(cpu.PC)
|
||||
cpu.php(nil)
|
||||
cpu.PC = cpu.Read16(0xFFFE)
|
||||
cpu.I = 1
|
||||
cpu.Cycles += 7
|
||||
}
|
||||
|
||||
// ADC - Add with Carry
|
||||
func (cpu *CPU) adc(info *stepInfo) {
|
||||
a := cpu.A
|
||||
b := cpu.Read(info.address)
|
||||
c := cpu.C
|
||||
cpu.A = a + b + c
|
||||
cpu.setZN(cpu.A)
|
||||
if int(a)+int(b)+int(c) > 0xFF {
|
||||
cpu.C = 1
|
||||
} else {
|
||||
cpu.C = 0
|
||||
}
|
||||
if (a^b)&0x80 == 0 && (a^cpu.A)&0x80 != 0 {
|
||||
cpu.V = 1
|
||||
} else {
|
||||
cpu.V = 0
|
||||
}
|
||||
}
|
||||
|
||||
// AND - Logical AND
|
||||
func (cpu *CPU) and(info *stepInfo) {
|
||||
cpu.A = cpu.A & cpu.Read(info.address)
|
||||
cpu.setZN(cpu.A)
|
||||
}
|
||||
|
||||
// ASL - Arithmetic Shift Left
|
||||
func (cpu *CPU) asl(info *stepInfo) {
|
||||
if info.mode == modeAccumulator {
|
||||
cpu.C = (cpu.A >> 7) & 1
|
||||
cpu.A <<= 1
|
||||
cpu.setZN(cpu.A)
|
||||
} else {
|
||||
value := cpu.Read(info.address)
|
||||
cpu.C = (value >> 7) & 1
|
||||
value <<= 1
|
||||
cpu.Write(info.address, value)
|
||||
cpu.setZN(value)
|
||||
}
|
||||
}
|
||||
|
||||
// BCC - Branch if Carry Clear
|
||||
func (cpu *CPU) bcc(info *stepInfo) {
|
||||
if cpu.C == 0 {
|
||||
cpu.PC = info.address
|
||||
cpu.addBranchCycles(info)
|
||||
}
|
||||
}
|
||||
|
||||
// BCS - Branch if Carry Set
|
||||
func (cpu *CPU) bcs(info *stepInfo) {
|
||||
if cpu.C != 0 {
|
||||
cpu.PC = info.address
|
||||
cpu.addBranchCycles(info)
|
||||
}
|
||||
}
|
||||
|
||||
// BEQ - Branch if Equal
|
||||
func (cpu *CPU) beq(info *stepInfo) {
|
||||
if cpu.Z != 0 {
|
||||
cpu.PC = info.address
|
||||
cpu.addBranchCycles(info)
|
||||
}
|
||||
}
|
||||
|
||||
// BIT - Bit Test
|
||||
func (cpu *CPU) bit(info *stepInfo) {
|
||||
value := cpu.Read(info.address)
|
||||
cpu.V = (value >> 6) & 1
|
||||
cpu.setZ(value & cpu.A)
|
||||
cpu.setN(value)
|
||||
}
|
||||
|
||||
// BMI - Branch if Minus
|
||||
func (cpu *CPU) bmi(info *stepInfo) {
|
||||
if cpu.N != 0 {
|
||||
cpu.PC = info.address
|
||||
cpu.addBranchCycles(info)
|
||||
}
|
||||
}
|
||||
|
||||
// BNE - Branch if Not Equal
|
||||
func (cpu *CPU) bne(info *stepInfo) {
|
||||
if cpu.Z == 0 {
|
||||
cpu.PC = info.address
|
||||
cpu.addBranchCycles(info)
|
||||
}
|
||||
}
|
||||
|
||||
// BPL - Branch if Positive
|
||||
func (cpu *CPU) bpl(info *stepInfo) {
|
||||
if cpu.N == 0 {
|
||||
cpu.PC = info.address
|
||||
cpu.addBranchCycles(info)
|
||||
}
|
||||
}
|
||||
|
||||
// BRK - Force Interrupt
|
||||
func (cpu *CPU) brk(info *stepInfo) {
|
||||
cpu.push16(cpu.PC)
|
||||
cpu.php(info)
|
||||
cpu.sei(info)
|
||||
cpu.PC = cpu.Read16(0xFFFE)
|
||||
}
|
||||
|
||||
// BVC - Branch if Overflow Clear
|
||||
func (cpu *CPU) bvc(info *stepInfo) {
|
||||
if cpu.V == 0 {
|
||||
cpu.PC = info.address
|
||||
cpu.addBranchCycles(info)
|
||||
}
|
||||
}
|
||||
|
||||
// BVS - Branch if Overflow Set
|
||||
func (cpu *CPU) bvs(info *stepInfo) {
|
||||
if cpu.V != 0 {
|
||||
cpu.PC = info.address
|
||||
cpu.addBranchCycles(info)
|
||||
}
|
||||
}
|
||||
|
||||
// CLC - Clear Carry Flag
|
||||
func (cpu *CPU) clc(info *stepInfo) {
|
||||
cpu.C = 0
|
||||
}
|
||||
|
||||
// CLD - Clear Decimal Mode
|
||||
func (cpu *CPU) cld(info *stepInfo) {
|
||||
cpu.D = 0
|
||||
}
|
||||
|
||||
// CLI - Clear Interrupt Disable
|
||||
func (cpu *CPU) cli(info *stepInfo) {
|
||||
cpu.I = 0
|
||||
}
|
||||
|
||||
// CLV - Clear Overflow Flag
|
||||
func (cpu *CPU) clv(info *stepInfo) {
|
||||
cpu.V = 0
|
||||
}
|
||||
|
||||
// CMP - Compare
|
||||
func (cpu *CPU) cmp(info *stepInfo) {
|
||||
value := cpu.Read(info.address)
|
||||
cpu.compare(cpu.A, value)
|
||||
}
|
||||
|
||||
// CPX - Compare X Register
|
||||
func (cpu *CPU) cpx(info *stepInfo) {
|
||||
value := cpu.Read(info.address)
|
||||
cpu.compare(cpu.X, value)
|
||||
}
|
||||
|
||||
// CPY - Compare Y Register
|
||||
func (cpu *CPU) cpy(info *stepInfo) {
|
||||
value := cpu.Read(info.address)
|
||||
cpu.compare(cpu.Y, value)
|
||||
}
|
||||
|
||||
// DEC - Decrement Memory
|
||||
func (cpu *CPU) dec(info *stepInfo) {
|
||||
value := cpu.Read(info.address) - 1
|
||||
cpu.Write(info.address, value)
|
||||
cpu.setZN(value)
|
||||
}
|
||||
|
||||
// DEX - Decrement X Register
|
||||
func (cpu *CPU) dex(info *stepInfo) {
|
||||
cpu.X--
|
||||
cpu.setZN(cpu.X)
|
||||
}
|
||||
|
||||
// DEY - Decrement Y Register
|
||||
func (cpu *CPU) dey(info *stepInfo) {
|
||||
cpu.Y--
|
||||
cpu.setZN(cpu.Y)
|
||||
}
|
||||
|
||||
// EOR - Exclusive OR
|
||||
func (cpu *CPU) eor(info *stepInfo) {
|
||||
cpu.A = cpu.A ^ cpu.Read(info.address)
|
||||
cpu.setZN(cpu.A)
|
||||
}
|
||||
|
||||
// INC - Increment Memory
|
||||
func (cpu *CPU) inc(info *stepInfo) {
|
||||
value := cpu.Read(info.address) + 1
|
||||
cpu.Write(info.address, value)
|
||||
cpu.setZN(value)
|
||||
}
|
||||
|
||||
// INX - Increment X Register
|
||||
func (cpu *CPU) inx(info *stepInfo) {
|
||||
cpu.X++
|
||||
cpu.setZN(cpu.X)
|
||||
}
|
||||
|
||||
// INY - Increment Y Register
|
||||
func (cpu *CPU) iny(info *stepInfo) {
|
||||
cpu.Y++
|
||||
cpu.setZN(cpu.Y)
|
||||
}
|
||||
|
||||
// JMP - Jump
|
||||
func (cpu *CPU) jmp(info *stepInfo) {
|
||||
cpu.PC = info.address
|
||||
}
|
||||
|
||||
// JSR - Jump to Subroutine
|
||||
func (cpu *CPU) jsr(info *stepInfo) {
|
||||
cpu.push16(cpu.PC - 1)
|
||||
cpu.PC = info.address
|
||||
}
|
||||
|
||||
// LDA - Load Accumulator
|
||||
func (cpu *CPU) lda(info *stepInfo) {
|
||||
cpu.A = cpu.Read(info.address)
|
||||
cpu.setZN(cpu.A)
|
||||
}
|
||||
|
||||
// LDX - Load X Register
|
||||
func (cpu *CPU) ldx(info *stepInfo) {
|
||||
cpu.X = cpu.Read(info.address)
|
||||
cpu.setZN(cpu.X)
|
||||
}
|
||||
|
||||
// LDY - Load Y Register
|
||||
func (cpu *CPU) ldy(info *stepInfo) {
|
||||
cpu.Y = cpu.Read(info.address)
|
||||
cpu.setZN(cpu.Y)
|
||||
}
|
||||
|
||||
// LSR - Logical Shift Right
|
||||
func (cpu *CPU) lsr(info *stepInfo) {
|
||||
if info.mode == modeAccumulator {
|
||||
cpu.C = cpu.A & 1
|
||||
cpu.A >>= 1
|
||||
cpu.setZN(cpu.A)
|
||||
} else {
|
||||
value := cpu.Read(info.address)
|
||||
cpu.C = value & 1
|
||||
value >>= 1
|
||||
cpu.Write(info.address, value)
|
||||
cpu.setZN(value)
|
||||
}
|
||||
}
|
||||
|
||||
// NOP - No Operation
|
||||
func (cpu *CPU) nop(info *stepInfo) {
|
||||
}
|
||||
|
||||
// ORA - Logical Inclusive OR
|
||||
func (cpu *CPU) ora(info *stepInfo) {
|
||||
cpu.A = cpu.A | cpu.Read(info.address)
|
||||
cpu.setZN(cpu.A)
|
||||
}
|
||||
|
||||
// PHA - Push Accumulator
|
||||
func (cpu *CPU) pha(info *stepInfo) {
|
||||
cpu.push(cpu.A)
|
||||
}
|
||||
|
||||
// PHP - Push Processor Status
|
||||
func (cpu *CPU) php(info *stepInfo) {
|
||||
cpu.push(cpu.Flags() | 0x10)
|
||||
}
|
||||
|
||||
// PLA - Pull Accumulator
|
||||
func (cpu *CPU) pla(info *stepInfo) {
|
||||
cpu.A = cpu.pull()
|
||||
cpu.setZN(cpu.A)
|
||||
}
|
||||
|
||||
// PLP - Pull Processor Status
|
||||
func (cpu *CPU) plp(info *stepInfo) {
|
||||
cpu.SetFlags(cpu.pull()&0xEF | 0x20)
|
||||
}
|
||||
|
||||
// ROL - Rotate Left
|
||||
func (cpu *CPU) rol(info *stepInfo) {
|
||||
if info.mode == modeAccumulator {
|
||||
c := cpu.C
|
||||
cpu.C = (cpu.A >> 7) & 1
|
||||
cpu.A = (cpu.A << 1) | c
|
||||
cpu.setZN(cpu.A)
|
||||
} else {
|
||||
c := cpu.C
|
||||
value := cpu.Read(info.address)
|
||||
cpu.C = (value >> 7) & 1
|
||||
value = (value << 1) | c
|
||||
cpu.Write(info.address, value)
|
||||
cpu.setZN(value)
|
||||
}
|
||||
}
|
||||
|
||||
// ROR - Rotate Right
|
||||
func (cpu *CPU) ror(info *stepInfo) {
|
||||
if info.mode == modeAccumulator {
|
||||
c := cpu.C
|
||||
cpu.C = cpu.A & 1
|
||||
cpu.A = (cpu.A >> 1) | (c << 7)
|
||||
cpu.setZN(cpu.A)
|
||||
} else {
|
||||
c := cpu.C
|
||||
value := cpu.Read(info.address)
|
||||
cpu.C = value & 1
|
||||
value = (value >> 1) | (c << 7)
|
||||
cpu.Write(info.address, value)
|
||||
cpu.setZN(value)
|
||||
}
|
||||
}
|
||||
|
||||
// RTI - Return from Interrupt
|
||||
func (cpu *CPU) rti(info *stepInfo) {
|
||||
cpu.SetFlags(cpu.pull()&0xEF | 0x20)
|
||||
cpu.PC = cpu.pull16()
|
||||
}
|
||||
|
||||
// RTS - Return from Subroutine
|
||||
func (cpu *CPU) rts(info *stepInfo) {
|
||||
cpu.PC = cpu.pull16() + 1
|
||||
}
|
||||
|
||||
// SBC - Subtract with Carry
|
||||
func (cpu *CPU) sbc(info *stepInfo) {
|
||||
a := cpu.A
|
||||
b := cpu.Read(info.address)
|
||||
c := cpu.C
|
||||
cpu.A = a - b - (1 - c)
|
||||
cpu.setZN(cpu.A)
|
||||
if int(a)-int(b)-int(1-c) >= 0 {
|
||||
cpu.C = 1
|
||||
} else {
|
||||
cpu.C = 0
|
||||
}
|
||||
if (a^b)&0x80 != 0 && (a^cpu.A)&0x80 != 0 {
|
||||
cpu.V = 1
|
||||
} else {
|
||||
cpu.V = 0
|
||||
}
|
||||
}
|
||||
|
||||
// SEC - Set Carry Flag
|
||||
func (cpu *CPU) sec(info *stepInfo) {
|
||||
cpu.C = 1
|
||||
}
|
||||
|
||||
// SED - Set Decimal Flag
|
||||
func (cpu *CPU) sed(info *stepInfo) {
|
||||
cpu.D = 1
|
||||
}
|
||||
|
||||
// SEI - Set Interrupt Disable
|
||||
func (cpu *CPU) sei(info *stepInfo) {
|
||||
cpu.I = 1
|
||||
}
|
||||
|
||||
// STA - Store Accumulator
|
||||
func (cpu *CPU) sta(info *stepInfo) {
|
||||
cpu.Write(info.address, cpu.A)
|
||||
}
|
||||
|
||||
// STX - Store X Register
|
||||
func (cpu *CPU) stx(info *stepInfo) {
|
||||
cpu.Write(info.address, cpu.X)
|
||||
}
|
||||
|
||||
// STY - Store Y Register
|
||||
func (cpu *CPU) sty(info *stepInfo) {
|
||||
cpu.Write(info.address, cpu.Y)
|
||||
}
|
||||
|
||||
// TAX - Transfer Accumulator to X
|
||||
func (cpu *CPU) tax(info *stepInfo) {
|
||||
cpu.X = cpu.A
|
||||
cpu.setZN(cpu.X)
|
||||
}
|
||||
|
||||
// TAY - Transfer Accumulator to Y
|
||||
func (cpu *CPU) tay(info *stepInfo) {
|
||||
cpu.Y = cpu.A
|
||||
cpu.setZN(cpu.Y)
|
||||
}
|
||||
|
||||
// TSX - Transfer Stack Pointer to X
|
||||
func (cpu *CPU) tsx(info *stepInfo) {
|
||||
cpu.X = cpu.SP
|
||||
cpu.setZN(cpu.X)
|
||||
}
|
||||
|
||||
// TXA - Transfer X to Accumulator
|
||||
func (cpu *CPU) txa(info *stepInfo) {
|
||||
cpu.A = cpu.X
|
||||
cpu.setZN(cpu.A)
|
||||
}
|
||||
|
||||
// TXS - Transfer X to Stack Pointer
|
||||
func (cpu *CPU) txs(info *stepInfo) {
|
||||
cpu.SP = cpu.X
|
||||
}
|
||||
|
||||
// TYA - Transfer Y to Accumulator
|
||||
func (cpu *CPU) tya(info *stepInfo) {
|
||||
cpu.A = cpu.Y
|
||||
cpu.setZN(cpu.A)
|
||||
}
|
||||
|
||||
// illegal opcodes below
|
||||
|
||||
func (cpu *CPU) ahx(info *stepInfo) {
|
||||
}
|
||||
|
||||
func (cpu *CPU) alr(info *stepInfo) {
|
||||
}
|
||||
|
||||
func (cpu *CPU) anc(info *stepInfo) {
|
||||
}
|
||||
|
||||
func (cpu *CPU) arr(info *stepInfo) {
|
||||
}
|
||||
|
||||
func (cpu *CPU) axs(info *stepInfo) {
|
||||
}
|
||||
|
||||
func (cpu *CPU) dcp(info *stepInfo) {
|
||||
}
|
||||
|
||||
func (cpu *CPU) isc(info *stepInfo) {
|
||||
}
|
||||
|
||||
func (cpu *CPU) kil(info *stepInfo) {
|
||||
}
|
||||
|
||||
func (cpu *CPU) las(info *stepInfo) {
|
||||
}
|
||||
|
||||
func (cpu *CPU) lax(info *stepInfo) {
|
||||
}
|
||||
|
||||
func (cpu *CPU) rla(info *stepInfo) {
|
||||
}
|
||||
|
||||
func (cpu *CPU) rra(info *stepInfo) {
|
||||
}
|
||||
|
||||
func (cpu *CPU) sax(info *stepInfo) {
|
||||
}
|
||||
|
||||
func (cpu *CPU) shx(info *stepInfo) {
|
||||
}
|
||||
|
||||
func (cpu *CPU) shy(info *stepInfo) {
|
||||
}
|
||||
|
||||
func (cpu *CPU) slo(info *stepInfo) {
|
||||
}
|
||||
|
||||
func (cpu *CPU) sre(info *stepInfo) {
|
||||
}
|
||||
|
||||
func (cpu *CPU) tas(info *stepInfo) {
|
||||
}
|
||||
|
||||
func (cpu *CPU) xaa(info *stepInfo) {
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
package nes
|
||||
|
||||
import "math"
|
||||
|
||||
type Filter interface {
|
||||
Step(x float32) float32
|
||||
}
|
||||
|
||||
// First order filters are defined by the following parameters.
|
||||
// y[n] = B0*x[n] + B1*x[n-1] - A1*y[n-1]
|
||||
type FirstOrderFilter struct {
|
||||
B0 float32
|
||||
B1 float32
|
||||
A1 float32
|
||||
prevX float32
|
||||
prevY float32
|
||||
}
|
||||
|
||||
func (f *FirstOrderFilter) Step(x float32) float32 {
|
||||
y := f.B0*x + f.B1*f.prevX - f.A1*f.prevY
|
||||
f.prevY = y
|
||||
f.prevX = x
|
||||
return y
|
||||
}
|
||||
|
||||
// sampleRate: samples per second
|
||||
// cutoffFreq: oscillations per second
|
||||
func LowPassFilter(sampleRate float32, cutoffFreq float32) Filter {
|
||||
c := sampleRate / math.Pi / cutoffFreq
|
||||
a0i := 1 / (1 + c)
|
||||
return &FirstOrderFilter{
|
||||
B0: a0i,
|
||||
B1: a0i,
|
||||
A1: (1 - c) * a0i,
|
||||
}
|
||||
}
|
||||
|
||||
func HighPassFilter(sampleRate float32, cutoffFreq float32) Filter {
|
||||
c := sampleRate / math.Pi / cutoffFreq
|
||||
a0i := 1 / (1 + c)
|
||||
return &FirstOrderFilter{
|
||||
B0: c * a0i,
|
||||
B1: -c * a0i,
|
||||
A1: (1 - c) * a0i,
|
||||
}
|
||||
}
|
||||
|
||||
type FilterChain []Filter
|
||||
|
||||
func (fc FilterChain) Step(x float32) float32 {
|
||||
if fc != nil {
|
||||
for i := range fc {
|
||||
x = fc[i].Step(x)
|
||||
}
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
package nes
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
const iNESFileMagic = 0x1a53454e
|
||||
|
||||
type iNESFileHeader struct {
|
||||
Magic uint32 // iNES magic number
|
||||
NumPRG byte // number of PRG-ROM banks (16KB each)
|
||||
NumCHR byte // number of CHR-ROM banks (8KB each)
|
||||
Control1 byte // control bits
|
||||
Control2 byte // control bits
|
||||
NumRAM byte // PRG-RAM size (x 8KB)
|
||||
_ [7]byte // unused padding
|
||||
}
|
||||
|
||||
// LoadNESFile reads an iNES file (.nes) and returns a Cartridge on success.
|
||||
// http://wiki.nesdev.com/w/index.php/INES
|
||||
// http://nesdev.com/NESDoc.pdf (page 28)
|
||||
func LoadNESFile(path string) (*Cartridge, error) {
|
||||
// open file
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// read file header
|
||||
header := iNESFileHeader{}
|
||||
if err := binary.Read(file, binary.LittleEndian, &header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// verify header magic number
|
||||
if header.Magic != iNESFileMagic {
|
||||
return nil, errors.New("invalid .nes file")
|
||||
}
|
||||
|
||||
// mapper type
|
||||
mapper1 := header.Control1 >> 4
|
||||
mapper2 := header.Control2 >> 4
|
||||
mapper := mapper1 | mapper2<<4
|
||||
|
||||
// mirroring type
|
||||
mirror1 := header.Control1 & 1
|
||||
mirror2 := (header.Control1 >> 3) & 1
|
||||
mirror := mirror1 | mirror2<<1
|
||||
|
||||
// battery-backed RAM
|
||||
battery := (header.Control1 >> 1) & 1
|
||||
|
||||
// read trainer if present (unused)
|
||||
if header.Control1&4 == 4 {
|
||||
trainer := make([]byte, 512)
|
||||
if _, err := io.ReadFull(file, trainer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// read prg-rom bank(s)
|
||||
prg := make([]byte, int(header.NumPRG)*16384)
|
||||
if _, err := io.ReadFull(file, prg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// read chr-rom bank(s)
|
||||
chr := make([]byte, int(header.NumCHR)*8192)
|
||||
if _, err := io.ReadFull(file, chr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// provide chr-rom/ram if not in file
|
||||
if header.NumCHR == 0 {
|
||||
chr = make([]byte, 8192)
|
||||
}
|
||||
|
||||
// success
|
||||
return NewCartridge(prg, chr, mapper, mirror, battery), nil
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package nes
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Mapper interface {
|
||||
Read(address uint16) byte
|
||||
Write(address uint16, value byte)
|
||||
Step()
|
||||
Save(encoder *gob.Encoder) error
|
||||
Load(decoder *gob.Decoder) error
|
||||
}
|
||||
|
||||
func NewMapper(console *Console) (Mapper, error) {
|
||||
cartridge := console.Cartridge
|
||||
switch cartridge.Mapper {
|
||||
case 0:
|
||||
return NewMapper2(cartridge), nil
|
||||
case 1:
|
||||
return NewMapper1(cartridge), nil
|
||||
case 2:
|
||||
return NewMapper2(cartridge), nil
|
||||
case 3:
|
||||
return NewMapper3(cartridge), nil
|
||||
case 4:
|
||||
return NewMapper4(console, cartridge), nil
|
||||
case 7:
|
||||
return NewMapper7(cartridge), nil
|
||||
case 225:
|
||||
return NewMapper225(cartridge), nil
|
||||
}
|
||||
err := fmt.Errorf("unsupported mapper: %d", cartridge.Mapper)
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1,205 +0,0 @@
|
|||
package nes
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"log"
|
||||
)
|
||||
|
||||
type Mapper1 struct {
|
||||
*Cartridge
|
||||
shiftRegister byte
|
||||
control byte
|
||||
prgMode byte
|
||||
chrMode byte
|
||||
prgBank byte
|
||||
chrBank0 byte
|
||||
chrBank1 byte
|
||||
prgOffsets [2]int
|
||||
chrOffsets [2]int
|
||||
}
|
||||
|
||||
func NewMapper1(cartridge *Cartridge) Mapper {
|
||||
m := Mapper1{}
|
||||
m.Cartridge = cartridge
|
||||
m.shiftRegister = 0x10
|
||||
m.prgOffsets[1] = m.prgBankOffset(-1)
|
||||
return &m
|
||||
}
|
||||
|
||||
func (m *Mapper1) Save(encoder *gob.Encoder) error {
|
||||
encoder.Encode(m.shiftRegister)
|
||||
encoder.Encode(m.control)
|
||||
encoder.Encode(m.prgMode)
|
||||
encoder.Encode(m.chrMode)
|
||||
encoder.Encode(m.prgBank)
|
||||
encoder.Encode(m.chrBank0)
|
||||
encoder.Encode(m.chrBank1)
|
||||
encoder.Encode(m.prgOffsets)
|
||||
encoder.Encode(m.chrOffsets)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mapper1) Load(decoder *gob.Decoder) error {
|
||||
decoder.Decode(&m.shiftRegister)
|
||||
decoder.Decode(&m.control)
|
||||
decoder.Decode(&m.prgMode)
|
||||
decoder.Decode(&m.chrMode)
|
||||
decoder.Decode(&m.prgBank)
|
||||
decoder.Decode(&m.chrBank0)
|
||||
decoder.Decode(&m.chrBank1)
|
||||
decoder.Decode(&m.prgOffsets)
|
||||
decoder.Decode(&m.chrOffsets)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mapper1) Step() {
|
||||
}
|
||||
|
||||
func (m *Mapper1) Read(address uint16) byte {
|
||||
switch {
|
||||
case address < 0x2000:
|
||||
bank := address / 0x1000
|
||||
offset := address % 0x1000
|
||||
return m.CHR[m.chrOffsets[bank]+int(offset)]
|
||||
case address >= 0x8000:
|
||||
address = address - 0x8000
|
||||
bank := address / 0x4000
|
||||
offset := address % 0x4000
|
||||
return m.PRG[m.prgOffsets[bank]+int(offset)]
|
||||
case address >= 0x6000:
|
||||
return m.SRAM[int(address)-0x6000]
|
||||
default:
|
||||
log.Fatalf("unhandled mapper1 read at address: 0x%04X", address)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Mapper1) Write(address uint16, value byte) {
|
||||
switch {
|
||||
case address < 0x2000:
|
||||
bank := address / 0x1000
|
||||
offset := address % 0x1000
|
||||
m.CHR[m.chrOffsets[bank]+int(offset)] = value
|
||||
case address >= 0x8000:
|
||||
m.loadRegister(address, value)
|
||||
case address >= 0x6000:
|
||||
m.SRAM[int(address)-0x6000] = value
|
||||
default:
|
||||
log.Fatalf("unhandled mapper1 write at address: 0x%04X", address)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mapper1) loadRegister(address uint16, value byte) {
|
||||
if value&0x80 == 0x80 {
|
||||
m.shiftRegister = 0x10
|
||||
m.writeControl(m.control | 0x0C)
|
||||
} else {
|
||||
complete := m.shiftRegister&1 == 1
|
||||
m.shiftRegister >>= 1
|
||||
m.shiftRegister |= (value & 1) << 4
|
||||
if complete {
|
||||
m.writeRegister(address, m.shiftRegister)
|
||||
m.shiftRegister = 0x10
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mapper1) writeRegister(address uint16, value byte) {
|
||||
switch {
|
||||
case address <= 0x9FFF:
|
||||
m.writeControl(value)
|
||||
case address <= 0xBFFF:
|
||||
m.writeCHRBank0(value)
|
||||
case address <= 0xDFFF:
|
||||
m.writeCHRBank1(value)
|
||||
case address <= 0xFFFF:
|
||||
m.writePRGBank(value)
|
||||
}
|
||||
}
|
||||
|
||||
// Control (internal, $8000-$9FFF)
|
||||
func (m *Mapper1) writeControl(value byte) {
|
||||
m.control = value
|
||||
m.chrMode = (value >> 4) & 1
|
||||
m.prgMode = (value >> 2) & 3
|
||||
mirror := value & 3
|
||||
switch mirror {
|
||||
case 0:
|
||||
m.Cartridge.Mirror = MirrorSingle0
|
||||
case 1:
|
||||
m.Cartridge.Mirror = MirrorSingle1
|
||||
case 2:
|
||||
m.Cartridge.Mirror = MirrorVertical
|
||||
case 3:
|
||||
m.Cartridge.Mirror = MirrorHorizontal
|
||||
}
|
||||
m.updateOffsets()
|
||||
}
|
||||
|
||||
// CHR bank 0 (internal, $A000-$BFFF)
|
||||
func (m *Mapper1) writeCHRBank0(value byte) {
|
||||
m.chrBank0 = value
|
||||
m.updateOffsets()
|
||||
}
|
||||
|
||||
// CHR bank 1 (internal, $C000-$DFFF)
|
||||
func (m *Mapper1) writeCHRBank1(value byte) {
|
||||
m.chrBank1 = value
|
||||
m.updateOffsets()
|
||||
}
|
||||
|
||||
// PRG bank (internal, $E000-$FFFF)
|
||||
func (m *Mapper1) writePRGBank(value byte) {
|
||||
m.prgBank = value & 0x0F
|
||||
m.updateOffsets()
|
||||
}
|
||||
|
||||
func (m *Mapper1) prgBankOffset(index int) int {
|
||||
if index >= 0x80 {
|
||||
index -= 0x100
|
||||
}
|
||||
index %= len(m.PRG) / 0x4000
|
||||
offset := index * 0x4000
|
||||
if offset < 0 {
|
||||
offset += len(m.PRG)
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
func (m *Mapper1) chrBankOffset(index int) int {
|
||||
if index >= 0x80 {
|
||||
index -= 0x100
|
||||
}
|
||||
index %= len(m.CHR) / 0x1000
|
||||
offset := index * 0x1000
|
||||
if offset < 0 {
|
||||
offset += len(m.CHR)
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
// PRG ROM bank mode (0, 1: switch 32 KB at $8000, ignoring low bit of bank number;
|
||||
// 2: fix first bank at $8000 and switch 16 KB bank at $C000;
|
||||
// 3: fix last bank at $C000 and switch 16 KB bank at $8000)
|
||||
// CHR ROM bank mode (0: switch 8 KB at a time; 1: switch two separate 4 KB banks)
|
||||
func (m *Mapper1) updateOffsets() {
|
||||
switch m.prgMode {
|
||||
case 0, 1:
|
||||
m.prgOffsets[0] = m.prgBankOffset(int(m.prgBank & 0xFE))
|
||||
m.prgOffsets[1] = m.prgBankOffset(int(m.prgBank | 0x01))
|
||||
case 2:
|
||||
m.prgOffsets[0] = 0
|
||||
m.prgOffsets[1] = m.prgBankOffset(int(m.prgBank))
|
||||
case 3:
|
||||
m.prgOffsets[0] = m.prgBankOffset(int(m.prgBank))
|
||||
m.prgOffsets[1] = m.prgBankOffset(-1)
|
||||
}
|
||||
switch m.chrMode {
|
||||
case 0:
|
||||
m.chrOffsets[0] = m.chrBankOffset(int(m.chrBank0 & 0xFE))
|
||||
m.chrOffsets[1] = m.chrBankOffset(int(m.chrBank0 | 0x01))
|
||||
case 1:
|
||||
m.chrOffsets[0] = m.chrBankOffset(int(m.chrBank0))
|
||||
m.chrOffsets[1] = m.chrBankOffset(int(m.chrBank1))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
package nes
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"log"
|
||||
)
|
||||
|
||||
type Mapper2 struct {
|
||||
*Cartridge
|
||||
prgBanks int
|
||||
prgBank1 int
|
||||
prgBank2 int
|
||||
}
|
||||
|
||||
func NewMapper2(cartridge *Cartridge) Mapper {
|
||||
prgBanks := len(cartridge.PRG) / 0x4000
|
||||
prgBank1 := 0
|
||||
prgBank2 := prgBanks - 1
|
||||
return &Mapper2{cartridge, prgBanks, prgBank1, prgBank2}
|
||||
}
|
||||
|
||||
func (m *Mapper2) Save(encoder *gob.Encoder) error {
|
||||
encoder.Encode(m.prgBanks)
|
||||
encoder.Encode(m.prgBank1)
|
||||
encoder.Encode(m.prgBank2)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mapper2) Load(decoder *gob.Decoder) error {
|
||||
decoder.Decode(&m.prgBanks)
|
||||
decoder.Decode(&m.prgBank1)
|
||||
decoder.Decode(&m.prgBank2)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mapper2) Step() {
|
||||
}
|
||||
|
||||
func (m *Mapper2) Read(address uint16) byte {
|
||||
switch {
|
||||
case address < 0x2000:
|
||||
return m.CHR[address]
|
||||
case address >= 0xC000:
|
||||
index := m.prgBank2*0x4000 + int(address-0xC000)
|
||||
return m.PRG[index]
|
||||
case address >= 0x8000:
|
||||
index := m.prgBank1*0x4000 + int(address-0x8000)
|
||||
return m.PRG[index]
|
||||
case address >= 0x6000:
|
||||
index := int(address) - 0x6000
|
||||
return m.SRAM[index]
|
||||
default:
|
||||
log.Fatalf("unhandled mapper2 read at address: 0x%04X", address)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Mapper2) Write(address uint16, value byte) {
|
||||
switch {
|
||||
case address < 0x2000:
|
||||
m.CHR[address] = value
|
||||
case address >= 0x8000:
|
||||
m.prgBank1 = int(value) % m.prgBanks
|
||||
case address >= 0x6000:
|
||||
index := int(address) - 0x6000
|
||||
m.SRAM[index] = value
|
||||
default:
|
||||
log.Fatalf("unhandled mapper2 write at address: 0x%04X", address)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
package nes
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"log"
|
||||
)
|
||||
|
||||
// https://github.com/asfdfdfd/fceux/blob/master/src/boards/225.cpp
|
||||
// https://wiki.nesdev.com/w/index.php/INES_Mapper_225
|
||||
|
||||
type Mapper225 struct {
|
||||
*Cartridge
|
||||
chrBank int
|
||||
prgBank1 int
|
||||
prgBank2 int
|
||||
}
|
||||
|
||||
func NewMapper225(cartridge *Cartridge) Mapper {
|
||||
prgBanks := len(cartridge.PRG) / 0x4000
|
||||
return &Mapper225{cartridge, 0, 0, prgBanks - 1}
|
||||
}
|
||||
|
||||
func (m *Mapper225) Save(encoder *gob.Encoder) error {
|
||||
encoder.Encode(m.chrBank)
|
||||
encoder.Encode(m.prgBank1)
|
||||
encoder.Encode(m.prgBank2)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mapper225) Load(decoder *gob.Decoder) error {
|
||||
decoder.Decode(&m.chrBank)
|
||||
decoder.Decode(&m.prgBank1)
|
||||
decoder.Decode(&m.prgBank2)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mapper225) Step() {
|
||||
}
|
||||
|
||||
func (m *Mapper225) Read(address uint16) byte {
|
||||
switch {
|
||||
case address < 0x2000:
|
||||
index := m.chrBank*0x2000 + int(address)
|
||||
return m.CHR[index]
|
||||
case address >= 0xC000:
|
||||
index := m.prgBank2*0x4000 + int(address-0xC000)
|
||||
return m.PRG[index]
|
||||
case address >= 0x8000:
|
||||
index := m.prgBank1*0x4000 + int(address-0x8000)
|
||||
return m.PRG[index]
|
||||
case address >= 0x6000:
|
||||
index := int(address) - 0x6000
|
||||
return m.SRAM[index]
|
||||
default:
|
||||
log.Fatalf("unhandled Mapper225 read at address: 0x%04X", address)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Mapper225) Write(address uint16, value byte) {
|
||||
if (address < 0x8000) {
|
||||
return
|
||||
}
|
||||
|
||||
A := int(address)
|
||||
bank := (A >> 14) & 1
|
||||
m.chrBank = (A & 0x3f) | (bank << 6)
|
||||
prg := ((A >> 6) & 0x3f) | (bank << 6)
|
||||
mode := (A >> 12) & 1;
|
||||
if (mode == 1) {
|
||||
m.prgBank1 = prg
|
||||
m.prgBank2 = prg
|
||||
} else {
|
||||
m.prgBank1 = prg
|
||||
m.prgBank2 = prg + 1
|
||||
}
|
||||
mirr := (A >> 13) & 1
|
||||
if (mirr == 1) {
|
||||
m.Cartridge.Mirror = MirrorHorizontal
|
||||
} else {
|
||||
m.Cartridge.Mirror = MirrorVertical
|
||||
}
|
||||
|
||||
// fmt.Println(address, mirr, mode, prg)
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
package nes
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"log"
|
||||
)
|
||||
|
||||
type Mapper3 struct {
|
||||
*Cartridge
|
||||
chrBank int
|
||||
prgBank1 int
|
||||
prgBank2 int
|
||||
}
|
||||
|
||||
func NewMapper3(cartridge *Cartridge) Mapper {
|
||||
prgBanks := len(cartridge.PRG) / 0x4000
|
||||
return &Mapper3{cartridge, 0, 0, prgBanks - 1}
|
||||
}
|
||||
|
||||
func (m *Mapper3) Save(encoder *gob.Encoder) error {
|
||||
encoder.Encode(m.chrBank)
|
||||
encoder.Encode(m.prgBank1)
|
||||
encoder.Encode(m.prgBank2)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mapper3) Load(decoder *gob.Decoder) error {
|
||||
decoder.Decode(&m.chrBank)
|
||||
decoder.Decode(&m.prgBank1)
|
||||
decoder.Decode(&m.prgBank2)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mapper3) Step() {
|
||||
}
|
||||
|
||||
func (m *Mapper3) Read(address uint16) byte {
|
||||
switch {
|
||||
case address < 0x2000:
|
||||
index := m.chrBank*0x2000 + int(address)
|
||||
return m.CHR[index]
|
||||
case address >= 0xC000:
|
||||
index := m.prgBank2*0x4000 + int(address-0xC000)
|
||||
return m.PRG[index]
|
||||
case address >= 0x8000:
|
||||
index := m.prgBank1*0x4000 + int(address-0x8000)
|
||||
return m.PRG[index]
|
||||
case address >= 0x6000:
|
||||
index := int(address) - 0x6000
|
||||
return m.SRAM[index]
|
||||
default:
|
||||
log.Fatalf("unhandled mapper3 read at address: 0x%04X", address)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Mapper3) Write(address uint16, value byte) {
|
||||
switch {
|
||||
case address < 0x2000:
|
||||
index := m.chrBank*0x2000 + int(address)
|
||||
m.CHR[index] = value
|
||||
case address >= 0x8000:
|
||||
m.chrBank = int(value & 3)
|
||||
case address >= 0x6000:
|
||||
index := int(address) - 0x6000
|
||||
m.SRAM[index] = value
|
||||
default:
|
||||
log.Fatalf("unhandled mapper3 write at address: 0x%04X", address)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,234 +0,0 @@
|
|||
package nes
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"log"
|
||||
)
|
||||
|
||||
type Mapper4 struct {
|
||||
*Cartridge
|
||||
console *Console
|
||||
register byte
|
||||
registers [8]byte
|
||||
prgMode byte
|
||||
chrMode byte
|
||||
prgOffsets [4]int
|
||||
chrOffsets [8]int
|
||||
reload byte
|
||||
counter byte
|
||||
irqEnable bool
|
||||
}
|
||||
|
||||
func NewMapper4(console *Console, cartridge *Cartridge) Mapper {
|
||||
m := Mapper4{Cartridge: cartridge, console: console}
|
||||
m.prgOffsets[0] = m.prgBankOffset(0)
|
||||
m.prgOffsets[1] = m.prgBankOffset(1)
|
||||
m.prgOffsets[2] = m.prgBankOffset(-2)
|
||||
m.prgOffsets[3] = m.prgBankOffset(-1)
|
||||
return &m
|
||||
}
|
||||
|
||||
func (m *Mapper4) Save(encoder *gob.Encoder) error {
|
||||
encoder.Encode(m.register)
|
||||
encoder.Encode(m.registers)
|
||||
encoder.Encode(m.prgMode)
|
||||
encoder.Encode(m.chrMode)
|
||||
encoder.Encode(m.prgOffsets)
|
||||
encoder.Encode(m.chrOffsets)
|
||||
encoder.Encode(m.reload)
|
||||
encoder.Encode(m.counter)
|
||||
encoder.Encode(m.irqEnable)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mapper4) Load(decoder *gob.Decoder) error {
|
||||
decoder.Decode(&m.register)
|
||||
decoder.Decode(&m.registers)
|
||||
decoder.Decode(&m.prgMode)
|
||||
decoder.Decode(&m.chrMode)
|
||||
decoder.Decode(&m.prgOffsets)
|
||||
decoder.Decode(&m.chrOffsets)
|
||||
decoder.Decode(&m.reload)
|
||||
decoder.Decode(&m.counter)
|
||||
decoder.Decode(&m.irqEnable)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mapper4) Step() {
|
||||
ppu := m.console.PPU
|
||||
if ppu.Cycle != 280 { // TODO: this *should* be 260
|
||||
return
|
||||
}
|
||||
if ppu.ScanLine > 239 && ppu.ScanLine < 261 {
|
||||
return
|
||||
}
|
||||
if ppu.flagShowBackground == 0 && ppu.flagShowSprites == 0 {
|
||||
return
|
||||
}
|
||||
m.HandleScanLine()
|
||||
}
|
||||
|
||||
func (m *Mapper4) HandleScanLine() {
|
||||
if m.counter == 0 {
|
||||
m.counter = m.reload
|
||||
} else {
|
||||
m.counter--
|
||||
if m.counter == 0 && m.irqEnable {
|
||||
m.console.CPU.triggerIRQ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mapper4) Read(address uint16) byte {
|
||||
switch {
|
||||
case address < 0x2000:
|
||||
bank := address / 0x0400
|
||||
offset := address % 0x0400
|
||||
return m.CHR[m.chrOffsets[bank]+int(offset)]
|
||||
case address >= 0x8000:
|
||||
address = address - 0x8000
|
||||
bank := address / 0x2000
|
||||
offset := address % 0x2000
|
||||
return m.PRG[m.prgOffsets[bank]+int(offset)]
|
||||
case address >= 0x6000:
|
||||
return m.SRAM[int(address)-0x6000]
|
||||
default:
|
||||
log.Fatalf("unhandled mapper4 read at address: 0x%04X", address)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Mapper4) Write(address uint16, value byte) {
|
||||
switch {
|
||||
case address < 0x2000:
|
||||
bank := address / 0x0400
|
||||
offset := address % 0x0400
|
||||
m.CHR[m.chrOffsets[bank]+int(offset)] = value
|
||||
case address >= 0x8000:
|
||||
m.writeRegister(address, value)
|
||||
case address >= 0x6000:
|
||||
m.SRAM[int(address)-0x6000] = value
|
||||
default:
|
||||
log.Fatalf("unhandled mapper4 write at address: 0x%04X", address)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mapper4) writeRegister(address uint16, value byte) {
|
||||
switch {
|
||||
case address <= 0x9FFF && address%2 == 0:
|
||||
m.writeBankSelect(value)
|
||||
case address <= 0x9FFF && address%2 == 1:
|
||||
m.writeBankData(value)
|
||||
case address <= 0xBFFF && address%2 == 0:
|
||||
m.writeMirror(value)
|
||||
case address <= 0xBFFF && address%2 == 1:
|
||||
m.writeProtect(value)
|
||||
case address <= 0xDFFF && address%2 == 0:
|
||||
m.writeIRQLatch(value)
|
||||
case address <= 0xDFFF && address%2 == 1:
|
||||
m.writeIRQReload(value)
|
||||
case address <= 0xFFFF && address%2 == 0:
|
||||
m.writeIRQDisable(value)
|
||||
case address <= 0xFFFF && address%2 == 1:
|
||||
m.writeIRQEnable(value)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mapper4) writeBankSelect(value byte) {
|
||||
m.prgMode = (value >> 6) & 1
|
||||
m.chrMode = (value >> 7) & 1
|
||||
m.register = value & 7
|
||||
m.updateOffsets()
|
||||
}
|
||||
|
||||
func (m *Mapper4) writeBankData(value byte) {
|
||||
m.registers[m.register] = value
|
||||
m.updateOffsets()
|
||||
}
|
||||
|
||||
func (m *Mapper4) writeMirror(value byte) {
|
||||
switch value & 1 {
|
||||
case 0:
|
||||
m.Cartridge.Mirror = MirrorVertical
|
||||
case 1:
|
||||
m.Cartridge.Mirror = MirrorHorizontal
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mapper4) writeProtect(value byte) {
|
||||
}
|
||||
|
||||
func (m *Mapper4) writeIRQLatch(value byte) {
|
||||
m.reload = value
|
||||
}
|
||||
|
||||
func (m *Mapper4) writeIRQReload(value byte) {
|
||||
m.counter = 0
|
||||
}
|
||||
|
||||
func (m *Mapper4) writeIRQDisable(value byte) {
|
||||
m.irqEnable = false
|
||||
}
|
||||
|
||||
func (m *Mapper4) writeIRQEnable(value byte) {
|
||||
m.irqEnable = true
|
||||
}
|
||||
|
||||
func (m *Mapper4) prgBankOffset(index int) int {
|
||||
if index >= 0x80 {
|
||||
index -= 0x100
|
||||
}
|
||||
index %= len(m.PRG) / 0x2000
|
||||
offset := index * 0x2000
|
||||
if offset < 0 {
|
||||
offset += len(m.PRG)
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
func (m *Mapper4) chrBankOffset(index int) int {
|
||||
if index >= 0x80 {
|
||||
index -= 0x100
|
||||
}
|
||||
index %= len(m.CHR) / 0x0400
|
||||
offset := index * 0x0400
|
||||
if offset < 0 {
|
||||
offset += len(m.CHR)
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
func (m *Mapper4) updateOffsets() {
|
||||
switch m.prgMode {
|
||||
case 0:
|
||||
m.prgOffsets[0] = m.prgBankOffset(int(m.registers[6]))
|
||||
m.prgOffsets[1] = m.prgBankOffset(int(m.registers[7]))
|
||||
m.prgOffsets[2] = m.prgBankOffset(-2)
|
||||
m.prgOffsets[3] = m.prgBankOffset(-1)
|
||||
case 1:
|
||||
m.prgOffsets[0] = m.prgBankOffset(-2)
|
||||
m.prgOffsets[1] = m.prgBankOffset(int(m.registers[7]))
|
||||
m.prgOffsets[2] = m.prgBankOffset(int(m.registers[6]))
|
||||
m.prgOffsets[3] = m.prgBankOffset(-1)
|
||||
}
|
||||
switch m.chrMode {
|
||||
case 0:
|
||||
m.chrOffsets[0] = m.chrBankOffset(int(m.registers[0] & 0xFE))
|
||||
m.chrOffsets[1] = m.chrBankOffset(int(m.registers[0] | 0x01))
|
||||
m.chrOffsets[2] = m.chrBankOffset(int(m.registers[1] & 0xFE))
|
||||
m.chrOffsets[3] = m.chrBankOffset(int(m.registers[1] | 0x01))
|
||||
m.chrOffsets[4] = m.chrBankOffset(int(m.registers[2]))
|
||||
m.chrOffsets[5] = m.chrBankOffset(int(m.registers[3]))
|
||||
m.chrOffsets[6] = m.chrBankOffset(int(m.registers[4]))
|
||||
m.chrOffsets[7] = m.chrBankOffset(int(m.registers[5]))
|
||||
case 1:
|
||||
m.chrOffsets[0] = m.chrBankOffset(int(m.registers[2]))
|
||||
m.chrOffsets[1] = m.chrBankOffset(int(m.registers[3]))
|
||||
m.chrOffsets[2] = m.chrBankOffset(int(m.registers[4]))
|
||||
m.chrOffsets[3] = m.chrBankOffset(int(m.registers[5]))
|
||||
m.chrOffsets[4] = m.chrBankOffset(int(m.registers[0] & 0xFE))
|
||||
m.chrOffsets[5] = m.chrBankOffset(int(m.registers[0] | 0x01))
|
||||
m.chrOffsets[6] = m.chrBankOffset(int(m.registers[1] & 0xFE))
|
||||
m.chrOffsets[7] = m.chrBankOffset(int(m.registers[1] | 0x01))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
package nes
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"log"
|
||||
)
|
||||
|
||||
type Mapper7 struct {
|
||||
*Cartridge
|
||||
prgBank int
|
||||
}
|
||||
|
||||
func NewMapper7(cartridge *Cartridge) Mapper {
|
||||
return &Mapper7{cartridge, 0}
|
||||
}
|
||||
|
||||
func (m *Mapper7) Save(encoder *gob.Encoder) error {
|
||||
encoder.Encode(m.prgBank)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mapper7) Load(decoder *gob.Decoder) error {
|
||||
decoder.Decode(&m.prgBank)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mapper7) Step() {
|
||||
}
|
||||
|
||||
func (m *Mapper7) Read(address uint16) byte {
|
||||
switch {
|
||||
case address < 0x2000:
|
||||
return m.CHR[address]
|
||||
case address >= 0x8000:
|
||||
index := m.prgBank*0x8000 + int(address-0x8000)
|
||||
return m.PRG[index]
|
||||
case address >= 0x6000:
|
||||
index := int(address) - 0x6000
|
||||
return m.SRAM[index]
|
||||
default:
|
||||
log.Fatalf("unhandled mapper7 read at address: 0x%04X", address)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Mapper7) Write(address uint16, value byte) {
|
||||
switch {
|
||||
case address < 0x2000:
|
||||
m.CHR[address] = value
|
||||
case address >= 0x8000:
|
||||
m.prgBank = int(value & 7)
|
||||
switch value & 0x10 {
|
||||
case 0x00:
|
||||
m.Cartridge.Mirror = MirrorSingle0
|
||||
case 0x10:
|
||||
m.Cartridge.Mirror = MirrorSingle1
|
||||
}
|
||||
case address >= 0x6000:
|
||||
index := int(address) - 0x6000
|
||||
m.SRAM[index] = value
|
||||
default:
|
||||
log.Fatalf("unhandled mapper7 write at address: 0x%04X", address)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
package nes
|
||||
|
||||
import "log"
|
||||
|
||||
type Memory interface {
|
||||
Read(address uint16) byte
|
||||
Write(address uint16, value byte)
|
||||
}
|
||||
|
||||
// CPU Memory Map
|
||||
|
||||
type cpuMemory struct {
|
||||
console *Console
|
||||
}
|
||||
|
||||
func NewCPUMemory(console *Console) Memory {
|
||||
return &cpuMemory{console}
|
||||
}
|
||||
|
||||
func (mem *cpuMemory) Read(address uint16) byte {
|
||||
switch {
|
||||
case address < 0x2000:
|
||||
return mem.console.RAM[address%0x0800]
|
||||
case address < 0x4000:
|
||||
return mem.console.PPU.readRegister(0x2000 + address%8)
|
||||
case address == 0x4014:
|
||||
return mem.console.PPU.readRegister(address)
|
||||
case address == 0x4015:
|
||||
return mem.console.APU.readRegister(address)
|
||||
case address == 0x4016:
|
||||
return mem.console.Controller1.Read()
|
||||
case address == 0x4017:
|
||||
return mem.console.Controller2.Read()
|
||||
case address < 0x6000:
|
||||
// TODO: I/O registers
|
||||
case address >= 0x6000:
|
||||
return mem.console.Mapper.Read(address)
|
||||
default:
|
||||
log.Fatalf("unhandled cpu memory read at address: 0x%04X", address)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (mem *cpuMemory) Write(address uint16, value byte) {
|
||||
switch {
|
||||
case address < 0x2000:
|
||||
mem.console.RAM[address%0x0800] = value
|
||||
case address < 0x4000:
|
||||
mem.console.PPU.writeRegister(0x2000+address%8, value)
|
||||
case address < 0x4014:
|
||||
mem.console.APU.writeRegister(address, value)
|
||||
case address == 0x4014:
|
||||
mem.console.PPU.writeRegister(address, value)
|
||||
case address == 0x4015:
|
||||
mem.console.APU.writeRegister(address, value)
|
||||
case address == 0x4016:
|
||||
mem.console.Controller1.Write(value)
|
||||
mem.console.Controller2.Write(value)
|
||||
case address == 0x4017:
|
||||
mem.console.APU.writeRegister(address, value)
|
||||
case address < 0x6000:
|
||||
// TODO: I/O registers
|
||||
case address >= 0x6000:
|
||||
mem.console.Mapper.Write(address, value)
|
||||
default:
|
||||
log.Fatalf("unhandled cpu memory write at address: 0x%04X", address)
|
||||
}
|
||||
}
|
||||
|
||||
// PPU Memory Map
|
||||
|
||||
type ppuMemory struct {
|
||||
console *Console
|
||||
}
|
||||
|
||||
func NewPPUMemory(console *Console) Memory {
|
||||
return &ppuMemory{console}
|
||||
}
|
||||
|
||||
func (mem *ppuMemory) Read(address uint16) byte {
|
||||
address = address % 0x4000
|
||||
switch {
|
||||
case address < 0x2000:
|
||||
return mem.console.Mapper.Read(address)
|
||||
case address < 0x3F00:
|
||||
mode := mem.console.Cartridge.Mirror
|
||||
return mem.console.PPU.nameTableData[MirrorAddress(mode, address)%2048]
|
||||
case address < 0x4000:
|
||||
return mem.console.PPU.readPalette(address % 32)
|
||||
default:
|
||||
log.Fatalf("unhandled ppu memory read at address: 0x%04X", address)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (mem *ppuMemory) Write(address uint16, value byte) {
|
||||
address = address % 0x4000
|
||||
switch {
|
||||
case address < 0x2000:
|
||||
mem.console.Mapper.Write(address, value)
|
||||
case address < 0x3F00:
|
||||
mode := mem.console.Cartridge.Mirror
|
||||
mem.console.PPU.nameTableData[MirrorAddress(mode, address)%2048] = value
|
||||
case address < 0x4000:
|
||||
mem.console.PPU.writePalette(address%32, value)
|
||||
default:
|
||||
log.Fatalf("unhandled ppu memory write at address: 0x%04X", address)
|
||||
}
|
||||
}
|
||||
|
||||
// Mirroring Modes
|
||||
|
||||
const (
|
||||
MirrorHorizontal = 0
|
||||
MirrorVertical = 1
|
||||
MirrorSingle0 = 2
|
||||
MirrorSingle1 = 3
|
||||
MirrorFour = 4
|
||||
)
|
||||
|
||||
var MirrorLookup = [...][4]uint16{
|
||||
{0, 0, 1, 1},
|
||||
{0, 1, 0, 1},
|
||||
{0, 0, 0, 0},
|
||||
{1, 1, 1, 1},
|
||||
{0, 1, 2, 3},
|
||||
}
|
||||
|
||||
func MirrorAddress(mode byte, address uint16) uint16 {
|
||||
address = (address - 0x2000) % 0x1000
|
||||
table := address / 0x0400
|
||||
offset := address % 0x0400
|
||||
return 0x2000 + MirrorLookup[mode][table]*0x0400 + offset
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package nes
|
||||
|
||||
import "image/color"
|
||||
|
||||
var Palette [64]color.RGBA
|
||||
|
||||
func init() {
|
||||
colors := []uint32{
|
||||
0x666666, 0x002A88, 0x1412A7, 0x3B00A4, 0x5C007E, 0x6E0040, 0x6C0600, 0x561D00,
|
||||
0x333500, 0x0B4800, 0x005200, 0x004F08, 0x00404D, 0x000000, 0x000000, 0x000000,
|
||||
0xADADAD, 0x155FD9, 0x4240FF, 0x7527FE, 0xA01ACC, 0xB71E7B, 0xB53120, 0x994E00,
|
||||
0x6B6D00, 0x388700, 0x0C9300, 0x008F32, 0x007C8D, 0x000000, 0x000000, 0x000000,
|
||||
0xFFFEFF, 0x64B0FF, 0x9290FF, 0xC676FF, 0xF36AFF, 0xFE6ECC, 0xFE8170, 0xEA9E22,
|
||||
0xBCBE00, 0x88D800, 0x5CE430, 0x45E082, 0x48CDDE, 0x4F4F4F, 0x000000, 0x000000,
|
||||
0xFFFEFF, 0xC0DFFF, 0xD3D2FF, 0xE8C8FF, 0xFBC2FF, 0xFEC4EA, 0xFECCC5, 0xF7D8A5,
|
||||
0xE4E594, 0xCFEF96, 0xBDF4AB, 0xB3F3CC, 0xB5EBF2, 0xB8B8B8, 0x000000, 0x000000,
|
||||
}
|
||||
for i, c := range colors {
|
||||
r := byte(c >> 16)
|
||||
g := byte(c >> 8)
|
||||
b := byte(c)
|
||||
Palette[i] = color.RGBA{r, g, b, 0xFF}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,740 +0,0 @@
|
|||
package nes
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"image"
|
||||
)
|
||||
|
||||
type PPU struct {
|
||||
Memory // memory interface
|
||||
console *Console // reference to parent object
|
||||
|
||||
Cycle int // 0-340
|
||||
ScanLine int // 0-261, 0-239=visible, 240=post, 241-260=vblank, 261=pre
|
||||
Frame uint64 // frame counter
|
||||
|
||||
// storage variables
|
||||
paletteData [32]byte
|
||||
nameTableData [2048]byte
|
||||
oamData [256]byte
|
||||
front *image.RGBA
|
||||
back *image.RGBA
|
||||
|
||||
// PPU registers
|
||||
v uint16 // current vram address (15 bit)
|
||||
t uint16 // temporary vram address (15 bit)
|
||||
x byte // fine x scroll (3 bit)
|
||||
w byte // write toggle (1 bit)
|
||||
f byte // even/odd frame flag (1 bit)
|
||||
|
||||
register byte
|
||||
|
||||
// NMI flags
|
||||
nmiOccurred bool
|
||||
nmiOutput bool
|
||||
nmiPrevious bool
|
||||
nmiDelay byte
|
||||
|
||||
// background temporary variables
|
||||
nameTableByte byte
|
||||
attributeTableByte byte
|
||||
lowTileByte byte
|
||||
highTileByte byte
|
||||
tileData uint64
|
||||
|
||||
// sprite temporary variables
|
||||
spriteCount int
|
||||
spritePatterns [8]uint32
|
||||
spritePositions [8]byte
|
||||
spritePriorities [8]byte
|
||||
spriteIndexes [8]byte
|
||||
|
||||
// $2000 PPUCTRL
|
||||
flagNameTable byte // 0: $2000; 1: $2400; 2: $2800; 3: $2C00
|
||||
flagIncrement byte // 0: add 1; 1: add 32
|
||||
flagSpriteTable byte // 0: $0000; 1: $1000; ignored in 8x16 mode
|
||||
flagBackgroundTable byte // 0: $0000; 1: $1000
|
||||
flagSpriteSize byte // 0: 8x8; 1: 8x16
|
||||
flagMasterSlave byte // 0: read EXT; 1: write EXT
|
||||
|
||||
// $2001 PPUMASK
|
||||
flagGrayscale byte // 0: color; 1: grayscale
|
||||
flagShowLeftBackground byte // 0: hide; 1: show
|
||||
flagShowLeftSprites byte // 0: hide; 1: show
|
||||
flagShowBackground byte // 0: hide; 1: show
|
||||
flagShowSprites byte // 0: hide; 1: show
|
||||
flagRedTint byte // 0: normal; 1: emphasized
|
||||
flagGreenTint byte // 0: normal; 1: emphasized
|
||||
flagBlueTint byte // 0: normal; 1: emphasized
|
||||
|
||||
// $2002 PPUSTATUS
|
||||
flagSpriteZeroHit byte
|
||||
flagSpriteOverflow byte
|
||||
|
||||
// $2003 OAMADDR
|
||||
oamAddress byte
|
||||
|
||||
// $2007 PPUDATA
|
||||
bufferedData byte // for buffered reads
|
||||
}
|
||||
|
||||
func NewPPU(console *Console) *PPU {
|
||||
ppu := PPU{Memory: NewPPUMemory(console), console: console}
|
||||
ppu.front = image.NewRGBA(image.Rect(0, 0, 256, 240))
|
||||
ppu.back = image.NewRGBA(image.Rect(0, 0, 256, 240))
|
||||
ppu.Reset()
|
||||
return &ppu
|
||||
}
|
||||
|
||||
func (ppu *PPU) Save(encoder *gob.Encoder) error {
|
||||
encoder.Encode(ppu.Cycle)
|
||||
encoder.Encode(ppu.ScanLine)
|
||||
encoder.Encode(ppu.Frame)
|
||||
encoder.Encode(ppu.paletteData)
|
||||
encoder.Encode(ppu.nameTableData)
|
||||
encoder.Encode(ppu.oamData)
|
||||
encoder.Encode(ppu.v)
|
||||
encoder.Encode(ppu.t)
|
||||
encoder.Encode(ppu.x)
|
||||
encoder.Encode(ppu.w)
|
||||
encoder.Encode(ppu.f)
|
||||
encoder.Encode(ppu.register)
|
||||
encoder.Encode(ppu.nmiOccurred)
|
||||
encoder.Encode(ppu.nmiOutput)
|
||||
encoder.Encode(ppu.nmiPrevious)
|
||||
encoder.Encode(ppu.nmiDelay)
|
||||
encoder.Encode(ppu.nameTableByte)
|
||||
encoder.Encode(ppu.attributeTableByte)
|
||||
encoder.Encode(ppu.lowTileByte)
|
||||
encoder.Encode(ppu.highTileByte)
|
||||
encoder.Encode(ppu.tileData)
|
||||
encoder.Encode(ppu.spriteCount)
|
||||
encoder.Encode(ppu.spritePatterns)
|
||||
encoder.Encode(ppu.spritePositions)
|
||||
encoder.Encode(ppu.spritePriorities)
|
||||
encoder.Encode(ppu.spriteIndexes)
|
||||
encoder.Encode(ppu.flagNameTable)
|
||||
encoder.Encode(ppu.flagIncrement)
|
||||
encoder.Encode(ppu.flagSpriteTable)
|
||||
encoder.Encode(ppu.flagBackgroundTable)
|
||||
encoder.Encode(ppu.flagSpriteSize)
|
||||
encoder.Encode(ppu.flagMasterSlave)
|
||||
encoder.Encode(ppu.flagGrayscale)
|
||||
encoder.Encode(ppu.flagShowLeftBackground)
|
||||
encoder.Encode(ppu.flagShowLeftSprites)
|
||||
encoder.Encode(ppu.flagShowBackground)
|
||||
encoder.Encode(ppu.flagShowSprites)
|
||||
encoder.Encode(ppu.flagRedTint)
|
||||
encoder.Encode(ppu.flagGreenTint)
|
||||
encoder.Encode(ppu.flagBlueTint)
|
||||
encoder.Encode(ppu.flagSpriteZeroHit)
|
||||
encoder.Encode(ppu.flagSpriteOverflow)
|
||||
encoder.Encode(ppu.oamAddress)
|
||||
encoder.Encode(ppu.bufferedData)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ppu *PPU) Load(decoder *gob.Decoder) error {
|
||||
decoder.Decode(&ppu.Cycle)
|
||||
decoder.Decode(&ppu.ScanLine)
|
||||
decoder.Decode(&ppu.Frame)
|
||||
decoder.Decode(&ppu.paletteData)
|
||||
decoder.Decode(&ppu.nameTableData)
|
||||
decoder.Decode(&ppu.oamData)
|
||||
decoder.Decode(&ppu.v)
|
||||
decoder.Decode(&ppu.t)
|
||||
decoder.Decode(&ppu.x)
|
||||
decoder.Decode(&ppu.w)
|
||||
decoder.Decode(&ppu.f)
|
||||
decoder.Decode(&ppu.register)
|
||||
decoder.Decode(&ppu.nmiOccurred)
|
||||
decoder.Decode(&ppu.nmiOutput)
|
||||
decoder.Decode(&ppu.nmiPrevious)
|
||||
decoder.Decode(&ppu.nmiDelay)
|
||||
decoder.Decode(&ppu.nameTableByte)
|
||||
decoder.Decode(&ppu.attributeTableByte)
|
||||
decoder.Decode(&ppu.lowTileByte)
|
||||
decoder.Decode(&ppu.highTileByte)
|
||||
decoder.Decode(&ppu.tileData)
|
||||
decoder.Decode(&ppu.spriteCount)
|
||||
decoder.Decode(&ppu.spritePatterns)
|
||||
decoder.Decode(&ppu.spritePositions)
|
||||
decoder.Decode(&ppu.spritePriorities)
|
||||
decoder.Decode(&ppu.spriteIndexes)
|
||||
decoder.Decode(&ppu.flagNameTable)
|
||||
decoder.Decode(&ppu.flagIncrement)
|
||||
decoder.Decode(&ppu.flagSpriteTable)
|
||||
decoder.Decode(&ppu.flagBackgroundTable)
|
||||
decoder.Decode(&ppu.flagSpriteSize)
|
||||
decoder.Decode(&ppu.flagMasterSlave)
|
||||
decoder.Decode(&ppu.flagGrayscale)
|
||||
decoder.Decode(&ppu.flagShowLeftBackground)
|
||||
decoder.Decode(&ppu.flagShowLeftSprites)
|
||||
decoder.Decode(&ppu.flagShowBackground)
|
||||
decoder.Decode(&ppu.flagShowSprites)
|
||||
decoder.Decode(&ppu.flagRedTint)
|
||||
decoder.Decode(&ppu.flagGreenTint)
|
||||
decoder.Decode(&ppu.flagBlueTint)
|
||||
decoder.Decode(&ppu.flagSpriteZeroHit)
|
||||
decoder.Decode(&ppu.flagSpriteOverflow)
|
||||
decoder.Decode(&ppu.oamAddress)
|
||||
decoder.Decode(&ppu.bufferedData)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ppu *PPU) Reset() {
|
||||
ppu.Cycle = 340
|
||||
ppu.ScanLine = 240
|
||||
ppu.Frame = 0
|
||||
ppu.writeControl(0)
|
||||
ppu.writeMask(0)
|
||||
ppu.writeOAMAddress(0)
|
||||
}
|
||||
|
||||
func (ppu *PPU) readPalette(address uint16) byte {
|
||||
if address >= 16 && address%4 == 0 {
|
||||
address -= 16
|
||||
}
|
||||
return ppu.paletteData[address]
|
||||
}
|
||||
|
||||
func (ppu *PPU) writePalette(address uint16, value byte) {
|
||||
if address >= 16 && address%4 == 0 {
|
||||
address -= 16
|
||||
}
|
||||
ppu.paletteData[address] = value
|
||||
}
|
||||
|
||||
func (ppu *PPU) readRegister(address uint16) byte {
|
||||
switch address {
|
||||
case 0x2002:
|
||||
return ppu.readStatus()
|
||||
case 0x2004:
|
||||
return ppu.readOAMData()
|
||||
case 0x2007:
|
||||
return ppu.readData()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (ppu *PPU) writeRegister(address uint16, value byte) {
|
||||
ppu.register = value
|
||||
switch address {
|
||||
case 0x2000:
|
||||
ppu.writeControl(value)
|
||||
case 0x2001:
|
||||
ppu.writeMask(value)
|
||||
case 0x2003:
|
||||
ppu.writeOAMAddress(value)
|
||||
case 0x2004:
|
||||
ppu.writeOAMData(value)
|
||||
case 0x2005:
|
||||
ppu.writeScroll(value)
|
||||
case 0x2006:
|
||||
ppu.writeAddress(value)
|
||||
case 0x2007:
|
||||
ppu.writeData(value)
|
||||
case 0x4014:
|
||||
ppu.writeDMA(value)
|
||||
}
|
||||
}
|
||||
|
||||
// $2000: PPUCTRL
|
||||
func (ppu *PPU) writeControl(value byte) {
|
||||
ppu.flagNameTable = (value >> 0) & 3
|
||||
ppu.flagIncrement = (value >> 2) & 1
|
||||
ppu.flagSpriteTable = (value >> 3) & 1
|
||||
ppu.flagBackgroundTable = (value >> 4) & 1
|
||||
ppu.flagSpriteSize = (value >> 5) & 1
|
||||
ppu.flagMasterSlave = (value >> 6) & 1
|
||||
ppu.nmiOutput = (value>>7)&1 == 1
|
||||
ppu.nmiChange()
|
||||
// t: ....BA.. ........ = d: ......BA
|
||||
ppu.t = (ppu.t & 0xF3FF) | ((uint16(value) & 0x03) << 10)
|
||||
}
|
||||
|
||||
// $2001: PPUMASK
|
||||
func (ppu *PPU) writeMask(value byte) {
|
||||
ppu.flagGrayscale = (value >> 0) & 1
|
||||
ppu.flagShowLeftBackground = (value >> 1) & 1
|
||||
ppu.flagShowLeftSprites = (value >> 2) & 1
|
||||
ppu.flagShowBackground = (value >> 3) & 1
|
||||
ppu.flagShowSprites = (value >> 4) & 1
|
||||
ppu.flagRedTint = (value >> 5) & 1
|
||||
ppu.flagGreenTint = (value >> 6) & 1
|
||||
ppu.flagBlueTint = (value >> 7) & 1
|
||||
}
|
||||
|
||||
// $2002: PPUSTATUS
|
||||
func (ppu *PPU) readStatus() byte {
|
||||
result := ppu.register & 0x1F
|
||||
result |= ppu.flagSpriteOverflow << 5
|
||||
result |= ppu.flagSpriteZeroHit << 6
|
||||
if ppu.nmiOccurred {
|
||||
result |= 1 << 7
|
||||
}
|
||||
ppu.nmiOccurred = false
|
||||
ppu.nmiChange()
|
||||
// w: = 0
|
||||
ppu.w = 0
|
||||
return result
|
||||
}
|
||||
|
||||
// $2003: OAMADDR
|
||||
func (ppu *PPU) writeOAMAddress(value byte) {
|
||||
ppu.oamAddress = value
|
||||
}
|
||||
|
||||
// $2004: OAMDATA (read)
|
||||
func (ppu *PPU) readOAMData() byte {
|
||||
return ppu.oamData[ppu.oamAddress]
|
||||
}
|
||||
|
||||
// $2004: OAMDATA (write)
|
||||
func (ppu *PPU) writeOAMData(value byte) {
|
||||
ppu.oamData[ppu.oamAddress] = value
|
||||
ppu.oamAddress++
|
||||
}
|
||||
|
||||
// $2005: PPUSCROLL
|
||||
func (ppu *PPU) writeScroll(value byte) {
|
||||
if ppu.w == 0 {
|
||||
// t: ........ ...HGFED = d: HGFED...
|
||||
// x: CBA = d: .....CBA
|
||||
// w: = 1
|
||||
ppu.t = (ppu.t & 0xFFE0) | (uint16(value) >> 3)
|
||||
ppu.x = value & 0x07
|
||||
ppu.w = 1
|
||||
} else {
|
||||
// t: .CBA..HG FED..... = d: HGFEDCBA
|
||||
// w: = 0
|
||||
ppu.t = (ppu.t & 0x8FFF) | ((uint16(value) & 0x07) << 12)
|
||||
ppu.t = (ppu.t & 0xFC1F) | ((uint16(value) & 0xF8) << 2)
|
||||
ppu.w = 0
|
||||
}
|
||||
}
|
||||
|
||||
// $2006: PPUADDR
|
||||
func (ppu *PPU) writeAddress(value byte) {
|
||||
if ppu.w == 0 {
|
||||
// t: ..FEDCBA ........ = d: ..FEDCBA
|
||||
// t: .X...... ........ = 0
|
||||
// w: = 1
|
||||
ppu.t = (ppu.t & 0x80FF) | ((uint16(value) & 0x3F) << 8)
|
||||
ppu.w = 1
|
||||
} else {
|
||||
// t: ........ HGFEDCBA = d: HGFEDCBA
|
||||
// v = t
|
||||
// w: = 0
|
||||
ppu.t = (ppu.t & 0xFF00) | uint16(value)
|
||||
ppu.v = ppu.t
|
||||
ppu.w = 0
|
||||
}
|
||||
}
|
||||
|
||||
// $2007: PPUDATA (read)
|
||||
func (ppu *PPU) readData() byte {
|
||||
value := ppu.Read(ppu.v)
|
||||
// emulate buffered reads
|
||||
if ppu.v%0x4000 < 0x3F00 {
|
||||
buffered := ppu.bufferedData
|
||||
ppu.bufferedData = value
|
||||
value = buffered
|
||||
} else {
|
||||
ppu.bufferedData = ppu.Read(ppu.v - 0x1000)
|
||||
}
|
||||
// increment address
|
||||
if ppu.flagIncrement == 0 {
|
||||
ppu.v += 1
|
||||
} else {
|
||||
ppu.v += 32
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// $2007: PPUDATA (write)
|
||||
func (ppu *PPU) writeData(value byte) {
|
||||
ppu.Write(ppu.v, value)
|
||||
if ppu.flagIncrement == 0 {
|
||||
ppu.v += 1
|
||||
} else {
|
||||
ppu.v += 32
|
||||
}
|
||||
}
|
||||
|
||||
// $4014: OAMDMA
|
||||
func (ppu *PPU) writeDMA(value byte) {
|
||||
cpu := ppu.console.CPU
|
||||
address := uint16(value) << 8
|
||||
for i := 0; i < 256; i++ {
|
||||
ppu.oamData[ppu.oamAddress] = cpu.Read(address)
|
||||
ppu.oamAddress++
|
||||
address++
|
||||
}
|
||||
cpu.stall += 513
|
||||
if cpu.Cycles%2 == 1 {
|
||||
cpu.stall++
|
||||
}
|
||||
}
|
||||
|
||||
// NTSC Timing Helper Functions
|
||||
|
||||
func (ppu *PPU) incrementX() {
|
||||
// increment hori(v)
|
||||
// if coarse X == 31
|
||||
if ppu.v&0x001F == 31 {
|
||||
// coarse X = 0
|
||||
ppu.v &= 0xFFE0
|
||||
// switch horizontal nametable
|
||||
ppu.v ^= 0x0400
|
||||
} else {
|
||||
// increment coarse X
|
||||
ppu.v++
|
||||
}
|
||||
}
|
||||
|
||||
func (ppu *PPU) incrementY() {
|
||||
// increment vert(v)
|
||||
// if fine Y < 7
|
||||
if ppu.v&0x7000 != 0x7000 {
|
||||
// increment fine Y
|
||||
ppu.v += 0x1000
|
||||
} else {
|
||||
// fine Y = 0
|
||||
ppu.v &= 0x8FFF
|
||||
// let y = coarse Y
|
||||
y := (ppu.v & 0x03E0) >> 5
|
||||
if y == 29 {
|
||||
// coarse Y = 0
|
||||
y = 0
|
||||
// switch vertical nametable
|
||||
ppu.v ^= 0x0800
|
||||
} else if y == 31 {
|
||||
// coarse Y = 0, nametable not switched
|
||||
y = 0
|
||||
} else {
|
||||
// increment coarse Y
|
||||
y++
|
||||
}
|
||||
// put coarse Y back into v
|
||||
ppu.v = (ppu.v & 0xFC1F) | (y << 5)
|
||||
}
|
||||
}
|
||||
|
||||
func (ppu *PPU) copyX() {
|
||||
// hori(v) = hori(t)
|
||||
// v: .....F.. ...EDCBA = t: .....F.. ...EDCBA
|
||||
ppu.v = (ppu.v & 0xFBE0) | (ppu.t & 0x041F)
|
||||
}
|
||||
|
||||
func (ppu *PPU) copyY() {
|
||||
// vert(v) = vert(t)
|
||||
// v: .IHGF.ED CBA..... = t: .IHGF.ED CBA.....
|
||||
ppu.v = (ppu.v & 0x841F) | (ppu.t & 0x7BE0)
|
||||
}
|
||||
|
||||
func (ppu *PPU) nmiChange() {
|
||||
nmi := ppu.nmiOutput && ppu.nmiOccurred
|
||||
if nmi && !ppu.nmiPrevious {
|
||||
// TODO: this fixes some games but the delay shouldn't have to be so
|
||||
// long, so the timings are off somewhere
|
||||
ppu.nmiDelay = 15
|
||||
}
|
||||
ppu.nmiPrevious = nmi
|
||||
}
|
||||
|
||||
func (ppu *PPU) setVerticalBlank() {
|
||||
ppu.front, ppu.back = ppu.back, ppu.front
|
||||
ppu.nmiOccurred = true
|
||||
ppu.nmiChange()
|
||||
}
|
||||
|
||||
func (ppu *PPU) clearVerticalBlank() {
|
||||
ppu.nmiOccurred = false
|
||||
ppu.nmiChange()
|
||||
}
|
||||
|
||||
func (ppu *PPU) fetchNameTableByte() {
|
||||
v := ppu.v
|
||||
address := 0x2000 | (v & 0x0FFF)
|
||||
ppu.nameTableByte = ppu.Read(address)
|
||||
}
|
||||
|
||||
func (ppu *PPU) fetchAttributeTableByte() {
|
||||
v := ppu.v
|
||||
address := 0x23C0 | (v & 0x0C00) | ((v >> 4) & 0x38) | ((v >> 2) & 0x07)
|
||||
shift := ((v >> 4) & 4) | (v & 2)
|
||||
ppu.attributeTableByte = ((ppu.Read(address) >> shift) & 3) << 2
|
||||
}
|
||||
|
||||
func (ppu *PPU) fetchLowTileByte() {
|
||||
fineY := (ppu.v >> 12) & 7
|
||||
table := ppu.flagBackgroundTable
|
||||
tile := ppu.nameTableByte
|
||||
address := 0x1000*uint16(table) + uint16(tile)*16 + fineY
|
||||
ppu.lowTileByte = ppu.Read(address)
|
||||
}
|
||||
|
||||
func (ppu *PPU) fetchHighTileByte() {
|
||||
fineY := (ppu.v >> 12) & 7
|
||||
table := ppu.flagBackgroundTable
|
||||
tile := ppu.nameTableByte
|
||||
address := 0x1000*uint16(table) + uint16(tile)*16 + fineY
|
||||
ppu.highTileByte = ppu.Read(address + 8)
|
||||
}
|
||||
|
||||
func (ppu *PPU) storeTileData() {
|
||||
var data uint32
|
||||
for i := 0; i < 8; i++ {
|
||||
a := ppu.attributeTableByte
|
||||
p1 := (ppu.lowTileByte & 0x80) >> 7
|
||||
p2 := (ppu.highTileByte & 0x80) >> 6
|
||||
ppu.lowTileByte <<= 1
|
||||
ppu.highTileByte <<= 1
|
||||
data <<= 4
|
||||
data |= uint32(a | p1 | p2)
|
||||
}
|
||||
ppu.tileData |= uint64(data)
|
||||
}
|
||||
|
||||
func (ppu *PPU) fetchTileData() uint32 {
|
||||
return uint32(ppu.tileData >> 32)
|
||||
}
|
||||
|
||||
func (ppu *PPU) backgroundPixel() byte {
|
||||
if ppu.flagShowBackground == 0 {
|
||||
return 0
|
||||
}
|
||||
data := ppu.fetchTileData() >> ((7 - ppu.x) * 4)
|
||||
return byte(data & 0x0F)
|
||||
}
|
||||
|
||||
func (ppu *PPU) spritePixel() (byte, byte) {
|
||||
if ppu.flagShowSprites == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
for i := 0; i < ppu.spriteCount; i++ {
|
||||
offset := (ppu.Cycle - 1) - int(ppu.spritePositions[i])
|
||||
if offset < 0 || offset > 7 {
|
||||
continue
|
||||
}
|
||||
offset = 7 - offset
|
||||
color := byte((ppu.spritePatterns[i] >> byte(offset*4)) & 0x0F)
|
||||
if color%4 == 0 {
|
||||
continue
|
||||
}
|
||||
return byte(i), color
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func (ppu *PPU) renderPixel() {
|
||||
x := ppu.Cycle - 1
|
||||
y := ppu.ScanLine
|
||||
background := ppu.backgroundPixel()
|
||||
i, sprite := ppu.spritePixel()
|
||||
if x < 8 && ppu.flagShowLeftBackground == 0 {
|
||||
background = 0
|
||||
}
|
||||
if x < 8 && ppu.flagShowLeftSprites == 0 {
|
||||
sprite = 0
|
||||
}
|
||||
b := background%4 != 0
|
||||
s := sprite%4 != 0
|
||||
var color byte
|
||||
if !b && !s {
|
||||
color = 0
|
||||
} else if !b && s {
|
||||
color = sprite | 0x10
|
||||
} else if b && !s {
|
||||
color = background
|
||||
} else {
|
||||
if ppu.spriteIndexes[i] == 0 && x < 255 {
|
||||
ppu.flagSpriteZeroHit = 1
|
||||
}
|
||||
if ppu.spritePriorities[i] == 0 {
|
||||
color = sprite | 0x10
|
||||
} else {
|
||||
color = background
|
||||
}
|
||||
}
|
||||
c := Palette[ppu.readPalette(uint16(color))%64]
|
||||
ppu.back.SetRGBA(x, y, c)
|
||||
}
|
||||
|
||||
func (ppu *PPU) fetchSpritePattern(i, row int) uint32 {
|
||||
tile := ppu.oamData[i*4+1]
|
||||
attributes := ppu.oamData[i*4+2]
|
||||
var address uint16
|
||||
if ppu.flagSpriteSize == 0 {
|
||||
if attributes&0x80 == 0x80 {
|
||||
row = 7 - row
|
||||
}
|
||||
table := ppu.flagSpriteTable
|
||||
address = 0x1000*uint16(table) + uint16(tile)*16 + uint16(row)
|
||||
} else {
|
||||
if attributes&0x80 == 0x80 {
|
||||
row = 15 - row
|
||||
}
|
||||
table := tile & 1
|
||||
tile &= 0xFE
|
||||
if row > 7 {
|
||||
tile++
|
||||
row -= 8
|
||||
}
|
||||
address = 0x1000*uint16(table) + uint16(tile)*16 + uint16(row)
|
||||
}
|
||||
a := (attributes & 3) << 2
|
||||
lowTileByte := ppu.Read(address)
|
||||
highTileByte := ppu.Read(address + 8)
|
||||
var data uint32
|
||||
for i := 0; i < 8; i++ {
|
||||
var p1, p2 byte
|
||||
if attributes&0x40 == 0x40 {
|
||||
p1 = (lowTileByte & 1) << 0
|
||||
p2 = (highTileByte & 1) << 1
|
||||
lowTileByte >>= 1
|
||||
highTileByte >>= 1
|
||||
} else {
|
||||
p1 = (lowTileByte & 0x80) >> 7
|
||||
p2 = (highTileByte & 0x80) >> 6
|
||||
lowTileByte <<= 1
|
||||
highTileByte <<= 1
|
||||
}
|
||||
data <<= 4
|
||||
data |= uint32(a | p1 | p2)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (ppu *PPU) evaluateSprites() {
|
||||
var h int
|
||||
if ppu.flagSpriteSize == 0 {
|
||||
h = 8
|
||||
} else {
|
||||
h = 16
|
||||
}
|
||||
count := 0
|
||||
for i := 0; i < 64; i++ {
|
||||
y := ppu.oamData[i*4+0]
|
||||
a := ppu.oamData[i*4+2]
|
||||
x := ppu.oamData[i*4+3]
|
||||
row := ppu.ScanLine - int(y)
|
||||
if row < 0 || row >= h {
|
||||
continue
|
||||
}
|
||||
if count < 8 {
|
||||
ppu.spritePatterns[count] = ppu.fetchSpritePattern(i, row)
|
||||
ppu.spritePositions[count] = x
|
||||
ppu.spritePriorities[count] = (a >> 5) & 1
|
||||
ppu.spriteIndexes[count] = byte(i)
|
||||
}
|
||||
count++
|
||||
}
|
||||
if count > 8 {
|
||||
count = 8
|
||||
ppu.flagSpriteOverflow = 1
|
||||
}
|
||||
ppu.spriteCount = count
|
||||
}
|
||||
|
||||
// tick updates Cycle, ScanLine and Frame counters
|
||||
func (ppu *PPU) tick() {
|
||||
if ppu.nmiDelay > 0 {
|
||||
ppu.nmiDelay--
|
||||
if ppu.nmiDelay == 0 && ppu.nmiOutput && ppu.nmiOccurred {
|
||||
ppu.console.CPU.triggerNMI()
|
||||
}
|
||||
}
|
||||
|
||||
if ppu.flagShowBackground != 0 || ppu.flagShowSprites != 0 {
|
||||
if ppu.f == 1 && ppu.ScanLine == 261 && ppu.Cycle == 339 {
|
||||
ppu.Cycle = 0
|
||||
ppu.ScanLine = 0
|
||||
ppu.Frame++
|
||||
ppu.f ^= 1
|
||||
return
|
||||
}
|
||||
}
|
||||
ppu.Cycle++
|
||||
if ppu.Cycle > 340 {
|
||||
ppu.Cycle = 0
|
||||
ppu.ScanLine++
|
||||
if ppu.ScanLine > 261 {
|
||||
ppu.ScanLine = 0
|
||||
ppu.Frame++
|
||||
ppu.f ^= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step executes a single PPU cycle
|
||||
func (ppu *PPU) Step() {
|
||||
ppu.tick()
|
||||
|
||||
renderingEnabled := ppu.flagShowBackground != 0 || ppu.flagShowSprites != 0
|
||||
preLine := ppu.ScanLine == 261
|
||||
visibleLine := ppu.ScanLine < 240
|
||||
// postLine := ppu.ScanLine == 240
|
||||
renderLine := preLine || visibleLine
|
||||
preFetchCycle := ppu.Cycle >= 321 && ppu.Cycle <= 336
|
||||
visibleCycle := ppu.Cycle >= 1 && ppu.Cycle <= 256
|
||||
fetchCycle := preFetchCycle || visibleCycle
|
||||
|
||||
// background logic
|
||||
if renderingEnabled {
|
||||
if visibleLine && visibleCycle {
|
||||
ppu.renderPixel()
|
||||
}
|
||||
if renderLine && fetchCycle {
|
||||
ppu.tileData <<= 4
|
||||
switch ppu.Cycle % 8 {
|
||||
case 1:
|
||||
ppu.fetchNameTableByte()
|
||||
case 3:
|
||||
ppu.fetchAttributeTableByte()
|
||||
case 5:
|
||||
ppu.fetchLowTileByte()
|
||||
case 7:
|
||||
ppu.fetchHighTileByte()
|
||||
case 0:
|
||||
ppu.storeTileData()
|
||||
}
|
||||
}
|
||||
if preLine && ppu.Cycle >= 280 && ppu.Cycle <= 304 {
|
||||
ppu.copyY()
|
||||
}
|
||||
if renderLine {
|
||||
if fetchCycle && ppu.Cycle%8 == 0 {
|
||||
ppu.incrementX()
|
||||
}
|
||||
if ppu.Cycle == 256 {
|
||||
ppu.incrementY()
|
||||
}
|
||||
if ppu.Cycle == 257 {
|
||||
ppu.copyX()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sprite logic
|
||||
if renderingEnabled {
|
||||
if ppu.Cycle == 257 {
|
||||
if visibleLine {
|
||||
ppu.evaluateSprites()
|
||||
} else {
|
||||
ppu.spriteCount = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// vblank logic
|
||||
if ppu.ScanLine == 241 && ppu.Cycle == 1 {
|
||||
ppu.setVerticalBlank()
|
||||
}
|
||||
if preLine && ppu.Cycle == 1 {
|
||||
ppu.clearVerticalBlank()
|
||||
ppu.flagSpriteZeroHit = 0
|
||||
ppu.flagSpriteOverflow = 0
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
117
emulator/util.go
|
|
@ -1,117 +0,0 @@
|
|||
// credit to https://github.com/fogleman/nes
|
||||
package emulator
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image/gif"
|
||||
"image/png"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/giongto35/cloud-game/emulator/nes"
|
||||
)
|
||||
|
||||
func combineButtons(a, b [8]bool) [8]bool {
|
||||
var result [8]bool
|
||||
for i := 0; i < 8; i++ {
|
||||
result[i] = a[i] || b[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func copyImage(src image.Image) *image.RGBA {
|
||||
dst := image.NewRGBA(src.Bounds())
|
||||
draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src)
|
||||
return dst
|
||||
}
|
||||
|
||||
func loadPNG(path string) (image.Image, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
return png.Decode(file)
|
||||
}
|
||||
|
||||
func savePNG(path string, im image.Image) error {
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
return png.Encode(file, im)
|
||||
}
|
||||
|
||||
func saveGIF(path string, frames []image.Image) error {
|
||||
var palette []color.Color
|
||||
for _, c := range nes.Palette {
|
||||
palette = append(palette, c)
|
||||
}
|
||||
g := gif.GIF{}
|
||||
for i, src := range frames {
|
||||
if i%3 != 0 {
|
||||
continue
|
||||
}
|
||||
dst := image.NewPaletted(src.Bounds(), palette)
|
||||
draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src)
|
||||
g.Image = append(g.Image, dst)
|
||||
g.Delay = append(g.Delay, 5)
|
||||
}
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
return gif.EncodeAll(file, &g)
|
||||
}
|
||||
|
||||
func screenshot(im image.Image) {
|
||||
for i := 0; i < 1000; i++ {
|
||||
path := fmt.Sprintf("%03d.png", i)
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
savePNG(path, im)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func animation(frames []image.Image) {
|
||||
for i := 0; i < 1000; i++ {
|
||||
path := fmt.Sprintf("%03d.gif", i)
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
saveGIF(path, frames)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeSRAM(filename string, sram []byte) error {
|
||||
dir, _ := path.Split(filename)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
return binary.Write(file, binary.LittleEndian, sram)
|
||||
}
|
||||
|
||||
func readSRAM(filename string) ([]byte, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
sram := make([]byte, 0x2000)
|
||||
if err := binary.Read(file, binary.LittleEndian, sram); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sram, nil
|
||||
}
|
||||
9
encoder/type.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package encoder
|
||||
|
||||
import "image"
|
||||
|
||||
type Encoder interface {
|
||||
GetInputChan() chan *image.RGBA
|
||||
GetOutputChan() chan []byte
|
||||
Stop()
|
||||
}
|
||||
BIN
games/Pokemon - Fire Red Version (U) (V1.1).gba
vendored
Normal file
10
go.mod
vendored
|
|
@ -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
|
|
@ -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
|
|
@ -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
BIN
libretro/cores/mednafen_psx_libretro.so
vendored
Executable file
BIN
libretro/cores/mednafen_snes_libretro.so
vendored
Executable 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")
|
||||
|
|
|
|||
|
|
@ -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
BIN
libretro/system/scph5501.bin
vendored
Normal file
BIN
libretro/system/scph5502.bin
vendored
Normal 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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
After Width: | Height: | Size: 100 KiB |
BIN
static/img/screen_background5.png
vendored
Normal file
|
After Width: | Height: | Size: 76 KiB |
14
static/js/controller.js
vendored
|
|
@ -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();
|
||||
|
|
|
|||
6
static/js/gesture_keyboard.js
vendored
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
||||
513
vendor/cloud.google.com/go/compute/metadata/metadata.go
generated
Normal 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
|
|
@ -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)
|
||||
}
|
||||
54
vendor/cloud.google.com/go/internal/annotate.go
generated
Normal 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...))
|
||||
}
|
||||
108
vendor/cloud.google.com/go/internal/optional/optional.go
generated
Normal 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))
|
||||
}
|
||||
54
vendor/cloud.google.com/go/internal/retry.go
generated
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
109
vendor/cloud.google.com/go/internal/trace/trace.go
generated
Normal 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...)
|
||||
}
|
||||
6
vendor/cloud.google.com/go/internal/version/update_version.sh
generated
vendored
Normal 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
|
||||
|
||||
71
vendor/cloud.google.com/go/internal/version/version.go
generated
Normal 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
|
|
@ -0,0 +1,32 @@
|
|||
## Cloud Storage [](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
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
1192
vendor/cloud.google.com/go/storage/bucket.go
generated
Normal file
228
vendor/cloud.google.com/go/storage/copy.go
generated
Normal 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
|
|
@ -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"
|
||||
32
vendor/cloud.google.com/go/storage/go110.go
generated
Normal 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
|
|
@ -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
|
||||
}
|
||||
37
vendor/cloud.google.com/go/storage/invoke.go
generated
Normal 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
|
||||
})
|
||||
}
|
||||
42
vendor/cloud.google.com/go/storage/not_go110.go
generated
Normal 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
|
||||
}
|
||||
}
|
||||
188
vendor/cloud.google.com/go/storage/notifications.go
generated
Normal 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()
|
||||
}
|
||||
385
vendor/cloud.google.com/go/storage/reader.go
generated
Normal 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
|
||||
}
|
||||
1351
vendor/cloud.google.com/go/storage/storage.go
generated
Normal file
30067
vendor/cloud.google.com/go/storage/storage.replay
generated
vendored
Normal file
261
vendor/cloud.google.com/go/storage/writer.go
generated
Normal 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
|
|
@ -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
316
vendor/github.com/beorn7/perks/quantile/stream.go
generated
Normal 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
|
|
@ -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.
|
||||
|
||||
2
vendor/github.com/cheekybits/genny/generic/doc.go
generated
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
// Package generic contains the generic marker types.
|
||||
package generic
|
||||
13
vendor/github.com/cheekybits/genny/generic/generic.go
generated
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
Milan Nikolic <gen2brain@gmail.com>
|
||||
340
vendor/github.com/gen2brain/x264-go/COPYING
generated
vendored
Normal 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
|
|
@ -0,0 +1,77 @@
|
|||
## x264-go
|
||||
[](https://travis-ci.org/gen2brain/x264-go)
|
||||
[](https://ci.appveyor.com/project/gen2brain/x264-go)
|
||||
[](https://godoc.org/github.com/gen2brain/x264-go)
|
||||
[](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).
|
||||
212
vendor/github.com/gen2brain/x264-go/encode.go
generated
Normal 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(¶m, e.opts.Preset, e.opts.Tune)
|
||||
if ret < 0 {
|
||||
err = fmt.Errorf("x264: invalid preset/tune name")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
x264c.ParamDefault(¶m)
|
||||
}
|
||||
|
||||
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(¶m, e.opts.Profile)
|
||||
if ret < 0 {
|
||||
err = fmt.Errorf("x264: invalid profile name")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
e.e = x264c.EncoderOpen(¶m)
|
||||
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
|
||||
}
|
||||
42
vendor/github.com/gen2brain/x264-go/x264c/external/x264/common/aarch64/asm-offsets.c
generated
vendored
Normal 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);
|
||||
39
vendor/github.com/gen2brain/x264-go/x264c/external/x264/common/aarch64/asm-offsets.h
generated
vendored
Normal 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
|
||||
67
vendor/github.com/gen2brain/x264-go/x264c/external/x264/common/aarch64/dct.h
generated
vendored
Normal 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
|
||||
281
vendor/github.com/gen2brain/x264-go/x264c/external/x264/common/aarch64/mc-c.c
generated
vendored
Normal 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
|
||||
}
|
||||
31
vendor/github.com/gen2brain/x264-go/x264c/external/x264/common/aarch64/mc.h
generated
vendored
Normal 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
|
||||