Commit Graph
882 Commits
Author SHA1 Message Date
LibretroAdmin 71cd6b5384 CLI: reject an invalid port in --command
A bad port fell back to the default, so the command went to the wrong
place and the send reported success.
2026-11-09 03:03:38 +01:00
LibretroAdmin 45c64b3c73 Fix clang warnings in the Windows build
The Win64 build's -Wshorten-64-to-32 narrowings are made explicit where
the value is bounded: socket()/accept() handles kept as RetroArch's int
descriptors, ai_addrlen passed as socklen_t, UDP and pipe lengths,
COM/D3D counts and shader frame counters, WASAPI/XAudio2 buffer sizes.
net_socket clamps each send/recv to Winsock's int count on _WIN32 and
lets the existing loops move the rest; the remote-input descriptors are
plain ints on every platform, as socket_init returns them.

xaudio29.h declares its interfaces through a macro that, in C, leaves
out DECLARE_INTERFACE's second typedef of the forward-declared names.
2026-11-08 20:17:34 +01:00
Davey Hughes 5756db8659 Input: Plays As, several controllers on one player, with hotkeys of their own (#19916)
* OpenXR: the controllers' slots are not read from the driver's view

A joypad driver that keeps its pads' state lends it to the snapshot
bridge through view(), and the bridge reads that in place of a copy
(48f9503c16). The headset's joypad wrapper is made from the driver it
wraps and kept that driver's view(), so under udev or winraw the
bridge read the controllers' slots from the driver's own pads there,
which are empty: the OpenXR controllers gave no input at all.

The wrapper of a driver that lends now has a view() of its own. It
lends none for the controllers' slots, which the bridge then copies
through the wrapper's button and axis calls, and the driver's for
every other slot.

openxr_input wraps a driver that lends all-zero views, and reads the
controllers' slot and one of the driver's the way the bridge does.

* Menu: bind capture goes to the bound user, not the controller slot

A bind capture knows the user whose binds it sets and the slot of the
controller it listens to, that user's Device Index. Three of its steps
used the user whose number is the slot instead: Bind All set every
bind after the first in that user's binds, each capture took the key
it replaced, whose mapping bit it clears, from that user's bind, and a
mouse bind listened to that user's mouse.

So with user 1's Device Index set by hand to the controller in slot 3,
Bind All for user 1 wrote all but its first bind over user 3's, which
were saved as such, and cleared the bit of a key user 3 still had
bound. A mouse bind for user 1 was never heard with one mouse, and
took user 3's click with a mouse for each user, though at run time
user 1's binds read user 1's mouse. All three now go to the bound
user.

frontend_overhead runs Bind All for user 1 on slot 3, and clicks each
user's mouse while binding user 1's B there.

* samples: hotkey_menu reads its sources as UTF-8

extract.py read input/input_driver.c in the locale's encoding, cp1252
for a native Windows Python, which fails on that file's UTF-8, so the
harness did not build there without PYTHONUTF8=1. It reads and writes
UTF-8 now; on Linux its output is unchanged.

* Input: a core port's rumble reaches every user mapped to it

A core port's rumble now goes to every user mapped to that port, the
users its input is read from, rather than to the user with the port's
number. Its motor answers true when any of theirs does; a port no user
is mapped to rumbles nothing and answers false. With every user on its
own port nothing changes.

* Input: Tap Delay holds Hotkey Enable back from its first frame

The hotkey pass reads the controllers as the last poll left them, and
the frame's own poll comes after it, under early, normal and late
polling alike. So with Hotkey Enable Tap Delay on, each press of the
Enable button reached the core for one frame before the tap saw it
and began holding it back.

While the tap is idle, a core read that finds the Enable's game button
down now asks whether the Enable is held in the frame's poll, and if
so holds the button back. The same button from another controller or
key, with the Enable up, is still the game's. Other reads cost one
test, and with Tap Delay at 0 nothing changes.

* Input: users sharing a core port drive the menu and fire profile hotkeys

With the menu up, user 1 and every user mapped to its core port drive
it. The other users on the hotkey user's core port fire hotkeys from
their own controllers in the one hotkey pass; with Hotkeys Follow
Player 1 those are core port 1's other users. With user 1 on None, the
users on core port 1 do both, and user 1 keeps them. With every user
on its own port nothing changes.

A sharing controller fires the hotkeys its profile binds, never the
hand-set ones, which stay the hotkey user's pad's: a hand-set button
names a button of one controller, and on a pad of another model it is
some other button. Its Hotkey Enable is its profile's too. While the
hotkey user has a Hotkey Enable, a sharer's hotkeys need the sharer's
own or the keyboard's, and a sharer holding its own opens only its own
hotkeys, not the hotkey user's; held on any of them, it holds game
input back. With none for the hotkey user, sharers fire freely. The
table of a sharer's profile hotkeys is made when a bind or its profile
changes, not each frame.

With Hotkey Enable Tap Delay each controller taps on its own: a tap
holds back and replays that controller's own Enable button. A hotkey
swallows the tap of the controller that fired it, and the keyboard's
tap when the keyboard's Enable opened it. Any controller's Enable held
short of the delay holds game input back, another controller's Enable
held past it included. Enable presses released while the delay is 0
are not played once it is turned on, user 1's included.

* Input: Plays As, a controller's own player, places it on connect

Sixteen entries, input_plays_as_device_N and input_plays_as_player_N,
name controllers as a reservation does, vid:pid then name, each with a
player (0 Auto). When they are read, each connected controller, lowest
slot first, takes the first entry naming it that no other has taken; a
0000:0000 vid:pid matches by the name after it, not every controller
without one. Giving a controller a player makes an entry for it,
refused when all sixteen are in use; set back to Auto, its entry is
freed.

The entries are read with the binds and written with the input
entries, a free entry's keys taken out. Two of a model are told apart
only by order, so a model's entries are written at their own places
in the order a start hands them out: lowest slot first, a gone one's
in its place, and one on Auto ahead of another of its model as an
Auto entry of its own. Once the main configuration is saved, the
entries in memory are laid out the same way, so reading it again in
the session gives each its own too. Saving an override puts them back
after its comparison load, as it does the binds. The reservations'
vid:pid parse is shared with them, unchanged.

On connect, a controller set to player N takes port N. Whoever else is
there moves to the first free port, unless it also plays as N, by its
own player or, on Auto, by N's reservation naming it: then the
newcomer shares, from the highest free port below Max Users. An Auto
controller that N's reservation names shares the same way when port N
holds a controller set to N. An explicit player beats a reservation
naming the controller. With any reservation set, Auto controllers are
otherwise left to the reservations; with none, and a player set by an
entry, they take the first free port. With no player set nothing
changes.

The moves are for this run only, through input_driver_exchange_ports(),
now in every build: the configuration keeps the ports the user set. A
reservation's own placement made while the ports are in a runtime
layout is runtime as well, so it is not saved either; reservations
place again at every connect.

A joypad driver restart lets go of every claim, since its pads can come
back in other slots with no disconnect: a pad reported again where it
is plugged in takes back its entry, the rest take theirs in slot order.
While a player is in effect, the registry's restore of the ports is
left to placement. A config reload keeps each controller's entry while
it still names it, so two of a model keep theirs. The entries count
among the settings whose change starts the joypad driver again at a
video restart, and so does a config reload that undid runtime moves.

* Input: a controller playing as another's player shares its core port

A user whose controller plays as another user's player feeds that
user's core port through the port map: that user's Mapped Port, from
remap files or identity. Its own Mapped Port is never changed or
saved. Everything that reads a user's core port reads this effective
port, and the Quick Menu's Mapped Port row reads "Shares Player 1 (core
port 1)" for a sharer.

An Auto controller placed to share with the player a reservation names
keeps sharing while it stays connected, also once that player's
controller leaves. The poll rebuilds the map when who shares changes,
and the core is told its controllers again between frames, not while
netplay or a replay owns the ports. Under First Press a press on any
of a player's controllers gives that player's user its port.

