diff --git a/HOOKS.md b/HOOKS.md index bd6583c83..99fbfbd92 100644 --- a/HOOKS.md +++ b/HOOKS.md @@ -2,86 +2,121 @@ ## The idea -Hooks are platform-specific, just like paks. The launcher reads them from: +Hooks are pak-scoped and self-registering. A Tools pak that wants to react to +a point in the system lifecycle drops one or more of these files right next +to its own `launch.sh`: ``` -$USERDATA_PATH/.hooks/ - boot.d/ # scripts run on boot - pre-launch.d/ # scripts run before launch - post-launch.d/ # scripts run after launch exits - pre-sleep.d/ # scripts run before device goes to sleep - post-resume.d/ # scripts run after device wakes from sleep +Tools//SomePak.pak/ + launch.sh + boot.sh # optional -- run once at boot + pre-launch.sh # optional -- run before a launch, can cancel it + post-launch.sh # optional -- run after a launch exits + pre-sleep.sh # optional -- run before the device sleeps or powers off + post-resume.sh # optional -- run after the device wakes from sleep ``` -On device, `USERDATA_PATH` resolves to: +`pak-hooks.sh` scans every installed Tools pak for these filenames and runs +whichever exist at the matching point in the lifecycle, in alphabetical +order by pak folder name. The file's presence *is* the registration: there +is no install step, no arming (no need to open the pak first), and removing +the pak removes its hooks with it. -``` -/mnt/SDCARD/.userdata/$PLATFORM -``` - -So the actual hook directories on device are: - -``` -/mnt/SDCARD/.userdata//.hooks/pre-launch.d/ -/mnt/SDCARD/.userdata//.hooks/post-launch.d/ - -``` - -Example installed hook path: - -``` -/mnt/SDCARD/.userdata/tg5040/.hooks/post-launch.d/shortcuts-resume.sh - -``` - -If these directories don't exist, nothing happens and there is no overhead. +If no pak registers a given phase, nothing happens and there is no overhead. ## Environment variables -Hook scripts inherit all standard NextUI environment variables (`SDCARD_PATH`, `PLATFORM`, `USERDATA_PATH`, `SHARED_USERDATA_PATH`, etc.) plus these launch-specific ones: +Hook scripts inherit all standard NextUI environment variables (`SDCARD_PATH`, `PLATFORM`, `USERDATA_PATH`, `SHARED_USERDATA_PATH`, etc.) plus these launch-specific ones, for `pre-launch.sh`/`post-launch.sh`: | Variable | Description | |---|---| -| `HOOK_PHASE` | `pre` or `post` | | `HOOK_TYPE` | `rom` or `pak` | | `HOOK_CMD` | The raw launch command | | `HOOK_EMU_PATH` | Path to the emulator or pak `launch.sh` | | `HOOK_ROM_PATH` | Path to the ROM file (empty for pak launches) | | `HOOK_LAST` | Contents of `/tmp/last.txt` (the last selected menu entry) | -These can then be used by the underlying Pak to ingest information about the hook that just occurred. +`boot.sh`/`pre-sleep.sh`/`post-resume.sh` get none of these -- there's no +launch command to describe at those points. There's no `HOOK_PHASE` either: +your script's own filename (`pre-launch.sh`, `post-resume.sh`, ...) already +says which phase it's running for. ## Writing a hook script -A hook script is any executable `.sh` file in one of the hook directories. Scripts run in alphabetical order. - ```sh #!/bin/sh -# my-hook.sh — log every ROM launch +# pre-launch.sh — log every ROM launch [ "$HOOK_TYPE" = "rom" ] || exit 0 echo "$(date): launched $HOOK_ROM_PATH" >> "$LOGS_PATH/launches.log" ``` +This is how the built-in `Game Tracker.pak` hangs `gametimectl.elf +start/stop/stop_all/resume` off the lifecycle -- see its own +`pre-launch.sh`/`post-launch.sh`/`pre-sleep.sh`/`post-resume.sh`. Core code +(`api.c`, `nextui.c`) has no direct knowledge of gametimectl any more, it +only fires the generic `pak-hooks.sh` events. + ## Rules -- Each script runs in a subshell. A crash or non-zero exit will not affect the launcher or other hooks. +- Each script runs in a subshell. A crash will not affect the launcher or other paks' hooks. - Script output (stdout/stderr) is suppressed. If you need logging, write to your own log file. -- Pre-launch hooks cannot cancel the launch. They are for observation and setup only. -- Keep hooks fast. A slow hook delays the launch or the return to the menu. -- Unlike auto.sh, each pak should manage their own hook and use a descriptive filename to avoid collisions. - - -## Example: sync after ROM exit - -```sh -#!/bin/sh -# shortcuts-resume.sh — one-shot resume metadata sync after a ROM exits - -[ "$HOOK_TYPE" = "rom" ] || exit 0 - -SHORTCUTS_PAK="$SDCARD_PATH/Tools/$PLATFORM/Shortcuts.pak" -[ -x "$SHORTCUTS_PAK/shortcuts" ] || exit 0 - -"$SHORTCUTS_PAK/shortcuts" --resume-sync-hook >> "$LOGS_PATH/shortcuts-resume-sync.txt" 2>&1 -``` +- Only `pre-launch.sh` can cancel anything: a non-zero exit from **any** + registered pak's `pre-launch.sh` cancels the launch -- every pak that + registers one is assumed to need to agree before a game runs. `boot.sh`, + `post-launch.sh`, `pre-sleep.sh` and `post-resume.sh` are fire-and-forget; + their exit code is ignored. Sleep in particular isn't cancellable. +- Every registered `pre-launch.sh` runs in the same pass, in alphabetical + order by pak folder name, with **no ordering guarantee relative to + another pak's veto**. If your hook does something that must be undone + when a *different* pak vetoes the launch, that vetoing pak is responsible + for the cleanup itself -- see `Game Tracker.pak/pre-launch.sh`, which + always starts tracking unconditionally because it cannot know in advance + whether another pak will refuse the launch. +- Keep hooks fast. A slow hook delays the launch, sleep, or the return to the menu. +- Use a descriptive filename inside your own pak folder; collisions across + paks aren't possible since each hook lives inside its owning pak. +- If several of your phases share logic, don't merge them into one script + dispatched by an argument or env var -- that would force `pak-hooks.sh` to + invoke it for every phase just to find out which ones it actually handles, + losing the whole point of presence-based registration. Put the shared part + in a plain file next to your hooks and `source` it from each one instead: + `. "$(dirname "$0")/common.sh"`. + +## Caching + +The list of paks with at least one registered hook is cached +(`/tmp/pak_hooks_cache.txt`) to avoid re-scanning every Tools pak on every +single launch or sleep. It's built fresh on the very first call of the +session -- in practice `pak-hooks.sh boot`, so it's always current right +after a reboot. + +After that, it's only rebuilt when a **Tools pak** (not a rom) exits -- +see `MinUI.pak/launch.sh`, right after `eval $CMD` when `$HOOK_TYPE` is +`pak`. A rom can't change what's under `Tools/`, so the overwhelming +majority of returns to the menu (finishing a game) skip the rescan +entirely; a pak that plausibly did change something there (Pak Store, +Files.pak, ...) triggers a refresh as soon as it closes, so a newly +installed/removed pak is picked up on the very next launch instead of +staying stale until the next reboot. + +## Legacy hooks (deprecated) + +Before `pak-hooks.sh`, hooks were plain scripts a pak copied into +`$USERDATA_PATH/.hooks/{boot,pre-launch,post-launch,pre-sleep,post-resume}.d/` +(usually the first time it was opened), run by `run_hooks.sh`. This +mechanism is **deprecated** and kept only so a pak that already dropped +scripts there doesn't silently break: + +- It requires that install/arming step, and nothing removes the script if + the pak is later deleted -- it becomes an orphan. +- `pre-launch.d`/`post-launch.d` scripts here **cannot veto a launch**; only + a pak-scoped `pre-launch.sh` can. +- `run_hooks.sh` logs a warning to `$LOGS_PATH/hooks-deprecated.txt` + whenever it actually finds a script to run in one of these directories, + so a stale legacy hook doesn't go unnoticed. + +New hooks should always be pak-scoped (`pak-hooks.sh`), never dropped into +`.hooks/`. Migrating an existing legacy hook just means moving the script +into your pak's own folder under the matching name (`pre-launch.sh`, +`post-launch.sh`, `pre-sleep.sh`, `post-resume.sh`, or `boot.sh`). diff --git a/skeleton/EXTRAS/Tools/desktop/Game Tracker.pak/post-launch.sh b/skeleton/EXTRAS/Tools/desktop/Game Tracker.pak/post-launch.sh new file mode 100755 index 000000000..c0929a6b5 --- /dev/null +++ b/skeleton/EXTRAS/Tools/desktop/Game Tracker.pak/post-launch.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# post-launch.sh -- close the play_activity row for the rom that just exited. + +[ "$HOOK_TYPE" = "rom" ] || exit 0 + +gametimectl.elf stop "$HOOK_ROM_PATH" diff --git a/skeleton/EXTRAS/Tools/desktop/Game Tracker.pak/post-resume.sh b/skeleton/EXTRAS/Tools/desktop/Game Tracker.pak/post-resume.sh new file mode 100755 index 000000000..bd4149e40 --- /dev/null +++ b/skeleton/EXTRAS/Tools/desktop/Game Tracker.pak/post-resume.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# post-resume.sh -- reopen tracking for the last rom after the device wakes. + +gametimectl.elf resume diff --git a/skeleton/EXTRAS/Tools/desktop/Game Tracker.pak/pre-launch.sh b/skeleton/EXTRAS/Tools/desktop/Game Tracker.pak/pre-launch.sh new file mode 100755 index 000000000..7dbc624b7 --- /dev/null +++ b/skeleton/EXTRAS/Tools/desktop/Game Tracker.pak/pre-launch.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# pre-launch.sh -- start tracking play time for the rom about to launch. +# +# Scanned by pak-hooks.sh alongside every other registered pak's pre-launch.sh +# in the same pass, with no ordering guarantee relative to any pak that might +# still veto the launch -- so this always starts tracking unconditionally. +# Any pak that vetoes a launch (exits non-zero from its own pre-launch.sh) +# is responsible for calling `gametimectl.elf stop_all` itself -- see the +# ordering note in HOOKS.md. + +[ "$HOOK_TYPE" = "rom" ] || exit 0 + +gametimectl.elf start "$HOOK_ROM_PATH" diff --git a/skeleton/EXTRAS/Tools/desktop/Game Tracker.pak/pre-sleep.sh b/skeleton/EXTRAS/Tools/desktop/Game Tracker.pak/pre-sleep.sh new file mode 100755 index 000000000..acb7698c8 --- /dev/null +++ b/skeleton/EXTRAS/Tools/desktop/Game Tracker.pak/pre-sleep.sh @@ -0,0 +1,5 @@ +#!/bin/sh +# pre-sleep.sh -- close the open play_activity row before the device sleeps +# or powers off (fire-and-forget, cannot veto sleep). + +gametimectl.elf stop_all diff --git a/skeleton/EXTRAS/Tools/tg5040/Game Tracker.pak/post-launch.sh b/skeleton/EXTRAS/Tools/tg5040/Game Tracker.pak/post-launch.sh new file mode 100755 index 000000000..c0929a6b5 --- /dev/null +++ b/skeleton/EXTRAS/Tools/tg5040/Game Tracker.pak/post-launch.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# post-launch.sh -- close the play_activity row for the rom that just exited. + +[ "$HOOK_TYPE" = "rom" ] || exit 0 + +gametimectl.elf stop "$HOOK_ROM_PATH" diff --git a/skeleton/EXTRAS/Tools/tg5040/Game Tracker.pak/post-resume.sh b/skeleton/EXTRAS/Tools/tg5040/Game Tracker.pak/post-resume.sh new file mode 100755 index 000000000..bd4149e40 --- /dev/null +++ b/skeleton/EXTRAS/Tools/tg5040/Game Tracker.pak/post-resume.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# post-resume.sh -- reopen tracking for the last rom after the device wakes. + +gametimectl.elf resume diff --git a/skeleton/EXTRAS/Tools/tg5040/Game Tracker.pak/pre-launch.sh b/skeleton/EXTRAS/Tools/tg5040/Game Tracker.pak/pre-launch.sh new file mode 100755 index 000000000..7dbc624b7 --- /dev/null +++ b/skeleton/EXTRAS/Tools/tg5040/Game Tracker.pak/pre-launch.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# pre-launch.sh -- start tracking play time for the rom about to launch. +# +# Scanned by pak-hooks.sh alongside every other registered pak's pre-launch.sh +# in the same pass, with no ordering guarantee relative to any pak that might +# still veto the launch -- so this always starts tracking unconditionally. +# Any pak that vetoes a launch (exits non-zero from its own pre-launch.sh) +# is responsible for calling `gametimectl.elf stop_all` itself -- see the +# ordering note in HOOKS.md. + +[ "$HOOK_TYPE" = "rom" ] || exit 0 + +gametimectl.elf start "$HOOK_ROM_PATH" diff --git a/skeleton/EXTRAS/Tools/tg5040/Game Tracker.pak/pre-sleep.sh b/skeleton/EXTRAS/Tools/tg5040/Game Tracker.pak/pre-sleep.sh new file mode 100755 index 000000000..acb7698c8 --- /dev/null +++ b/skeleton/EXTRAS/Tools/tg5040/Game Tracker.pak/pre-sleep.sh @@ -0,0 +1,5 @@ +#!/bin/sh +# pre-sleep.sh -- close the open play_activity row before the device sleeps +# or powers off (fire-and-forget, cannot veto sleep). + +gametimectl.elf stop_all diff --git a/skeleton/EXTRAS/Tools/tg5050/Game Tracker.pak/post-launch.sh b/skeleton/EXTRAS/Tools/tg5050/Game Tracker.pak/post-launch.sh new file mode 100755 index 000000000..c0929a6b5 --- /dev/null +++ b/skeleton/EXTRAS/Tools/tg5050/Game Tracker.pak/post-launch.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# post-launch.sh -- close the play_activity row for the rom that just exited. + +[ "$HOOK_TYPE" = "rom" ] || exit 0 + +gametimectl.elf stop "$HOOK_ROM_PATH" diff --git a/skeleton/EXTRAS/Tools/tg5050/Game Tracker.pak/post-resume.sh b/skeleton/EXTRAS/Tools/tg5050/Game Tracker.pak/post-resume.sh new file mode 100755 index 000000000..bd4149e40 --- /dev/null +++ b/skeleton/EXTRAS/Tools/tg5050/Game Tracker.pak/post-resume.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# post-resume.sh -- reopen tracking for the last rom after the device wakes. + +gametimectl.elf resume diff --git a/skeleton/EXTRAS/Tools/tg5050/Game Tracker.pak/pre-launch.sh b/skeleton/EXTRAS/Tools/tg5050/Game Tracker.pak/pre-launch.sh new file mode 100755 index 000000000..7dbc624b7 --- /dev/null +++ b/skeleton/EXTRAS/Tools/tg5050/Game Tracker.pak/pre-launch.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# pre-launch.sh -- start tracking play time for the rom about to launch. +# +# Scanned by pak-hooks.sh alongside every other registered pak's pre-launch.sh +# in the same pass, with no ordering guarantee relative to any pak that might +# still veto the launch -- so this always starts tracking unconditionally. +# Any pak that vetoes a launch (exits non-zero from its own pre-launch.sh) +# is responsible for calling `gametimectl.elf stop_all` itself -- see the +# ordering note in HOOKS.md. + +[ "$HOOK_TYPE" = "rom" ] || exit 0 + +gametimectl.elf start "$HOOK_ROM_PATH" diff --git a/skeleton/EXTRAS/Tools/tg5050/Game Tracker.pak/pre-sleep.sh b/skeleton/EXTRAS/Tools/tg5050/Game Tracker.pak/pre-sleep.sh new file mode 100755 index 000000000..acb7698c8 --- /dev/null +++ b/skeleton/EXTRAS/Tools/tg5050/Game Tracker.pak/pre-sleep.sh @@ -0,0 +1,5 @@ +#!/bin/sh +# pre-sleep.sh -- close the open play_activity row before the device sleeps +# or powers off (fire-and-forget, cannot veto sleep). + +gametimectl.elf stop_all diff --git a/skeleton/SYSTEM/desktop/bin/pak-hooks.sh b/skeleton/SYSTEM/desktop/bin/pak-hooks.sh new file mode 100755 index 000000000..048a74667 --- /dev/null +++ b/skeleton/SYSTEM/desktop/bin/pak-hooks.sh @@ -0,0 +1,66 @@ +#!/bin/sh +# pak-hooks.sh - self-registering, pak-scoped lifecycle hooks +# +# The sole hook mechanism in NextUI: scans installed Tools paks for a +# boot.sh / pre-launch.sh / post-launch.sh / pre-sleep.sh / post-resume.sh +# file sitting right next to their launch.sh. Presence of the file *is* the +# registration: no install step, no arming, and removing the pak removes +# its hooks with it. +# +# Usage: pak-hooks.sh rebuild-cache +# pak-hooks.sh boot (fire-and-forget, exit code ignored) +# pak-hooks.sh pre-launch (exit != 0 vetoes the launch) +# pak-hooks.sh post-launch +# pak-hooks.sh pre-sleep (fire-and-forget, exit code ignored) +# pak-hooks.sh post-resume (fire-and-forget, exit code ignored) +# +# The list of qualifying paks is cached to avoid re-scanning every Tools pak +# on every single launch/sleep. The cache is rebuilt when returning to the +# main menu (see nextui.c), not at call time, so a freshly installed/removed +# pak is picked up on the very next visit to the menu instead of going stale +# until the next reboot. At boot, no menu visit has happened yet, so the +# first call below builds the cache fresh. + +: "${SDCARD_PATH:=/var/tmp/nextui/sdcard}" +: "${PLATFORM:=desktop}" +CACHE="/tmp/pak_hooks_cache.txt" + +rebuild_cache() { + : > "$CACHE" + for pak in "$SDCARD_PATH"/Tools/"$PLATFORM"/*.pak; do + [ -d "$pak" ] || continue + if [ -x "$pak/boot.sh" ] || [ -x "$pak/pre-launch.sh" ] \ + || [ -x "$pak/post-launch.sh" ] || [ -x "$pak/pre-sleep.sh" ] \ + || [ -x "$pak/post-resume.sh" ]; then + echo "$pak" >> "$CACHE" + fi + done +} + +run_phase() { + PHASE="$1" # boot | pre-launch | post-launch | pre-sleep | post-resume + SCRIPT_NAME="$PHASE.sh" + # no HOOK_PHASE env var here: the script's own filename already tells it + # which phase it's running for, so a separate variable would just repeat + # that. Share logic across phases by sourcing a common file instead. + + [ -f "$CACHE" ] || rebuild_cache + + VETOED=0 + while IFS= read -r pak; do + [ -n "$pak" ] || continue + [ -x "$pak/$SCRIPT_NAME" ] || continue + if ! "$pak/$SCRIPT_NAME"; then + VETOED=1 + fi + done < "$CACHE" + + # only pre-launch actually vetoes anything; every other phase is + # fire-and-forget and callers don't check this exit code + exit $VETOED +} + +case "$1" in + rebuild-cache) rebuild_cache ;; + boot|pre-launch|post-launch|pre-sleep|post-resume) run_phase "$1" ;; +esac diff --git a/skeleton/SYSTEM/desktop/bin/run_hooks.sh b/skeleton/SYSTEM/desktop/bin/run_hooks.sh index 30b86ca60..9af79f060 100755 --- a/skeleton/SYSTEM/desktop/bin/run_hooks.sh +++ b/skeleton/SYSTEM/desktop/bin/run_hooks.sh @@ -1,5 +1,11 @@ #!/bin/sh -# run_hooks.sh - shared hook runner for NextUI +# run_hooks.sh - DEPRECATED legacy hook runner for $USERDATA_PATH/.hooks/ +# +# Superseded by pak-hooks.sh: self-registering, no arming step, no orphaned +# scripts left behind when a pak is deleted. See HOOKS.md. Kept only so a +# pak that already dropped scripts into .hooks/ doesn't silently stop +# working -- do not build anything new against this mechanism. +# # Usage: run_hooks.sh [--sync-only] # # dir-name: directory name under $USERDATA_PATH/.hooks/ (e.g. pre-launch.d, boot.d) @@ -14,10 +20,19 @@ SYNC_ONLY="${2:-}" : "${SDCARD_PATH:=/var/tmp/nextui/sdcard}" : "${PLATFORM:=desktop}" : "${USERDATA_PATH:=$SDCARD_PATH/.userdata/$PLATFORM}" +: "${LOGS_PATH:=$USERDATA_PATH/logs}" HOOK_DIR="$USERDATA_PATH/.hooks/$DIR_NAME" [ -d "$HOOK_DIR" ] || exit 0 +# only warn when there's actually a script to run here -- an unused, +# never-populated .hooks/ directory shouldn't spam the log +for _probe in "$HOOK_DIR"/*.sh; do + [ -f "$_probe" ] || continue + echo "$(date): DEPRECATED - $HOOK_DIR/$(basename "$_probe") uses the legacy .hooks/ mechanism (run_hooks.sh). Migrate to a pak-scoped hook (pak-hooks.sh) -- see HOOKS.md." >> "$LOGS_PATH/hooks-deprecated.txt" + break +done + case "$DIR_NAME" in pre-*) export HOOK_PHASE="pre" ;; post-*) export HOOK_PHASE="post" ;; diff --git a/skeleton/SYSTEM/desktop/paks/MinUI.pak/launch.sh b/skeleton/SYSTEM/desktop/paks/MinUI.pak/launch.sh index 9ba15c8d6..29b85e105 100755 --- a/skeleton/SYSTEM/desktop/paks/MinUI.pak/launch.sh +++ b/skeleton/SYSTEM/desktop/paks/MinUI.pak/launch.sh @@ -18,7 +18,7 @@ export CORES_PATH="$SYSTEM_PATH/cores" export USERDATA_PATH="$SDCARD_PATH/.userdata/$PLATFORM" export SHARED_USERDATA_PATH="$SDCARD_PATH/.userdata/shared" export LOGS_PATH="$USERDATA_PATH/logs" -export HOOKS_PATH="$USERDATA_PATH/.hooks" +export HOOKS_PATH="$USERDATA_PATH/.hooks" # deprecated, see HOOKS.md export DATETIME_PATH="$SHARED_USERDATA_PATH/datetime.txt" mkdir -p "$BIOS_PATH" @@ -27,7 +27,7 @@ mkdir -p "$SAVES_PATH" mkdir -p "$CHEATS_PATH" mkdir -p "$USERDATA_PATH" mkdir -p "$LOGS_PATH" -mkdir -p "$HOOKS_PATH" +mkdir -p "$HOOKS_PATH" # deprecated, see HOOKS.md mkdir -p "$SHARED_USERDATA_PATH/.minui" export IS_NEXT="yes" @@ -48,7 +48,8 @@ if [ -f "$AUTO_PATH" ]; then fi # Composable boot hooks (run after auto.sh for backward compatibility) -"$SYSTEM_PATH/bin/run_hooks.sh" boot.d +"$SYSTEM_PATH/bin/run_hooks.sh" boot.d # deprecated, see HOOKS.md +"$SYSTEM_PATH/bin/pak-hooks.sh" boot cd $(dirname "$0") @@ -81,9 +82,20 @@ touch "$EXEC_PATH" && sync if [ -f $NEXT_PATH ]; then CMD=`cat $NEXT_PATH` parse_hook_cmd "$CMD" - "$SYSTEM_PATH/bin/run_hooks.sh" pre-launch.d - eval $CMD - "$SYSTEM_PATH/bin/run_hooks.sh" post-launch.d + # a registered pak-hooks.sh pre-launch.sh that exits non-zero cancels the + # launch (the deprecated run_hooks.sh pre-launch.d cannot veto, see + # HOOKS.md). Game Tracker.pak's own pre-launch.sh/post-launch.sh is what + # starts/stops gametimectl -- this shell has no direct knowledge of it. + "$SYSTEM_PATH/bin/run_hooks.sh" pre-launch.d # deprecated, see HOOKS.md + if "$SYSTEM_PATH/bin/pak-hooks.sh" pre-launch; then + eval $CMD + "$SYSTEM_PATH/bin/run_hooks.sh" post-launch.d # deprecated, see HOOKS.md + "$SYSTEM_PATH/bin/pak-hooks.sh" post-launch + # only a Tools pak (eg. Pak Store, Files.pak) can plausibly have + # changed what's under Tools/ -- a rom never does, so skip the + # rescan on the overwhelming majority of returns to the menu + [ "$HOOK_TYPE" = "pak" ] && "$SYSTEM_PATH/bin/pak-hooks.sh" rebuild-cache & + fi rm -f $NEXT_PATH fi #done diff --git a/skeleton/SYSTEM/tg5040/bin/pak-hooks.sh b/skeleton/SYSTEM/tg5040/bin/pak-hooks.sh new file mode 100755 index 000000000..840e36a38 --- /dev/null +++ b/skeleton/SYSTEM/tg5040/bin/pak-hooks.sh @@ -0,0 +1,66 @@ +#!/bin/sh +# pak-hooks.sh - self-registering, pak-scoped lifecycle hooks +# +# The sole hook mechanism in NextUI: scans installed Tools paks for a +# boot.sh / pre-launch.sh / post-launch.sh / pre-sleep.sh / post-resume.sh +# file sitting right next to their launch.sh. Presence of the file *is* the +# registration: no install step, no arming, and removing the pak removes +# its hooks with it. +# +# Usage: pak-hooks.sh rebuild-cache +# pak-hooks.sh boot (fire-and-forget, exit code ignored) +# pak-hooks.sh pre-launch (exit != 0 vetoes the launch) +# pak-hooks.sh post-launch +# pak-hooks.sh pre-sleep (fire-and-forget, exit code ignored) +# pak-hooks.sh post-resume (fire-and-forget, exit code ignored) +# +# The list of qualifying paks is cached to avoid re-scanning every Tools pak +# on every single launch/sleep. The cache is rebuilt when returning to the +# main menu (see nextui.c), not at call time, so a freshly installed/removed +# pak is picked up on the very next visit to the menu instead of going stale +# until the next reboot. At boot, no menu visit has happened yet, so the +# first call below builds the cache fresh. + +: "${SDCARD_PATH:=/mnt/SDCARD}" +: "${PLATFORM:=tg5040}" +CACHE="/tmp/pak_hooks_cache.txt" + +rebuild_cache() { + : > "$CACHE" + for pak in "$SDCARD_PATH"/Tools/"$PLATFORM"/*.pak; do + [ -d "$pak" ] || continue + if [ -x "$pak/boot.sh" ] || [ -x "$pak/pre-launch.sh" ] \ + || [ -x "$pak/post-launch.sh" ] || [ -x "$pak/pre-sleep.sh" ] \ + || [ -x "$pak/post-resume.sh" ]; then + echo "$pak" >> "$CACHE" + fi + done +} + +run_phase() { + PHASE="$1" # boot | pre-launch | post-launch | pre-sleep | post-resume + SCRIPT_NAME="$PHASE.sh" + # no HOOK_PHASE env var here: the script's own filename already tells it + # which phase it's running for, so a separate variable would just repeat + # that. Share logic across phases by sourcing a common file instead. + + [ -f "$CACHE" ] || rebuild_cache + + VETOED=0 + while IFS= read -r pak; do + [ -n "$pak" ] || continue + [ -x "$pak/$SCRIPT_NAME" ] || continue + if ! "$pak/$SCRIPT_NAME"; then + VETOED=1 + fi + done < "$CACHE" + + # only pre-launch actually vetoes anything; every other phase is + # fire-and-forget and callers don't check this exit code + exit $VETOED +} + +case "$1" in + rebuild-cache) rebuild_cache ;; + boot|pre-launch|post-launch|pre-sleep|post-resume) run_phase "$1" ;; +esac diff --git a/skeleton/SYSTEM/tg5040/bin/run_hooks.sh b/skeleton/SYSTEM/tg5040/bin/run_hooks.sh index 96fbe4833..e6ff7f921 100755 --- a/skeleton/SYSTEM/tg5040/bin/run_hooks.sh +++ b/skeleton/SYSTEM/tg5040/bin/run_hooks.sh @@ -1,5 +1,11 @@ #!/bin/sh -# run_hooks.sh - shared hook runner for NextUI +# run_hooks.sh - DEPRECATED legacy hook runner for $USERDATA_PATH/.hooks/ +# +# Superseded by pak-hooks.sh: self-registering, no arming step, no orphaned +# scripts left behind when a pak is deleted. See HOOKS.md. Kept only so a +# pak that already dropped scripts into .hooks/ doesn't silently stop +# working -- do not build anything new against this mechanism. +# # Usage: run_hooks.sh [--sync-only] # # dir-name: directory name under $USERDATA_PATH/.hooks/ (e.g. pre-launch.d, boot.d) @@ -14,10 +20,19 @@ SYNC_ONLY="${2:-}" : "${SDCARD_PATH:=/mnt/SDCARD}" : "${PLATFORM:=tg5040}" : "${USERDATA_PATH:=$SDCARD_PATH/.userdata/$PLATFORM}" +: "${LOGS_PATH:=$USERDATA_PATH/logs}" HOOK_DIR="$USERDATA_PATH/.hooks/$DIR_NAME" [ -d "$HOOK_DIR" ] || exit 0 +# only warn when there's actually a script to run here -- an unused, +# never-populated .hooks/ directory shouldn't spam the log +for _probe in "$HOOK_DIR"/*.sh; do + [ -f "$_probe" ] || continue + echo "$(date): DEPRECATED - $HOOK_DIR/$(basename "$_probe") uses the legacy .hooks/ mechanism (run_hooks.sh). Migrate to a pak-scoped hook (pak-hooks.sh) -- see HOOKS.md." >> "$LOGS_PATH/hooks-deprecated.txt" + break +done + case "$DIR_NAME" in pre-*) export HOOK_PHASE="pre" ;; post-*) export HOOK_PHASE="post" ;; diff --git a/skeleton/SYSTEM/tg5040/bin/suspend b/skeleton/SYSTEM/tg5040/bin/suspend index 593159423..e6af28443 100644 --- a/skeleton/SYSTEM/tg5040/bin/suspend +++ b/skeleton/SYSTEM/tg5040/bin/suspend @@ -16,9 +16,10 @@ asound_state_dir=/tmp/asound-suspend before() { >&2 echo "Preparing for suspend..." - # Run pre-sleep hooks synchronously before services are stopped. - # Subshell ensures a crashing hook cannot block suspend. - ( "$SYSTEM_PATH/bin/run_hooks.sh" pre-sleep.d --sync-only ) >/dev/null 2>&1 || true + # Run pre-sleep hooks before services are stopped. Subshell ensures a + # crashing hook cannot block suspend. + ( "$SYSTEM_PATH/bin/run_hooks.sh" pre-sleep.d --sync-only ) >/dev/null 2>&1 || true # deprecated, see HOOKS.md + ( "$SYSTEM_PATH/bin/pak-hooks.sh" pre-sleep ) >/dev/null 2>&1 || true >&2 echo "Saving mixer state..." mkdir -p "$asound_state_dir" @@ -41,7 +42,8 @@ after() { >&2 echo "Resumed from suspend." # Run post-resume hooks in background immediately after wake. - ( "$SYSTEM_PATH/bin/run_hooks.sh" post-resume.d ) >/dev/null 2>&1 & + ( "$SYSTEM_PATH/bin/run_hooks.sh" post-resume.d ) >/dev/null 2>&1 & # deprecated, see HOOKS.md + ( "$SYSTEM_PATH/bin/pak-hooks.sh" post-resume ) >/dev/null 2>&1 & if [ -n "$wifid_running" ]; then >&2 echo "Starting wpa_supplicant..." diff --git a/skeleton/SYSTEM/tg5040/paks/MinUI.pak/launch.sh b/skeleton/SYSTEM/tg5040/paks/MinUI.pak/launch.sh index e4a585713..366dbfde3 100755 --- a/skeleton/SYSTEM/tg5040/paks/MinUI.pak/launch.sh +++ b/skeleton/SYSTEM/tg5040/paks/MinUI.pak/launch.sh @@ -22,7 +22,7 @@ export CORES_PATH="$SYSTEM_PATH/cores" export USERDATA_PATH="$SDCARD_PATH/.userdata/$PLATFORM" export SHARED_USERDATA_PATH="$SDCARD_PATH/.userdata/shared" export LOGS_PATH="$USERDATA_PATH/logs" -export HOOKS_PATH="$USERDATA_PATH/.hooks" +export HOOKS_PATH="$USERDATA_PATH/.hooks" # deprecated, see HOOKS.md export DATETIME_PATH="$SHARED_USERDATA_PATH/datetime.txt" export HOME="$USERDATA_PATH" @@ -45,7 +45,7 @@ mkdir -p "$SAVES_PATH" mkdir -p "$CHEATS_PATH" mkdir -p "$USERDATA_PATH" mkdir -p "$LOGS_PATH" -mkdir -p "$HOOKS_PATH" +mkdir -p "$HOOKS_PATH" # deprecated, see HOOKS.md mkdir -p "$SHARED_USERDATA_PATH/.minui" export TRIMUI_MODEL=`strings /usr/trimui/bin/MainUI | grep ^Trimui` @@ -153,7 +153,8 @@ if [ -f "$AUTO_PATH" ]; then fi # Composable boot hooks (run after auto.sh for backward compatibility) -"$SYSTEM_PATH/bin/run_hooks.sh" boot.d +"$SYSTEM_PATH/bin/run_hooks.sh" boot.d # deprecated, see HOOKS.md +"$SYSTEM_PATH/bin/pak-hooks.sh" boot cd $(dirname "$0") @@ -191,9 +192,20 @@ while [ -f $EXEC_PATH ]; do if [ -f $NEXT_PATH ]; then CMD=`cat $NEXT_PATH` parse_hook_cmd "$CMD" - "$SYSTEM_PATH/bin/run_hooks.sh" pre-launch.d - eval $CMD - "$SYSTEM_PATH/bin/run_hooks.sh" post-launch.d + # a registered pak-hooks.sh pre-launch.sh that exits non-zero cancels the + # launch (the deprecated run_hooks.sh pre-launch.d cannot veto, see + # HOOKS.md). Game Tracker.pak's own pre-launch.sh/post-launch.sh is what + # starts/stops gametimectl -- this shell has no direct knowledge of it. + "$SYSTEM_PATH/bin/run_hooks.sh" pre-launch.d # deprecated, see HOOKS.md + if "$SYSTEM_PATH/bin/pak-hooks.sh" pre-launch; then + eval $CMD + "$SYSTEM_PATH/bin/run_hooks.sh" post-launch.d # deprecated, see HOOKS.md + "$SYSTEM_PATH/bin/pak-hooks.sh" post-launch + # only a Tools pak (eg. Pak Store, Files.pak) can plausibly have + # changed what's under Tools/ -- a rom never does, so skip the + # rescan on the overwhelming majority of returns to the menu + [ "$HOOK_TYPE" = "pak" ] && "$SYSTEM_PATH/bin/pak-hooks.sh" rebuild-cache & + fi rm -f $NEXT_PATH # reset to performance when exiting, UI will reset to auto if needed sh "$SYSTEM_PATH/bin/governor.sh" "performance" diff --git a/skeleton/SYSTEM/tg5050/bin/pak-hooks.sh b/skeleton/SYSTEM/tg5050/bin/pak-hooks.sh new file mode 100755 index 000000000..1ee6459ee --- /dev/null +++ b/skeleton/SYSTEM/tg5050/bin/pak-hooks.sh @@ -0,0 +1,66 @@ +#!/bin/sh +# pak-hooks.sh - self-registering, pak-scoped lifecycle hooks +# +# The sole hook mechanism in NextUI: scans installed Tools paks for a +# boot.sh / pre-launch.sh / post-launch.sh / pre-sleep.sh / post-resume.sh +# file sitting right next to their launch.sh. Presence of the file *is* the +# registration: no install step, no arming, and removing the pak removes +# its hooks with it. +# +# Usage: pak-hooks.sh rebuild-cache +# pak-hooks.sh boot (fire-and-forget, exit code ignored) +# pak-hooks.sh pre-launch (exit != 0 vetoes the launch) +# pak-hooks.sh post-launch +# pak-hooks.sh pre-sleep (fire-and-forget, exit code ignored) +# pak-hooks.sh post-resume (fire-and-forget, exit code ignored) +# +# The list of qualifying paks is cached to avoid re-scanning every Tools pak +# on every single launch/sleep. The cache is rebuilt when returning to the +# main menu (see nextui.c), not at call time, so a freshly installed/removed +# pak is picked up on the very next visit to the menu instead of going stale +# until the next reboot. At boot, no menu visit has happened yet, so the +# first call below builds the cache fresh. + +: "${SDCARD_PATH:=/mnt/SDCARD}" +: "${PLATFORM:=tg5050}" +CACHE="/tmp/pak_hooks_cache.txt" + +rebuild_cache() { + : > "$CACHE" + for pak in "$SDCARD_PATH"/Tools/"$PLATFORM"/*.pak; do + [ -d "$pak" ] || continue + if [ -x "$pak/boot.sh" ] || [ -x "$pak/pre-launch.sh" ] \ + || [ -x "$pak/post-launch.sh" ] || [ -x "$pak/pre-sleep.sh" ] \ + || [ -x "$pak/post-resume.sh" ]; then + echo "$pak" >> "$CACHE" + fi + done +} + +run_phase() { + PHASE="$1" # boot | pre-launch | post-launch | pre-sleep | post-resume + SCRIPT_NAME="$PHASE.sh" + # no HOOK_PHASE env var here: the script's own filename already tells it + # which phase it's running for, so a separate variable would just repeat + # that. Share logic across phases by sourcing a common file instead. + + [ -f "$CACHE" ] || rebuild_cache + + VETOED=0 + while IFS= read -r pak; do + [ -n "$pak" ] || continue + [ -x "$pak/$SCRIPT_NAME" ] || continue + if ! "$pak/$SCRIPT_NAME"; then + VETOED=1 + fi + done < "$CACHE" + + # only pre-launch actually vetoes anything; every other phase is + # fire-and-forget and callers don't check this exit code + exit $VETOED +} + +case "$1" in + rebuild-cache) rebuild_cache ;; + boot|pre-launch|post-launch|pre-sleep|post-resume) run_phase "$1" ;; +esac diff --git a/skeleton/SYSTEM/tg5050/bin/run_hooks.sh b/skeleton/SYSTEM/tg5050/bin/run_hooks.sh index 7e6886546..d7c81f563 100755 --- a/skeleton/SYSTEM/tg5050/bin/run_hooks.sh +++ b/skeleton/SYSTEM/tg5050/bin/run_hooks.sh @@ -1,5 +1,11 @@ #!/bin/sh -# run_hooks.sh - shared hook runner for NextUI +# run_hooks.sh - DEPRECATED legacy hook runner for $USERDATA_PATH/.hooks/ +# +# Superseded by pak-hooks.sh: self-registering, no arming step, no orphaned +# scripts left behind when a pak is deleted. See HOOKS.md. Kept only so a +# pak that already dropped scripts into .hooks/ doesn't silently stop +# working -- do not build anything new against this mechanism. +# # Usage: run_hooks.sh [--sync-only] # # dir-name: directory name under $USERDATA_PATH/.hooks/ (e.g. pre-launch.d, boot.d) @@ -14,10 +20,19 @@ SYNC_ONLY="${2:-}" : "${SDCARD_PATH:=/mnt/SDCARD}" : "${PLATFORM:=tg5050}" : "${USERDATA_PATH:=$SDCARD_PATH/.userdata/$PLATFORM}" +: "${LOGS_PATH:=$USERDATA_PATH/logs}" HOOK_DIR="$USERDATA_PATH/.hooks/$DIR_NAME" [ -d "$HOOK_DIR" ] || exit 0 +# only warn when there's actually a script to run here -- an unused, +# never-populated .hooks/ directory shouldn't spam the log +for _probe in "$HOOK_DIR"/*.sh; do + [ -f "$_probe" ] || continue + echo "$(date): DEPRECATED - $HOOK_DIR/$(basename "$_probe") uses the legacy .hooks/ mechanism (run_hooks.sh). Migrate to a pak-scoped hook (pak-hooks.sh) -- see HOOKS.md." >> "$LOGS_PATH/hooks-deprecated.txt" + break +done + case "$DIR_NAME" in pre-*) export HOOK_PHASE="pre" ;; post-*) export HOOK_PHASE="post" ;; diff --git a/skeleton/SYSTEM/tg5050/bin/suspend b/skeleton/SYSTEM/tg5050/bin/suspend index 090cfbad5..6484300ca 100644 --- a/skeleton/SYSTEM/tg5050/bin/suspend +++ b/skeleton/SYSTEM/tg5050/bin/suspend @@ -16,9 +16,10 @@ asound_state_dir=/tmp/asound-suspend before() { >&2 echo "Preparing for suspend..." - # Run pre-sleep hooks synchronously before services are stopped. - # Subshell ensures a crashing hook cannot block suspend. - ( "$SYSTEM_PATH/bin/run_hooks.sh" pre-sleep.d --sync-only ) >/dev/null 2>&1 || true + # Run pre-sleep hooks before services are stopped. Subshell ensures a + # crashing hook cannot block suspend. + ( "$SYSTEM_PATH/bin/run_hooks.sh" pre-sleep.d --sync-only ) >/dev/null 2>&1 || true # deprecated, see HOOKS.md + ( "$SYSTEM_PATH/bin/pak-hooks.sh" pre-sleep ) >/dev/null 2>&1 || true >&2 echo "Saving mixer state..." mkdir -p "$asound_state_dir" @@ -41,7 +42,8 @@ after() { >&2 echo "Resumed from suspend." # Run post-resume hooks in background immediately after wake. - ( "$SYSTEM_PATH/bin/run_hooks.sh" post-resume.d ) >/dev/null 2>&1 & + ( "$SYSTEM_PATH/bin/run_hooks.sh" post-resume.d ) >/dev/null 2>&1 & # deprecated, see HOOKS.md + ( "$SYSTEM_PATH/bin/pak-hooks.sh" post-resume ) >/dev/null 2>&1 & if [ -n "$wifid_running" ]; then >&2 echo "Starting wpa_supplicant..." diff --git a/skeleton/SYSTEM/tg5050/paks/MinUI.pak/launch.sh b/skeleton/SYSTEM/tg5050/paks/MinUI.pak/launch.sh index ec5a9af56..06bf55dc8 100755 --- a/skeleton/SYSTEM/tg5050/paks/MinUI.pak/launch.sh +++ b/skeleton/SYSTEM/tg5050/paks/MinUI.pak/launch.sh @@ -22,7 +22,7 @@ export CORES_PATH="$SYSTEM_PATH/cores" export USERDATA_PATH="$SDCARD_PATH/.userdata/$PLATFORM" export SHARED_USERDATA_PATH="$SDCARD_PATH/.userdata/shared" export LOGS_PATH="$USERDATA_PATH/logs" -export HOOKS_PATH="$USERDATA_PATH/.hooks" +export HOOKS_PATH="$USERDATA_PATH/.hooks" # deprecated, see HOOKS.md export DATETIME_PATH="$SHARED_USERDATA_PATH/datetime.txt" export HOME="$USERDATA_PATH" @@ -45,7 +45,7 @@ mkdir -p "$SAVES_PATH" mkdir -p "$CHEATS_PATH" mkdir -p "$USERDATA_PATH" mkdir -p "$LOGS_PATH" -mkdir -p "$HOOKS_PATH" +mkdir -p "$HOOKS_PATH" # deprecated, see HOOKS.md mkdir -p "$SHARED_USERDATA_PATH/.minui" export TRIMUI_MODEL=`strings /usr/trimui/bin/MainUI | grep ^Trimui` @@ -153,7 +153,8 @@ if [ -f "$AUTO_PATH" ]; then fi # Composable boot hooks (run after auto.sh for backward compatibility) -"$SYSTEM_PATH/bin/run_hooks.sh" boot.d +"$SYSTEM_PATH/bin/run_hooks.sh" boot.d # deprecated, see HOOKS.md +"$SYSTEM_PATH/bin/pak-hooks.sh" boot cd $(dirname "$0") @@ -191,9 +192,20 @@ while [ -f $EXEC_PATH ]; do if [ -f $NEXT_PATH ]; then CMD=`cat $NEXT_PATH` parse_hook_cmd "$CMD" - "$SYSTEM_PATH/bin/run_hooks.sh" pre-launch.d - eval $CMD - "$SYSTEM_PATH/bin/run_hooks.sh" post-launch.d + # a registered pak-hooks.sh pre-launch.sh that exits non-zero cancels the + # launch (the deprecated run_hooks.sh pre-launch.d cannot veto, see + # HOOKS.md). Game Tracker.pak's own pre-launch.sh/post-launch.sh is what + # starts/stops gametimectl -- this shell has no direct knowledge of it. + "$SYSTEM_PATH/bin/run_hooks.sh" pre-launch.d # deprecated, see HOOKS.md + if "$SYSTEM_PATH/bin/pak-hooks.sh" pre-launch; then + eval $CMD + "$SYSTEM_PATH/bin/run_hooks.sh" post-launch.d # deprecated, see HOOKS.md + "$SYSTEM_PATH/bin/pak-hooks.sh" post-launch + # only a Tools pak (eg. Pak Store, Files.pak) can plausibly have + # changed what's under Tools/ -- a rom never does, so skip the + # rescan on the overwhelming majority of returns to the menu + [ "$HOOK_TYPE" = "pak" ] && "$SYSTEM_PATH/bin/pak-hooks.sh" rebuild-cache & + fi rm -f $NEXT_PATH # reset to performance when exiting, UI will reset to auto if needed sh "$SYSTEM_PATH/bin/governor.sh" "performance" diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index 19f3f5439..84222ef33 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -4081,7 +4081,9 @@ void PWR_update(int *_dirty, int *_show_setting, PWR_callback_t before_sleep, PW { if (before_sleep) before_sleep(); - system("gametimectl.elf stop_all"); + // reuse the pre-sleep hook: an outright poweroff needs the same + // "close the open session" cleanup as going to sleep does + system("pak-hooks.sh pre-sleep"); PWR_powerOff(0); } @@ -4349,7 +4351,10 @@ void PWR_sleep(void) { LOG_info("Entering hybrid sleep\n"); - system("gametimectl.elf stop_all"); + // api.c has no business knowing gametimectl exists -- Game Tracker.pak's + // own pre-sleep.sh/post-resume.sh is what actually stops/resumes + // tracking. See pak-hooks.sh. + system("pak-hooks.sh pre-sleep"); GFX_clear(gfx.screen); PAD_reset(); @@ -4358,7 +4363,7 @@ void PWR_sleep(void) PWR_exitSleep(); PAD_reset(); - system("gametimectl.elf resume"); + system("pak-hooks.sh post-resume"); pwr.resume_tick = SDL_GetTicks(); } diff --git a/workspace/all/nextui/nextui.c b/workspace/all/nextui/nextui.c index 5ed6d612e..0ef2b6e56 100644 --- a/workspace/all/nextui/nextui.c +++ b/workspace/all/nextui/nextui.c @@ -1205,12 +1205,13 @@ static int autoResume(void) { // putFile(LAST_PATH, FAUX_RECENT_PATH); // saveLast() will crash here because top is NULL - char act[256]; - sprintf(act, "gametimectl.elf start '%s'", escapeSingleQuotes(sd_path)); - system(act); + // gametimectl.elf start is now Game Tracker.pak's own pre-launch.sh, run by + // pak-hooks.sh -- this file has no direct knowledge of gametimectl any + // more. Still need the escaping side effect on sd_path below though. + escapeSingleQuotes(sd_path); char cmd[256]; - // dont escape sd_path again because it was already escaped for gametimectl and function modifies input str aswell + // dont escape sd_path again -- it was already escaped above, and the function modifies its input str sprintf(cmd, "'%s' '%s'", escapeSingleQuotes(emu_path), sd_path); putInt(RESUME_SLOT_PATH, AUTO_RESUME_SLOT); queueNext(cmd); @@ -1319,11 +1320,12 @@ static void openRom(char* path, char* last) { // so we need to save the path before we call that addRecent(recent_path, recent_alias); // yiiikes saveLast(last==NULL ? sd_path : last); - char act[256]; - sprintf(act, "gametimectl.elf start '%s'", escapeSingleQuotes(sd_path)); - system(act); + // gametimectl.elf start is now Game Tracker.pak's own pre-launch.sh, run by + // pak-hooks.sh -- this file has no direct knowledge of gametimectl any + // more. Still need the escaping side effect on sd_path below though. + escapeSingleQuotes(sd_path); char cmd[256]; - // dont escape sd_path again because it was already escaped for gametimectl and function modifies input str aswell + // dont escape sd_path again -- it was already escaped above, and the function modifies its input str sprintf(cmd, "'%s' '%s'", escapeSingleQuotes(emu_path), sd_path); queueNext(cmd); } @@ -2290,9 +2292,15 @@ int main (int argc, char *argv[]) { if(currentScreen == SCREEN_GAMESWITCHER) lastScreen = SCREEN_GAME; - // make sure we have no running games logged as active anymore (we might be launching back into the UI here) + // make sure we have no running games logged as active anymore (we might be + // launching back into the UI here). Kept as a direct call, not a hook: this + // is a defensive catch-all for any process that dies without a clean + // post-launch (crash, forced quit), not one of the regular lifecycle events. system("gametimectl.elf stop_all"); + // the pak-hooks.sh cache is rebuilt from MinUI.pak/launch.sh right after a + // Tools pak (not a rom) exits, not here -- see HOOKS.md. + GFX_setVsync(VSYNC_STRICT); PWR_setCPUSpeed(CPU_SPEED_AUTO);