Skip to content

Commit

Permalink
Replace the hasMultitap option with a more general solution
Browse files Browse the repository at this point in the history
The new hid option enables users to map a specific Libretro device (or multiple devices) to the input ports. For instance, this allows users to map a Multitap controller with the snes9x core.
  • Loading branch information
sergystepanov committed Mar 5, 2024
1 parent cdbb5e9 commit 91ace06
Show file tree
Hide file tree
Showing 17 changed files with 46 additions and 87 deletions.
3 changes: 0 additions & 3 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ const (
SaveGame PT = 106
LoadGame PT = 107
ChangePlayer PT = 108
ToggleMultitap PT = 109
RecordGame PT = 110
GetWorkerList PT = 111
ErrNoFreeSlots PT = 112
Expand Down Expand Up @@ -112,8 +111,6 @@ func (p PT) String() string {
return "SaveGame"
case LoadGame:
return "LoadGame"
case ToggleMultitap:
return "ToggleMultitap"
case RecordGame:
return "RecordGame"
case GetWorkerList:
Expand Down
3 changes: 0 additions & 3 deletions pkg/api/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@ type (
TerminateSessionRequest[T Id] struct {
Stateful[T]
}
ToggleMultitapRequest[T Id] struct {
StatefulRoom[T]
}
WebrtcAnswerRequest[T Id] struct {
Stateful[T]
Sdp string `json:"sdp"`
Expand Down
12 changes: 10 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) -- (removed)
# - coreAspectRatio (bool) -- correct the aspect ratio on the client with the info from the core.
# - hid (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 typically,
# 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,11 @@ emulator:
snes:
lib: snes9x_libretro
roms: [ "smc", "sfc", "swc", "fig", "bs" ]
hasMultitap: true
hid:
# 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
2 changes: 1 addition & 1 deletion pkg/config/emulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ type LibretroCoreConfig struct {
CoreAspectRatio bool
Folder string
Hacks []string
HasMultitap bool
Height int
Hid map[int][]int
IsGlAllowed bool
Lib string
Options map[string]string
Expand Down
2 changes: 0 additions & 2 deletions pkg/coordinator/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ func (u *User) HandleRequests(info HasServerInfo, launcher games.Launcher, conf
return api.ErrMalformed
}
u.HandleChangePlayer(*rq)
case api.ToggleMultitap:
u.HandleToggleMultitap()
case api.RecordGame:
if !conf.Recording.Enabled {
return api.ErrForbidden
Expand Down
2 changes: 0 additions & 2 deletions pkg/coordinator/userhandlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,6 @@ func (u *User) HandleChangePlayer(rq api.ChangePlayerUserRequest) {
u.Notify(api.ChangePlayer, rq)
}

func (u *User) HandleToggleMultitap() { u.w.ToggleMultitap(u.Id()) }

func (u *User) HandleRecordGame(rq api.RecordGameRequest[com.Uid]) {
if u.w == nil {
return
Expand Down
4 changes: 0 additions & 4 deletions pkg/coordinator/workerapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,6 @@ func (w *Worker) ChangePlayer(id com.Uid, index int) (*api.ChangePlayerResponse,
w.Send(api.ChangePlayer, api.ChangePlayerRequest[com.Uid]{StatefulRoom: StateRoom(id, w.RoomId), Index: index}))
}

func (w *Worker) ToggleMultitap(id com.Uid) {
_, _ = w.Send(api.ToggleMultitap, api.ToggleMultitapRequest[com.Uid]{StatefulRoom: StateRoom(id, w.RoomId)})
}

func (w *Worker) RecordGame(id com.Uid, rec bool, recUser string) (*api.RecordGameResponse, error) {
return api.UnwrapChecked[api.RecordGameResponse](
w.Send(api.RecordGame, api.RecordGameRequest[com.Uid]{StatefulRoom: StateRoom(id, w.RoomId), Active: rec, User: recUser}))
Expand Down
5 changes: 1 addition & 4 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 @@ -159,8 +157,8 @@ func (f *Frontend) LoadCore(emu string) {
meta := nanoarch.Metadata{
AutoGlContext: conf.AutoGlContext,
Hacks: conf.Hacks,
HasMultitap: conf.HasMultitap,
HasVFR: conf.VFR,
Hid: conf.Hid,
IsGlAllowed: conf.IsGlAllowed,
LibPath: conf.Lib,
Options: conf.Options,
Expand Down Expand Up @@ -309,7 +307,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
72 changes: 31 additions & 41 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 @@ -96,10 +92,10 @@ type Metadata struct {
IsGlAllowed bool
UsesLibCo bool
AutoGlContext bool
HasMultitap bool
HasVFR bool
Options map[string]string
Hacks []string
Hid map[int][]int
CoreAspectRatio bool
}

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.Hid {
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 @@ -775,23 +758,30 @@ 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 {
if Nan0.log.GetLevel() > logger.DebugLevel {
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
6 changes: 0 additions & 6 deletions pkg/worker/coordinator.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,6 @@ func (c *coordinator) HandleRequests(w *Worker) chan struct{} {
} else {
out = c.HandleChangePlayer(*dat, w)
}
case api.ToggleMultitap:
if dat := api.Unwrap[api.ToggleMultitapRequest[com.Uid]](x.Payload); dat == nil {
err, out = api.ErrMalformed, api.EmptyPacket
} else {
c.HandleToggleMultitap(*dat, w)
}
case api.RecordGame:
if dat := api.Unwrap[api.RecordGameRequest[com.Uid]](x.Payload); dat == nil {
err, out = api.ErrMalformed, api.EmptyPacket
Expand Down
9 changes: 0 additions & 9 deletions pkg/worker/coordinatorhandlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,15 +234,6 @@ func (c *coordinator) HandleChangePlayer(rq api.ChangePlayerRequest[com.Uid], w
return api.Out{Payload: rq.Index}
}

func (c *coordinator) HandleToggleMultitap(rq api.ToggleMultitapRequest[com.Uid], w *Worker) api.Out {
r := w.router.FindRoom(rq.Rid)
if r == nil {
return api.ErrPacket
}
room.WithEmulator(r.App()).ToggleMultitap()
return api.OkPacket
}

func (c *coordinator) HandleRecordGame(rq api.RecordGameRequest[com.Uid], w *Worker) api.Out {
if !w.conf.Recording.Enabled {
return api.ErrPacket
Expand Down
4 changes: 2 additions & 2 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ <h1>Options</h1>
<script src="js/event/event.js?v=5"></script>
<script src="js/input/keys.js?v=3"></script>
<script src="js/settings/opts.js?v=2"></script>
<script src="js/settings/settings.js?v=4"></script>
<script src="js/settings/settings.js?v=5"></script>
<script src="js/env.js?v=5"></script>
<script src="js/input/input.js?v=3"></script>
<script src="js/gameList.js?v=3"></script>
Expand All @@ -136,7 +136,7 @@ <h1>Options</h1>
<script src="js/api/api.js?v=3"></script>
<script src="js/workerManager.js?v=1"></script>
<script src="js/stats/stats.js?v=1"></script>
<script src="js/controller.js?v=9"></script>
<script src="js/controller.js?v=10"></script>
<script src="js/input/keyboard.js?v=6"></script>
<script src="js/input/touch.js?v=3"></script>
<script src="js/input/joystick.js?v=3"></script>
Expand Down
2 changes: 0 additions & 2 deletions web/js/api/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const api = (() => {
GAME_SAVE: 106,
GAME_LOAD: 107,
GAME_SET_PLAYER_INDEX: 108,
GAME_TOGGLE_MULTITAP: 109,
GAME_RECORDING: 110,
GET_WORKER_LIST: 111,
GAME_ERROR_NO_FREE_SLOTS: 112,
Expand Down Expand Up @@ -58,7 +57,6 @@ const api = (() => {
record: record,
record_user: recordUser,
}),
toggleMultitap: () => packet(endpoints.GAME_TOGGLE_MULTITAP),
toggleRecording: (active = false, userName = '') =>
packet(endpoints.GAME_RECORDING, {
active: active,
Expand Down
3 changes: 0 additions & 3 deletions web/js/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,9 +393,6 @@
case KEY.PAD4:
updatePlayerIndex(3);
break;
case KEY.MULTITAP:
api.game.toggleMultitap();
break;
case KEY.QUIT:
input.poll.disable();
api.game.quit(room.getId());
Expand Down
1 change: 0 additions & 1 deletion web/js/input/keyboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ const keyboard = (() => {
KeyH: KEY.HELP,
Backslash: KEY.STATS,
Digit9: KEY.SETTINGS,
KeyM: KEY.MULTITAP,
KeyT: KEY.DTOGGLE
});

Expand Down
1 change: 0 additions & 1 deletion web/js/input/keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ const KEY = (() => {
R2: 'r2',
L3: 'l3',
R3: 'r3',
MULTITAP: 'multitap',
REC: 'rec',
}
})();
2 changes: 1 addition & 1 deletion web/js/settings/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/
const settings = (() => {
// internal structure version
const revision = 1.3;
const revision = 1.4;

// default settings
// keep them for revert to defaults option
Expand Down

0 comments on commit 91ace06

Please sign in to comment.