* Menu: a controller's player in Input > Controllers and Port N Controls

Settings > Input > Controllers, after RetroPad Binds, has a row per
connected controller in port order, then those past Max Users, then
the Plays As entries of controllers that are gone. A row's value is
the controller's player or, on Auto with a port, "Auto (1)", the player
Auto gives it. The sublabel names the player first and a controller
port only by its page name: "Player 1", "Player 1, shared · binds in
Port 3 Controls" for a controller sharing a player from another port,
"Not playing: more controllers than Max Users", or "Not connected".

On a Controllers row, Left and Right step through Auto, 1 to Max Users;
OK opens them as a list. RetroPad Binds > Port N Controls starts with a
row for each connected controller playing as the player port N feeds,
the same setting, then Add Controller: the other connected controllers
with a port, each with the player it plays as now, where a pick moves
that controller to this player. One controller on several players stays
Device Index's.

A change to a connected controller marks the configuration changed and
places it again as its connect did, with any controller it pushes off a
port, and no driver restarted. The entry point is in task_autodetect.c,
beside the reservation placement it reuses. A controller whose change
left no player in effect goes to the first free port. While Plays As is
in use, a change of Max Users places again the controllers left with no
port and those whose player comes within it. With every entry taken a
new one is refused with a notice.

A controller set to a player above Max Users reads "Above Max Users:
plays as Auto (Player 2)". On Port N Controls a row's sublabel reads
"This port" or "Shared · binds in Port 3 Controls". Device Index's note
for a controller another port reads becomes "used by Port 2".

After Add Controller, Port N Controls links to Port M Controls for each
controller playing as player N from port M, such as "OpenXR Controllers
(Port 4 Controls)": that controller's binds are port M's. Quick Menu >
Controls > Port N Controls starts with the same links to the pages that
hold its remaps. Both lists and the Quick Menu's Port N Controls are
made again on hotplug, and Port N Controls after a Device Index change,
with the cursor kept on its row.

The menu lanes are in stereo_headset_test, which already links the
shipping menu objects and runs in CI's OpenXR jobs; a build without
OpenXR skips it.

* Input: a controller's own hotkey buttons

A Plays As entry keeps a button and an axis of its own for any hotkey,
input_plays_as_<hotkey>_btn_N and _axis_N, written only where set; an
entry that has one is kept on Auto. It counts neither as a player nor
among the settings whose change starts the joypad driver again. A
sharer reads its own first, then its profile's, never the set's; the
hotkey port's own controller reads its own, then the set's, then its
profile's. Button and axis go each on their own.

The bind values, "8", "h0up", "+1", are read and written by helpers in
input_driver.c that the configuration's own binds now use too.

* Input: Hotkey Enable pairs with the controller it is on

A sharing controller's hotkeys go by its own Hotkey Enable, its bind or
its profile's, whatever the hotkey user has: one with an Enable fires
while it holds it or the keyboard's, one with none fires freely. With
Hotkey Device Type Merge on and a keyboard Hotkey Enable bound, a
sharer with none waits for the keyboard's, as master makes user 1's pad
wait. Holding a sharer's Enable holds game input back as any Hotkey
Enable does, and opens only that sharer's hotkeys.

Keyboard and mouse hotkeys keep master's Hotkey Enable rule whatever
sharers are connected: a sharer's Enable does not open them. Nor does
it open an input_combo_N entry with a key; an entry of RetroPad buttons
alone opens with it, as those buttons are every port's. A sharer with
an Enable of its own needs it for the menu toggle too, and whether a
pending menu tap is kept is read from the sharer's own binds, not the
set's.

The hotkey user's pad Enable plays no part in a sharer's hotkeys: each
controller's hotkeys are its own, and so is its Enable.

With Hotkey Enable Tap Delay, the hotkey of a sharer with no Enable of
its own leaves the keyboard's tap alone, as the keyboard's Enable did
not open it. Under Hotkey Device Type Merge, where such a sharer waits
for the keyboard's Enable, its hotkey swallows the keyboard's tap.

* Menu: hotkeys bound from a sharing controller are its own

Binding a hotkey also listens to the controllers that fire the hotkeys,
those on the hotkey user's core port, and its prompt gains "Listening
to Player 1's controllers", for the player the hotkey user plays as. A
press on the keyboard, the mouse, user 1's pad or the hotkey user's
goes to the set as before; a sharing controller's press is kept as that
controller's own, with an entry made for it at its player, refused with
the full notice when none is free. A press from a controller that has
its own bind for the hotkey replaces that bind, user 1's pad included.

Any bind, RetroPad binds too, also hears the port's own controller when
it is in a slot at or past Max Users, where Plays As placement can put
it, so that port's binds can be set from it.

A hotkey's row shows each connected controller's own after the set's,
"Select · OpenXR Controllers: L3", and clearing it clears theirs too.

* OpenXR: the headset shares player 1 on Auto and moves no one

For the headset's controllers, Combined or each hand in Separate, Auto
means sharing player 1, so a VR player's pad and headset both drive
player 1 with no setup. They take the highest free slot and the highest
free port below Max Users, so pads keep the low ones, and share their
player whether anyone is on its port or not, moving no one; a pad
turned on later lands on port 1 and shares with them. Set to a number,
they do the same with that player. Every other controller's Auto takes
the first free port.

A reservation naming the headset beats its Auto, as for pads, and a
number beats a reservation. A pad set to the player whose port a
headset on Auto holds takes that port, and the headset moves to the
highest free port, or gets none. Any change of Max Users places the
headset again at once, on the highest free port. Wherever Plays As asks
whether a player is in effect, a connected headset on Auto counts as
player 1, as one set to 1 does.

OpenXR looks up each controller's player only when the devices or the
entries change, and places a controller again when it starts or stops
playing as a player, or when a slot below Max Users frees up for one
placed past it. A player's controller that a pad pushed out of its slot
is placed by its player rather than taking the port with it. A headset
entry names the controller by the name OpenXR looks it up by.

The laser answers the core once, for the core port the headset's
controllers play on, so it no longer mixes with another user's mouse;
in Separate each hand's player gets it, the pointing hand aiming and
firing and the other reading offscreen. A headset controller with no
player drives no pointer, and the Monado e2e cases follow the new
placement.

mapped_port_test is built a second time with OpenXR, as
mapped_port_openxr_test, and CI runs both.

* Menu: OpenXR Controller Layout under Input > Controllers

Combined or Separate is how the headset's controllers appear, so it
moves from the headset's video rows to the bottom of
Settings > Input > Controllers, named for what it sets. The key and its
values are unchanged. Switching it reconnects the headset's
controllers, and the cursor stays on the row.

* Input: Plays As in core, content directory and game overrides

Save Override now saves the controllers' own players and hotkeys. Each
controller whose player or own hotkeys differ from the main
configuration's (with --appendconfig) has its whole entry written, all
of its entries for two of a model, in the order a start hands them
out, one on Auto ahead of another of its model as an Auto entry; one
the main configuration gives an entry and the session none is written
as Auto. An entry the same as the main configuration's is not written,
so a controller set back drops out of the override.

An override's entries are read on their own and go over the ones read
before by controller, as entries are matched to controllers (vid:pid,
else name), not by number: the last file naming a controller gives it
its whole entry, in the place of the one it had, two of a model taking
a file's entries in order, an Auto one included, the connected ones
lowest slot first; a controller no override names keeps the main
configuration's. Auto takes one back to Auto, and a controller new to
the entries takes a free one, if any of the sixteen is. Until now the
files' keys merged one by one, so an entry could pair one file's
controller with another's player, and no override could take an entry
back to Auto.

Loading or unloading an override from the menu, or loading the active
ones again, places the controllers again at once, as a Player change
does: those whose player changed, or all of them where the reload read
the configured ports back over the moves. Content load and close leave
it to the joypad driver's restart, as before.

