From 0dd4bacf96973d64bdc4990d35ee113637920af0 Mon Sep 17 00:00:00 2001 From: Ronald Brown Date: Thu, 8 Oct 2026 22:38:33 +0200 Subject: [PATCH 1/3] Lakka: Show Install Update on reboot entry, if update exists. --- intl/msg_hash_us.h | 4 ++++ lakka.h | 13 +++++++++++++ menu/cbs/menu_cbs_get_value.c | 29 ++++++++++++++++++++++++++++- msg_hash.h | 1 + retroarch.c | 27 +++++++++++++++++++++++++++ 5 files changed, 73 insertions(+), 1 deletion(-) diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 122eeb50225a..86693132cdf2 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -33160,6 +33160,10 @@ MSG_HASH( MSG_VALUE_REBOOTING, "Rebooting..." ) +MSG_HASH( + MSG_VALUE_INSTALL_UPDATE, + "Install Update" + ) MSG_HASH( MSG_VALUE_SHUTTING_DOWN, "Shutting down..." diff --git a/lakka.h b/lakka.h index 2e187dc3580b..39c7a540f200 100644 --- a/lakka.h +++ b/lakka.h @@ -18,6 +18,9 @@ #ifndef __RARCH_LAKKA_H #define __RARCH_LAKKA_H +#include +#include + #define LAKKA_SSH_PATH "/storage/.cache/services/sshd.conf" #define LAKKA_SAMBA_PATH "/storage/.cache/services/samba.conf" #define LAKKA_SAMBA_DISABLED_FILE_PATH "/storage/.cache/services/samba.disabled" @@ -33,4 +36,14 @@ #define DEFAULT_TIMEZONE "UTC" #define TIMEZONE_LENGTH 255 +RETRO_BEGIN_DECLS + +/* Returns true when an update image is staged in LAKKA_UPDATE_DIR, + * i.e. a file matching one of the patterns the Lakka initramfs + * check_update step installs on the next boot (*.tar, *.img.gz, + * *.img). */ +bool lakka_update_pending(void); + +RETRO_END_DECLS + #endif diff --git a/menu/cbs/menu_cbs_get_value.c b/menu/cbs/menu_cbs_get_value.c index df677551c826..76f8f3eee9d0 100644 --- a/menu/cbs/menu_cbs_get_value.c +++ b/menu/cbs/menu_cbs_get_value.c @@ -47,6 +47,9 @@ #include "../../playlist.h" #include "../../manual_content_scan.h" #include "../misc/cpufreq/cpufreq.h" +#ifdef HAVE_LAKKA +#include "../../lakka.h" +#endif #include "../../audio/audio_driver.h" #ifdef HAVE_NETWORKING @@ -691,6 +694,26 @@ static size_t menu_action_cpu_governor_label( MENU_ENUM_LABEL_VALUE_CPU_POLICY_GOVERNOR), len2); return strlcpy(s, d->scaling_governor, len); } + +/* Value column of the main menu "Reboot" entry: shows + * "Install Update" while an update image is staged, so the user + * knows the next reboot will install it. Empty otherwise. */ +static size_t menu_action_setting_disp_reboot( + file_list_t* list, + unsigned *w, unsigned type, unsigned i, + const char *label, + char *s, size_t len, + const char *path, + char *s2, size_t len2) +{ + *s = '\0'; + *w = 19; + if (path) + strlcpy(s2, path, len2); + if (lakka_update_pending()) + return strlcpy(s, msg_hash_to_str(MSG_VALUE_INSTALL_UPDATE), len); + return 0; +} #endif static size_t menu_action_setting_disp_set_label_core_lock( @@ -2475,12 +2498,16 @@ int menu_cbs_init_bind_get_string_representation(menu_file_list_cbs_t *cbs, #ifdef HAVE_NETWORKING BIND_ACTION_GET_VALUE(cbs, menu_action_setting_disp_set_label_netplay_mitm_server); +#endif + return 0; + case MENU_ENUM_LABEL_REBOOT: +#ifdef HAVE_LAKKA + BIND_ACTION_GET_VALUE(cbs, menu_action_setting_disp_reboot); #endif return 0; case MENU_ENUM_LABEL_RESTART_RETROARCH: case MENU_ENUM_LABEL_QUIT_RETROARCH: case MENU_ENUM_LABEL_SWITCH_GPU_PROFILE: - case MENU_ENUM_LABEL_REBOOT: case MENU_ENUM_LABEL_SHUTDOWN: case MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG: case MENU_ENUM_LABEL_SAVE_NEW_CONFIG: diff --git a/msg_hash.h b/msg_hash.h index 1bef71b7ffe8..6711dec86647 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -373,6 +373,7 @@ enum msg_hash_enums MSG_FILE_NOT_FOUND, MSG_VALUE_SHUTTING_DOWN, MSG_VALUE_REBOOTING, + MSG_VALUE_INSTALL_UPDATE, MSG_FAILED_TO_START_AUDIO_DRIVER, MSG_FOUND_LAST_STATE_SLOT, MSG_FOUND_LAST_REPLAY_SLOT, diff --git a/retroarch.c b/retroarch.c index 93d375b3349a..d827f7f80247 100644 --- a/retroarch.c +++ b/retroarch.c @@ -269,12 +269,39 @@ #endif #ifdef HAVE_LAKKA +#include #include "lakka.h" #include #endif #define _PSUPP(var, name, desc) printf(" %s:\n\t\t%s: %s\n", name, desc, var ? "yes" : "no") +#ifdef HAVE_LAKKA +bool lakka_update_pending(void) +{ + bool found = false; + struct RDIR *dir = retro_opendir(LAKKA_UPDATE_DIR); + + if (!dir) + return false; + + while (!found && retro_readdir(dir)) + { + const char *name = retro_dirent_get_name(dir); + + if (retro_dirent_is_dir(dir, NULL)) + continue; + + found = string_ends_with(name, ".tar") + || string_ends_with(name, ".img.gz") + || string_ends_with(name, ".img"); + } + + retro_closedir(dir); + return found; +} +#endif + #define FAIL_CPU(simd_type) do { \ RARCH_ERR(simd_type " code is compiled in, but CPU does not support this feature. Cannot continue.\n"); \ retroarch_fail(1, "validate_cpu_features()"); \ From 5c9d4aa7e41134a443e622d0113e4cf3f66c8ee9 Mon Sep 17 00:00:00 2001 From: Ronald Brown Date: Thu, 8 Oct 2026 23:29:20 +0200 Subject: [PATCH 2/3] Lakka: Switch: Add reboot2payload driver --- Makefile.common | 1 + config.def.h | 1 + configuration.c | 7 + configuration.h | 11 + intl/msg_hash_lbl.h | 8 + intl/msg_hash_us.h | 16 + menu/cbs/menu_cbs_get_value.c | 39 ++- menu/cbs/menu_cbs_left.c | 19 ++ menu/cbs/menu_cbs_ok.c | 8 + menu/cbs/menu_cbs_right.c | 19 ++ menu/cbs/menu_cbs_sublabel.c | 8 + menu/menu_displaylist.c | 43 ++- menu/menu_setting.c | 15 + misc/reboot2payload/reboot2payload.c | 422 +++++++++++++++++++++++++++ misc/reboot2payload/reboot2payload.h | 76 +++++ msg_hash.h | 2 + msg_hash_lbl_str.h | 2 + retroarch.c | 27 ++ 18 files changed, 711 insertions(+), 13 deletions(-) create mode 100644 misc/reboot2payload/reboot2payload.c create mode 100644 misc/reboot2payload/reboot2payload.h diff --git a/Makefile.common b/Makefile.common index 68b2e6ba5e95..ae9a71c19ff8 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1039,6 +1039,7 @@ endif ifeq ($(HAVE_LAKKA_SWITCH), 1) DEFINES += -DHAVE_LAKKA_SWITCH + OBJ += misc/reboot2payload/reboot2payload.o endif ifeq ($(HAVE_MENU_COMMON), 1) diff --git a/config.def.h b/config.def.h index ac7dde5a5c38..e047bfd64e4a 100644 --- a/config.def.h +++ b/config.def.h @@ -214,6 +214,7 @@ #define DEFAULT_SWITCH_OC false #define DEFAULT_SWITCH_CEC true #define DEFAULT_BLUETOOTH_ERTM false +#define DEFAULT_REBOOT_FORCE_SELF_ON_UPDATE true #endif #if (defined(_WIN32) && !defined(_XBOX)) || (defined(__linux) && !defined(ANDROID) && !defined(HAVE_LAKKA)) || (defined(__MACH__) && !TARGET_OS_IPHONE) || defined(__EMSCRIPTEN__) diff --git a/configuration.c b/configuration.c index 0a66a428bd0c..0f9a2874b30b 100644 --- a/configuration.c +++ b/configuration.c @@ -2054,6 +2054,9 @@ static struct config_array_setting *populate_settings_array( SETTING_ARRAY("cpu_main_gov", settings->arrays.cpu_main_gov, false, NULL, true); SETTING_ARRAY("cpu_menu_gov", settings->arrays.cpu_menu_gov, false, NULL, true); #endif +#ifdef HAVE_LAKKA_SWITCH + SETTING_ARRAY("reboot_payload_name", settings->arrays.reboot_payload_name, false, NULL, true); +#endif *size = count; @@ -2258,6 +2261,7 @@ static struct config_bool_setting *populate_settings_bool( SETTING_BOOL("switch_oc", &settings->bools.switch_oc, true, DEFAULT_SWITCH_OC, false); SETTING_BOOL("switch_cec", &settings->bools.switch_cec, true, DEFAULT_SWITCH_CEC, false); SETTING_BOOL("bluetooth_ertm_disable", &settings->bools.bluetooth_ertm_disable, true, DEFAULT_BLUETOOTH_ERTM, false); + SETTING_BOOL("reboot_force_self_on_update", &settings->bools.reboot_force_self_on_update, true, DEFAULT_REBOOT_FORCE_SELF_ON_UPDATE, false); #endif SETTING_BOOL("audio_rate_control", &settings->bools.audio_rate_control, true, DEFAULT_RATE_CONTROL, false); SETTING_BOOL("audio_enable_menu_ok", &settings->bools.audio_enable_menu_ok, true, DEFAULT_AUDIO_ENABLE_MENU_OK, false); @@ -4398,6 +4402,9 @@ static struct config_uint_setting *populate_settings_uint( SETTING_UINT("cpu_min_freq", &settings->uints.cpu_min_freq, true, 1, false); SETTING_UINT("cpu_max_freq", &settings->uints.cpu_max_freq, true, ~0U, false); #endif +#ifdef HAVE_LAKKA_SWITCH + SETTING_UINT("reboot_payload_kind", &settings->uints.reboot_payload_kind, true, 0, false); +#endif diff --git a/configuration.h b/configuration.h index 8f69a2d2e5d9..58581bdd7141 100644 --- a/configuration.h +++ b/configuration.h @@ -449,6 +449,10 @@ typedef struct settings unsigned cpu_min_freq; unsigned cpu_max_freq; #endif +#ifdef HAVE_LAKKA_SWITCH + /* enum r2p_payload_kind; paired with reboot_payload_name */ + unsigned reboot_payload_kind; +#endif #ifdef HAVE_MIST unsigned steam_rich_presence_format; @@ -1128,6 +1132,10 @@ typedef struct settings bool switch_oc; bool switch_cec; bool bluetooth_ertm_disable; + /* When an update is staged, reboot back into the running + * Lakka entry instead of the selected payload so the + * initramfs installs it. */ + bool reboot_force_self_on_update; #endif #ifdef HAVE_LAKKA bool ssh_enable; @@ -1247,6 +1255,9 @@ typedef struct settings char cpu_main_gov[32]; char cpu_menu_gov[32]; #endif +#ifdef HAVE_LAKKA_SWITCH + char reboot_payload_name[64]; /* R2P_NAME_MAXLEN */ +#endif #ifdef HAVE_MICROPHONE char microphone_driver[32]; char microphone_resampler[32]; diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index f7b2966d9757..bd864d4adaea 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -2494,6 +2494,14 @@ MSG_HASH( MENU_ENUM_LABEL_BLUETOOTH_ERTM_DISABLE, MENU_ENUM_LABEL_BLUETOOTH_ERTM_DISABLE_STR ) +MSG_HASH( + MENU_ENUM_LABEL_REBOOT_PAYLOAD, + MENU_ENUM_LABEL_REBOOT_PAYLOAD_STR + ) +MSG_HASH( + MENU_ENUM_LABEL_REBOOT_FORCE_SELF_ON_UPDATE, + MENU_ENUM_LABEL_REBOOT_FORCE_SELF_ON_UPDATE_STR + ) #endif MSG_HASH( MENU_ENUM_LABEL_SYSTEM_BGM_ENABLE, diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 86693132cdf2..3f7a7aca6249 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -35578,6 +35578,22 @@ MSG_HASH( MENU_ENUM_SUBLABEL_BLUETOOTH_ERTM_DISABLE, "Disable Bluetooth ERTM to fix pairing of some devices" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_REBOOT_PAYLOAD, + "Reboot Payload" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_REBOOT_PAYLOAD, + "Choose which Hekate entry 'Reboot' in the Main Menu boots into. Use Left/Right to cycle through the entries in hekate_ipl.ini and bootloader/ini/. Defaults to the entry currently running." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_REBOOT_FORCE_SELF_ON_UPDATE, + "Force Reboot to Lakka if Update Exists" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_REBOOT_FORCE_SELF_ON_UPDATE, + "When a Lakka update is staged, reboot back into Lakka instead of the selected payload so the update is installed. Disable to always honour the payload selection." + ) #endif MSG_HASH( MSG_LOCALAP_SWITCHING_OFF, diff --git a/menu/cbs/menu_cbs_get_value.c b/menu/cbs/menu_cbs_get_value.c index 76f8f3eee9d0..70842214e515 100644 --- a/menu/cbs/menu_cbs_get_value.c +++ b/menu/cbs/menu_cbs_get_value.c @@ -50,6 +50,9 @@ #ifdef HAVE_LAKKA #include "../../lakka.h" #endif +#ifdef HAVE_LAKKA_SWITCH +#include "../../misc/reboot2payload/reboot2payload.h" +#endif #include "../../audio/audio_driver.h" #ifdef HAVE_NETWORKING @@ -697,7 +700,9 @@ static size_t menu_action_cpu_governor_label( /* Value column of the main menu "Reboot" entry: shows * "Install Update" while an update image is staged, so the user - * knows the next reboot will install it. Empty otherwise. */ + * knows the next reboot will install it. On Switch with pmc_r2p it + * otherwise shows the selected reboot payload (and an update only + * wins when reboot_force_self_on_update is set). Empty otherwise. */ static size_t menu_action_setting_disp_reboot( file_list_t* list, unsigned *w, unsigned type, unsigned i, @@ -710,12 +715,38 @@ static size_t menu_action_setting_disp_reboot( *w = 19; if (path) strlcpy(s2, path, len2); +#ifdef HAVE_LAKKA_SWITCH + if (r2p_is_supported()) + { + settings_t *settings = config_get_ptr(); + if ( settings->bools.reboot_force_self_on_update + && lakka_update_pending()) + return strlcpy(s, msg_hash_to_str(MSG_VALUE_INSTALL_UPDATE), len); + return strlcpy(s, r2p_get_destination_name(), len); + } +#endif if (lakka_update_pending()) return strlcpy(s, msg_hash_to_str(MSG_VALUE_INSTALL_UPDATE), len); return 0; } #endif +#ifdef HAVE_LAKKA_SWITCH +static size_t menu_action_setting_disp_reboot_payload( + file_list_t* list, + unsigned *w, unsigned type, unsigned i, + const char *label, + char *s, size_t len, + const char *path, + char *s2, size_t len2) +{ + *w = 19; + if (path) + strlcpy(s2, path, len2); + return strlcpy(s, r2p_get_destination_name(), len); +} +#endif + static size_t menu_action_setting_disp_set_label_core_lock( file_list_t* list, unsigned *w, unsigned type, unsigned i, @@ -2229,6 +2260,12 @@ static int menu_cbs_init_bind_get_string_representation_compare_label( BIND_ACTION_GET_VALUE(cbs, menu_action_setting_disp_cpu_gov_mode); break; +#ifdef HAVE_LAKKA_SWITCH + case MENU_ENUM_LABEL_REBOOT_PAYLOAD: + BIND_ACTION_GET_VALUE(cbs, + menu_action_setting_disp_reboot_payload); + break; +#endif case MENU_ENUM_LABEL_CPU_POLICY_CORE_GOVERNOR: case MENU_ENUM_LABEL_CPU_POLICY_MENU_GOVERNOR: BIND_ACTION_GET_VALUE(cbs, diff --git a/menu/cbs/menu_cbs_left.c b/menu/cbs/menu_cbs_left.c index 90ff2eb9b8a8..9901de9a3ba8 100644 --- a/menu/cbs/menu_cbs_left.c +++ b/menu/cbs/menu_cbs_left.c @@ -48,6 +48,9 @@ #include "../../playlist.h" #include "../../manual_content_scan.h" #include "../misc/cpufreq/cpufreq.h" +#ifdef HAVE_LAKKA_SWITCH +#include "../../misc/reboot2payload/reboot2payload.h" +#endif #ifndef BIND_ACTION_LEFT #define BIND_ACTION_LEFT(cbs, name) (cbs)->action_left = (name) @@ -952,6 +955,17 @@ static int cpu_policy_freq_tweak(unsigned type, const char *label, } #endif +#ifdef HAVE_LAKKA_SWITCH +static int reboot_payload_left(unsigned type, const char *label, + bool wraparound) +{ + struct menu_state *menu_st = menu_state_get_ptr(); + r2p_cycle_selection(config_get_ptr(), false); + menu_st->flags |= MENU_ST_FLAG_ENTRIES_NEED_REFRESH; + return 0; +} +#endif + static int core_setting_left(unsigned type, const char *label, bool wraparound) { @@ -1323,6 +1337,11 @@ static int menu_cbs_init_bind_left_compare_label(menu_file_list_cbs_t *cbs, BIND_ACTION_LEFT(cbs, cpu_policy_freq_managed_gov); break; #endif + #ifdef HAVE_LAKKA_SWITCH + case MENU_ENUM_LABEL_REBOOT_PAYLOAD: + BIND_ACTION_LEFT(cbs, reboot_payload_left); + break; + #endif default: return -1; } diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index 5dfeb6d5ff5e..d22f1814db23 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -7087,6 +7087,13 @@ STATIC_DEFAULT_ACTION_OK_FUNC(action_ok_menu_bottom_list, ACTION_OK_DL_MENU_BOTT #endif #ifdef HAVE_LAKKA_SWITCH STATIC_DEFAULT_ACTION_OK_FUNC(action_ok_lakka_switch_options, ACTION_OK_DL_LAKKA_SWITCH_OPTIONS_LIST) + +/* Left/Right cycles the payload selection; OK does nothing. */ +static int action_ok_reboot_payload(const char *path, const char *label, + unsigned type, size_t idx, size_t entry_idx) +{ + return 0; +} #endif STATIC_DEFAULT_ACTION_OK_FUNC(action_ok_rdb_entry, ACTION_OK_DL_RDB_ENTRY) #ifdef HAVE_AUDIOMIXER @@ -9929,6 +9936,7 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs, #endif #ifdef HAVE_LAKKA_SWITCH {MENU_ENUM_LABEL_LAKKA_SWITCH_OPTIONS, action_ok_lakka_switch_options}, + {MENU_ENUM_LABEL_REBOOT_PAYLOAD, action_ok_reboot_payload}, #endif {MENU_ENUM_LABEL_SCREEN_RESOLUTION, action_ok_video_resolution}, {MENU_ENUM_LABEL_PLAYLIST_MANAGER_DEFAULT_CORE, action_ok_playlist_default_core}, diff --git a/menu/cbs/menu_cbs_right.c b/menu/cbs/menu_cbs_right.c index 7becce7d1b91..1a5c490be398 100644 --- a/menu/cbs/menu_cbs_right.c +++ b/menu/cbs/menu_cbs_right.c @@ -49,6 +49,9 @@ #include "../../playlist.h" #include "../../manual_content_scan.h" #include "../misc/cpufreq/cpufreq.h" +#ifdef HAVE_LAKKA_SWITCH +#include "../../misc/reboot2payload/reboot2payload.h" +#endif #ifndef BIND_ACTION_RIGHT #define BIND_ACTION_RIGHT(cbs, name) (cbs)->action_right = (name) @@ -1128,6 +1131,17 @@ static int cpu_policy_freq_tweak(unsigned type, const char *label, } #endif +#ifdef HAVE_LAKKA_SWITCH +static int reboot_payload_right(unsigned type, const char *label, + bool wraparound) +{ + struct menu_state *menu_st = menu_state_get_ptr(); + r2p_cycle_selection(config_get_ptr(), true); + menu_st->flags |= MENU_ST_FLAG_ENTRIES_NEED_REFRESH; + return 0; +} +#endif + static int core_setting_right(unsigned type, const char *label, bool wraparound) { @@ -1472,6 +1486,11 @@ static int menu_cbs_init_bind_right_compare_label(menu_file_list_cbs_t *cbs, BIND_ACTION_RIGHT(cbs, cpu_policy_freq_managed_gov); break; #endif + #ifdef HAVE_LAKKA_SWITCH + case MENU_ENUM_LABEL_REBOOT_PAYLOAD: + BIND_ACTION_RIGHT(cbs, reboot_payload_right); + break; + #endif default: return -1; } diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index 9b414c52e587..0af75b1f16bc 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -287,6 +287,8 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_switch_options, MENU_ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_switch_oc_enable, MENU_ENUM_SUBLABEL_SWITCH_OC_ENABLE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_switch_cec_enable, MENU_ENUM_SUBLABEL_SWITCH_CEC_ENABLE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_bluetooth_ertm_disable, MENU_ENUM_SUBLABEL_BLUETOOTH_ERTM_DISABLE) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_reboot_payload, MENU_ENUM_SUBLABEL_REBOOT_PAYLOAD) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_reboot_force_self_on_update, MENU_ENUM_SUBLABEL_REBOOT_FORCE_SELF_ON_UPDATE) #endif DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_user_remap_settings, MENU_ENUM_SUBLABEL_INPUT_USER_REMAPS) @@ -3774,6 +3776,12 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_BLUETOOTH_ERTM_DISABLE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_bluetooth_ertm_disable); break; + case MENU_ENUM_LABEL_REBOOT_PAYLOAD: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_reboot_payload); + break; + case MENU_ENUM_LABEL_REBOOT_FORCE_SELF_ON_UPDATE: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_reboot_force_self_on_update); + break; #endif #ifdef HAVE_VIDEO_FILTER case MENU_ENUM_LABEL_VIDEO_FILTER_ENABLE: diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 43ea406484c7..b7671f9f3091 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -145,6 +145,9 @@ #include "../manual_content_scan.h" #include "../core_backup.h" #include "../misc/cpufreq/cpufreq.h" +#ifdef HAVE_LAKKA_SWITCH +#include "../misc/reboot2payload/reboot2payload.h" +#endif #include "../input/input_remapping.h" #ifdef HAVE_MICROPHONE @@ -12420,21 +12423,37 @@ unsigned menu_displaylist_build_list( #endif #ifdef HAVE_LAKKA_SWITCH case DISPLAYLIST_LAKKA_SWITCH_OPTIONS_LIST: + if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, + MENU_ENUM_LABEL_SWITCH_OC_ENABLE, + PARSE_ONLY_BOOL, false) == 0) + count++; + + /* Reboot-to-payload needs the pmc_r2p kernel driver. The + * payload selector has no backing rarch_setting_t; its + * Left/Right/OK/value callbacks live in menu/cbs. */ + if (r2p_is_supported()) { - static const menu_displaylist_build_info_t build_list[] = { - {MENU_ENUM_LABEL_SWITCH_OC_ENABLE, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_SWITCH_CEC_ENABLE, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_BLUETOOTH_ERTM_DISABLE, PARSE_ONLY_BOOL}, - }; + if (menu_entries_append(list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_REBOOT_PAYLOAD), + msg_hash_to_str(MENU_ENUM_LABEL_REBOOT_PAYLOAD), + MENU_ENUM_LABEL_REBOOT_PAYLOAD, + MENU_SETTING_ACTION, 0, 0, NULL)) + count++; - for (i = 0; i < ARRAY_SIZE(build_list); i++) - { - if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, - build_list[i].enum_idx, build_list[i].parse_type, - false) == 0) - count++; - } + if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, + MENU_ENUM_LABEL_REBOOT_FORCE_SELF_ON_UPDATE, + PARSE_ONLY_BOOL, false) == 0) + count++; } + + if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, + MENU_ENUM_LABEL_SWITCH_CEC_ENABLE, + PARSE_ONLY_BOOL, false) == 0) + count++; + if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, + MENU_ENUM_LABEL_BLUETOOTH_ERTM_DISABLE, + PARSE_ONLY_BOOL, false) == 0) + count++; break; #endif case DISPLAYLIST_MENU_VIEWS_SETTINGS_LIST: diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 4950ebce6613..2538a97bffa3 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -18182,6 +18182,21 @@ static void settings_build_lakka_switch_options( general_read_handler, SD_FLAG_NONE); SETTINGS_ACTION_SET(change, &(*list)[list_info->index - 1], bluetooth_ertm_disable_toggle_change_handler) + + CONFIG_BOOL( + list, list_info, + &settings->bools.reboot_force_self_on_update, + MENU_ENUM_LABEL_REBOOT_FORCE_SELF_ON_UPDATE, + MENU_ENUM_LABEL_VALUE_REBOOT_FORCE_SELF_ON_UPDATE, + DEFAULT_REBOOT_FORCE_SELF_ON_UPDATE, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE); GROUP_END(); } diff --git a/misc/reboot2payload/reboot2payload.c b/misc/reboot2payload/reboot2payload.c new file mode 100644 index 000000000000..89ef1d5e131d --- /dev/null +++ b/misc/reboot2payload/reboot2payload.c @@ -0,0 +1,422 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2026 - gavin_darkglider + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "reboot2payload.h" +#include "../../configuration.h" +#include "../../verbosity.h" + +#define R2P_SYSFS_DIR "/sys/devices/r2p" +#define R2P_SYSFS_ACTION R2P_SYSFS_DIR "/action" +#define R2P_SYSFS_PARAM1 R2P_SYSFS_DIR "/param1" +#define R2P_SYSFS_PARAM2 R2P_SYSFS_DIR "/param2" + +/* Lakka's initramfs always mounts the FAT boot partition at /flash. */ +#define R2P_BOOT_ROOT "/flash" +#define R2P_HEKATE_IPL R2P_BOOT_ROOT "/bootloader/hekate_ipl.ini" +#define R2P_HEKATE_INIDIR R2P_BOOT_ROOT "/bootloader/ini" + +#define R2P_CMDLINE_PATH "/proc/cmdline" +#define R2P_DEFAULT_ACTION "self" +#define R2P_SELF_SUFFIX " (self)" +#define R2P_HEKATE_MENU "Hekate Menu" + +enum r2p_payload_kind +{ + R2P_KIND_BOOTLOADER = 0, /* action=bootloader */ + R2P_KIND_LAUNCH, /* action=self, param2=0 (ipl.ini) */ + R2P_KIND_INI_CONFIG /* action=self, param2=1 (ini/) */ +}; + +typedef struct r2p_payload +{ + char name[R2P_NAME_MAXLEN]; + enum r2p_payload_kind kind; + unsigned index; +} r2p_payload_t; + +static r2p_payload_t *r2p_list = NULL; +static size_t r2p_list_count = 0; +static size_t r2p_list_capacity = 0; +static size_t r2p_selected = 0; +static bool r2p_list_initialized = false; + +/* pmc_r2p.* values from /proc/cmdline: the entry Lakka was booted + * from. Seeds the default selection and feeds r2p_arm_self(). */ +static char r2p_self_action[16] = R2P_DEFAULT_ACTION; +static unsigned r2p_self_param1 = 0; +static unsigned r2p_self_param2 = 0; + +static bool r2p_append_payload(const char *name, + enum r2p_payload_kind kind, unsigned index) +{ + r2p_payload_t *slot; + + if (string_is_empty(name)) + return false; + + if (r2p_list_count >= r2p_list_capacity) + { + size_t new_cap = (r2p_list_capacity == 0) + ? 8 : r2p_list_capacity * 2; + r2p_payload_t *grown = (r2p_payload_t*)realloc(r2p_list, + new_cap * sizeof(*grown)); + if (!grown) + return false; + r2p_list = grown; + r2p_list_capacity = new_cap; + } + + slot = &r2p_list[r2p_list_count++]; + strlcpy(slot->name, name, sizeof(slot->name)); + slot->kind = kind; + slot->index = index; + return true; +} + +static char *r2p_trim(char *s) +{ + char *end; + while (*s && isspace((unsigned char)*s)) + s++; + end = s + strlen(s); + while (end > s && isspace((unsigned char)end[-1])) + end--; + *end = '\0'; + return s; +} + +static void r2p_parse_cmdline(void) +{ + RFILE *fp; + char line[1024]; + char *tok; + char *saveptr = NULL; + + strlcpy(r2p_self_action, R2P_DEFAULT_ACTION, sizeof(r2p_self_action)); + r2p_self_param1 = 0; + r2p_self_param2 = 0; + + if (!(fp = filestream_open(R2P_CMDLINE_PATH, + RETRO_VFS_FILE_ACCESS_READ, + RETRO_VFS_FILE_ACCESS_HINT_NONE))) + return; + + if (!filestream_gets(fp, line, sizeof(line))) + { + filestream_close(fp); + return; + } + filestream_close(fp); + + for (tok = strtok_r(line, " \t\r\n", &saveptr); + tok; + tok = strtok_r(NULL, " \t\r\n", &saveptr)) + { + if (string_starts_with(tok, "pmc_r2p.action=")) + strlcpy(r2p_self_action, tok + STRLEN_CONST("pmc_r2p.action="), + sizeof(r2p_self_action)); + else if (string_starts_with(tok, "pmc_r2p.param1=")) + r2p_self_param1 = (unsigned)strtoul( + tok + STRLEN_CONST("pmc_r2p.param1="), NULL, 0); + else if (string_starts_with(tok, "pmc_r2p.param2=")) + r2p_self_param2 = (unsigned)strtoul( + tok + STRLEN_CONST("pmc_r2p.param2="), NULL, 0); + } +} + +/* Appends one entry per [section] of a Hekate INI, numbering them + * from *index_inout the same way Hekate does. */ +static void r2p_parse_ini_file(const char *ini_path, + enum r2p_payload_kind kind, unsigned *index_inout) +{ + char line[256]; + RFILE *fp = filestream_open(ini_path, + RETRO_VFS_FILE_ACCESS_READ, + RETRO_VFS_FILE_ACCESS_HINT_NONE); + + if (!fp) + return; + + while (filestream_gets(fp, line, sizeof(line))) + { + char *section = r2p_trim(line); + size_t _len = strlen(section); + + if (_len < 2 || section[0] != '[' || section[_len - 1] != ']') + continue; + + section[_len - 1] = '\0'; + section = r2p_trim(section + 1); + + /* [config] holds Hekate's global options, not a boot entry. */ + if ( string_is_empty(section) + || string_is_equal_case_insensitive(section, "config")) + continue; + + r2p_append_payload(section, kind, *index_inout); + (*index_inout)++; + } + + filestream_close(fp); +} + +static void r2p_clear_payloads(void) +{ + free(r2p_list); + r2p_list = NULL; + r2p_list_count = 0; + r2p_list_capacity = 0; +} + +/* List index of the entry Lakka was booted from, or 0 (Hekate menu) + * when no parsed entry matches the kernel command line. */ +static size_t r2p_index_for_self(void) +{ + size_t i; + enum r2p_payload_kind want_kind; + + if (!string_is_equal_case_insensitive(r2p_self_action, "self")) + return 0; + + want_kind = (r2p_self_param2 == 1) + ? R2P_KIND_INI_CONFIG + : R2P_KIND_LAUNCH; + + for (i = 0; i < r2p_list_count; i++) + if ( r2p_list[i].kind == want_kind + && r2p_list[i].index == r2p_self_param1) + return i; + + return 0; +} + +static void r2p_refresh_payloads(void) +{ + unsigned launch_index = 1; + unsigned cfg_index = 1; + + r2p_clear_payloads(); + r2p_parse_cmdline(); + + r2p_append_payload(R2P_HEKATE_MENU, R2P_KIND_BOOTLOADER, 0); + + if (path_is_valid(R2P_HEKATE_IPL)) + r2p_parse_ini_file(R2P_HEKATE_IPL, R2P_KIND_LAUNCH, &launch_index); + + if (path_is_directory(R2P_HEKATE_INIDIR)) + { + struct string_list *ini_files = dir_list_new(R2P_HEKATE_INIDIR, + "ini", false, false, false, false); + if (ini_files) + { + size_t i; + dir_list_sort(ini_files, true); + for (i = 0; i < ini_files->size; i++) + r2p_parse_ini_file(ini_files->elems[i].data, + R2P_KIND_INI_CONFIG, &cfg_index); + string_list_free(ini_files); + } + } + + r2p_selected = r2p_index_for_self(); + r2p_list_initialized = true; + + RARCH_LOG("[R2P] %u entries loaded; cmdline action=%s " + "param1=%u param2=%u, default idx=%u.\n", + (unsigned)r2p_list_count, r2p_self_action, + r2p_self_param1, r2p_self_param2, + (unsigned)r2p_selected); +} + +static void r2p_ensure_initialized(void) +{ + if (!r2p_list_initialized) + r2p_refresh_payloads(); +} + +bool r2p_is_supported(void) +{ + return path_is_directory(R2P_SYSFS_DIR); +} + +void r2p_load_selection(const struct settings *settings) +{ + size_t i; + unsigned saved_kind; + const char *saved_name; + + r2p_refresh_payloads(); + + if (!settings) + return; + + saved_kind = settings->uints.reboot_payload_kind; + saved_name = settings->arrays.reboot_payload_name; + + if (string_is_empty(saved_name)) + return; + + for (i = 0; i < r2p_list_count; i++) + { + if ( (unsigned)r2p_list[i].kind == saved_kind + && string_is_equal(r2p_list[i].name, saved_name)) + { + r2p_selected = i; + return; + } + } + + RARCH_WARN("[R2P] Saved payload \"%s\" not found, " + "using the running entry.\n", saved_name); +} + +void r2p_cycle_selection(struct settings *settings, bool forward) +{ + const r2p_payload_t *p; + + r2p_ensure_initialized(); + + if (r2p_list_count == 0) + return; + + if (forward) + r2p_selected = (r2p_selected + 1) % r2p_list_count; + else + r2p_selected = (r2p_selected == 0) + ? r2p_list_count - 1 + : r2p_selected - 1; + + if (!settings) + return; + + p = &r2p_list[r2p_selected]; + settings->uints.reboot_payload_kind = (unsigned)p->kind; + strlcpy(settings->arrays.reboot_payload_name, p->name, + sizeof(settings->arrays.reboot_payload_name)); +} + +const char *r2p_get_destination_name(void) +{ + static char buf[R2P_NAME_MAXLEN + sizeof(R2P_SELF_SUFFIX)]; + const r2p_payload_t *p; + + r2p_ensure_initialized(); + + if (r2p_selected >= r2p_list_count) + return R2P_HEKATE_MENU; + + p = &r2p_list[r2p_selected]; + if (r2p_selected != r2p_index_for_self()) + return p->name; + + snprintf(buf, sizeof(buf), "%s%s", p->name, R2P_SELF_SUFFIX); + return buf; +} + +static bool r2p_write_sysfs(const char *action, + const char *param1, const char *param2) +{ + if (!filestream_write_file(R2P_SYSFS_ACTION, action, strlen(action))) + { + RARCH_ERR("[R2P] Failed to write \"%s\".\n", R2P_SYSFS_ACTION); + return false; + } + if (!filestream_write_file(R2P_SYSFS_PARAM1, param1, strlen(param1))) + { + RARCH_ERR("[R2P] Failed to write \"%s\".\n", R2P_SYSFS_PARAM1); + return false; + } + if (!filestream_write_file(R2P_SYSFS_PARAM2, param2, strlen(param2))) + { + RARCH_ERR("[R2P] Failed to write \"%s\".\n", R2P_SYSFS_PARAM2); + return false; + } + return true; +} + +bool r2p_arm_selected(void) +{ + const r2p_payload_t *p; + const char *action = "self"; + char param1[16]; + const char *param2 = "0"; + + r2p_ensure_initialized(); + + if (r2p_selected >= r2p_list_count) + return false; + + p = &r2p_list[r2p_selected]; + snprintf(param1, sizeof(param1), "%u", p->index); + + switch (p->kind) + { + case R2P_KIND_BOOTLOADER: + action = "bootloader"; + break; + case R2P_KIND_LAUNCH: + break; + case R2P_KIND_INI_CONFIG: + param2 = "1"; + break; + default: + return false; + } + + if (!r2p_write_sysfs(action, param1, param2)) + return false; + + RARCH_LOG("[R2P] Armed action=%s param1=%s param2=%s (\"%s\").\n", + action, param1, param2, p->name); + return true; +} + +bool r2p_arm_self(void) +{ + char param1[16]; + char param2[16]; + + r2p_ensure_initialized(); + + snprintf(param1, sizeof(param1), "%u", r2p_self_param1); + snprintf(param2, sizeof(param2), "%u", r2p_self_param2); + + if (!r2p_write_sysfs(r2p_self_action, param1, param2)) + return false; + + RARCH_LOG("[R2P] Armed self action=%s param1=%s param2=%s.\n", + r2p_self_action, param1, param2); + return true; +} + +void r2p_deinit(void) +{ + r2p_clear_payloads(); + r2p_selected = 0; + r2p_list_initialized = false; +} diff --git a/misc/reboot2payload/reboot2payload.h b/misc/reboot2payload/reboot2payload.h new file mode 100644 index 000000000000..d3c7046b36f4 --- /dev/null +++ b/misc/reboot2payload/reboot2payload.h @@ -0,0 +1,76 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2026 - gavin_darkglider + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#ifndef __RARCH_REBOOT2PAYLOAD_H +#define __RARCH_REBOOT2PAYLOAD_H + +#include + +#include +#include + +RETRO_BEGIN_DECLS + +/* Reboot-to-payload support for Lakka on the Nintendo Switch. + * + * The Switchroot L4T kernel exposes the pmc_r2p driver at + * /sys/devices/r2p. Writing action/param1/param2 there selects what + * Hekate boots on the next reboot: + * + * action=bootloader -> stop in the Hekate menu + * action=self, param2=0, param1=N -> Nth [entry] of hekate_ipl.ini + * action=self, param2=1, param1=N -> Nth [entry] across + * bootloader/ini/ (sorted) + * + * The list of destinations is parsed from the Hekate configuration on + * the FAT boot partition (mounted at /flash by Lakka). The default + * selection is the entry Lakka was booted from, taken from the + * pmc_r2p.* parameters on the kernel command line. */ + +#define R2P_NAME_MAXLEN 64 + +struct settings; + +/* True when the running kernel provides the pmc_r2p sysfs interface. */ +bool r2p_is_supported(void); + +/* Re-parses the payload list, then restores the selection persisted in + * settings (reboot_payload_kind / reboot_payload_name). The persisted + * entry is matched by kind and name, so it survives INI reordering; + * if it no longer exists the selection falls back to the entry Lakka + * was booted from. */ +void r2p_load_selection(const struct settings *settings); + +/* Steps the selection forward or backward (wrapping) and mirrors the + * new selection into settings so it is saved with the config. */ +void r2p_cycle_selection(struct settings *settings, bool forward); + +/* Display name of the selected destination. The entry Lakka was + * booted from is suffixed with " (self)". */ +const char *r2p_get_destination_name(void); + +/* Arms pmc_r2p for the selected destination. */ +bool r2p_arm_selected(void); + +/* Arms pmc_r2p with the pmc_r2p.* values from the kernel command + * line, i.e. reboots back into the running Lakka entry regardless + * of the selection. */ +bool r2p_arm_self(void); + +void r2p_deinit(void); + +RETRO_END_DECLS + +#endif diff --git a/msg_hash.h b/msg_hash.h index 6711dec86647..d391469217b4 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -22571,6 +22571,8 @@ enum msg_hash_enums MENU_LABEL(SWITCH_OC_ENABLE), MENU_LABEL(SWITCH_CEC_ENABLE), MENU_LABEL(BLUETOOTH_ERTM_DISABLE), + MENU_LABEL(REBOOT_PAYLOAD), + MENU_LABEL(REBOOT_FORCE_SELF_ON_UPDATE), #endif MENU_LBL_H(NETPLAY_START_AS_SPECTATOR), MENU_LABEL(NETPLAY_FADE_CHAT), diff --git a/msg_hash_lbl_str.h b/msg_hash_lbl_str.h index f844558bb919..f4eaddf0e53b 100644 --- a/msg_hash_lbl_str.h +++ b/msg_hash_lbl_str.h @@ -625,6 +625,8 @@ #define MENU_ENUM_LABEL_SWITCH_OC_ENABLE_STR "Switch_OC_enable" #define MENU_ENUM_LABEL_SWITCH_CEC_ENABLE_STR "Switch_CEC_enable" #define MENU_ENUM_LABEL_BLUETOOTH_ERTM_DISABLE_STR "Switch_ERTM_Disable" +#define MENU_ENUM_LABEL_REBOOT_PAYLOAD_STR "reboot_payload" +#define MENU_ENUM_LABEL_REBOOT_FORCE_SELF_ON_UPDATE_STR "reboot_force_self_on_update" #define MENU_ENUM_LABEL_SYSTEM_BGM_ENABLE_STR "system_bgm_enable" #define MENU_ENUM_LABEL_SYSTEM_INFORMATION_STR "system_information" #define MENU_ENUM_LABEL_SYSTEM_INFO_ENTRY_STR "system_info_entry" diff --git a/retroarch.c b/retroarch.c index d827f7f80247..c482510e4645 100644 --- a/retroarch.c +++ b/retroarch.c @@ -271,6 +271,9 @@ #ifdef HAVE_LAKKA #include #include "lakka.h" +#ifdef HAVE_LAKKA_SWITCH +#include "misc/reboot2payload/reboot2payload.h" +#endif #include #endif @@ -1943,6 +1946,13 @@ void drivers_init( gfx_display_init_first_driver(p_disp, video_is_threaded); } +#ifdef HAVE_LAKKA_SWITCH + /* Parse the Hekate boot entries and restore the saved reboot + * payload before the menu driver is initialised, so the first + * menu build already shows the right destination. */ + r2p_load_selection(settings); +#endif + #ifdef HAVE_MENU if (flags & DRIVER_VIDEO_MASK) { @@ -2171,6 +2181,9 @@ void driver_uninit(int flags, enum driver_lifetime_flags lifetime_flags) #ifdef HAVE_LAKKA cpu_scaling_driver_free(); #endif +#ifdef HAVE_LAKKA_SWITCH + r2p_deinit(); +#endif } static void retroarch_deinit_drivers(struct retro_callbacks *cbs) @@ -5181,6 +5194,20 @@ bool command_event(enum event_command cmd, void *data) { #if defined(__linux__) && !defined(ANDROID) const char *_msg = msg_hash_to_str(MSG_VALUE_REBOOTING); +#ifdef HAVE_LAKKA_SWITCH + /* Tell Hekate what to boot next via pmc_r2p. A staged + * update reboots back into the running Lakka entry + * (unless the user opted out) so the initramfs installs + * it. If arming fails this is a plain reboot. */ + if (r2p_is_supported()) + { + if ( settings->bools.reboot_force_self_on_update + && lakka_update_pending()) + r2p_arm_self(); + else + r2p_arm_selected(); + } +#endif if (settings->bools.config_save_on_exit) command_event(CMD_EVENT_MENU_SAVE_CURRENT_CONFIG, NULL); runloop_msg_queue_push(_msg, strlen(_msg), 1, 180, true, NULL, From 74f0ec64f495fef142339acd37f36c9a67bec31e Mon Sep 17 00:00:00 2001 From: LibretroAdmin Date: Mon, 12 Oct 2026 12:43:24 +0200 Subject: [PATCH 3/3] Lakka: no directory scan or stat behind the Reboot entry's value The menu asks for an entry's value every frame. Whether an update is staged is looked up when the main menu is built and before a reboot, and whether the kernel has pmc_r2p once. The kernel command line is read whole: Hekate's pmc_r2p.* values come at its end. --- lakka.h | 12 +++++++---- menu/menu_displaylist.c | 4 ++++ misc/reboot2payload/reboot2payload.c | 32 +++++++++++++++------------- retroarch.c | 13 +++++++++-- 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/lakka.h b/lakka.h index 39c7a540f200..59ecf717ff7e 100644 --- a/lakka.h +++ b/lakka.h @@ -38,10 +38,14 @@ RETRO_BEGIN_DECLS -/* Returns true when an update image is staged in LAKKA_UPDATE_DIR, - * i.e. a file matching one of the patterns the Lakka initramfs - * check_update step installs on the next boot (*.tar, *.img.gz, - * *.img). */ +/* Looks in LAKKA_UPDATE_DIR for an update image the Lakka initramfs + * check_update step installs on the next boot (*.tar, *.img.gz, *.img) + * and keeps the answer for lakka_update_pending(). Reads a directory: + * called when the menu builds the main list and before a reboot, never + * per frame. */ +void lakka_update_check(void); + +/* Whether the last lakka_update_check() found an update staged. */ bool lakka_update_pending(void); RETRO_END_DECLS diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index b7671f9f3091..078637ad712c 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -17557,10 +17557,14 @@ static bool menu_displaylist_ctl_internal( #if defined(HAVE_LAKKA) if (settings->bools.menu_show_reboot) + { + /* What the Reboot entry's value shows. */ + lakka_update_check(); if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(info->list, MENU_ENUM_LABEL_REBOOT, PARSE_ACTION, false) == 0) count++; + } if (settings->bools.menu_show_shutdown) if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(info->list, diff --git a/misc/reboot2payload/reboot2payload.c b/misc/reboot2payload/reboot2payload.c index 89ef1d5e131d..2e48dd47a27e 100644 --- a/misc/reboot2payload/reboot2payload.c +++ b/misc/reboot2payload/reboot2payload.c @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -112,26 +113,21 @@ static char *r2p_trim(char *s) static void r2p_parse_cmdline(void) { - RFILE *fp; - char line[1024]; - char *tok; - char *saveptr = NULL; + void *buf = NULL; + int64_t _len = 0; + char *line; + char *tok; + char *saveptr = NULL; strlcpy(r2p_self_action, R2P_DEFAULT_ACTION, sizeof(r2p_self_action)); r2p_self_param1 = 0; r2p_self_param2 = 0; - if (!(fp = filestream_open(R2P_CMDLINE_PATH, - RETRO_VFS_FILE_ACCESS_READ, - RETRO_VFS_FILE_ACCESS_HINT_NONE))) + /* The whole of it: a command line runs to several KiB, and the + * pmc_r2p.* values Hekate appends come last. */ + if (!filestream_read_file(R2P_CMDLINE_PATH, &buf, &_len) || !buf) return; - - if (!filestream_gets(fp, line, sizeof(line))) - { - filestream_close(fp); - return; - } - filestream_close(fp); + line = (char*)buf; for (tok = strtok_r(line, " \t\r\n", &saveptr); tok; @@ -147,6 +143,7 @@ static void r2p_parse_cmdline(void) r2p_self_param2 = (unsigned)strtoul( tok + STRLEN_CONST("pmc_r2p.param2="), NULL, 0); } + free(buf); } /* Appends one entry per [section] of a Hekate INI, numbering them @@ -261,7 +258,12 @@ static void r2p_ensure_initialized(void) bool r2p_is_supported(void) { - return path_is_directory(R2P_SYSFS_DIR); + /* The kernel's: it does not come or go while running, and the menu + * asks every frame. */ + static int supported = -1; + if (supported < 0) + supported = path_is_directory(R2P_SYSFS_DIR) ? 1 : 0; + return supported == 1; } void r2p_load_selection(const struct settings *settings) diff --git a/retroarch.c b/retroarch.c index c482510e4645..0cb530efa4d7 100644 --- a/retroarch.c +++ b/retroarch.c @@ -280,13 +280,21 @@ #define _PSUPP(var, name, desc) printf(" %s:\n\t\t%s: %s\n", name, desc, var ? "yes" : "no") #ifdef HAVE_LAKKA +static bool lakka_update_staged = false; + bool lakka_update_pending(void) +{ + return lakka_update_staged; +} + +void lakka_update_check(void) { bool found = false; struct RDIR *dir = retro_opendir(LAKKA_UPDATE_DIR); + lakka_update_staged = false; if (!dir) - return false; + return; while (!found && retro_readdir(dir)) { @@ -301,7 +309,7 @@ bool lakka_update_pending(void) } retro_closedir(dir); - return found; + lakka_update_staged = found; } #endif @@ -5201,6 +5209,7 @@ bool command_event(enum event_command cmd, void *data) * it. If arming fails this is a plain reboot. */ if (r2p_is_supported()) { + lakka_update_check(); if ( settings->bools.reboot_force_self_on_update && lakka_update_pending()) r2p_arm_self();