From 2636fe88f43fc7570438d385acb4c830197db1f9 Mon Sep 17 00:00:00 2001 From: Sergey Stepanov Date: Thu, 23 Nov 2023 01:46:25 +0300 Subject: [PATCH] Replace hasMultitap option with more general customPortDevices The new customPortDevices option allows users to map a specific Libretro device (or multiple devices) to the input ports. For example, this allows users to map a Multitap controller with the snes9x core. --- pkg/config/config.yaml | 11 ++- pkg/config/emulator.go | 30 +++---- pkg/worker/caged/libretro/frontend.go | 21 ++--- .../caged/libretro/nanoarch/nanoarch.go | 87 ++++++++----------- pkg/worker/coordinatorhandlers.go | 2 +- 5 files changed, 71 insertions(+), 80 deletions(-) diff --git a/pkg/config/config.yaml b/pkg/config/config.yaml index 27f1eaac2..79cbd76f2 100644 --- a/pkg/config/config.yaml +++ b/pkg/config/config.yaml @@ -180,8 +180,12 @@ emulator: # - ratio (float) # - isGlAllowed (bool) # - usesLibCo (bool) - # - hasMultitap (bool) + # - hasMultitap (bool) -- (deprecated) # - coreAspectRatio (bool) -- correct the aspect ratio on the client with the info from the core. + # - customPortDevices (map[int][]int) + # A list of device ids to bind to the input ports. + # Some cores allow binding multiple devices to a single port (DosBox), but normally + # you should bind just one device to one port. # - vfr (bool) # (experimental) # Enable variable frame rate only for cores that can't produce a constant frame rate. @@ -226,7 +230,10 @@ emulator: snes: lib: snes9x_libretro roms: [ "smc", "sfc", "swc", "fig", "bs" ] - hasMultitap: true + customPortDevices: + # set the 2nd port to RETRO_DEVICE_JOYPAD_MULTITAP ((1<<8) | 1) as snes9x requires it + # in order to support up to 5 player games, see: https://nintendo.fandom.com/wiki/Super_Multitap + 1: 257 n64: lib: mupen64plus_next_libretro roms: [ "n64", "v64", "z64" ] diff --git a/pkg/config/emulator.go b/pkg/config/emulator.go index da8f5b2ea..01018aeab 100644 --- a/pkg/config/emulator.go +++ b/pkg/config/emulator.go @@ -39,21 +39,21 @@ type LibretroRepoConfig struct { } type LibretroCoreConfig struct { - AltRepo bool - AutoGlContext bool // hack: keep it here to pass it down the emulator - CoreAspectRatio bool - Folder string - Hacks []string - HasMultitap bool - Height int - IsGlAllowed bool - Lib string - Options map[string]string - Roms []string - Scale float64 - UsesLibCo bool - VFR bool - Width int + AltRepo bool + AutoGlContext bool // hack: keep it here to pass it down the emulator + CoreAspectRatio bool + CustomPortDevices map[int][]int + Folder string + Hacks []string + Height int + IsGlAllowed bool + Lib string + Options map[string]string + Roms []string + Scale float64 + UsesLibCo bool + VFR bool + Width int } type CoreInfo struct { diff --git a/pkg/worker/caged/libretro/frontend.go b/pkg/worker/caged/libretro/frontend.go index 0386b0a86..481966635 100644 --- a/pkg/worker/caged/libretro/frontend.go +++ b/pkg/worker/caged/libretro/frontend.go @@ -43,8 +43,6 @@ type Emulator interface { HasSave() bool // Close will be called when the game is done Close() - // ToggleMultitap toggles multitap controller. - ToggleMultitap() // Input passes input to the emulator Input(player int, data []byte) // Scale returns set video scale factor @@ -157,15 +155,15 @@ func NewFrontend(conf config.Emulator, log *logger.Logger) (*Frontend, error) { func (f *Frontend) LoadCore(emu string) { conf := f.conf.GetLibretroCoreConfig(emu) meta := nanoarch.Metadata{ - AutoGlContext: conf.AutoGlContext, - Hacks: conf.Hacks, - HasMultitap: conf.HasMultitap, - HasVFR: conf.VFR, - IsGlAllowed: conf.IsGlAllowed, - LibPath: conf.Lib, - Options: conf.Options, - UsesLibCo: conf.UsesLibCo, - CoreAspectRatio: conf.CoreAspectRatio, + AutoGlContext: conf.AutoGlContext, + Hacks: conf.Hacks, + HasVFR: conf.VFR, + IsGlAllowed: conf.IsGlAllowed, + LibPath: conf.Lib, + Options: conf.Options, + UsesLibCo: conf.UsesLibCo, + CoreAspectRatio: conf.CoreAspectRatio, + CustomPortDevices: conf.CustomPortDevices, } f.mu.Lock() scale := 1.0 @@ -310,7 +308,6 @@ func (f *Frontend) SetSessionId(name string) { f.storage.SetMainSaveName(na func (f *Frontend) SetDataCb(cb func([]byte)) { f.onData = cb } func (f *Frontend) SetVideoCb(ff func(app.Video)) { f.onVideo = ff } func (f *Frontend) Tick() { f.mu.Lock(); f.nano.Run(); f.mu.Unlock() } -func (f *Frontend) ToggleMultitap() { f.nano.ToggleMultitap() } func (f *Frontend) ViewportRecalculate() { f.mu.Lock(); f.vw, f.vh = f.ViewportCalc(); f.mu.Unlock() } func (f *Frontend) ViewportSize() (int, int) { return f.vw, f.vh } diff --git a/pkg/worker/caged/libretro/nanoarch/nanoarch.go b/pkg/worker/caged/libretro/nanoarch/nanoarch.go index 86ca6bd92..8850522d1 100644 --- a/pkg/worker/caged/libretro/nanoarch/nanoarch.go +++ b/pkg/worker/caged/libretro/nanoarch/nanoarch.go @@ -43,11 +43,7 @@ type Nanoarch struct { Handlers LastFrameTime int64 LibCo bool - multitap struct { - supported bool - enabled bool - value C.unsigned - } + meta Metadata options *map[string]string reserved chan struct{} // limits concurrent use Rot uint @@ -92,15 +88,15 @@ type FrameInfo struct { } type Metadata struct { - LibPath string // the full path to some emulator lib - IsGlAllowed bool - UsesLibCo bool - AutoGlContext bool - HasMultitap bool - HasVFR bool - Options map[string]string - Hacks []string - CoreAspectRatio bool + LibPath string // the full path to some emulator lib + IsGlAllowed bool + UsesLibCo bool + AutoGlContext bool + HasVFR bool + Options map[string]string + Hacks []string + CoreAspectRatio bool + CustomPortDevices map[int][]int } type PixFmt struct { @@ -159,6 +155,7 @@ func (n *Nanoarch) SetVideoDebounce(t time.Duration) { n.limiter = NewLimit(t) } func (n *Nanoarch) CoreLoad(meta Metadata) { var err error + n.meta = meta n.LibCo = meta.UsesLibCo n.vfr = meta.HasVFR n.Aspect = meta.CoreAspectRatio @@ -170,10 +167,6 @@ func (n *Nanoarch) CoreLoad(meta Metadata) { n.options = &meta.Options - n.multitap.supported = meta.HasMultitap - n.multitap.enabled = false - n.multitap.value = 0 - filePath := meta.LibPath if ar, err := arch.Guess(); err == nil { filePath = filePath + ar.LibExt @@ -298,34 +291,24 @@ func (n *Nanoarch) LoadGame(path string) error { } // set default controller types on all ports + // needed for nestopia for i := 0; i < MaxPort; i++ { C.bridge_retro_set_controller_port_device(retroSetControllerPortDevice, C.uint(i), C.RETRO_DEVICE_JOYPAD) } + // map custom devices to ports + for k, v := range n.meta.CustomPortDevices { + for _, device := range v { + C.bridge_retro_set_controller_port_device(retroSetControllerPortDevice, C.uint(k), C.unsigned(device)) + n.log.Debug().Msgf("set custom port-device: %v:%v", k, device) + } + } + n.LastFrameTime = time.Now().UnixNano() return nil } -// ToggleMultitap toggles multitap controller for cores. -// -// 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. -func (n *Nanoarch) ToggleMultitap() { - if !n.multitap.supported || n.multitap.value == 0 { - return - } - mt := n.multitap.value - if n.multitap.enabled { - mt = C.RETRO_DEVICE_JOYPAD - } - C.bridge_retro_set_controller_port_device(retroSetControllerPortDevice, 1, mt) - n.multitap.enabled = !n.multitap.enabled -} - func (n *Nanoarch) Shutdown() { if n.LibCo { thread.Main(func() { @@ -776,22 +759,26 @@ func coreEnvironment(cmd C.unsigned, data unsafe.Pointer) C.bool { return false case C.RETRO_ENVIRONMENT_SET_CONTROLLER_INFO: // !to rewrite - if !Nan0.multitap.supported { - return false - } - 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" { - Nan0.multitap.value = types[j].id - return true + info := (*[64]C.struct_retro_controller_info)(data) + for c, controller := range info { + tp := unsafe.Pointer(controller.types) + if tp == nil { + break + } + cInfo := strings.Builder{} + cInfo.WriteString(fmt.Sprintf("Controller [%v] ", c)) + cd := (*[32]C.struct_retro_controller_description)(tp) + delim := ", " + n := int(controller.num_types) + for i := 0; i < n; i++ { + if i == n-1 { + delim = "" } + cInfo.WriteString(fmt.Sprintf("%v: %v%s", cd[i].id, C.GoString(cd[i].desc), delim)) } + Nan0.log.Debug().Msgf("%v", cInfo.String()) } - return false + return true case C.RETRO_ENVIRONMENT_GET_CLEAR_ALL_THREAD_WAITS_CB: C.bridge_clear_all_thread_waits_cb(data) return true diff --git a/pkg/worker/coordinatorhandlers.go b/pkg/worker/coordinatorhandlers.go index d5d9dd8e7..41e150f8c 100644 --- a/pkg/worker/coordinatorhandlers.go +++ b/pkg/worker/coordinatorhandlers.go @@ -234,11 +234,11 @@ func (c *coordinator) HandleChangePlayer(rq api.ChangePlayerRequest[com.Uid], w } func (c *coordinator) HandleToggleMultitap(rq api.ToggleMultitapRequest[com.Uid], w *Worker) api.Out { + // !to remove completely r := w.router.FindRoom(rq.Rid) if r == nil { return api.ErrPacket } - room.WithEmulator(r.App()).ToggleMultitap() return api.OkPacket }