Add multitap support to SNES

Change SNES emulator to Snes9X
Add client control to toggle multitap
This commit is contained in:
88hcsif 2020-06-27 22:42:37 +01:00
parent 42514fa09a
commit f7f044d039
16 changed files with 120 additions and 9 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -61,6 +61,7 @@ type EmulatorMeta struct {
Rotation image.Rotate
IsGlAllowed bool
UsesLibCo bool
HasMultitap bool
}
var EmulatorConfig = map[string]EmulatorMeta{
@ -80,9 +81,10 @@ var EmulatorConfig = map[string]EmulatorMeta{
Height: 240,
},
"snes": {
Path: "assets/emulator/libretro/cores/mednafen_snes_libretro",
Path: "assets/emulator/libretro/cores/snes9x_libretro",
Width: 256,
Height: 224,
HasMultitap: true,
},
"mame": {
Path: "assets/emulator/libretro/cores/fbneo_libretro",

View file

@ -171,4 +171,19 @@ func (o *Server) RouteBrowser(client *BrowserClient) {
return resp
})
client.Receive("multitap", func(resp cws.WSPacket) (req cws.WSPacket) {
client.Println("Received multitap request from a browser -> relay to worker")
// TODO: Async
resp.SessionID = client.SessionID
resp.RoomID = client.RoomID
wc, ok := o.workerClients[client.WorkerID]
if !ok {
return cws.EmptyPacket
}
resp = wc.SyncSend(resp)
return resp
})
}

View file