mapped_port_test covers the merge, what is saved and the placing
again; video_screens_test saves, unloads and loads a game override
with a Plays As change.

* Input: a controller keeps its Plays As entry while its twin is unplugged

Two controllers of one model share the entries that name them, each
taking the first one no other connected controller holds. With one of
the two unplugged, anything that made the other take an entry again
could hand it the unplugged one's: a Player or Max Users change,
loading or unloading an override, any re-read of the main config, a
joypad driver restart, and Online Updater > Update Controller
Profiles, which reports every connected controller again. The
controller left then played the other one's player, and that one came
back to its twin's.

An unplugged controller's entry now stays its own for the rest of the
session, and only a connect takes it: that controller coming back, or
another of its model. A reload keeps it while the entry in its place
is still of its model, and an override's entries pair with it as if
the unplugged controller still held it from its slot. So an override
loaded with a twin unplugged gives the other twin the entry it adds
and leaves the unplugged one its own. Save Configuration and Save
Override count it as that controller's too, so what they write pairs
the same way when it is loaded again.

Placed again for a Player or Max Users change, or by Update Controller
Profiles, a controller keeps the entry it holds, or none. Reported
again by its driver while still connected, it keeps the one it holds.
A reload is judged as a whole once its last override is read: a
controller takes an entry it did not hold before only where the
reload added it for its model, so one an override newly names still
gets that entry.

A joypad driver restart still lets every claim go and hands the
entries out again in the order the controllers are reported, as a
start does, but its reports leave an unplugged controller's entry
alone. A driver's report queued behind a profile update's is not
dropped as a repeat of it, since the update takes no entry, and the
update it supersedes shows no toast, so one shows, as before.

* OpenXR e2e: FRAME_BINDS catches up with the grip poses already bound

Every profile binds pointer/grip to both hands' grip poses, and
input-frame-profile matches FRAME_BINDS exactly, so the table now lists
the poses as they are bound.

* Menu: OpenXR touch settings on Input > Controllers

Touch Hands, Touch Button and Touch Passthrough are added for the hands
that touch a screen they point at, the button that touches, and whether
that button's other binds fire too, with Touch Hands None to turn touch
off for the headset's controllers; a later commit puts them to use.
They follow OpenXR Controller Layout, Laser Pointer and Laser Beam,
which move here from Stereo 3D & Screens, at the end of Settings >
Input > Controllers, shown only for a video driver with a headset path.
A hotplug keeps the cursor on any of these rows, and overrides save
them per game.

* OpenXR: the headset's menu button is button 16, bound to Menu Toggle

The menu action of each profile presses button 16 of the pad, the left
hand's in Separate, instead of being a hotkey of its own, and the
built-in profiles bind Menu Toggle to it and label it "Menu", as pad
profiles name their Guide or Home button. It opens the menu from the
same button as before and can be bound elsewhere like a pad's Guide
button. Touch's one menu button presses button 16 once held a second.
In Edit Screens the menu button always opens the menu, whatever is
bound and whether or not Hotkey Enable is held.

Sharing player 1, the button goes by Hotkey Enable as a sharer's Guide
does where its profile has none: it opens the menu whether or not
player 1's Hotkey Enable is held, and the enabler alone opens nothing.
mapped_port_test covers both.

With the menu open, a press of the controllers' buttons hides the
laser's dots until a trigger is pulled, so hand drift cannot undo their
navigation. The menu button navigates nothing, and with no content Menu
Toggle leaves the menu open, so its press does not count.

* OpenXR: the Touch Button's buttons as pointer actions of each hand

Grip, stick click, bumper and A to Y each have an action per hand in the
laser's action set, bound from a table per profile in the same
suggestion as the pad's rows, so either controller layout can read them;
the trigger is the pad's own. The Frame's exact binding list in the e2e
gains them.

A runtime that refuses a suggestion loses its palm first, then these
rows, then edit mode's, a smaller loss tried before a larger, and keeps
the rest of the profile. INPUT_OPENXR_MAX_BINDS goes from 48 to 64 to
hold these rows with edit mode's (Index 52, Touch 51, the Frame 58); a
profile whose pad and Touch Button rows pass it is not suggested at
all, and the log names it.

* OpenXR: touch with the chosen button and hands

The Touch Button, read from its own action or, for the trigger, from the
pad's, touches a live screen the hand points at; the trigger still
clicks the menu, and is L2 or R2 on a screen once it is not the Touch
Button. A hand touches only if Touch Hands has it and its controller has
the button, as read from each hand's interaction profile; only such
hands aim at the screens, become the pointer or show a dot there, and
with none the screens stay the mouse's. The others aim at the menu
alone, which video_xr_pick() now takes as a flag. Touch Passthrough lets
the trigger's L2 or R2 through its touch.

A hand takes a changed setting at its next press. A button held through
a change of Touch Button, Touch Hands or the hand's controller profile
is no press: the trigger goes by its own last read, and any other button
counts as held until it is let go.

* OpenXR: a touch holds back its button's other binds

OpenXR gives an input to every action bound to it, so a Touch Button
other than the trigger also reaches the pad. While it touches, or a
press of it would, the pad's buttons bound to the same input of that
hand in the profile's rows are let go, in Combined and in Separate: the
Index grip's L or R, the Frame right stick's menu button. Touch
Passthrough leaves them pressed.

With the menu open, a press of the controllers takes the laser from the
dots until a trigger pull. Like the triggers' L2 and R2, what a Touch
Button's input feeds does not count as such a press, with Touch
Passthrough on or off, so the button can still touch a live screen
there. Another button on the same hand still counts.

* Menu: Touch Button offers the buttons the controllers have

The dropdown lists the buttons either hand's controller has, as its
interaction profile names them, and the one chosen; left and right step
through the same, skipping the rest. A choice the controllers lack, as
from an override made on another controller, reads "(not on this
controller)". With no controller known every button is offered. The poll
publishes those buttons through an atomic, since with threaded video XMB
draws the value on the video thread.

* docs: OpenXR controllers, touching and the menu button

Where the headset's input rows are now, what Touch Hands, Touch Button
and Touch Passthrough do, None included, and that the menu button is a
Menu Toggle bind that Hotkey Enable applies to as to a pad's Guide; on
Touch controllers in Combined it is Start, and button 16 once held a
second. Laser Pointer's description says the Touch Button touches a
screen and the trigger clicks the menu, true with Touch Passthrough on
too.
2026-11-08 16:16:19 +01:00
LibretroAdmin ca4be58216 retroarch: a save turned away gives back the slot it advanced
With Increment Save State Index on, Save State moves the slot up
before the save is pushed. A save the queue turned away - the saves
and loads held behind the one in flight already at their bound -
wrote nothing there, yet the slot stayed advanced, so the next Load
State named a slot with no file in it. command_event_main_state()
also called that save a success, and pruned old saves for it.

It returns false for a save turned away now, and prunes nothing for
it, and the slot goes back to where it was. A save that is pushed and
fails later on (a full disk) still leaves the slot advanced: later
saves may have moved it again by then.

The state-hold-full lane gains the case: with the hold full and
Increment Save State Index on, Save State must be refused and leave
the slot where it was. It fails on the old code.
2026-11-02 17:02:53 +01:00
LibretroAdmin 3d525c2723 command: a Save or Load State turned away says so
A save or load pressed while another runs is held behind it, up to
four; past that bound it is turned away. Save State then logged
queued=NO and Load State returned false, and neither put anything on
screen: the hotkey did nothing, silently.

Both say so now - "Failed to save state to <file>." and "Failed to
load state." - through the message command_event_main_state() already
shows for its other outcomes.

