From 7139aa6aaf3130a7f93a9e2e5e04515a5ab2fa5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 00:41:36 +0200 Subject: [PATCH 01/24] Settings: Remove side effects from building the tabs Building a settings tab should only create views, so that the tabs can later be described as data and searched without being shown. - Don't reset the MSAA level when the device has none. The GPU already falls back to no MSAA (UpdateMSAALevel), and the reset overwrote the setting, including per-game ones. - Read the reporting state in the constructor. It was only read when the System tab was built, but always written back on exit, so leaving settings without opening System turned reporting off. - Move the Windows memstick location probe (which writes a temp file next to the exe) out of the System tab, and keep the checkboxes' enabled state in members instead of poking at the views. - Show the Android/UWP memstick path through a callback instead of a copy made at build time, and drop a USB path computation whose result was never used. - Make the reliable-UDP checkbox follow the UDP setting directly instead of recreating all views when it changes. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/GameSettingsScreen.cpp | 149 ++++++++++++++++---------------------- UI/GameSettingsScreen.h | 12 ++- 2 files changed, 67 insertions(+), 94 deletions(-) diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 32390e8061..bd2fbe05a2 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -68,7 +68,6 @@ #include "UI/AdhocServerScreen.h" #include "Common/File/FileUtil.h" -#include "Common/File/AndroidContentURI.h" #include "Common/StringUtils.h" #include "Core/Config.h" #include "Core/ConfigValues.h" @@ -119,6 +118,8 @@ GameSettingsScreen::GameSettingsScreen(const Path &gamePath, std::string gameID, : UITabbedBaseDialogScreen(gamePath, &g_Config.iSettingsCurrentTab, TabDialogFlags::HorizontalOnlyIcons | TabDialogFlags::VerticalShowIcons), gameID_(gameID), editGameSpecificThenRestore_(editThenRestore) { prevInflightFrames_ = g_Config.iInflightFrames; analogSpeedMapped_ = KeyMap::InputMappingsFromPspButton(VIRTKEY_SPEED_ANALOG, nullptr, true); + // Written back on exit, so it must be read even if the System tab is never opened. + enableReports_ = Reporting::IsEnabled(); if (editGameSpecificThenRestore_) { std::shared_ptr info = g_gameInfoCache->GetInfo(nullptr, gamePath_, GameInfoFlags::PARAM_SFO); @@ -160,6 +161,9 @@ GameSettingsScreen::~GameSettingsScreen() { void GameSettingsScreen::PreCreateViews() { ReloadAllPostShaderInfo(screenManager()->getDrawContext()); ReloadAllThemeInfo(); +#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) + CheckMemstickInstallState(); +#endif } static bool UsingHardwareTextureScaling() { @@ -188,30 +192,50 @@ static std::string *GPUDeviceNameSetting() { return nullptr; } -static bool PathToVisualUsbPath(Path path, std::string &outPath) { - switch (path.Type()) { - case PathType::NATIVE: - if (path.StartsWith(g_Config.memStickDirectory)) { - return g_Config.memStickDirectory.ComputePathTo(path, outPath); +#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) +// Works out where the memstick is (installed.txt next to the exe means My Documents or a custom +// folder), and which of the two checkboxes can be changed. +void GameSettingsScreen::CheckMemstickInstallState() { + const Path &PPSSPPpath = File::GetExeDirectory(); + const Path installedFile = PPSSPPpath / "installed.txt"; + const bool myDocsExists = W32Util::UserDocumentsPath().size() != 0; + + installed_ = File::Exists(installedFile); + otherinstalled_ = false; + memstickMyDocEnabled_ = !PSP_IsInited(); + memstickOtherEnabled_ = false; + if (!installed_ && myDocsExists) { + // Disable the setting if we can't create & delete a file next to the exe. + if (File::CreateEmptyFile(PPSSPPpath / "installedTEMP.txt")) { + if (!(File::Delete(PPSSPPpath / "installedTEMP.txt"))) + memstickMyDocEnabled_ = false; + else + memstickOtherEnabled_ = !PSP_IsInited(); + } else + memstickMyDocEnabled_ = false; + } else { + if (installed_ && myDocsExists) { + FILE *testInstalled = File::OpenCFile(installedFile, "rt"); + if (testInstalled) { + char temp[2048]; + char *tempStr = fgets(temp, sizeof(temp), testInstalled); + // Skip UTF-8 encoding bytes if there are any. There are 3 of them. + if (tempStr && strncmp(tempStr, "\xEF\xBB\xBF", 3) == 0) { + tempStr += 3; + } + memstickOtherEnabled_ = !PSP_IsInited(); + if (tempStr && strlen(tempStr) != 0 && strcmp(tempStr, "\n") != 0) { + installed_ = false; + otherinstalled_ = true; + } + fclose(testInstalled); + } + } else if (!myDocsExists) { + memstickMyDocEnabled_ = false; } - break; - case PathType::CONTENT_URI: -#if PPSSPP_PLATFORM(ANDROID) - { - // Try to parse something sensible out of the content URI. - AndroidContentURI uri(path.ToString()); - outPath = uri.RootPath(); - if (startsWith(outPath, "primary:")) { - outPath = "/" + outPath.substr(8); - } - return true; } -#endif - default: - break; - } - return false; } +#endif void GameSettingsScreen::CreateTabs() { using namespace UI; @@ -355,8 +379,6 @@ void GameSettingsScreen::CreateGraphicsSettings(UI::ViewGroup *graphicsSettings) msaaChoice->SetChoiceIcon(i, ImageID("I_WARNING")); } } - } else { - g_Config.iMultiSampleLevel = 0; } #if PPSSPP_PLATFORM(ANDROID) @@ -1041,13 +1063,12 @@ void GameSettingsScreen::CreateNetworkingSettings(UI::ViewGroup *networkingSetti CheckBox *relay_mode_enet = networkingSettings->Add(new CheckBox(&g_Config.bAdhocUseUdp, n->T("Connect to adhoc server using UDP (ENet) instead of TCP"))); relay_mode_enet->SetEnabled(!PSP_IsInited()); - relay_mode_enet->OnClick.Add([this](UI::EventParams &e) { - RecreateViews(); - }); networkingSettings->Add(new SettingHint(n->T("Try switching between TCP/UDP if you suffer frequent adhoc server disconnects. Some network environments favor TCP and some UDP."), nullptr)); CheckBox *relay_mode_enet_pdp_reliable = networkingSettings->Add(new CheckBox(&g_Config.bAdhocRelayModeEnetPdpReliable, n->T("Reliable datagram packet delivery on packet relay in UDP mode"))); - relay_mode_enet_pdp_reliable->SetEnabled(!PSP_IsInited() && g_Config.bAdhocUseUdp); + relay_mode_enet_pdp_reliable->SetEnabledFunc([] { + return !PSP_IsInited() && g_Config.bAdhocUseUdp; + }); networkingSettings->Add(new SettingHint(n->T("When disabled, packet loss on datagram adhoc sockets will be exposed to game's netcode. Keep this enabled if you don't know what that means."), nullptr)); @@ -1298,74 +1319,31 @@ void GameSettingsScreen::CreateSystemSettings(UI::ViewGroup *systemSettings) { #if PPSSPP_PLATFORM(ANDROID) if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) != DEVICE_TYPE_VR) { - memstickDisplay_ = g_Config.memStickDirectory.ToVisualString(); - auto memstickPath = systemSettings->Add(new ChoiceWithValueDisplay(&memstickDisplay_, sy->T("Memory Stick folder"), I18NCat::NONE)); + auto memstickPath = systemSettings->Add(new ChoiceWithCallbackValueDisplay(sy->T("Memory Stick folder"), []() { + return g_Config.memStickDirectory.ToVisualString(); + })); memstickPath->SetEnabled(!PSP_IsInited()); memstickPath->OnClick.Handle(this, &GameSettingsScreen::OnShowMemstickScreen); - - // Display USB path for convenience. - std::string usbPath; - if (PathToVisualUsbPath(g_Config.memStickDirectory, usbPath)) { - if (usbPath.empty()) { - // Probably it's just the root. So let's add PSP to make it clear. - usbPath = "/PSP"; - } - } } #elif defined(_WIN32) #if PPSSPP_PLATFORM(UWP) - memstickDisplay_ = g_Config.memStickDirectory.ToVisualString(); - auto memstickPath = systemSettings->Add(new ChoiceWithValueDisplay(&memstickDisplay_, sy->T("Memory Stick folder"), I18NCat::NONE)); + auto memstickPath = systemSettings->Add(new ChoiceWithCallbackValueDisplay(sy->T("Memory Stick folder"), []() { + return g_Config.memStickDirectory.ToVisualString(); + })); memstickPath->SetEnabled(!PSP_IsInited()); memstickPath->OnClick.Handle(this, &GameSettingsScreen::OnShowMemstickScreen); #else - SavePathInMyDocumentChoice = systemSettings->Add(new CheckBox(&installed_, sy->T("Memory Stick in My Documents"))); - SavePathInMyDocumentChoice->SetEnabled(!PSP_IsInited()); - SavePathInMyDocumentChoice->OnClick.Handle(this, &GameSettingsScreen::OnMemoryStickMyDoc); - SavePathInOtherChoice = systemSettings->Add(new CheckBox(&otherinstalled_, sy->T("Memory Stick in installed.txt"))); - SavePathInOtherChoice->SetEnabled(false); - SavePathInOtherChoice->OnClick.Handle(this, &GameSettingsScreen::OnMemoryStickOther); - const bool myDocsExists = W32Util::UserDocumentsPath().size() != 0; + CheckBox *myDocsChoice = systemSettings->Add(new CheckBox(&installed_, sy->T("Memory Stick in My Documents"))); + myDocsChoice->SetEnabledPtr(&memstickMyDocEnabled_); + myDocsChoice->OnClick.Handle(this, &GameSettingsScreen::OnMemoryStickMyDoc); + CheckBox *otherChoice = systemSettings->Add(new CheckBox(&otherinstalled_, sy->T("Memory Stick in installed.txt"))); + otherChoice->SetEnabledPtr(&memstickOtherEnabled_); + otherChoice->OnClick.Handle(this, &GameSettingsScreen::OnMemoryStickOther); if (!System_GetPropertyBool(SYSPROP_CAN_GET_FREE_SPACE_FAST)) { // It might be slow, so use an option. systemSettings->Add(new CheckBox(&g_Config.bReportAccurateFreeStorageSpace, sy->T("Report free storage space accurately"))); } - - const Path &PPSSPPpath = File::GetExeDirectory(); - const Path installedFile = PPSSPPpath / "installed.txt"; - installed_ = File::Exists(installedFile); - otherinstalled_ = false; - if (!installed_ && myDocsExists) { - if (File::CreateEmptyFile(PPSSPPpath / "installedTEMP.txt")) { - // Disable the setting whether cannot create & delete file - if (!(File::Delete(PPSSPPpath / "installedTEMP.txt"))) - SavePathInMyDocumentChoice->SetEnabled(false); - else - SavePathInOtherChoice->SetEnabled(!PSP_IsInited()); - } else - SavePathInMyDocumentChoice->SetEnabled(false); - } else { - if (installed_ && myDocsExists) { - FILE *testInstalled = File::OpenCFile(installedFile, "rt"); - if (testInstalled) { - char temp[2048]; - char *tempStr = fgets(temp, sizeof(temp), testInstalled); - // Skip UTF-8 encoding bytes if there are any. There are 3 of them. - if (tempStr && strncmp(tempStr, "\xEF\xBB\xBF", 3) == 0) { - tempStr += 3; - } - SavePathInOtherChoice->SetEnabled(!PSP_IsInited()); - if (tempStr && strlen(tempStr) != 0 && strcmp(tempStr, "\n") != 0) { - installed_ = false; - otherinstalled_ = true; - } - fclose(testInstalled); - } - } else if (!myDocsExists) { - SavePathInMyDocumentChoice->SetEnabled(false); - } - } #endif #endif systemSettings->Add(new CheckBox(&g_Config.bMemStickInserted, sy->T("Memory Stick inserted"))); @@ -1373,9 +1351,6 @@ void GameSettingsScreen::CreateSystemSettings(UI::ViewGroup *systemSettings) { sizeChoice->SetFormat("%d GB"); systemSettings->Add(new ItemHeader(sy->T("Help the PPSSPP team"))); - if (!enableReportsSet_) - enableReports_ = Reporting::IsEnabled(); - enableReportsSet_ = true; CheckBox *enableReportsCheckbox; enableReportsCheckbox = new CheckBox(&enableReports_, sy->T("Enable Compatibility Server Reports")); enableReportsCheckbox->SetEnabledFunc([]() { return Reporting::IsSupported(); }); @@ -1607,7 +1582,7 @@ void GameSettingsScreen::OnMemoryStickOther(UI::EventParams &e) { } else { File::Delete(PPSSPPpath / "installed.txt"); - SavePathInMyDocumentChoice->SetEnabled(true); + memstickMyDocEnabled_ = true; otherinstalled_ = false; installed_ = false; g_Config.memStickDirectory = PPSSPPpath / "memstick"; diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index e99a8d9c2d..0b8fe66983 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -59,16 +59,15 @@ private: void CreateVRSettings(UI::ViewGroup *vrSettings); std::string gameID_; -#ifdef _WIN32 - UI::CheckBox *SavePathInMyDocumentChoice = nullptr; - UI::CheckBox *SavePathInOtherChoice = nullptr; - // Used to enable/disable the above two options. +#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) + void CheckMemstickInstallState(); + bool installed_ = false; bool otherinstalled_ = false; + bool memstickMyDocEnabled_ = false; + bool memstickOtherEnabled_ = false; #endif - std::string memstickDisplay_; - // Global settings handlers void OnChangeBackground(UI::EventParams &e); void OnRestoreDefaultSettings(UI::EventParams &e); @@ -93,7 +92,6 @@ private: int iAlternateSpeedPercentAnalog_ = 0; int prevInflightFrames_ = -1; bool enableReports_ = false; - bool enableReportsSet_ = false; bool analogSpeedMapped_ = false; // edit the game-specific settings and restore the global settings after exiting From 9ba1d569e7b29a8067e9b2896777c3f9a21280df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 00:42:10 +0200 Subject: [PATCH 02/24] Settings: Show the analog alternative speed as a percentage The format was set on the wrong slider (alternative speed 2, which already had it). Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/GameSettingsScreen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index bd2fbe05a2..ac8a9dbe90 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -474,7 +474,7 @@ void GameSettingsScreen::CreateGraphicsSettings(UI::ViewGroup *graphicsSettings) if (analogSpeedMapped_) { PopupSliderChoice *analogSpeed = graphicsSettings->Add(new PopupSliderChoice(&iAlternateSpeedPercentAnalog_, 1, 1000, UI::NO_DEFAULT_INT, gr->T("Analog alternative speed", "Analog alternative speed (in %)"), 5, screenManager(), "%")); - altSpeed2->SetFormat("%i%%"); + analogSpeed->SetFormat("%i%%"); } graphicsSettings->Add(new ItemHeader(gr->T("Speed Hacks", "Speed Hacks (can cause rendering errors!)"))); From edae3049f34b3c1aad60781d70af390714eae1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 08:51:54 +0200 Subject: [PATCH 03/24] SystemInfo: Show the CPU name again, and use the right translation category The static analysis cleanup in d6ebfed432 dropped the negation, so the CPU name was only shown when it was "Unknown". Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/SystemInfoScreen.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/UI/SystemInfoScreen.cpp b/UI/SystemInfoScreen.cpp index 4114832bf8..fdef8aa57e 100644 --- a/UI/SystemInfoScreen.cpp +++ b/UI/SystemInfoScreen.cpp @@ -38,7 +38,7 @@ void SystemInfoScreen::update() { // TODO: How can we de-duplicate this and SystemInfoScreen::CreateTabs? static void CopySummaryToClipboard(Draw::DrawContext *draw) { auto di = GetI18NCategory(I18NCat::DIALOG); - auto si = GetI18NCategory(I18NCat::DIALOG); + auto si = GetI18NCategory(I18NCat::SYSINFO); char *summary = new char[100000]; StringWriter w(summary, 100000); @@ -145,7 +145,7 @@ void SystemInfoScreen::CreateDeviceInfoTab(UI::LinearLayout *deviceSpecs) { UI::CollapsibleSection *cpuInfo = deviceSpecs->Add(new UI::CollapsibleSection(si->T("CPU Information"))); // Don't bother showing the CPU name if we don't have one. - if (equals(cpu_info.brand_string, "Unknown")) { + if (!equals(cpu_info.brand_string, "Unknown")) { cpuInfo->Add(new InfoItem(si->T("CPU Name"), cpu_info.brand_string)); } @@ -393,7 +393,8 @@ void SystemInfoScreen::CreateBuildConfigTab(UI::LinearLayout *buildConfig) { } System_CopyStringToClipboard(buildConfigStr.c_str()); auto di = GetI18NCategory(I18NCat::DIALOG); - g_OSD.Show(OSDType::MESSAGE_INFO, ApplySafeSubstitutions(di->T("Copied to clipboard: %1"), di->T("Build Configuration")), 0.0f, "copyToClip"); + auto si = GetI18NCategory(I18NCat::SYSINFO); + g_OSD.Show(OSDType::MESSAGE_INFO, ApplySafeSubstitutions(di->T("Copied to clipboard: %1"), si->T("Build Configuration")), 0.0f, "copyToClip"); }); for (auto &item : buildConfigItems) { From bbe84de2fe0b2691333d826f53db9d8c8e427a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 08:51:59 +0200 Subject: [PATCH 04/24] Store: Tag the product panel, not the list, so both keep their scroll position Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/Store.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UI/Store.cpp b/UI/Store.cpp index 342e935aac..83e401ebfd 100644 --- a/UI/Store.cpp +++ b/UI/Store.cpp @@ -480,7 +480,7 @@ void StoreScreen::CreateDialogViews(UI::ViewGroup *parent) { // TODO: Similar apps, etc etc productPanel_ = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(0.5f)); - leftScroll->SetTag("StoreMainProduct"); + productPanel_->SetTag("StoreMainProduct"); content->Add(productPanel_); ProductItemView *selectedItem = GetSelectedItem(); From 8a7b64daa37712091df8124e3d2b99aeacd7630c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 08:51:59 +0200 Subject: [PATCH 05/24] Adhoc: Start editing the server address from the current one The static string was only initialized on the first call, so the text editor (used where there's no native input dialog) showed whatever was typed the first time, even after picking another server from the list. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/AdhocServerScreen.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/UI/AdhocServerScreen.cpp b/UI/AdhocServerScreen.cpp index dbbf3761f7..30c7b2368a 100644 --- a/UI/AdhocServerScreen.cpp +++ b/UI/AdhocServerScreen.cpp @@ -867,7 +867,8 @@ void AskToEditCurrentServer(int requestToken, ScreenManager *screenManager) { }); return; } - static std::string editText = g_Config.sProAdhocServer; + static std::string editText; + editText = g_Config.sProAdhocServer; TextEditPopupScreen *popupScreen = new TextEditPopupScreen(&editText, editText, n->T("Ad hoc server address"), 256); if (System_GetPropertyBool(SYSPROP_KEYBOARD_IS_SOFT)) { popupScreen->SetAlignTop(true); From 3e46790a005fa0d3a7ebf7b893c9a499ea2e0a2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 08:52:16 +0200 Subject: [PATCH 06/24] Store: Make Retry download the listing again It only recreated the views, which showed the same connection error. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/Store.cpp | 6 ++++++ UI/Store.h | 1 + 2 files changed, 7 insertions(+) diff --git a/UI/Store.cpp b/UI/Store.cpp index 83e401ebfd..ab2f300dcf 100644 --- a/UI/Store.cpp +++ b/UI/Store.cpp @@ -354,7 +354,12 @@ void ProductView::OnLaunchClick(UI::EventParams &e) { StoreScreen::StoreScreen() : UISimpleBaseDialogScreen(Path(), SimpleDialogFlags::Default) { lang_ = g_Config.sLanguageIni; + StartListingDownload(); +} + +void StoreScreen::StartListingDownload() { loading_ = true; + connectionError_ = false; std::string indexPath = StoreBaseUrl() + "index.json"; const char *acceptMime = "application/json, */*; q=0.8"; @@ -532,6 +537,7 @@ void StoreScreen::OnGameLaunch(UI::EventParams &e) { } void StoreScreen::OnRetry(UI::EventParams &e) { + StartListingDownload(); RecreateViews(); } diff --git a/UI/Store.h b/UI/Store.h index 9d31f03560..c6e5b90cda 100644 --- a/UI/Store.h +++ b/UI/Store.h @@ -77,6 +77,7 @@ protected: std::string_view GetTitle() const override; private: + void StartListingDownload(); void ParseListing(const std::string &json); ProductItemView *GetSelectedItem(); From bba29944e60a328c63a972e03697ee6763de72ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 08:52:36 +0200 Subject: [PATCH 07/24] InstallZip: Don't let each pane forget the other's views CreateSettingsViews and CreateContentViews both cleared all the view pointers, and which runs first depends on orientation. In landscape, Install and Play were never disabled during an install; in portrait, the done and error notices never showed. Reset them once in BeforeCreateViews instead. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/InstallZipScreen.cpp | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/UI/InstallZipScreen.cpp b/UI/InstallZipScreen.cpp index 4a2c94c6c6..66c859edc6 100644 --- a/UI/InstallZipScreen.cpp +++ b/UI/InstallZipScreen.cpp @@ -78,11 +78,6 @@ void InstallZipScreen::CreateSettingsViews(UI::ViewGroup *parent) { std::string shortFilename = zipPath_.GetFilename(); bool showDeleteCheckbox = false; - returnToHomebrew_ = false; - installChoice_ = nullptr; - playChoice_ = nullptr; - doneView_ = nullptr; - existingSaveView_ = nullptr; std::vector destOptions; @@ -144,12 +139,6 @@ void InstallZipScreen::CreateContentViews(UI::ViewGroup *parent) { std::string shortFilename = zipPath_.GetFilename(); - returnToHomebrew_ = false; - installChoice_ = nullptr; - playChoice_ = nullptr; - doneView_ = nullptr; - existingSaveView_ = nullptr; - overwriteWarning_ = nullptr; destFolders_.clear(); std::vector destOptions; @@ -320,8 +309,13 @@ void InstallZipScreen::CreateContentViews(UI::ViewGroup *parent) { } void InstallZipScreen::BeforeCreateViews() { - File::FileInfo fileInfo; - File::GetFileInfo(zipPath_, &fileInfo); + // The two panes are created in a different order depending on orientation, so neither can reset the other's views. + returnToHomebrew_ = false; + installChoice_ = nullptr; + playChoice_ = nullptr; + doneView_ = nullptr; + existingSaveView_ = nullptr; + overwriteWarning_ = nullptr; } bool InstallZipScreen::key(const KeyInput &key) { From a79fbbc606769c1520265bd1604c3f2dc4a3a77e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 08:53:01 +0200 Subject: [PATCH 08/24] GameScreen, CwCheat: Use the game ID from the info cache Both worked out the ID themselves with a copy of the homebrew rule that lacks the PS1 exception added to GameInfoCache in 7ced173c17. For a PS1 EBOOT in PSP/GAME, Game Settings then edited the global config even though Create Game Config had made a per-game one under the real ID. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/CwCheatScreen.cpp | 7 ++----- UI/GameScreen.cpp | 6 ++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/UI/CwCheatScreen.cpp b/UI/CwCheatScreen.cpp index 3d0d1c5e21..e6cd45a3b6 100644 --- a/UI/CwCheatScreen.cpp +++ b/UI/CwCheatScreen.cpp @@ -160,14 +160,11 @@ bool CwCheatScreen::WantsTextInput() const { bool CwCheatScreen::TryLoadCheatInfo() { std::shared_ptr info = g_gameInfoCache->GetInfo(nullptr, gamePath_, GameInfoFlags::PARAM_SFO); - std::string gameID; if (!info->Ready(GameInfoFlags::PARAM_SFO)) { return false; } - gameID = info->GetParamSFO().GetValueString("DISC_ID"); - if ((info->id.empty() || !info->disc_total) && gamePath_.FilePathContainsNoCase("PSP/GAME/")) { - gameID = g_paramSFO.GenerateFakeID(gamePath_); - } + // The cache has already replaced the ID of homebrew with a generated one. + const std::string &gameID = info->id; if (!engine_ || gameID != gameID_) { gameID_ = gameID; diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index 0468fd7b64..65b7817e56 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -799,10 +799,8 @@ void GameScreen::OnDeleteConfig(UI::EventParams &e) { void GameScreen::OnGameSettings(UI::EventParams &e) { std::shared_ptr info_ = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO); if (info_ && info_->Ready(GameInfoFlags::PARAM_SFO)) { - std::string discID = info_->GetParamSFO().GetValueString("DISC_ID"); - if ((discID.empty() || !info_->disc_total) && gamePath_.FilePathContainsNoCase("PSP/GAME/")) - discID = g_paramSFO.GenerateFakeID(gamePath_); - screenManager()->push(new GameSettingsScreen(gamePath_, discID, true)); + // The cache has already replaced the ID of homebrew with a generated one. + screenManager()->push(new GameSettingsScreen(gamePath_, info_->id, true)); } } From 5de5a1b74a6628231a973411e0403e8a1e09887e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 08:53:25 +0200 Subject: [PATCH 09/24] CwCheat: Fix import error messages, a file leak, and a download on Back - Importing showed "File not found" when the file existed and the import failed, and the other message when it was missing. - The game ID check returned without closing the cheat file. - Closing the download popup with Back started a download of an empty URL. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/CwCheatScreen.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/UI/CwCheatScreen.cpp b/UI/CwCheatScreen.cpp index e6cd45a3b6..bb3b1587a5 100644 --- a/UI/CwCheatScreen.cpp +++ b/UI/CwCheatScreen.cpp @@ -202,6 +202,9 @@ bool CwCheatScreen::key(const KeyInput &input) { void CwCheatScreen::dialogFinished(const Screen *dialog, DialogResult result) { if (auto downloadScreen = dynamic_cast(dialog)) { + if (result != DR_OK || downloadScreen->ChosenUrl().empty()) { + return; + } if (downloadRequest_) { ERROR_LOG(Log::CwCheats, "Download already in progress, ignoring new download request."); return; @@ -426,7 +429,7 @@ void CwCheatScreen::ImportAndReport(const Path &cheatFile) { int cheatCount = 0; if (!ImportCheats(cheatFile, &cheatCount)) { // Show an error message? - if (File::Exists(cheatFile)) { + if (!File::Exists(cheatFile)) { auto er = GetI18NCategory(I18NCat::ERRORS); errorMessage_ = ApplySafeSubstitutions(er->T("File not found: %1"), ""); errorLevel_ = NoticeLevel::WARN; @@ -471,14 +474,14 @@ void CwCheatScreen::OnImportCheat(UI::EventParams ¶ms) { } bool CwCheatScreen::ImportCheats(const Path &cheatFile, int *cheatsFound) { - FILE *in = File::OpenCFile(cheatFile, "rt"); - if (!in) { - WARN_LOG(Log::CwCheats, "Unable to open %s\n", cheatFile.c_str()); + if (gameID_.length() != 9 || !engine_) { + WARN_LOG(Log::CwCheats, "CWCHEAT: Incorrect ID(%s) - can't import cheats.", gameID_.c_str()); return false; } - if (gameID_.length() != 9 || !engine_) { - WARN_LOG(Log::CwCheats, "CWCHEAT: Incorrect ID(%s) - can't import cheats.", gameID_.c_str()); + FILE *in = File::OpenCFile(cheatFile, "rt"); + if (!in) { + WARN_LOG(Log::CwCheats, "Unable to open %s\n", cheatFile.c_str()); return false; } From 33e700350efbaaed1e5bdeb4ec19229d852ab5cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 08:53:46 +0200 Subject: [PATCH 10/24] MemStick: Fix the missing file browser popup and the folder switch error - Cancel on the "missing file browser" popup opened the help page anyway, and More info closed the memstick screen as if a folder had been picked, because any child dialog finishing with DR_OK did that. - When switching to the new folder failed, the error was set but never shown. After a move, the screen was left with no buttons. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/MemStickScreen.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/UI/MemStickScreen.cpp b/UI/MemStickScreen.cpp index 3b13b21dff..f00e6245b4 100644 --- a/UI/MemStickScreen.cpp +++ b/UI/MemStickScreen.cpp @@ -442,6 +442,9 @@ void MemStickScreen::Browse(UI::EventParams ¶ms) { nw->T("Error"), // from the networking category of strings. ms->T("MissingFileBrowserError", "This device is missing a file/folder browser. If you disabled the 'Files' app, please enable it and try again."), di->T("More info"), di->T("Cancel"), [](bool ok) { + if (!ok) { + return; + } System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.ppsspp.org/docs/troubleshooting/cant-pick-folder/"); } )); @@ -450,7 +453,8 @@ void MemStickScreen::Browse(UI::EventParams ¶ms) { } void MemStickScreen::dialogFinished(const Screen *dialog, DialogResult result) { - if (result == DialogResult::DR_OK) { + // Other popups, like the missing file browser error, also finish with DR_OK. + if (result == DialogResult::DR_OK && !strcmp(dialog->tag(), "ConfirmMemstickMove")) { INFO_LOG(Log::System, "Confirmation screen done - moving on."); // There's a screen manager bug if we call TriggerFinish directly. // Can't be bothered right now, so we pick this up in update(). @@ -658,6 +662,7 @@ void ConfirmMemstickMoveScreen::FinishFolderMove() { if (!SwitchMemstickFolderTo(newMemstickFolder_)) { // TODO: More precise errors. error_ = ms->T("That folder doesn't work as a memstick folder."); + RecreateViews(); return; } From f5059f994851cb6c30d8b434053cd747d292f1b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 08:54:05 +0200 Subject: [PATCH 11/24] DevTools: Encode the notification positions like the other screens do None is -1, but the developer tools list had it last at index 8, which is CENTER. Building the tab clamped None to Bottom Left, and picking None showed notifications centered instead. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/DeveloperToolsScreen.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/UI/DeveloperToolsScreen.cpp b/UI/DeveloperToolsScreen.cpp index d2fb09a7ad..38e1c9471e 100644 --- a/UI/DeveloperToolsScreen.cpp +++ b/UI/DeveloperToolsScreen.cpp @@ -425,7 +425,8 @@ void DeveloperToolsScreen::CreateUITab(UI::LinearLayout *list) { list->Add(new ItemHeader(si->T("Slider test"))); list->Add(new Slider(&testSliderValue_, 0, 100, 1, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); - static const char *positions[] = {"Bottom Left", "Bottom Center", "Bottom Right", "Top Left", "Top Center", "Top Right", "Center Left", "Center Right", "None"}; + // Same encoding as in GameSettingsScreen: None is -1. + static const char *positions[] = { "None", "Bottom Left", "Bottom Center", "Bottom Right", "Top Left", "Top Center", "Top Right", "Center Left", "Center Right" }; list->Add(new ItemHeader(si->T("Notification tests"))); list->Add(new Choice(si->T("Error")))->OnClick.Add([&](UI::EventParams &) { @@ -476,8 +477,8 @@ void DeveloperToolsScreen::CreateUITab(UI::LinearLayout *list) { }); list->Add(new ItemHeader(ac->T("Notifications"))); - list->Add(new PopupMultiChoice(&g_Config.iNotificationPos, sy->T("Notification screen position"), positions, 0, ARRAY_SIZE(positions), I18NCat::DIALOG, screenManager())); - list->Add(new PopupMultiChoice(&g_Config.iAchievementsLeaderboardTrackerPos, ac->T("Leaderboard tracker"), positions, 0, ARRAY_SIZE(positions), I18NCat::DIALOG, screenManager())); + list->Add(new PopupMultiChoice(&g_Config.iNotificationPos, sy->T("Notification screen position"), positions, -1, ARRAY_SIZE(positions), I18NCat::DIALOG, screenManager())); + list->Add(new PopupMultiChoice(&g_Config.iAchievementsLeaderboardTrackerPos, ac->T("Leaderboard tracker"), positions, -1, ARRAY_SIZE(positions), I18NCat::DIALOG, screenManager())); #ifdef _DEBUG list->Add(new CheckBox(&pretendIngame_, ac->T("Pretend to be in-game (for testing)"))); From 0c8c44728407028a08eb9bd77d69bb57c9bcbbc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 08:54:34 +0200 Subject: [PATCH 12/24] Settings: Fix a blank page when reopening settings on the Search tab The saved tab was selected before the Search tab was added, so the index was out of range and no tab was created. Select it after adding Search, and create the current tab anyway if the saved one no longer exists (Search is hidden on small screens). Co-Authored-By: Claude Opus 5.5 (1M context) --- Common/UI/TabHolder.cpp | 4 ++++ UI/TabbedDialogScreen.cpp | 12 +++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Common/UI/TabHolder.cpp b/Common/UI/TabHolder.cpp index f7da78ad5e..32129466d9 100644 --- a/Common/UI/TabHolder.cpp +++ b/Common/UI/TabHolder.cpp @@ -138,6 +138,10 @@ bool TabHolder::EnsureTab(int index) { void TabHolder::SetInitialTab(int tab) { if (tab < 0 || tab >= (int)tabs_.size()) { + // The tab may no longer exist (the search tab is hidden on small screens, for example). + if (currentTab_ < (int)tabs_.size()) { + EnsureTab(currentTab_); + } return; } EnsureTab(tab); diff --git a/UI/TabbedDialogScreen.cpp b/UI/TabbedDialogScreen.cpp index ca626a7760..81eae5bc48 100644 --- a/UI/TabbedDialogScreen.cpp +++ b/UI/TabbedDialogScreen.cpp @@ -114,11 +114,6 @@ void UITabbedBaseDialogScreen::CreateViews() { // Let the subclass create its tabs. CreateTabs(); - if (currentTabSetting_) { - tabHolder_->SetInitialTab(*currentTabSetting_); - } else { - tabHolder_->EnsureTab(tabHolder_->GetCurrentTab()); - } tabHolder_->OnChangeTab.Add([this](UI::EventParams &e) { if (currentTabSetting_) { @@ -151,6 +146,13 @@ void UITabbedBaseDialogScreen::CreateViews() { } } + // After the search tab, which the saved tab can be. + if (currentTabSetting_) { + tabHolder_->SetInitialTab(*currentTabSetting_); + } else { + tabHolder_->EnsureTab(tabHolder_->GetCurrentTab()); + } + // Need to handle recreates, both important and accidental ones. if (!searchFilter_.empty()) { ApplySearchFilter(); From 8fc75c03440eadb9be3af701fcd1e0995fbdf814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 08:55:13 +0200 Subject: [PATCH 13/24] Settings: Write the alternative speeds back only when they're changed The percentages were converted back to frame rates whenever any dialog closed with OK. That overwrote the values Restore Defaults had just set, and the integer rounding drifted unchanged values down (100 fps became 99 on any unrelated change). Restoring defaults also had the old reporting choice written back on exit; it's now re-read too. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/GameSettingsScreen.cpp | 25 ++++++++++++++++++++----- UI/GameSettingsScreen.h | 1 + 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index ac8a9dbe90..3f7581e1f5 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -118,14 +118,19 @@ GameSettingsScreen::GameSettingsScreen(const Path &gamePath, std::string gameID, : UITabbedBaseDialogScreen(gamePath, &g_Config.iSettingsCurrentTab, TabDialogFlags::HorizontalOnlyIcons | TabDialogFlags::VerticalShowIcons), gameID_(gameID), editGameSpecificThenRestore_(editThenRestore) { prevInflightFrames_ = g_Config.iInflightFrames; analogSpeedMapped_ = KeyMap::InputMappingsFromPspButton(VIRTKEY_SPEED_ANALOG, nullptr, true); - // Written back on exit, so it must be read even if the System tab is never opened. - enableReports_ = Reporting::IsEnabled(); if (editGameSpecificThenRestore_) { std::shared_ptr info = g_gameInfoCache->GetInfo(nullptr, gamePath_, GameInfoFlags::PARAM_SFO); g_Config.LoadGameConfig(gameID_); } + ReadConfigTemporaries(); +} + +// Also called after restoring defaults, so the old values aren't written back over them. +void GameSettingsScreen::ReadConfigTemporaries() { + // Written back on exit, so it must be read even if the System tab is never opened. + enableReports_ = Reporting::IsEnabled(); iAlternateSpeedPercent1_ = g_Config.iFpsLimit1 < 0 ? -1 : (g_Config.iFpsLimit1 * 100) / 60; iAlternateSpeedPercent2_ = g_Config.iFpsLimit2 < 0 ? -1 : (g_Config.iFpsLimit2 * 100) / 60; iAlternateSpeedPercentAnalog_ = (g_Config.iAnalogFpsLimit * 100) / 60; @@ -466,15 +471,24 @@ void GameSettingsScreen::CreateGraphicsSettings(UI::ViewGroup *graphicsSettings) altSpeed1->SetFormat("%i%%"); altSpeed1->SetZeroLabel(gr->T("Unlimited")); altSpeed1->SetNegativeDisable(gr->T("Disabled")); + altSpeed1->OnChange.Add([this](UI::EventParams &) { + g_Config.iFpsLimit1 = iAlternateSpeedPercent1_ < 0 ? -1 : (iAlternateSpeedPercent1_ * 60) / 100; + }); PopupSliderChoice *altSpeed2 = graphicsSettings->Add(new PopupSliderChoice(&iAlternateSpeedPercent2_, 0, 1000, UI::NO_DEFAULT_INT, gr->T("Alternative Speed 2", "Alternative speed 2 (in %, 0 = unlimited)"), 5, screenManager(), gr->T("%, 0:unlimited"))); altSpeed2->SetFormat("%i%%"); altSpeed2->SetZeroLabel(gr->T("Unlimited")); altSpeed2->SetNegativeDisable(gr->T("Disabled")); + altSpeed2->OnChange.Add([this](UI::EventParams &) { + g_Config.iFpsLimit2 = iAlternateSpeedPercent2_ < 0 ? -1 : (iAlternateSpeedPercent2_ * 60) / 100; + }); if (analogSpeedMapped_) { PopupSliderChoice *analogSpeed = graphicsSettings->Add(new PopupSliderChoice(&iAlternateSpeedPercentAnalog_, 1, 1000, UI::NO_DEFAULT_INT, gr->T("Analog alternative speed", "Analog alternative speed (in %)"), 5, screenManager(), "%")); analogSpeed->SetFormat("%i%%"); + analogSpeed->OnChange.Add([this](UI::EventParams &) { + g_Config.iAnalogFpsLimit = (iAlternateSpeedPercentAnalog_ * 60) / 100; + }); } graphicsSettings->Add(new ItemHeader(gr->T("Speed Hacks", "Speed Hacks (can cause rendering errors!)"))); @@ -1672,9 +1686,9 @@ void GameSettingsScreen::dialogFinished(const Screen *dialog, DialogResult resul } if (result == DialogResult::DR_OK) { - g_Config.iFpsLimit1 = iAlternateSpeedPercent1_ < 0 ? -1 : (iAlternateSpeedPercent1_ * 60) / 100; - g_Config.iFpsLimit2 = iAlternateSpeedPercent2_ < 0 ? -1 : (iAlternateSpeedPercent2_ * 60) / 100; - g_Config.iAnalogFpsLimit = (iAlternateSpeedPercentAnalog_ * 60) / 100; + if (equals(dialog->tag(), "RestoreSettingsScreen")) { + ReadConfigTemporaries(); + } recreate = true; } @@ -1798,6 +1812,7 @@ void GameSettingsScreen::OnAudioDevice(UI::EventParams &e) { void GameSettingsScreen::CallbackRestoreDefaults(bool yes) { if (yes) { g_Config.RestoreDefaults(RestoreSettingsBits::SETTINGS); + ReadConfigTemporaries(); } System_Notify(SystemNotification::UI); } diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index 0b8fe66983..b4d1d3389e 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -85,6 +85,7 @@ private: void OnSustainedPerformanceModeChange(UI::EventParams &e); void TriggerRestartOrDo(std::function callback); + void ReadConfigTemporaries(); // Temporaries to convert setting types, cache enabled, etc. int iAlternateSpeedPercent1_ = 0; From 20686814a3abe5903c4ffa4fcbc5c1e4fc68f73f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 08:55:47 +0200 Subject: [PATCH 14/24] ControlMapping: Fix Autoconfigure in portrait, and Escape on the analog limiter - In portrait the settings pane is a popup, so it got the list's dialogFinished and the pad was never autoconfigured. Use a callback. - Escape was meant to cancel mapping the analog speed limiter (3cb4207369), but since the combo mapping rewrite it got mapped to it instead. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/ControlMappingScreen.cpp | 19 +++++++++---------- UI/ControlMappingScreen.h | 2 -- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/UI/ControlMappingScreen.cpp b/UI/ControlMappingScreen.cpp index 0c491b8905..7523041d8d 100644 --- a/UI/ControlMappingScreen.cpp +++ b/UI/ControlMappingScreen.cpp @@ -308,20 +308,16 @@ void ControlMappingScreen::OnAutoConfigure(UI::EventParams ¶ms) { } auto km = GetI18NCategory(I18NCat::KEYMAPPING); auto di = GetI18NCategory(I18NCat::DIALOG); - UI::ListPopupScreen *autoConfList = new UI::ListPopupScreen(km->T("Autoconfigure for device"), items, -1); + // Uses a callback, since in portrait the settings are in a popup, which would get dialogFinished instead of us. + UI::ListPopupScreen *autoConfList = new UI::ListPopupScreen(km->T("Autoconfigure for device"), items, -1, [items](int index) { + KeyMap::AutoConfForPad(items[index]); + }); autoConfList->SetNotification(NoticeLevel::WARN, di->T("This will overwrite the existing configuration")); if (params.v) autoConfList->SetPopupOrigin(params.v); screenManager()->push(autoConfList); } -void ControlMappingScreen::dialogFinished(const Screen *dialog, DialogResult result) { - if (result == DR_OK && !strcmp(dialog->tag(), "listpopup")) { - UI::ListPopupScreen *popup = (UI::ListPopupScreen *)dialog; - KeyMap::AutoConfForPad(popup->GetChoiceString()); - } -} - void KeyMappingNewKeyDialog::CreatePopupContents(UI::ViewGroup *parent) { using namespace UI; @@ -351,8 +347,11 @@ bool KeyMappingNewKeyDialog::key(const KeyInput &key) { return true; } - if (pspBtn_ == VIRTKEY_SPEED_ANALOG && !UI::IsEscapeKey(key)) { - // Only map analog values to this mapping. + if (pspBtn_ == VIRTKEY_SPEED_ANALOG) { + // Only map analog values to this mapping. Escape cancels. + if (UI::IsEscapeKey(key)) { + TriggerFinish(DR_CANCEL); + } return true; } diff --git a/UI/ControlMappingScreen.h b/UI/ControlMappingScreen.h index 6c7a4579ae..ab4744ec80 100644 --- a/UI/ControlMappingScreen.h +++ b/UI/ControlMappingScreen.h @@ -57,8 +57,6 @@ protected: private: void OnAutoConfigure(UI::EventParams ¶ms); - void dialogFinished(const Screen *dialog, DialogResult result) override; - std::vector mappers_; int keyMapGeneration_ = -1; From fbf101de36f5b318b927aca0cc45753b9966537b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 08:56:02 +0200 Subject: [PATCH 15/24] Touch controls: Rotate with the current orientation's display layout The D-pad and sticks always used the landscape layout's rotation settings, while the screen and physical pads use the current orientation's. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/GamepadEmu.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/UI/GamepadEmu.cpp b/UI/GamepadEmu.cpp index d49edbfdf3..79833e70d4 100644 --- a/UI/GamepadEmu.cpp +++ b/UI/GamepadEmu.cpp @@ -95,11 +95,12 @@ GamepadComponent::GamepadComponent(std::string_view key, UI::LayoutParams *layou static void rotateTouchHelper(float &dx, float &dy) { // rotates dx and dy depending on rotation count // modified from controlmapper.cpp - if (!g_Config.displayLayoutLandscape.bRotateControlsWithScreen) { + const DisplayLayoutConfig &layout = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation()); + if (!layout.bRotateControlsWithScreen) { return; } int rotations = 0; - switch (g_Config.displayLayoutLandscape.iInternalScreenRotation) { + switch (layout.iInternalScreenRotation) { case ROTATION_LOCKED_HORIZONTAL180: rotations = 2; break; case ROTATION_LOCKED_VERTICAL: rotations = 3; break; case ROTATION_LOCKED_VERTICAL180: rotations = 1; break; @@ -459,8 +460,9 @@ void PSPDpad::Draw(UIContext &dc) { return; // from control mapper.cpp int rotations = 0; - if (g_Config.displayLayoutLandscape.bRotateControlsWithScreen) { - switch (g_Config.displayLayoutLandscape.iInternalScreenRotation) { + const DisplayLayoutConfig &layout = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation()); + if (layout.bRotateControlsWithScreen) { + switch (layout.iInternalScreenRotation) { case ROTATION_LOCKED_HORIZONTAL180: rotations = 2; break; case ROTATION_LOCKED_VERTICAL: rotations = 3; break; case ROTATION_LOCKED_VERTICAL180: rotations = 1; break; From 75ac7b6de84a992bc8fa547fb20b7ef07335ec6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 08:56:23 +0200 Subject: [PATCH 16/24] DisplayLayout: Fix a few stale states - The aspect ratio slider captured a copy of the layout config, so its enabled state never followed the Stretch and Integer scale checkboxes. - Reset didn't refresh the views, which depend on the rotation. - The shader list popup kept its old index mapping when recreated, so the current shader wasn't highlighted. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/DisplayLayoutScreen.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/UI/DisplayLayoutScreen.cpp b/UI/DisplayLayoutScreen.cpp index 836c83ad30..208b1e4af9 100644 --- a/UI/DisplayLayoutScreen.cpp +++ b/UI/DisplayLayoutScreen.cpp @@ -268,7 +268,7 @@ void DisplayLayoutScreen::CreateViews() { PopupSliderChoiceFloat *aspectRatio = new PopupSliderChoiceFloat(&config.fDisplayAspectRatio, 0.1f, 2.0f, 1.0f, gr->T("Aspect Ratio"), screenManager()); rightColumn->Add(aspectRatio); - aspectRatio->SetEnabledFunc([config]() { + aspectRatio->SetEnabledFunc([&config]() { return !config.bDisplayStretch && !config.bDisplayIntegerScale; }); aspectRatio->SetHasDropShadow(false); @@ -322,9 +322,11 @@ void DisplayLayoutScreen::CreateViews() { rightColumn->Add(new Spacer(12.0f)); Choice *center = new Choice(di->T("Reset")); - center->OnClick.Add([&config, portrait](UI::EventParams &) { + center->OnClick.Add([this, &config, portrait](UI::EventParams &) { // Hm, not really ideal to have to use strings here. config.ResetToDefault(portrait ? "DisplayLayout.Portrait" : "DisplayLayout.Landscape"); + // Which checkboxes are shown depends on the rotation. + RecreateViews(); }); rightColumn->Add(center); @@ -514,6 +516,7 @@ void PostProcScreen::CreateViews() { std::vector items; int selected = -1; const std::string selectedName = id_ >= (int)g_Config.vPostShaderNames.size() ? "Off" : g_Config.vPostShaderNames[id_]; + indexTranslation_.clear(); for (int i = 0; i < (int)shaders_.size(); i++) { if (!shaders_[i].visible) From 75b0fbe30cbe7a3387b525b5f10897de75305638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 08:56:53 +0200 Subject: [PATCH 17/24] CustomButtonMapping: Keep unsaved button choices when the views are recreated The checkbox array was reloaded from the config on every view creation, but only saved when a popup closed or the screen finished. Rotating the device reverted the choices. Also stop leaking a handler per button on each rebuild. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/CustomButtonMappingScreen.cpp | 19 ++++++++++--------- UI/CustomButtonMappingScreen.h | 9 +-------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/UI/CustomButtonMappingScreen.cpp b/UI/CustomButtonMappingScreen.cpp index f402e03f25..08c479d8d0 100644 --- a/UI/CustomButtonMappingScreen.cpp +++ b/UI/CustomButtonMappingScreen.cpp @@ -136,14 +136,18 @@ void CustomButtonMappingScreen::CreateDialogViews(UI::ViewGroup *parent) { ConfigCustomButton *cfg = nullptr; bool *show = nullptr; - memset(array, 0, sizeof(array)); cfg = &g_Config.CustomButton[id_]; // This screen is reachable from the main menu, so neither GamepadEmu nor the layout screen // need have sanitized these yet - and everything below indexes the tables with them. CustomKeyData::Sanitize(*cfg); show = &touch.touchCustom[id_].show; - for (int i = 0; i < ARRAY_SIZE(g_customKeyList); i++) - array[i] = (0x01 == ((g_Config.CustomButton[id_].key >> i) & 0x01)); + // Only load once, since the views can be recreated (on rotation, for example) before the array is saved. + if (!arrayLoaded_) { + for (int i = 0; i < ARRAY_SIZE(g_customKeyList); i++) { + array[i] = (0x01 == ((g_Config.CustomButton[id_].key >> i) & 0x01)); + } + arrayLoaded_ = true; + } // TODO: Less hacky layout work const Bounds layoutBounds = GetLayoutBounds(*screenManager()->getUIContext()); @@ -205,8 +209,9 @@ void CustomButtonMappingScreen::CreateDialogViews(UI::ViewGroup *parent) { choice = new Choice(mc->T(KeyMap::GetPspButtonNameCharPointer(g_customKeyList[i].c)), new LinearLayoutParams(1.0f)); } - ChoiceEventHandler *choiceEventHandler = new ChoiceEventHandler(checkbox); - choice->OnClick.Handle(choiceEventHandler, &ChoiceEventHandler::onChoiceClick); + choice->OnClick.Add([checkbox](UI::EventParams &) { + checkbox->Toggle(); + }); choice->SetCentered(true); @@ -241,7 +246,3 @@ void CustomButtonMappingScreen::onFinish(DialogResult result) { saveArray(); g_Config.Save("CustomButtonMappingScreen::onFinish"); } - -void CustomButtonMappingScreen::ChoiceEventHandler::onChoiceClick(UI::EventParams &e){ - checkbox_->Toggle(); -}; diff --git a/UI/CustomButtonMappingScreen.h b/UI/CustomButtonMappingScreen.h index 9f6f2b5a00..159866f07a 100644 --- a/UI/CustomButtonMappingScreen.h +++ b/UI/CustomButtonMappingScreen.h @@ -46,15 +46,8 @@ private: void saveArray(); bool array[ARRAY_SIZE(CustomKeyData::g_customKeyList)]{}; + bool arrayLoaded_ = false; int id_; - class ChoiceEventHandler{ - public: - ChoiceEventHandler(UI::CheckBox *checkbox) : checkbox_(checkbox) {} - void onChoiceClick(UI::EventParams &e); - private: - UI::CheckBox *checkbox_; - }; - DeviceOrientation deviceOrientation_; }; From a897b95b75ef462a0c9abc8987b75c0c83ffc737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 08:57:29 +0200 Subject: [PATCH 18/24] GameScreen: Know whether the game is homebrew before creating either pane isHomebrew_ was set in CreateSettingsViews, but CreateContentViews uses it too, and in portrait it's created first. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/GameScreen.cpp | 7 +++++-- UI/GameScreen.h | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index 65b7817e56..6acc975c81 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -257,6 +257,11 @@ void GameScreen::RefreshInstalledUpdate() { hasInstalledUpdate_ = FindInstalledGameUpdate(info_->id, &installedUpdate_); } +// Both panes use isHomebrew_, and which is created first depends on orientation. +void GameScreen::BeforeCreateViews() { + isHomebrew_ = info_ && info_->region == GameRegion::HOMEBREW; +} + void GameScreen::CreateContentViews(UI::ViewGroup *parent) { if (!info_) { // Shouldn't happen @@ -579,8 +584,6 @@ void GameScreen::CreateSettingsViews(UI::ViewGroup *rightColumn) { }); } - isHomebrew_ = info_ && info_->region == GameRegion::HOMEBREW; - const bool packing = GamePacker::IsPacking(gamePath_); if (fileTypeSupportCRC && !isHomebrew_ && !Reporting::HasCRC(gamePath_) && !packing) { rightColumnItems->Add(new Choice(ga->T("Calculate CRC"), ImageID("I_CHECKMARK")))->OnClick.Add([this](UI::EventParams &) { diff --git a/UI/GameScreen.h b/UI/GameScreen.h index c2b667dc85..9c819c5e09 100644 --- a/UI/GameScreen.h +++ b/UI/GameScreen.h @@ -48,6 +48,7 @@ public: bool GameRemoved() const { return gameRemoved_; } protected: + void BeforeCreateViews() override; void CreateContentViews(UI::ViewGroup *parent) override; void CreateSettingsViews(UI::ViewGroup *parent) override; void CreateContextMenu(UI::ViewGroup *parent) override; From 0eeaeef6101ed0fc4138097560cc1fc07d6b4ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 08:57:29 +0200 Subject: [PATCH 19/24] Pause: Don't offer Load State on a screenshot in hardcore mode The slot list already hides it. From the screenshot view, the load was refused, but the pause menu still closed. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/PauseScreen.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/UI/PauseScreen.cpp b/UI/PauseScreen.cpp index fc1c75d7d5..6a551370c1 100644 --- a/UI/PauseScreen.cpp +++ b/UI/PauseScreen.cpp @@ -133,8 +133,10 @@ protected: } grid->Add(new Choice(pa->T("Save State"), ImageID("I_FILE_SAVE")))->OnClick.Handle(this, &ScreenshotViewScreen::OnSaveState); - // We can unconditionally show the load state button, because you can only pop this dialog up if a state exists. - grid->Add(new Choice(pa->T("Load State"), ImageID("I_FOLDER_OPEN")))->OnClick.Handle(this, &ScreenshotViewScreen::OnLoadState); + // You can only pop this dialog up if a state exists. Hardcore mode doesn't allow loading, like in SaveSlotView. + if (!Achievements::HardcoreModeActive()) { + grid->Add(new Choice(pa->T("Load State"), ImageID("I_FOLDER_OPEN")))->OnClick.Handle(this, &ScreenshotViewScreen::OnLoadState); + } grid->Add(new Choice(di->T("Delete"), ImageID("I_TRASHCAN")))->OnClick.Handle(this, &ScreenshotViewScreen::OnDeleteState); if (undoButton) { grid->Add(undoButton)->OnClick.Handle(this, &ScreenshotViewScreen::OnUndoState); From b5b37954b31dfd1b467362d6172b6d5ae32299af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 08:57:29 +0200 Subject: [PATCH 20/24] Achievements: Don't crash recreating a leaderboard screen after the game is unloaded Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/RetroAchievementScreens.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/UI/RetroAchievementScreens.cpp b/UI/RetroAchievementScreens.cpp index 35b7aa5db4..f8d91817db 100644 --- a/UI/RetroAchievementScreens.cpp +++ b/UI/RetroAchievementScreens.cpp @@ -290,6 +290,11 @@ void RetroAchievementsLeaderboardScreen::FetchEntries() { void RetroAchievementsLeaderboardScreen::CreateTabs() { auto ac = GetI18NCategory(I18NCat::ACHIEVEMENTS); const rc_client_leaderboard_t *leaderboard = rc_client_get_leaderboard_info(Achievements::GetClient(), leaderboardID_); + if (!leaderboard) { + // The game was unloaded. There has to be a tab, though. + AddTab("AchievementsLeaderboard", "", [](UI::LinearLayout *parent) {}); + return; + } using namespace UI; AddTab("AchievementsLeaderboard", leaderboard->title, [this, leaderboard](UI::LinearLayout *parent) { From 37be6ce4e8ff0586ee9ea2cabe8c725e7440324a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 09:03:18 +0200 Subject: [PATCH 21/24] Config: Clamp the touch layout snap grid size on load The layout editor divides by it, and drawing the grid never finishes when it's 0. Only the slider limited it, so a hand-edited ini could hang. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/Config.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/Config.cpp b/Core/Config.cpp index 45525140f0..ab7a631017 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -1666,6 +1666,9 @@ void Config::PostLoadCleanup() { // Three frames in flight showed no benefit over two, so the option is gone. Squash old values. iInflightFrames = std::clamp(iInflightFrames, 1, 2); + + // The touch layout editor divides by this, and loops forever drawing the grid if it's 0. + iTouchSnapGridSize = std::clamp(iTouchSnapGridSize, 2, 256); } void Config::PreSaveCleanup() { From b530b477f79b336b40f0094275bfb45670163868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 09:03:18 +0200 Subject: [PATCH 22/24] DevTools: Don't touch a deleted view when the trace file is picked The save dialog's callback set the path on a stored InfoItem pointer. If the views were recreated while the dialog was open, that view was gone. Recreate the views instead, which reads the path from the tracer. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/DeveloperToolsScreen.cpp | 7 +++---- UI/DeveloperToolsScreen.h | 2 -- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/UI/DeveloperToolsScreen.cpp b/UI/DeveloperToolsScreen.cpp index 38e1c9471e..abead2fb46 100644 --- a/UI/DeveloperToolsScreen.cpp +++ b/UI/DeveloperToolsScreen.cpp @@ -350,8 +350,7 @@ void DeveloperToolsScreen::CreateMIPSTracerTab(UI::LinearLayout *list) { return true; }); - MIPSTracerPath_ = mipsTracer.get_logging_path(); - MIPSTracerPath = list->Add(new InfoItem(dev->T("Current log file"), MIPSTracerPath_)); + list->Add(new InfoItem(dev->T("Current log file"), mipsTracer.get_logging_path())); PopupSliderChoice* storage_capacity = list->Add( new PopupSliderChoice( @@ -771,8 +770,8 @@ void DeveloperToolsScreen::OnMIPSTracerPathChanged(UI::EventParams &e) { BrowseFileType::ANY, [this](std::string_view value, int) { mipsTracer.set_logging_path(std::string(value)); - MIPSTracerPath_ = value; - MIPSTracerPath->SetRightText(MIPSTracerPath_); + // The views may have been recreated while the dialog was open, so don't keep a pointer to the path view. + RecreateViews(); } ); } diff --git a/UI/DeveloperToolsScreen.h b/UI/DeveloperToolsScreen.h index 0eb1a025ee..be5720f9f4 100644 --- a/UI/DeveloperToolsScreen.h +++ b/UI/DeveloperToolsScreen.h @@ -53,8 +53,6 @@ private: HasIni hasTexturesIni_ = HasIni::MAYBE; bool MIPSTracerEnabled_ = false; - std::string MIPSTracerPath_ = ""; - UI::InfoItem* MIPSTracerPath = nullptr; int testSliderValue_ = 0; bool pretendIngame_ = false; From 9e6fdb5be71c9ba3b9c479867d7ef786c8b8f543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 09:13:06 +0200 Subject: [PATCH 23/24] Settings: Make the two nickname fields behave the same The Networking copy showed a warning for the default name but didn't fill in an empty infrastructure username; the System one did the reverse, and didn't refresh the views. Both now use one helper that does both. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/GameSettingsScreen.cpp | 39 +++++++++++++++++++++++---------------- UI/GameSettingsScreen.h | 1 + 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 3f7581e1f5..a073aed3b9 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1000,13 +1000,33 @@ MacAddressChooser::MacAddressChooser(RequesterToken token, Path gamePath, std::s }); } +// The nickname is shown on both the Networking and System tabs. +void GameSettingsScreen::AddNicknameChoice(UI::ViewGroup *parent) { + using namespace UI; + + auto sy = GetI18NCategory(I18NCat::SYSTEM); + PopupTextInputChoice *nickname = parent->Add(new PopupTextInputChoice(GetRequesterToken(), &g_Config.sNickName, sy->T("Nickname"), "", 32, screenManager())); + if (g_Config.sNickName == "PPSSPP") { + nickname->SetIconRight(ImageID("I_WARNING")); + } + nickname->OnChange.Add([this](UI::EventParams &e) { + // Copy to infrastructure name if valid and not already set. + if (g_Config.sInfrastructureUsername.empty()) { + if (g_Config.sNickName == SanitizeString(g_Config.sNickName, StringRestriction::AlphaNumDashUnderscore, 3, 16)) { + g_Config.sInfrastructureUsername = g_Config.sNickName; + } + } + // Updates the warning icon, and the username notice on the Networking tab. + RecreateViews(); + }); +} + void GameSettingsScreen::CreateNetworkingSettings(UI::ViewGroup *networkingSettings) { using namespace UI; auto n = GetI18NCategory(I18NCat::NETWORKING); auto ms = GetI18NCategory(I18NCat::MAINSETTINGS); auto di = GetI18NCategory(I18NCat::DIALOG); - auto sy = GetI18NCategory(I18NCat::SYSTEM); networkingSettings->Add(new ItemHeader(ms->T("Networking"))); @@ -1064,13 +1084,7 @@ void GameSettingsScreen::CreateNetworkingSettings(UI::ViewGroup *networkingSetti relayModePopup->SetEnabled(!PSP_IsInited()); networkingSettings->Add(new SettingHint(n->T("PacketRelayHint", "Available on servers that provide 'aemu_postoffice' packet relay, like socom.cc. Disable this for LAN or VPN play. Can be more reliable, but sometimes slower."), relayModePopup)); - PopupTextInputChoice *nickname = networkingSettings->Add(new PopupTextInputChoice(GetRequesterToken(), &g_Config.sNickName, sy->T("Nickname"), "", 32, screenManager())); - if (g_Config.sNickName == "PPSSPP") { - nickname->SetIconRight(ImageID("I_WARNING")); - } - nickname->OnChange.Add([this](UI::EventParams &e) { - RecreateViews(); - }); + AddNicknameChoice(networkingSettings); networkingSettings->Add(new SettingHint(n->T("Try to pick a unique nickname for ad hoc play"), nullptr)); networkingSettings->Add(new CheckBox(&g_Config.bEnableAdhocServer, n->T("Enable built-in ad hoc server")))->OnClick.Add(requestLocalNetwork); @@ -1468,14 +1482,7 @@ void GameSettingsScreen::CreateSystemSettings(UI::ViewGroup *systemSettings) { systemSettings->Add(new PopupMultiChoice(&g_Config.iLanguage, psps->T("Game language"), defaultLanguages, -1, ARRAY_SIZE(defaultLanguages), I18NCat::PSPSETTINGS, screenManager())); static const char *models[] = { "PSP-1000", "PSP-2000/3000" }; systemSettings->Add(new PopupMultiChoice(&g_Config.iPSPModel, sy->T("PSP Model"), models, 0, ARRAY_SIZE(models), I18NCat::SYSTEM, screenManager()))->SetEnabled(!PSP_IsInited()); - systemSettings->Add(new PopupTextInputChoice(GetRequesterToken(), &g_Config.sNickName, sy->T("Nickname"), "", 32, screenManager()))->OnChange.Add([](UI::EventParams &e) { - // Copy to infrastructure name if valid and not already set. - if (g_Config.sInfrastructureUsername.empty()) { - if (g_Config.sNickName == SanitizeString(g_Config.sNickName, StringRestriction::AlphaNumDashUnderscore, 3, 16)) { - g_Config.sInfrastructureUsername = g_Config.sNickName; - } - } - }); + AddNicknameChoice(systemSettings); systemSettings->Add(new CheckBox(&g_Config.bDayLightSavings, sy->T("Daylight savings"))); static const char *dateFormat[] = { "YYYYMMDD", "MMDDYYYY", "DDMMYYYY" }; systemSettings->Add(new PopupMultiChoice(&g_Config.iDateFormat, sy->T("Date Format"), dateFormat, 0, ARRAY_SIZE(dateFormat), I18NCat::SYSTEM, screenManager())); diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index b4d1d3389e..6f803b1743 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -57,6 +57,7 @@ private: void CreateToolsSettings(UI::ViewGroup *tools); void CreateSystemSettings(UI::ViewGroup *systemSettings); void CreateVRSettings(UI::ViewGroup *vrSettings); + void AddNicknameChoice(UI::ViewGroup *parent); std::string gameID_; #if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) From 57dc5bf95608956e244b49a9e7051994aa29715e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 22 Oct 2026 10:59:44 +0200 Subject: [PATCH 24/24] Fix the enabled state of some settings hints --- UI/GameSettingsScreen.cpp | 14 +++++++------- UI/GameSettingsScreen.h | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index a073aed3b9..87baaed7fa 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1001,7 +1001,7 @@ MacAddressChooser::MacAddressChooser(RequesterToken token, Path gamePath, std::s } // The nickname is shown on both the Networking and System tabs. -void GameSettingsScreen::AddNicknameChoice(UI::ViewGroup *parent) { +UI::View *GameSettingsScreen::AddNicknameChoice(UI::ViewGroup *parent) { using namespace UI; auto sy = GetI18NCategory(I18NCat::SYSTEM); @@ -1019,6 +1019,7 @@ void GameSettingsScreen::AddNicknameChoice(UI::ViewGroup *parent) { // Updates the warning icon, and the username notice on the Networking tab. RecreateViews(); }); + return nickname; } void GameSettingsScreen::CreateNetworkingSettings(UI::ViewGroup *networkingSettings) { @@ -1084,21 +1085,20 @@ void GameSettingsScreen::CreateNetworkingSettings(UI::ViewGroup *networkingSetti relayModePopup->SetEnabled(!PSP_IsInited()); networkingSettings->Add(new SettingHint(n->T("PacketRelayHint", "Available on servers that provide 'aemu_postoffice' packet relay, like socom.cc. Disable this for LAN or VPN play. Can be more reliable, but sometimes slower."), relayModePopup)); - AddNicknameChoice(networkingSettings); - networkingSettings->Add(new SettingHint(n->T("Try to pick a unique nickname for ad hoc play"), nullptr)); + View *nicknameChoice = AddNicknameChoice(networkingSettings); + networkingSettings->Add(new SettingHint(n->T("Try to pick a unique nickname for ad hoc play"), nicknameChoice)); networkingSettings->Add(new CheckBox(&g_Config.bEnableAdhocServer, n->T("Enable built-in ad hoc server")))->OnClick.Add(requestLocalNetwork); CheckBox *relay_mode_enet = networkingSettings->Add(new CheckBox(&g_Config.bAdhocUseUdp, n->T("Connect to adhoc server using UDP (ENet) instead of TCP"))); relay_mode_enet->SetEnabled(!PSP_IsInited()); - networkingSettings->Add(new SettingHint(n->T("Try switching between TCP/UDP if you suffer frequent adhoc server disconnects. Some network environments favor TCP and some UDP."), nullptr)); + networkingSettings->Add(new SettingHint(n->T("Try switching between TCP/UDP if you suffer frequent adhoc server disconnects. Some network environments favor TCP and some UDP."), relay_mode_enet)); CheckBox *relay_mode_enet_pdp_reliable = networkingSettings->Add(new CheckBox(&g_Config.bAdhocRelayModeEnetPdpReliable, n->T("Reliable datagram packet delivery on packet relay in UDP mode"))); relay_mode_enet_pdp_reliable->SetEnabledFunc([] { return !PSP_IsInited() && g_Config.bAdhocUseUdp; }); - networkingSettings->Add(new SettingHint(n->T("When disabled, packet loss on datagram adhoc sockets will be exposed to game's netcode. Keep this enabled if you don't know what that means."), nullptr)); - + networkingSettings->Add(new SettingHint(n->T("When disabled, packet loss on datagram adhoc sockets will be exposed to game's netcode. Keep this enabled if you don't know what that means."), relay_mode_enet_pdp_reliable)); networkingSettings->Add(new ItemHeader(n->T("Infrastructure"))); if (g_Config.sInfrastructureUsername.empty()) { @@ -1482,7 +1482,7 @@ void GameSettingsScreen::CreateSystemSettings(UI::ViewGroup *systemSettings) { systemSettings->Add(new PopupMultiChoice(&g_Config.iLanguage, psps->T("Game language"), defaultLanguages, -1, ARRAY_SIZE(defaultLanguages), I18NCat::PSPSETTINGS, screenManager())); static const char *models[] = { "PSP-1000", "PSP-2000/3000" }; systemSettings->Add(new PopupMultiChoice(&g_Config.iPSPModel, sy->T("PSP Model"), models, 0, ARRAY_SIZE(models), I18NCat::SYSTEM, screenManager()))->SetEnabled(!PSP_IsInited()); - AddNicknameChoice(systemSettings); + UI::View *nicknameChoice = AddNicknameChoice(systemSettings); systemSettings->Add(new CheckBox(&g_Config.bDayLightSavings, sy->T("Daylight savings"))); static const char *dateFormat[] = { "YYYYMMDD", "MMDDYYYY", "DDMMYYYY" }; systemSettings->Add(new PopupMultiChoice(&g_Config.iDateFormat, sy->T("Date Format"), dateFormat, 0, ARRAY_SIZE(dateFormat), I18NCat::SYSTEM, screenManager())); diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index 6f803b1743..7c15ca0a44 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -57,7 +57,7 @@ private: void CreateToolsSettings(UI::ViewGroup *tools); void CreateSystemSettings(UI::ViewGroup *systemSettings); void CreateVRSettings(UI::ViewGroup *vrSettings); - void AddNicknameChoice(UI::ViewGroup *parent); + UI::View *AddNicknameChoice(UI::ViewGroup *parent); std::string gameID_; #if defined(_WIN32) && !PPSSPP_PLATFORM(UWP)