@ -83,6 +83,10 @@ bool bridge_retro_unserialize(void *f, void *data, size_t size) {
return ((bool (*)(void*, size_t))f)(data, size);
}
void bridge_retro_set_controller_port_device(void *f, unsigned port, unsigned device) {
return ((void (*)(unsigned, unsigned))f)(port, device);
}
bool coreEnvironment_cgo(unsigned cmd, void *data) {
bool coreEnvironment(unsigned, void*);
return coreEnvironment(cmd, data);

View file

@ -140,7 +140,7 @@ func (na *naEmulator) listenInput() {
}
func (na *naEmulator) LoadMeta(path string) config.EmulatorMeta {
coreLoad(na.meta.Path, na.meta.IsGlAllowed, na.meta.UsesLibCo, na.meta.Config)
coreLoad(na.meta)
coreLoadGame(path)
na.gamePath = path
@ -206,6 +206,14 @@ func (na *naEmulator) LoadGame() error {
return nil
}
func (na *naEmulator) ToggleMultitap() error {
if na.roomID != "" {
toggleMultitap()
}
return nil
}
func (na *naEmulator) GetHashPath() string {
return util.GetSavePath(na.roomID)
}

View file

@ -49,6 +49,7 @@ void* bridge_retro_get_memory_data(void *f, unsigned id);
bool bridge_retro_serialize(void *f, void *data, size_t size);
bool bridge_retro_unserialize(void *f, void *data, size_t size);
size_t bridge_retro_serialize_size(void *f);
void bridge_retro_set_controller_port_device(void *f, unsigned port, unsigned device);
bool coreEnvironment_cgo(unsigned cmd, void *data);
void coreVideoRefresh_cgo(void *data, unsigned width, unsigned height, size_t pitch);
@ -100,6 +101,12 @@ var isGlAllowed bool
var usesLibCo bool
var coreConfig ConfigProperties
var multitap struct {
supported bool
enabled bool
value C.unsigned
}
var systemDirectory = C.CString("./pkg/emulator/libretro/system")
var saveDirectory = C.CString(".")
var currentUser *C.char
@ -131,6 +138,7 @@ type CloudEmulator interface {
LoadGame() error
GetHashPath() string
Close()
ToggleMultitap() error
}
//export coreVideoRefresh
@ -328,6 +336,22 @@ func coreEnvironment(cmd C.unsigned, data unsafe.Pointer) C.bool {
return true
}
return false
case C.RETRO_ENVIRONMENT_SET_CONTROLLER_INFO:
if (multitap.supported) {
info := (*[100]C.struct_retro_controller_info)(data)
var i C.unsigned
for i = 0; unsafe.Pointer(info[i].types) != nil; i++ {
var j C.unsigned
types := (*[100]C.struct_retro_controller_description)(unsafe.Pointer(info[i].types))
for j = 0; j < info[i].num_types; j++ {
if (C.GoString(types[j].desc) == "Multitap") {
multitap.value = types[j].id
return true
}
}
}
}
return false
default:
//fmt.Println("[Env]: command not implemented", cmd)
return false
@ -478,6 +502,7 @@ var retroGetMemoryData unsafe.Pointer
var retroSerializeSize unsafe.Pointer
var retroSerialize unsafe.Pointer
var retroUnserialize unsafe.Pointer
var retroSetControllerPortDevice unsafe.Pointer
func loadFunction(handle unsafe.Pointer, name string) unsafe.Pointer {
cs := C.CString(name)
@ -486,15 +511,19 @@ func loadFunction(handle unsafe.Pointer, name string) unsafe.Pointer {
return pointer
}
func coreLoad(pathNoExt string, isGlAllowedParam bool, usesLibCoParam bool, pathToCfg string) {
isGlAllowed = isGlAllowedParam
usesLibCo = usesLibCoParam
coreConfig = ScanConfigFile(pathToCfg)
func coreLoad(meta config.EmulatorMeta) {
isGlAllowed = meta.IsGlAllowed
usesLibCo = meta.UsesLibCo
coreConfig = ScanConfigFile(meta.Config)
multitap.supported = meta.HasMultitap
multitap.enabled = false
multitap.value = 0
mu.Lock()
// Different OS requires different library, bruteforce till it finish
for _, ext := range config.EmulatorExtension {
pathWithExt := pathNoExt + ext
pathWithExt := meta.Path + ext
cs := C.CString(pathWithExt)
retroHandle = C.dlopen(cs, C.RTLD_LAZY)
C.free(unsafe.Pointer(cs))
@ -505,7 +534,7 @@ func coreLoad(pathNoExt string, isGlAllowedParam bool, usesLibCoParam bool, path
if retroHandle == nil {
err := C.dlerror()
log.Fatalf("error loading %s, err %+v", pathNoExt, *err)
log.Fatalf("error loading %s, err %+v", meta.Path, *err)
}
retroInit = loadFunction(retroHandle, "retro_init")
@ -525,6 +554,7 @@ func coreLoad(pathNoExt string, isGlAllowedParam bool, usesLibCoParam bool, path
retroSerializeSize = loadFunction(retroHandle, "retro_serialize_size")
retroSerialize = loadFunction(retroHandle, "retro_serialize")
retroUnserialize = loadFunction(retroHandle, "retro_unserialize")
retroSetControllerPortDevice = loadFunction(retroHandle, "retro_set_controller_port_device")
mu.Unlock()
@ -650,6 +680,21 @@ func coreLoadGame(filename string) {
}
}
func toggleMultitap() {
if (multitap.supported && multitap.value != 0) {
// Official SNES games only support a single multitap device
// Most require it to be plugged in player 2 port
// And Snes9X requires it to be "plugged" after the game is loaded
// Control this from the browser since player 2 will stop working in some games if multitap is "plugged" in
if (multitap.enabled) {
C.bridge_retro_set_controller_port_device(retroSetControllerPortDevice, 1, C.RETRO_DEVICE_JOYPAD)
} else {
C.bridge_retro_set_controller_port_device(retroSetControllerPortDevice, 1, multitap.value)
}
multitap.enabled = !multitap.enabled
}
}
// serializeSize returns the amount of data the implementation requires to serialize
// internal state (save states).
// Between calls to retro_load_game() and retro_unload_game(), the

View file

@ -19,4 +19,6 @@ type CloudEmulator interface {
GetHashPath() string
// Close will be called when the game is done
Close()
ToggleMultitap() error
}

View file

@ -265,6 +265,26 @@ func (h *Handler) RouteCoordinator() {
return req
})
oClient.Receive(
"multitap",
func(resp cws.WSPacket) (req cws.WSPacket) {
log.Println("Received a multitap toggle from coordinator")
req.ID = "multitap"
req.Data = "ok"
if resp.RoomID != "" {
room := h.getRoom(resp.RoomID)
err := room.ToggleMultitap()
if err != nil {
log.Println("[!] Could not toggle multitap state: ", err)
req.Data = "error"
}
} else {
req.Data = "error"
}
return req
})
oClient.Receive(
"terminateSession",
func(resp cws.WSPacket) (req cws.WSPacket) {

View file

@ -375,6 +375,12 @@ func (r *Room) LoadGame() error {
return err
}
func (r *Room) ToggleMultitap() error {
err := r.director.ToggleMultitap()
return err
}
func (r *Room) EmptySessions() bool {
return len(r.rtcSessions) == 0
}

View file

@ -379,6 +379,11 @@
updatePlayerIndex(3);
break;
// toggle multitap
case KEY.MULTITAP:
socket.toggleMultitap();
break;
// quit
case KEY.QUIT:
input.poll().disable();

View file

@ -31,6 +31,7 @@ const keyboard = (() => {
72: KEY.HELP, // h
220: KEY.STATS, // backslash
84: KEY.DTOGGLE, // t
77: KEY.MULTITAP, // m
};
let dpadMode = true;

View file

@ -28,5 +28,6 @@ const KEY = (() => {
R2: 'r2',
L3: 'l3',
R3: 'r3',
MULTITAP: 'multitap',
}
})();

View file

@ -114,6 +114,7 @@ const socket = (() => {
"player_index": playerIndex
});
const quitGame = (roomId) => send({"id": "quit", "data": "", "room_id": roomId});
const toggleMultitap = () => send({"id": "multitap", "data": ""});
return {
init: init,
@ -123,6 +124,7 @@ const socket = (() => {
loadGame: loadGame,
updatePlayerIndex: updatePlayerIndex,
startGame: startGame,
quitGame: quitGame
quitGame: quitGame,
toggleMultitap: toggleMultitap,
}
})($, event, log);