A state-hold-full lane in the content_load harness fires six Load
States in the frame after a Save State: some are held, one past the
bound must say so on screen, and the held ones still finish. It fails
on the old code.
2026-11-02 16:51:58 +01:00
sonninnos 6240401af4 Save task logging capitalization cleanup 2026-10-31 00:20:23 +02:00
Rob Loach ef41fe39e7 MCP: review fixes for frames, RAM, hardcore 2026-10-28 14:56:51 +01:00
Rob Loach ef3c8db6b3 MCP: no tools while achievements are hardcore 2026-10-28 14:56:51 +01:00
Rob Loach 35d113d308 MCP: memory tools read cores without a map 2026-10-28 14:56:51 +01:00
Rob Loach 495120bf76 MCP: GET_FRAME shares SCREENSHOT's reply 2026-10-28 14:56:51 +01:00
Rob Loach 30a01d0696 MCP: GET_FRAME, the screen without a file 2026-10-28 14:56:51 +01:00
Rob Loach 22ee469999 MCP: schema check, readable MENU, smaller tool list (#19878)
* MCP: schema check on all tools, readable MENU

* MCP: MENU marks choices, answers when closed

* MCP: MENU types, shows dialogs; drop menu option

* MCP: fold INPUT, OPTION, CORE_OPTION; trim list

* MCP: MENU path, more, tab names; clearer errors

* MCP: menu title as a text line; trim the list

* MCP: trim STEPS' descriptions

* MCP: INPUT says the core gets what is held

* MCP: MENU review fixes
2026-10-24 12:59:56 +02:00
Codex 01a88010f0 Defer video reinit until native recording resources retire
Finish native GPU recording before resetting its video device. Coalesce pending reinit flags and apply them once through the existing main-thread recorder poll after all native users retire. Drop superseded requests during content or quit teardown, with no waits, locks, frame allocations or new runloop call.
2026-10-23 14:20:40 +02:00
LibretroAdmin 0c7b7a0c6a Input: what a command holds is given to the core exactly
R4 asked whether input held from a command (SET_INPUT, a RUN_FRAMES
press) should go through SOCD cleaning and D-Pad rotation, and what a
hold means under netplay.

It stays exact: SOCD cleaning and rotation are for a person's hands on
a pad, and a script says what the game is to see - a TAS-style left
and right together, or up with the picture turned, means just that.
Under netplay a hold is the player's on this machine: netplay samples
the local device through the same reads, so what is held is sent like
a press and every peer runs the same frames.

That is now written where it is asked - SET_INPUT's description, which
the command list and the MCP tools show, and the handler - and the
frontend harness holds left and right with SOCD set to neutral and the
D-Pad turned, and checks the core is given both, unturned.
2026-10-20 20:03:20 +02:00
LibretroAdmin c1f5c0e17d audio: keep the device open across AV info that leaves video alone
A SET_SYSTEM_AV_INFO that keeps video - a new frame rate, base size or
sample rate - goes to the running audio driver in place: the rate is
recomputed with the frame rate's timing, and the threaded pipeline's
ring and slice, sized in video frames, follow a frame rate change
under the consumer's park. A full reinit remains the fallback for what
the in-place path cannot carry.

A driver reinit that included audio puts back what the audio reinit
restores after its teardown directly, instead of a second audio
reinit for the menu sounds; a reinit that did not run asks for none.
2026-10-19 22:40:57 +02:00
Rob LoachandLibretroAdmin be9f97d2cf MCP: review fixes, checkpoints, queued STEPS (#19861)
* MCP: cold paused, held input, stacks, arg types

* MCP: checkpoints, prompt, completions, records

* MCP: completion errors, sized prompt, CI shaders

* MCP: rollback via loader, STOP n, completion fixes

* MCP: ask input_driver whether a replay records

* mcp: Clarify steps, and ensure MCP standards

* MCP: queued STEPS, checkpoint step, 32 steps

* MCP: queued STEPS say how long they sat idle

* MCP: queue ends on load/reset; simplify

* MCP docs: queue stops, idle, list caching

* MCP: shorter STEPS wording

* MCP: notice when port taken, --command, taps

* MCP: drop the play_game prompt and completions

* MCP: queue, tap and reset fixes from a review

* MCP: queue outcomes, hex values, field fixes

* command: --command takes an empty host or port as the default

"CMD;;PORT" and "CMD;HOST;" fill in the default host or port, so an
empty segment after a ";" counts as a host or port field again. A STEPS
list still keeps its own ";" steps, which carry a space, a "|" or are
"checkpoint".

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-19 02:12:59 +02:00
Rob LoachandLibretroAdmin 7f86c7f53c MCP: STEPS, PRESS until, and fixes after WATCH (#19850)
* MCP: answer once when reply_to answered already

* Commands: refuse slots past INT_MAX

* MCP: follow-ups to WATCH and the folded tools

* Commands: take CRLF lines; keep long family names

* MCP: fixes from a review of the server

* stdio_e2e: tool list size after the combined fixes

* MCP: lone CR, over-long stdio lines, slot cap

* MCP: stdio keeps its agreed revision

* docs: MCP server doc matches the code again

* Leave CHANGES.md out of this change

* MCP: STEPS, PRESS until, 16-bit waits, 16 watches

* MCP: 4 kB off tools/list; CORE; LOAD_CONTENT paused

* MCP: refuse unknown params; taps; review fixes

* MCP: GET_LOG, READ, RUN_UNTIL say what they mean

* MCP: LOAD_CONTENT's frame back under budget; STEPS counts its taps

LOAD_CONTENT copies the content path, to the heap, only where a pause
setting follows it, which keeps its stack frame where it was. STEPS
counts the frame let go between two presses of a button against the
frames one request runs, so a list it accepts can finish in them.

Lane: the taps case in samples/network/command_frames.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-17 03:14:30 +02:00
Rob LoachandLibretroAdmin 75f278e286 mcp: WATCH a step and report what follows (#19834)
* WATCH: a step and the look that follows it, in one request

A field run's steps were a press and then a read for each thing it
needed to know: where the player was, where the enemies were, which
mode the game was in. Four requests to take one step, and the reads
were the bulk of a hundred calls for one level.

WATCH <label> <address> watches a byte, and RUN_FRAMES and PRESS carry
every watch along with their own answer, as a line of label, address
and value. A step needs no reads after it.

The lines land where the rows machinery already looks: both commands
answer with a line that opens with their own name, which the row reader
skips as a header, so the watches are the rows and the two commands
declare the shape they now answer in. A failure stays its reason and
carries no reading.

Each watch is resolved when it is set, as RUN_UNTIL resolves the byte
it waits on, so an address nothing maps is refused there rather than
read later. Content closing drops them all, which is what invalidates
where they point. Eight at once; a label already watched keeps its
place in the list when its address changes, so rows stay where a client
saw them.

RUN_UNTIL is left alone. It answers with the byte it waited on in three
columns of its own, and giving it watch rows would collide with those -
its format landed days ago, and changing it is a separate decision.

Also the test PRESS never had: it lets go only of what it pressed
itself, so a button held with SET_INPUT survives a press of the same
button. That was fixed by eye on the branch before #19815 landed and
has been unguarded since; GET_INPUT makes it three lines.

tools/list is 24715 bytes at 49 tools, from 22944 at 48. A tool's own
rows schema is about 450 bytes and this adds three of them, which is
most of the difference; the budget in the end-to-end test records what
each phase has cost it.

* WATCH: keep RUN_UNTIL rows, range-check addresses

* WATCH takes pairs; RUN_UNTIL carries watches

* Memory commands: check bytes and addresses first

* MCP: MENU, DISK and REINIT, three tools for 12

* SET_OPTION: drop streaming's unused event

* stdio_e2e: cap each tool, lower the total

* MCP: STATE and REPLAY tools; files are commands

* Review fixes: bounds, ERANGE, live watch lookups

* STATE: answer as STATE, not the command it runs

* MCP: family tools answer under their own names

* RUN_UNTIL: look its byte up every frame, as WATCH does

RUN_UNTIL keeps the system address it waits on and reads the byte
through the memory map each frame, so a core that moves its memory is
followed, and one that stops mapping the address ends the wait with an
error. VALUE carries the byte as last read. The step reply is clamped
to its buffer, and the deferred reply's family sits ahead of its
generation counter.

command_frames covers a remapped and an unmapped byte under RUN_UNTIL
and WATCH and the watches dropping when content closes; mcp_server
pins the structured RUN_UNTIL and step replies against the tables
command.h ships.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-16 00:27:30 +02:00
LibretroAdmin cc4de1186c RUN_UNTIL: wait nine seconds of the content's frames by default
With no frame count given, RUN_UNTIL waits about nine seconds of the
content's own frames, capped at 600, so its answer arrives inside a
structured interface's ten-second reply deadline at any frame rate:
540 frames at 60fps, 450 at 50fps. A request can still ask for up to
600. The command_frames lane checks the default against its core's
60fps.
2026-10-15 04:13:19 +02:00
LibretroAdmin a4a72b3c33 RUN_UNTIL: range-check the value and frames before narrowing
The value and frame count are compared as the unsigned long strtoul
returns, so an argument past 32 bits is refused instead of wrapping to
an accepted one, and spaces after the value no longer read as a frame
count of zero. The command_frames lane covers both, along with the <
and > conditions.

The LOAD_CORE comment moves back onto command_load_core, and the docs
say hardcore mode refuses SET_INPUT, not GET_INPUT.

Closes #19833
2026-10-15 03:52:22 +02:00
Rob Loach 297986dd43 GET_INPUT, and core paths checked before anything is unloaded
Three things probing master turned up, and a trim that pays for two of
them.

GET_INPUT [player] answers what a player holds without changing it.
There was no way to ask: SET_INPUT answers in that shape but has to set
something first, and RELEASE_INPUT lets go before it answers, so it
always reads none. A client could not ask what it was holding.

LOAD_CORE answered "The command failed." for a missing path and for a
library that is not a core alike - the generic fallback, because it
returned false without ever saying why. It says which now.

Neither LOAD_CORE nor LOAD_CONTENT unloads the running core for a path
that turns out not to be one. The library is probed first, with
libretro_get_system_info(), which is what core scanning already uses
and which keeps what it finds, so asking twice is free. Checking the
paths stopped a typo costing the game; a stale library or something
renamed still did.

tools/list pays for every tool's description on every session, so the
two new ones are paid for where the list repeated itself:
readOnlyHint is false by default and 37 tools said so anyway, which was
770 bytes, and the memory map named hexadecimal in four columns that
its own description already covers. 48 tools in 22861 bytes, from 46 in
21449.

RUN_UNTIL's conditions move into command.c: a table in the header is a
copy in every translation unit that includes it, and nothing outside
command.c compares them.
2026-10-15 03:52:18 +02:00
Rob Loach 84bc936dc0 RUN_UNTIL: wait on a byte, in one request
A field run cleared one level in over a hundred calls and spent most of
them waiting: run a few frames, read a byte, go round again. Until a
Goomba was twenty pixels away, until Mario landed, until a transition
finished.

RUN_UNTIL <address> <condition> <value> [max frames] runs the content
until the byte at a system address meets the condition and answers with
whether it was met, how many frames ran, and what the byte holds. The
loop runs inside the frontend, where a frame costs nothing, rather than
across a connection per iteration.

It is RUN_FRAMES' job with a predicate instead of a counter: the same
frame_work flag, the same deferred reply, the same one-at-a-time rule,
so nothing new blocks and there is no second state machine. Reading
core memory every frame from the runloop is what rcheevos_test() has
always done, on the same flag.

Two bounds keep it honest. An address nothing maps is refused before a
frame runs, rather than after a wait that could not have ended. The
frames are COMMAND_FRAMES_REPLY_MAX at most, and where they run out
first it answers "waiting" with how far it got, so the next request
carries on instead of the client being told it failed.

The harness core now maps a byte that follows its frame counter, so the
waiting itself is tested rather than only the refusals: already met
answers with no frames run, three frames away answers on the third and
not before, and the cap answers waiting.

RUN_UNTIL costs 1391 bytes of tools/list, which took it past the budget
the end-to-end test holds; the ceiling is raised deliberately, with the
cost recorded beside it.
2026-10-15 03:52:15 +02:00
Rob LoachandLibretroAdmin c2e5bce004 MCP: structured results, and LOAD_CONTENT checks before it unloads (#19815)
* LOAD_CONTENT: check paths before unloading

* mcp: results carry structuredContent

* LIST_CORES filters by content path

* Structured rows for core options and memory map

* MCP: do not advertise a cacheable tool list

* SET_INPUT holds several controls at once

* A row opening with MORE is a row, not a cursor

* MCP: fewer tools, and rows a client can rely on

structuredContent and outputSchema never reached a client over stdin
and stdout. The gate read the MCP-Protocol-Version header, or the
version the stateless revision sends with every request; stdio has
neither, so the revision agreed at initialize was parsed, used for the
handshake reply and then dropped. It now holds for the session, which
over stdio is the connection.

A command answers with structuredContent exactly when it declared an
outputSchema, so a client reads the schema it was handed rather than
probing each reply. SET_INPUT, RELEASE_INPUT and GET_LOG declare one
now.

The "MORE [next]" line was read by a literal in the server. The shape
of a reply names its own trailers instead - the lines that carry a
number about the reply rather than a row of it - so GET_LOG's NEXT and
SKIPPED are the same mechanism rather than two more special cases, and
a reply says nothing about being cut short where it cannot be. A reply
whose lines are prose declares no columns: GET_LOG's page runs to 60k,
and a row apiece would answer with it twice.

Thirty-four commands are no longer offered as tools. A hotkey that
steps a selection - the next state slot, the previous shader, the next
cheat - is a hotkey because a person presses it while watching the
screen. Where a command takes the value outright that one is the tool,
and stepping towards it is only a way to get the value wrong; where
nothing reports the selection either, there is nothing worth offering
yet. READ_CORE_RAM and WRITE_CORE_RAM go too: an achievement address
looks like READ_CORE_MEMORY's system address and is not one. The disc
and the menu are kept, because stepping them blind is all there is and
a disc swap is part of playing a game. All of them are still commands
on every line interface.

The tool list is 45 tools and 20k, from 79 and 23k, and the end-to-end
test holds it to a budget: every description is context a client pays
for before it does anything.

* PRESS: hold, run and let go in one request

A tap was four requests: SET_INPUT, RUN_FRAMES, SET_INPUT to release,
RUN_FRAMES again. Each is its own HTTP connection against four slots,
so a field test spent about 25 of them crossing a menu and about 10 on
one jump.

PRESS <player> <control>[+<control>...] <frames> holds the buttons,
runs that many frames and lets them go, answered once the last frame
has run. It is RUN_FRAMES' job with buttons held through it: the same
deferred reply, the same one-at-a-time rule, the same pause the runloop
applies to frame advance, so nothing new blocks and no second state
machine exists. Whatever ends the frames lets the buttons go, including
the error that ends them early.

Axes stay SET_INPUT's. They are held at a value rather than pressed,
and SET_INPUT with RUN_FRAMES is still the way to hold a control across
several steps.

SET_INPUT and PRESS read the same request - a player, controls joined
by "+", and a number - so that parse is one routine now, and both
resolve every control before either acts on any.

* CHANGES: the MCP and command work in this series

* GET_STATUS: say what it actually answers with

The description promised the content's CRC32 and the reply has never
carried one: it is the system and the content's file name, separated by
a comma.

HELP prints that text on the line interfaces and tools/list hands it to
every MCP client, where it is the prose a client reads to decide how to
take the reply apart. One told to expect three comma-separated fields
gets two.

Adding the CRC instead would be worse. Working it out means hashing the
content, which for a core using need_fullpath reads a whole disc image
from disk - task_movie.c already carries that note, having hit it - and
GET_STATUS is the command a client polls. It would also change the
reply for every client that reads the line today.

* command: descriptions that match the replies

tools/list hands a command's description to every MCP client, and HELP
prints it on the line interfaces: it is the prose a client reads to
decide how to take a reply apart. Five of them described a reply that
was not the one sent.

SET_INPUT and RELEASE_INPUT answer with three fields - the player, the
buttons held and the axes held - and the description named two. A
client splitting on what it was told got the player where it expected
the buttons. RELEASE_INPUT did not say it answered at all, and with no
player it lets go for everyone while answering for the first of them,
which is worth saying rather than leaving to be discovered.

GET_LOG can open with SKIPPED and a count where the lines asked for had
already been overwritten, which the description did not mention: a
client reads it as a log line.

READ_CORE_MEMORY and WRITE_CORE_MEMORY both answer - with the bytes,
and with how many were written - and neither said so.

* READ_CORE_MEMORY: format bytes without snprintf

Both memory reads wrote their reply with an snprintf per byte, to turn
one byte into two hexadecimal digits. At COMMAND_READ_NBYTES_MAX that
is 16384 calls, which costs 1.5ms on an aarch64 Pi - a tenth of a frame
spent formatting, on the main thread, in the command an assistant reads
memory with most. A nibble table is 0.02ms for the same bytes, 68 times
faster, and the output is identical for all 256 values of a byte in
both of the formats the two sites used.

The harness core now maps its memory, because nothing in the tree did:
READ_CORE_MEMORY, WRITE_CORE_MEMORY and GET_MEMORY_MAP had no test that
reached past "no memory map defined", so the formatting this changes
was uncovered. The test asserts the replies byte for byte, including
every digit of a byte, a read clamped at the end of its region, and one
outside every region. Breaking the table deliberately fails three of
them.

* PRESS: leave SET_INPUT's holds in place

PRESS lets go only of the buttons it pressed itself, so a button a
client already holds with SET_INPUT is still held once the press ends.

* command, mcp: trim comments and order new struct members

Drop the superseded LIST_CORES and reply-splitting comments, shorten
the new comments to what stays true, and order the new struct members
by alignment.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-14 18:59:27 +02:00
LibretroAdmin 1b86833a76 command: SET_INPUT and RELEASE_INPUT ask the input driver by name
They took the input state to write what a command holds, two more
takes than command.c is allowed. input_driver_command_hold(),
_release() and _held() own it now. input_state_grab_check passes
again.
2026-10-13 00:38:46 +02:00
libretroadmin ae32b07cfe command: GET_LOG, from a lock-free ring of the last lines logged
RARCH_LOG_V copies each formatted line into a ring of the last 256
lines once it is armed, which the MCP server's start or the first
GET_LOG does; unarmed, a log line costs one relaxed load more. Any
thread may log: a writer claims a line number, writes the slot as
atomic words between a busy mark and its stamp, and the reader keeps a
line only when the stamp matches before and after the copy, so a
reader never waits and never sees a torn line. GET_LOG answers the
lines since the one asked for, then NEXT; lines overwritten before they
were read are counted as SKIPPED. Desktop builds with the MCP server
only; samples/runloop/log_ring writes from four threads while reading,
under ASan and TSan.
2026-10-12 23:34:47 +02:00
libretroadmin 0a4aff89ef command: RUN_FRAMES
RUN_FRAMES pauses the content and runs it a given number of frames
through frame advance, answering once the last has run, so a client can
step the game and look between steps. While it runs a frame-work bit
makes each iteration a frame advance; after each core_run the command
counts the frame off, and closing the content answers it with an
error. With the bit clear the run loop does nothing new.
2026-10-12 23:34:45 +02:00
libretroadmin a808962621 command: SET_INPUT and RELEASE_INPUT
SET_INPUT holds a RetroPad button or stick axis for a player, read by
the core over the player's own controller through the input state
wrapper, until it is set again or RELEASE_INPUT lets it go; replays
record it like any other input. A single flag tells the wrapper there
is anything to add, so with nothing held a query costs one test.
Achievements hardcore mode refuses it.
2026-10-12 23:34:42 +02:00
libretroadmin d69939f054 command: GET_CORE_OPTION, SET_CORE_OPTION and GET_MEMORY_MAP
GET_CORE_OPTION lists the running core's options as key, value and
description, and with a key adds the values that option takes;
SET_CORE_OPTION sets one to one of those values, changing nothing when
it already has it. GET_MEMORY_MAP lists the core's memory map regions
with the addresses READ_CORE_MEMORY takes, their masks, kind, access,
byte order and address space, so a client can find memory without
guessing. All three are tools on the MCP server, described there.
2026-10-12 22:25:21 +02:00
Codex 8bf98af186 Resize windowed output without restarting unrelated drivers
Advertise live resizing separately from fullscreen transitions for Windows WGL, Vulkan and D3D10/11/12. Share it between menu and queued scale requests, with video/input-only recovery on unsupported or failed resizing.

Defer resizing during fullscreen, including CLI-forced fullscreen. Avoid exclusive Vulkan renegotiation when fullscreen state does not change. Cover menu, queued, deferred and failed resize requests in the setting-command regression.
2026-10-11 23:41:45 +02:00
64fcce996d Defer auto state load for cores requiring initialization (#19487)
* Defer auto state load for cores requiring initialization

* runloop: wait for deferred auto-state initialization

Keep the request pending until the primary core has run and reports a nonzero serialization size. Preserve one-shot loading without repeated state reads, reset execution tracking on lifecycle changes, and skip settings queries when no load is pending.

* tests: cover deferred auto-state readiness and cancellation

Drive the shipping runloop and command handler with a core that initializes over multiple frames. Check one-shot loading, idle overhead, cancellation, core reset, missing retro_run, paused frames, hardcore mode, and the unchanged non-quirk path. Add Linux sanitizer coverage and a Windows cross-build.

* runloop: avoid auto-state conflicts with master

Move deferred auto-state fields away from the lifecycle fields extended by master, and reset them after joining autosave workers. Keep the reset before core unloading so both the auto-state and audio teardown changes merge cleanly. Supply the new audio publication and between-frame polling dependencies in the regression harness.

---------

Co-authored-by: keycross <[email protected]>
Co-authored-by: Codex <[email protected]>
2026-10-10 21:15:37 +02:00
Rob LoachandCodex ce5c333180 MCP: Options instead of toggles, correct error handling (#19731)
* tests: cover MCP option state and speed changes

Exercise the shipping option handlers and runloop setters with counted driver events. Cover recorder conflicts, hardcore activity, rejected speed changes and repeated requests without extra work. Run the harness under Linux sanitizers and on Windows in the network CI workflow.

* mcp: SET_OPTION/GET_OPTION, real errors

* mcp: apply option changes to runtime state

Reject recording and streaming conflicts before initializing a recorder. Read hardcore activity rather than its configured preference. Apply speed targets synchronously through runloop setters, sharing the fast-forward side effects with hotkey handling and respecting runtime restrictions.

---------

Co-authored-by: Codex <[email protected]>
2026-10-10 20:09:23 +02:00
LibretroAdmin 681fdac478 Build without warnings on DOS, Emscripten and without Wayland
What a build without threads, BSV movies, networking, Wayland or the
XMB and Ozone menus does not use is not built there: the pipe's stereo
widening and frame bounds, the fallback font load states, the GL
texture lend slot, the softfilter core topology, the command reply
event, the History playlist settings, the netplay row's local and the
DRM context's Wayland device label. DJGPP's size_t is unsigned long,
so PRI_SIZET is "lu" there, and the SHA-1 digest words are printed as
unsigned.
2026-10-09 06:04:32 +02:00
LibretroAdmin 728cad4796 input: a hotkey held through a change of window is one press
F toggles fullscreen. Held a little longer than the change takes, it
toggled again, and again: under a test X server, where the change
takes some 50 ms, F held for 150 ms went to fullscreen and back, and
held for a second it toggled eight times. On a desktop the change is
slower and it shows as the occasional double toggle.

Two things made it. Going to fullscreen and back restyles the window
or replaces it, and either way it is without the focus for a moment;
the X11 input driver reads no keys without the focus, so the poll
after the change gives F as up and the one after that as down again -
a press. And nothing remembered the first press. Input waits to be
let go at such moments (INP_FLAG_WAIT_INPUT_RELEASE), and a hotkey
found down during the wait is passed over; but the wait ends when the
RetroPad's buttons are up, whatever the hotkeys are doing, and a
hotkey still down then was taken as newly pressed.

- The wait goes on while a hotkey is found down under it, as well as
  while the RetroPad's buttons are: everything is let go before input
  counts again, which is what the wait is for. Not for the hotkey
  enabler, which is held to reach the others. The mark is kept in the
  hold's own byte, which the wait is already armed from each frame.
- After a fullscreen toggle and after a video driver restart the wait
  is kept up until the window has been found focused on three checks
  running, or for half a second where it is not going to be
  (input_driver_hold_over_window_change()): long enough for the key to
  be read as down again. What the core reads is not touched - a
  direction held through the change is still held after it.

X11 under a test server, F held for 0.08, 0.15, 0.4 and 1.0 s: 1, 2,
3 and 8 toggles before; one each now. Pressed twice 0.7 s apart: two,
before and now. With Game Focus on F still does nothing.

The frame's input work with no controller, callgrind over 300 frames:
4193016 before and 4193016 now. Nothing is added to a frame on which
no hold is active: the two values the wait after a window change
needs are kept out of the input state, where they moved what the poll
clears every frame, and the mark needs no test of its own.

The frontend harness gains a lane for the hold itself: a key held
through the wait does not count, a press after it was let go does,
and the hotkey enabler is not held back.

By reading, this comes of the X11 driver's own way with the focus and
is older than the input driver being kept across restarts; an old
tree was not built to see it.
2026-10-07 10:08:58 +02:00
Rob Loach 62d5954fbc mcp: answer tools once their work is through
A tool whose work runs over later frames answers when that work is
through, with what came of it, or with its name, ERROR and the reason:
LOAD_CONTENT, START_CORE, CLOSE_CONTENT and UNLOAD_CORE once the load or
close ends; LOAD_STATE_SLOT and SAVE_STATE_SLOT once the state is
applied or written; PLAY_REPLAY_SLOT, RECORD_REPLAY, SEEK_REPLAY and the
replay checkpoint tools once the replay has started or the seek or
checkpoint has run; SET_SHADER once the preset has compiled; AI_SERVICE
with the service's text. A line-based interface keeps the answers it
had.

The waiting is one shape, task_notify_t (tasks/task_notify.h), held by
the work's owner: the content load, the save and load tasks, the movie
tasks, the deferred shader load and the translation service. State and
replay work a command starts runs from a main-thread task, outside the
core's own input poll. The command interfaces outlive a content load,
remade only when their settings change, and what arrives during a load
waits for it.

samples/tasks/content_load gains lanes for the state-slot, replay, seek and
content replies; samples/network/mcp_server follows.

Closes #19702
2026-10-05 11:33:27 +02:00
LibretroAdmin 88f238d21d menu: the capture of a bind asks the frontend for the controller
The capture of a bind read the joypad drivers itself: it took them
out of the input state, polled them, and read every button, axis and
hat of the controller, once as the capture started (where the axes
rest) and then every frame.

It asks the frontend, input_driver_capture_pad(), which does the same
reads in the same order and fills the menu's arrays. The menu holds no
joypad driver for it any more.

Two other places outside input/ called a joypad driver, and each has
a call that says what it wants:

- a driver restart polled the joypad and input drivers by hand, so as
  not to act on what was read before it: input_driver_poll_devices();
- saving a controller's profile from the menu looked the profile up
  again with the driver's name for the pad:
  input_driver_autoconfigure_pad().

With these, nothing outside input/ calls an input driver or a joypad
driver. tools/input_state_grab_check.py counts both and fails on
either.

Run in the Linux build with the scripted test controller: a bind
captured from a button (Button 20) and from an axis (Axis +2), and
from a key and a mouse button as before. The frontend harness passes.

menu/menu_setting.c fails the syntax gate before this change as after
it, on an include path the gate does not have.
2026-10-05 05:21:38 +02:00
LibretroAdmin 8cece8e07e input: the pointer is captured for named reasons
Mouse grab was one flag, INP_FLAG_GRAB_MOUSE_STATE, that about ten
places toggled or rewrote, each after looking at it, so what it ended
up as went by the order they ran in. Leaving exclusive fullscreen let
go of a grab game focus still wanted; game focus going off in a window
let go of one the user had asked for; a driver restart dropped the
user's grab by way of game focus being applied again.

Each of them now holds or releases its own reason: the user's toggle,
Automatic Mouse Grab, game focus, exclusive fullscreen, and any
fullscreen (which hides the cursor and grabs nothing). The pointer is
captured while any is held, and the flag is what that comes to,
written in one place. The reasons are kept above the drivers, so a
driver that restarts has them applied to it again and the special
cases that preserved the flag are gone.

What changes for a user: a grab toggled on outlasts game focus, a
fullscreen round trip and a driver restart; game focus keeps its grab
through a fullscreen round trip; game focus going off in borderless
fullscreen lets go. The toggle, pressed while captured, still lets go
of everything.

The frontend harness gains a lane, with an input driver that notes
what it is told, and the Wine smoke test a scenario with Automatic
Mouse Grab.
2026-10-04 10:47:07 +02:00
LibretroAdmin 71318ef1ec Fix build warnings on old GCC, uClibc and old wayland-scanner
Statics rely on implicit zero initialisation instead of {0}, and local
aggregates whose first member is an array get the matching braces, so
GCC 4.x -Wmissing-braces stays quiet. Locals the compiler cannot prove
written are initialised (rh265 keep[], rflac block header, query.c
lengths), loops older VRP misreads are reshaped, the unused SDL2
geometry forward declaration is gone, and task_queue no longer names a
variable main.

rzstd probes BMI2 with __get_cpuid_max/__cpuid_count, which every
cpuid.h the branch accepts provides. Hole punching needs uClibc-ng 1.0
with its Linux-specific API on uClibc. The sinc resampler's pop_options
does not warn on GCC 4.x MIPS. generate_wayland_protos.sh strips the
event type and deprecated-since attributes for scanners whose DTD
predates them, which ignore them anyway.
2026-10-04 04:23:50 +02:00
libretroadmin d9dfddab4b mcp: SCREENSHOT answers with the picture as well as its path
The MCP SCREENSHOT tool answers with the screenshot itself as image
content, PNG up to 4 MiB, followed by the file's path as text, so a
client on another machine, or without access to the screenshot
directory, can see what is on the screen. Past 4 MiB, or for another
format, it answers with the path alone as before.

The screenshot task reads the PNG it has just written back on its own
thread and base64-encodes it there, as the encoder streams rows into
the file and keeps no copy; the main thread only hands it on. A command
interface takes it through the new optional reply_image_to, which only
the MCP server sets; every other interface is sent the text.
2026-10-04 02:22:08 +02:00
libretroadmin c2b7cf0f9c mcp: a reply is an error only when it opens with its command and ERROR
An MCP tool result answered after its command returned is marked an
error when the reply opens with a command name and then ERROR, or with
-1, rather than when " ERROR" appears anywhere in it: SCREENSHOT now
answers with the screenshot's path, which a game named with ERROR in it
would carry, and that screenshot was reported as having failed.

command_screenshot() says why it takes the screenshot threaded: its
callback has to run after the reply's destination is taken.
2026-10-04 01:50:51 +02:00
Rob Loach 007ebda0e6 mcp: Add callback to tooling to fix return state 2026-10-04 01:53:36 +02:00
LibretroAdmin eb33c4d2e5 reinit: the menu gets vsync after a restart only if vsync is on
When the menu comes up, menu_driver_toggle() gives it vsync "if vsync
is on": it checks the setting first. The end of command_event_reinit()
did the same thing without the check. With vsync off, every driver
restart with the menu up - a fullscreen toggle, a driver or HDR
setting - started the new video driver without vsync, as configured,
and a few frames later switched it on. So the menu ran with a vsync
the user had turned off, where at startup it does not, and the
swapchain the driver had just made was thrown away and made again.

That second swapchain is where a fullscreen toggle went wrong on one
Windows machine (Vulkan, NVIDIA, HDR10, threaded video, vsync off).
Its logs show, each time, a new driver creating its swapchain with
present mode IMMEDIATE, then "Creating swapchain with present mode:
FIFO" right after the input drivers are back, and then either

  [Vulkan] QueuePresent failed (err = -4, result = -4), destroying swapchain.

or nothing more at all, with the run loop and the video thread still
running and the picture no longer changing. At startup, where the
swapchain is made once, the same fullscreen window works.

The check is the one menu_driver_toggle() has: vsync on, scanline sync
off. With vsync on the call is made as before, and asks for the
interval the driver already started with.

Why the driver there answers the rebuilt swapchain the way it does is
not known, and a user with vsync on who changes the swap interval
still rebuilds it. Built here and compiled for Windows with mingw; the
fullscreen toggle itself has not been run on Windows with this.
2026-10-03 02:58:58 +02:00
libretroadmin b0728d0177 command: LIST_CORES, LIST_PLAYLISTS and GET_PLAYLIST
A client could tell what was running but not find anything to run:
LOAD_CONTENT takes a core path and a content path and nothing reported
either. LIST_CORES gives the installed cores and their paths,
LIST_PLAYLISTS the playlists, and GET_PLAYLIST a playlist's entries -
index, label, content path, core path - 200 at a time with MORE <next>
to page on. All three are read-only commands on every interface and
tools for MCP clients.

The playlist commands read files, so they answer from a task: the text
is made on the task thread and sent from its callback through the
asking interface's reply_to, which holds an MCP request open until
then, and is dropped if the interfaces were re-created meanwhile.
2026-09-30 19:47:54 +02:00
libretroadmin de1fe72c69 network: an MCP server, the command interface for AI assistants
RetroArch serves the Model Context Protocol over Streamable HTTP as one
more command interface beside UDP and stdin, polled with them and
waiting on nothing. Its tools are the commands in command.h, listed
from the rows HELP prints with their read-only and destructive hints,
and a tool call runs the command's own handler; a command that answers
later holds its connection until it does. Both protocol eras are
answered: 2026-07-28 statelessly, with server/discover and the header
checks, and earlier revisions through initialize.

It is off by default and listens on 127.0.0.1. Every request carries a
bearer token, made on first start and kept in the keychain, and a
browser Origin other than this machine is refused. MCP Server, its port
and its token are under Settings > Network; docs/mcp-server.md covers
connecting a client.
2026-09-30 17:36:04 +02:00
libretroadmin 2790aa0cce command: every command describes itself; HELP lists them
Both command tables carry a one-line description and flags saying
whether a command only reports or may lose data or load code, so the
tables in command.h are the one description of what RetroArch can be
told to do. HELP answers with every command - name, argument, what it
does, [read-only] or [destructive] - or with one when named; interfaces
that hand the commands to a client can be generated from the same rows.
tools/command_table_check.py, run by CI, fails on a command added
without a description.

A command sent without an argument no longer has its argument pointed
one byte past the end of the line, and SHOW_MSG is documented as
taking the message it shows.
2026-09-30 09:43:00 +02:00
LibretroAdmin b6f41430e0 command: answer PLAY_REPLAY_SLOT from the frame loop
The command returns at once; the reply follows once the movie task has
installed the replay, to the sender that asked.  command.c joins the
no-wait guard.
2026-09-30 07:55:25 +02:00
LibretroAdmin 2db2e86ba7 content: load content as a staged job the frame loop advances
The menu load ran the whole close-core/init-core/init-drivers cycle
inside one runloop_iterate, so the window went dark - or dropped to
the compositor on webOS - for the length of the read.  content_load
is now a job of three stages the frame loop steps through one per
frame: the old core closes with the drivers kept up, the new core
comes up behind them, then one driver reinit follows.  Between stages
the loop presents the cached frame under the menu, so the window
keeps drawing.

retroarch_main_init splits into a core phase and a driver phase for
this: the drivers are found in the driver phase, after the previous
session's are freed, so both sessions never hold a driver at once.
Core deinit keeps the drivers while switching, a reinit the core asks
for mid-load defers to the driver stage, and the pending menu actions
(config replace, close, core reload) wait for the frame after.
Startup and the CLI still load in one go, before any frame runs.
2026-09-30 00:42:48 +02:00
LibretroAdmin fa49a43dc2 command: bound READ_CORE_RAM and WRITE_CORE_RAM to the memory region
rcheevos_patch_address returns a pointer with no length, so
READ_CORE_RAM served whatever byte count the request asked for and
WRITE_CORE_RAM wrote every byte in the payload, both running past the
end of the core's memory region. READ_CORE_RAM also computed its reply
buffer as `40 + nbytes * 3` in unsigned int: a request for
~1431655766 bytes wrapped that to a few bytes and the reply loop then
wrote 4 GiB through it.

Add rcheevos_patch_address_avail, a thin wrapper over
rc_libretro_memory_find_avail that also reports how many bytes remain
in the region. READ_CORE_RAM clamps nbytes to that, WRITE_CORE_RAM
stops there and logs that the rest of the payload was dropped. Both
READ_CORE_RAM and READ_CORE_MEMORY additionally refuse requests above
16 KiB: the reply is 3 characters per byte in one UDP datagram, so
larger requests could never be delivered anyway, and the cap keeps the
size arithmetic well clear of the wrap. READ_CORE_MEMORY already
clamped to the descriptor length.

Also size the " -1" reply against the allocation instead of
sizeof(reply), which was the size of the pointer.
2026-09-26 17:54:56 +02:00
LibretroAdmin b5172edaa1 command: add network_cmd_bind_address
The network command interface has always bound to every interface, so
with network_cmd_enable on, any host that can reach the port can send
LOAD_CORE, WRITE_CORE_RAM or WRITE_CORE_MEMORY. Many setups rely on
exactly that (phone remotes, cabinet front-ends, home automation), so
the default cannot change without breaking them.

Add network_cmd_bind_address. Empty, the default, keeps the historical
all-interfaces bind; "127.0.0.1" restricts the interface to the local
machine. command_network_new takes the address and passes it to
socket_init, which only sets AI_PASSIVE for a NULL server, so an
explicit address binds to that interface alone. The startup log now
names what the socket is bound to.
2026-09-26 17:54:56 +02:00
LibretroAdmin c6212745a6 video: the cached frame's API speaks its size word
video_driver_cached_frame_info, the cached_frame_read callback and
video_driver_cached_frame_publish pass the frame's size as the packed
word the cache already stores. The reinit snapshot keeps it as one
word from read to republish, and the screenshot copy carries it the
same way.
2026-09-22 22:32:17 +02:00
LibretroAdmin 39f38d8914 menu: the driver's render and OSK hooks take the size word
render, osk_ptr_at_pos and osk_pointer_over_textbox receive the output
size as one packed word, and the menu drivers pass it on whole: to the
screensaver, to materialui's entries box and to xmb's layout choice.
The menu drivers' init reads the output word without unpacking it, and
the pointer DPI cache keys on the same word.

The ozone_color_theme_defer sample hands the frame info's size word to
render.
2026-09-22 21:31:34 +02:00