Skip to content

Commit

Permalink
Replace hasMultitap option with more general customPortDevices
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sergystepanov committed Feb 17, 2024
1 parent b79b4c4 commit 2636fe8
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 80 deletions.
11 changes: 9 additions & 2 deletions pkg/config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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" ]
Expand Down
30 changes: 15 additions & 15 deletions pkg/config/emulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
21 changes: 9 additions & 12 deletions pkg/worker/caged/libretro/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 }

Expand Down
87 changes: 37 additions & 50 deletions pkg/worker/caged/libretro/nanoarch/nanoarch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pkg/worker/coordinatorhandlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down

0 comments on commit 2636fe8

Please sign in to comment.