mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-07-21 00:59:22 +00:00
Update Playstation core (#73)
* Update cores setting for ps1 * Update PS3 output + use bytes per row to get the correct number of bytes per row * Add more inputs * Update input * Fix input * Add resize image comment * Update screenshot * Try side by side * Update Image side by side * Add image side by side * Update landing page * Add landing page image * Update landing page image
This commit is contained in:
parent
29d718de08
commit
5f85b24469
24 changed files with 129 additions and 55 deletions
6
README.md
vendored
6
README.md
vendored
|
|
@ -14,8 +14,10 @@ This project aims to bring the most modern and convenient gaming experience to u
|
|||
Note: **Due to the high cost of hosting, I will Hibernate the servers for a while. I'm working on a big change and will turn on hosting again. Sorry for that :(**
|
||||
You can try hosting your own service following the instruction in the next session.
|
||||
|
||||

|
||||

|
||||
Screenshot | Screenshot
|
||||
:-------------------------:|:-------------------------:
|
||||
|
|
||||
|
|
||||
|
||||
## Feature
|
||||
1. Cloud gaming: Game logic is hosted on a remote server. User doesn't have to install or setup anything. Images and audio are streamed to user in the most optimal way.
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ var WSWait = 20 * time.Second
|
|||
var MatchWorkerRandom = false
|
||||
var ProdEnv = "prod"
|
||||
|
||||
var Codec = CODEC_H264
|
||||
//var Codec = CODEC_H264
|
||||
var Codec = CODEC_VP8
|
||||
var FileTypeToEmulator = map[string]string{
|
||||
"gba": "gba",
|
||||
"cue": "pcsx",
|
||||
|
|
|
|||
|
|
@ -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-gb.png
vendored
BIN
document/img/landing-page-gb.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 1 MiB After Width: | Height: | Size: 977 KiB |
BIN
document/img/landing-page-ps-hm.png
vendored
Normal file
BIN
document/img/landing-page-ps-hm.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
document/img/landing-page-ps-x4.png
vendored
Normal file
BIN
document/img/landing-page-ps-x4.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
|
|
@ -28,7 +28,7 @@ const (
|
|||
left2
|
||||
right2
|
||||
)
|
||||
const NumKeys = 8
|
||||
const NumKeys = 10
|
||||
|
||||
// Audio consts
|
||||
const (
|
||||
|
|
|
|||
1
go.mod
vendored
1
go.mod
vendored
|
|
@ -5,6 +5,7 @@ go 1.12
|
|||
require (
|
||||
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
|
||||
github.com/gofrs/uuid v3.2.0+incompatible
|
||||
github.com/gorilla/websocket v1.4.0
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
|
|
|
|||
2
go.sum
vendored
2
go.sum
vendored
|
|
@ -18,6 +18,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||
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=
|
||||
|
|
|
|||
BIN
libretro/cores/mednafen_psx_hw_libretro.so
vendored
Executable file
BIN
libretro/cores/mednafen_psx_hw_libretro.so
vendored
Executable file
Binary file not shown.
BIN
libretro/cores/mednafen_psx_libretro.so
vendored
Executable file
BIN
libretro/cores/mednafen_psx_libretro.so
vendored
Executable file
Binary file not shown.
BIN
libretro/cores/mednafen_snes_libretro.so
vendored
Executable file
BIN
libretro/cores/mednafen_snes_libretro.so
vendored
Executable file
Binary file not shown.
|
|
@ -66,10 +66,9 @@ 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",
|
||||
"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",
|
||||
}
|
||||
|
|
@ -97,8 +96,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 {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"unsafe"
|
||||
|
||||
"github.com/giongto35/cloud-game/emulator"
|
||||
"github.com/go-gl/gl/v2.1/gl"
|
||||
)
|
||||
|
||||
/*
|
||||
|
|
@ -71,20 +72,22 @@ 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,
|
||||
}
|
||||
|
||||
type CloudEmulator interface {
|
||||
|
|
@ -121,47 +124,68 @@ func videoConfigure(geom *C.struct_retro_game_geometry) (int, int) {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// toImageRGBA convert nanoarch 2d array to image.RGBA
|
||||
func toImageRGBA(data unsafe.Pointer) *image.RGBA {
|
||||
func toImageRGBA(data unsafe.Pointer, bytesPerRow 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
|
||||
sh.Len = bytesPerRow * eheight * 4
|
||||
sh.Cap = bytesPerRow * eheight * 4
|
||||
|
||||
if video.pixFmt == gl.UNSIGNED_SHORT_5_6_5 {
|
||||
return to565Image(data, bytes, bytesPerRow)
|
||||
} else if video.pixFmt == gl.UNSIGNED_INT_8_8_8_8_REV {
|
||||
return to8888Image(data, bytes, bytesPerRow)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func to8888Image(data unsafe.Pointer, bytes []byte, bytesPerRow int) *image.RGBA {
|
||||
seek := 0
|
||||
|
||||
// Convert bytes array to image
|
||||
// TODO: Reduce overhead of copying to bytes array by accessing unsafe.Pointer directly
|
||||
image := image.NewRGBA(image.Rect(0, 0, ewidth, eheight))
|
||||
for y := 0; y < eheight; y++ {
|
||||
for x := 0; x < w; x++ {
|
||||
for x := 0; x < bytesPerRow; x++ {
|
||||
if x < ewidth {
|
||||
b8 := bytes[seek]
|
||||
g8 := bytes[seek+1]
|
||||
r8 := bytes[seek+2]
|
||||
a8 := bytes[seek+3]
|
||||
|
||||
image.Set(x, y, 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) *image.RGBA {
|
||||
seek := 0
|
||||
|
||||
// Convert bytes array to image
|
||||
// TODO: Reduce overhead of copying to bytes array by accessing unsafe.Pointer directly
|
||||
image := image.NewRGBA(image.Rect(0, 0, ewidth, eheight))
|
||||
for y := 0; y < eheight; y++ {
|
||||
for x := 0; x < bytesPerRow; x++ {
|
||||
if x < ewidth {
|
||||
var bi int
|
||||
bi = (int)(bytes[seek]) + ((int)(bytes[seek+1]) << 8)
|
||||
|
|
@ -179,14 +203,15 @@ func toImageRGBA(data unsafe.Pointer) *image.RGBA {
|
|||
}
|
||||
}
|
||||
|
||||
// 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 +220,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
|
||||
|
|
@ -261,8 +286,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(".")
|
||||
|
|
@ -455,3 +483,32 @@ func nanoarchShutdown() {
|
|||
func nanoarchRun() {
|
||||
C.bridge_retro_run(retroRun)
|
||||
}
|
||||
|
||||
func videoSetPixelFormat(format uint32) C.bool {
|
||||
if video.texID != 0 {
|
||||
log.Fatal("Tried to change pixel format after initialization.")
|
||||
}
|
||||
|
||||
switch format {
|
||||
case C.RETRO_PIXEL_FORMAT_0RGB1555:
|
||||
video.pixFmt = gl.UNSIGNED_SHORT_5_5_5_1
|
||||
video.pixType = gl.BGRA
|
||||
video.bpp = 2
|
||||
break
|
||||
case C.RETRO_PIXEL_FORMAT_XRGB8888:
|
||||
video.pixFmt = gl.UNSIGNED_INT_8_8_8_8_REV
|
||||
video.pixType = gl.BGRA
|
||||
video.bpp = 4
|
||||
break
|
||||
case C.RETRO_PIXEL_FORMAT_RGB565:
|
||||
video.pixFmt = gl.UNSIGNED_SHORT_5_6_5
|
||||
video.pixType = gl.RGB
|
||||
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/scph5500.bin
vendored
Normal file
Binary file not shown.
BIN
libretro/system/scph5501.bin
vendored
Normal file
BIN
libretro/system/scph5501.bin
vendored
Normal file
Binary file not shown.
BIN
libretro/system/scph5502.bin
vendored
Normal file
BIN
libretro/system/scph5502.bin
vendored
Normal file
Binary file not shown.
8
static/js/controller.js
vendored
8
static/js/controller.js
vendored
|
|
@ -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
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
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,
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ func isValidGameType(gamePath string) bool {
|
|||
// getGameInfo returns game info from a path
|
||||
func getGameInfo(path string) GameInfo {
|
||||
// Remove prefix to obtain file names
|
||||
fileName := path[len(gamePath)+1:]
|
||||
fileName := filepath.Base(path)
|
||||
ext := filepath.Ext(fileName)
|
||||
return GameInfo{
|
||||
Name: strings.TrimSuffix(fileName, ext),
|
||||
|
|
|
|||
|
|
@ -155,8 +155,8 @@ func (w *WebRTC) StartClient(remoteSession string, iceCandidates []string) (stri
|
|||
|
||||
// Register text message handling
|
||||
inputTrack.OnMessage(func(msg webrtc.DataChannelMessage) {
|
||||
// TODO: Can add recover here
|
||||
w.InputChannel <- int(msg.Data[0])
|
||||
// TODO: Can add recover here + generalize
|
||||
w.InputChannel <- int(msg.Data[1])<<8 + int(msg.Data[0])
|
||||
})
|
||||
|
||||
inputTrack.OnClose(func() {
|
||||
|
|
|
|||
|
|
@ -88,15 +88,14 @@ func (h *Handler) GetOverlordClient() *OverlordClient {
|
|||
// detachPeerConn detach/remove a peerconnection from current room
|
||||
func (h *Handler) detachPeerConn(pc *webrtc.WebRTC) {
|
||||
log.Println("Detach peerconnection")
|
||||
roomID := pc.RoomID
|
||||
room := h.getRoom(roomID)
|
||||
room := h.getRoom(pc.RoomID)
|
||||
if room == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// If room has no sessions, close room
|
||||
if !room.EmptySessions() {
|
||||
room.RemoveSession(pc)
|
||||
// If no more session in that room, we close that room
|
||||
if room.EmptySessions() {
|
||||
log.Println("No session in room")
|
||||
room.Close()
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ func NewRoom(roomID string, gameName string, onlineStorage *storage.Client) *Roo
|
|||
roomID = generateRoomID(gameName)
|
||||
} else {
|
||||
gameName = getGameNameFromRoomID(roomID)
|
||||
log.Println("Get Gamename from RoomID", gameName)
|
||||
}
|
||||
gameInfo := gamelist.GetGameInfoFromName(gameName)
|
||||
|
||||
|
|
@ -168,9 +169,9 @@ func (r *Room) startWebRTCSession(peerconnection *webrtc.WebRTC, playerIndex int
|
|||
}
|
||||
|
||||
if peerconnection.IsConnected() {
|
||||
// the first 8 bits belong to player 1
|
||||
// the next 8 belongs to player 2 ...
|
||||
// We standardize and put it to inputChannel (16 bits)
|
||||
// the first 10 bits belong to player 1
|
||||
// the next 10 belongs to player 2 ...
|
||||
// We standardize and put it to inputChannel (20 bits)
|
||||
input = input << ((uint(playerIndex) - 1) * emulator.NumKeys)
|
||||
select {
|
||||
case r.inputChannel <- input:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue