Skip to content

GameSwitcher: Favorites toggle + fix crashes and game switching - #1939

Open
robcodedev wants to merge 1 commit into
OnionUI:v4.5-devfrom
robcodedev:gameswitcher-fixes
Open

GameSwitcher: Favorites toggle + fix crashes and game switching#1939
robcodedev wants to merge 1 commit into
OnionUI:v4.5-devfrom
robcodedev:gameswitcher-fixes

Conversation

@robcodedev

Copy link
Copy Markdown

Adds Favorites management to the GameSwitcher popup, and fixes several bugs found while extending that menu, including a race that killed the game you had just switched to.

Switching games could kill the game you switched to

Switching games shortly after opening GameSwitcher killed the new game and relaunched it at the boot screen, and exiting from there autosaved over the real state.

resumeGame() writes cmd_to_run.sh and syncs before overlay_exit() runs, so runtime.sh can start the new game as soon as the old RetroArch process exits. overlay_exit() meanwhile was shutting RetroArch down by name: killall -TERM retroarch, a wait loop polling pidof retroarch, and a final killall -9 retroarch after the timeout. Neither command distinguishes instances.

If the old process exits while that wait loop is still running, pidof matches the newly launched instance instead, the loop runs to its full five seconds, and the final killall -9 takes down the game that just started. Because resumeGame() also sets force_auto_load_state, the relaunch lands at the boot screen.

overlay_init() now captures the PID of the RetroArch instance that was running when GameSwitcher opened, and overlay_exit() signals and waits on that PID alone. On the normal captured-PID path, a new instance launched during the handoff is therefore not mistaken for the one being closed. If no PID was captured it falls back to the previous killall behaviour rather than risking leaving RetroArch running.

Favorites

Adds a popup item that adds or removes the current game from Roms/favourite.json, showing Add favorite or Remove favorite as appropriate. The file is written through a temporary file and renamed into place, so an interrupted write cannot leave a partially written favorites list.

Both labels are new strings, 302 (remove) and 303 (add), registered alongside NEXT and RESUME. Stock string 55 ("Add to favorites") already exists in all shipped .lang files and would have been translated automatically, but it reaches 24 characters in Dutch and Hungarian and 23 in German and Portuguese, which does not fit comfortably in a LIST_SMALL row. The shorter wording is deliberate.

Only en.lang defines them so far; the other languages fall back to English until translations are added.

Crash fixes

Making room for a fifth popup item exposed three existing lifetime and threading problems.

list_free() leaves stale state behind
It frees the item array but leaves item_count and the items pointer unchanged, so a later list_currentItem() could return a pointer into freed memory. popMenu_destroy() now clears the struct after freeing it. Fixing this inside list_free() would have been tidier but changes behaviour for every other consumer, so it is left alone here.

Saving from the popup continued using a destroyed list
action_saveGame() destroys the popup from inside its own callback, and handleUpdateKeystatePopMenu() carried on afterwards and accessed the list that callback had already freed. The handler now stops touching the list once an action has destroyed it.

The save-state scan thread modified UI objects
It called setLoadPreview() directly, which mutates ListItem state and frees SDL surfaces, while the main thread could be rendering those same objects. The worker now publishes only plain scan results; the preview update happens on the main thread after the join, keeping surfaces and ListItems owned by the UI thread.

The popup also allocated space for exactly four items and list_addItem() does no bounds checking, so the allocation grows to six: five in use and one spare.

Testing

On device:

  • Switched games immediately after opening GameSwitcher: the new game starts and stays running, no delayed kill once the old instance exits, no relaunch to the boot screen, no autosave over the existing state
  • Toggled Favorites on and off: the entry is added to and removed from Roms/favourite.json, and MainUI reflects the change
  • Exited to menu with RetroArch running

Switching games shortly after opening GameSwitcher killed the new game
and relaunched it at the boot screen, and exiting from there autosaved
over the real state. resumeGame() writes cmd_to_run.sh and syncs before
overlay_exit() runs, so runtime.sh can start the new game as soon as the
old one exits. overlay_exit() meanwhile shut RetroArch down by name:
killall -TERM, a loop polling pidof retroarch, then killall -9 after the
timeout. Neither distinguishes instances, so pidof matches the newly
launched game, the wait runs to its full five seconds, and the final
kill takes that game down. force_auto_load_state is already set by then,
which is why it comes back at the boot screen. overlay_init() now
captures the RetroArch PID and overlay_exit() signals and waits on that
instance alone. If no PID was captured, the old killall path remains as
fallback.

Also adds a menu item that adds or removes the current game from
Roms/favourite.json, written through a temp file and renamed into place
so an interrupted write can't leave a broken list.

Both labels are new strings, 302 and 303, registered next to NEXT and
RESUME. Stock string 55 would have come pre-translated but runs to 24
characters in some languages, which doesn't fit the popup, so these are
deliberately shorter. Only en.lang defines them so far; the other
languages fall back to English until translations are added.

Making room for a fifth item turned up three ways to end up in freed
memory. list_free() leaves item_count and the items pointer alone, so
list_currentItem() could hand back a pointer into the freed array;
popMenu_destroy() now clears the struct after freeing rather than
changing list_free() for every other consumer. action_saveGame()
destroys the popup from inside its own callback and
handleUpdateKeystatePopMenu() carried on using the list afterwards. The
save state scan thread also called setLoadPreview(), which mutates
ListItems and frees SDL surfaces, while the main thread was rendering
them; it now publishes plain data only and the preview update happens on
the main thread after the join. list_addItem() does no bounds checking
either, so the popup allocation grows from four to six.
Copilot AI added a commit to Amiga500/Onion that referenced this pull request Sep 9, 2026
…and previews, GameSwitcher favorites + crash fixes, fbmode framebuffer transitions

Co-authored-by: Amiga500 <16525337+Amiga500@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant