From bcfcdb997408732b6d85a49d6016e8fdf74bcae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 5 Nov 2026 20:03:37 +0100 Subject: [PATCH] Improve the look of the MemstickScreen --- Core/HLE/sceUtility.cpp | 35 +++++++ UI/MainScreen.cpp | 2 + UI/MemStickScreen.cpp | 194 +++++++++++++++++++------------------- UI/MemStickScreen.h | 9 +- UI/SimpleDialogScreen.cpp | 6 ++ UI/SimpleDialogScreen.h | 1 + 6 files changed, 145 insertions(+), 102 deletions(-) diff --git a/Core/HLE/sceUtility.cpp b/Core/HLE/sceUtility.cpp index cdfcf2ad7b..c36e32817c 100644 --- a/Core/HLE/sceUtility.cpp +++ b/Core/HLE/sceUtility.cpp @@ -86,6 +86,11 @@ static const int atrac3PlusModuleDeps[] = {0x0300, 0}; static const int mpegBaseModuleDeps[] = {0x0300, 0}; static const int mp4ModuleDeps[] = {0x0300, 0}; +static u32 vshHtmlViewerWorkspaceAddr; +static u32 vshHtmlViewerWorkspaceSize; + +static constexpr const char *VSH_HTMLVIEWER_WORKSPACE_TAG = "VSH htmlviewer workspace"; + // Loading the firmware's module for a library we've been told not to HLE. // // This is the path for a game that brings no copy of its own. One that does loads it directly and @@ -1782,6 +1787,34 @@ static u32 sceUtilityUnloadUsbModule(u32 module) { return hleLogWarning(Log::sceUtility, 0, "UNIMPL"); } +static u32 VshHtmlViewerAcquireWorkspace(u32 stateAddr, u32 ownerAddr) { + if (!Memory::IsValidRange(stateAddr, 12)) + return 0; + + u32 workspaceAddr = Memory::ReadUnchecked_U32(stateAddr); + u32 workspaceSize = Memory::ReadUnchecked_U32(stateAddr + 4); + + if (workspaceAddr == 0) { + workspaceSize = + Memory::g_MemorySize > 0x023FFFFF ? 0x00C00000 : 0x00800000; + + workspaceAddr = userMemory.Alloc( + workspaceSize, false, VSH_HTMLVIEWER_WORKSPACE_TAG); + + if (workspaceAddr == (u32)-1) + return 0; + + vshHtmlViewerWorkspaceAddr = workspaceAddr; + vshHtmlViewerWorkspaceSize = workspaceSize; + + Memory::WriteUnchecked_U32(workspaceAddr, stateAddr); + Memory::WriteUnchecked_U32(workspaceSize, stateAddr + 4); + } + + Memory::WriteUnchecked_U32(ownerAddr, stateAddr + 8); + return workspaceAddr; +} + const HLEFunction sceUtility[] = { {0X1579A159, &WrapU_U, "sceUtilityLoadNetModule", 'x', "x" }, {0X64D50C56, &WrapU_U, "sceUtilityUnloadNetModule", 'x', "x" }, @@ -1927,6 +1960,8 @@ const HLEFunction sceUtility[] = { {0xC0DE0001, &WrapI_I, "__UtilityFinishDialog", 'i', "i" }, {0xC0DE0002, &WrapI_I, "__UtilityWorkUs", 'i', "i" }, {0xC0DE0003, &WrapI_I, "__UtilityInitDialog", 'i', "i" }, + + {0xC0DE0004, &WrapU_UU, "__VshHtmlViewerAcquireWorkspace", 'x', "xx"}, }; void Register_sceUtility() diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index deb7c7f67a..4546b094db 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -538,6 +538,8 @@ void MainScreen::CreateViews() { LinearLayout *buttonGroup = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT, 1.0f, UI::Gravity::G_VCENTER)); + buttonGroup->SetPadding(Margins(0, 0, 12, 12)); + CreateMainButtons(buttonGroup, vertical); header->Add(buttonGroup); diff --git a/UI/MemStickScreen.cpp b/UI/MemStickScreen.cpp index c3f2c2bdfa..e23667e756 100644 --- a/UI/MemStickScreen.cpp +++ b/UI/MemStickScreen.cpp @@ -41,19 +41,14 @@ #include "Common/File/DiskFree.h" #include "Common/Thread/ThreadManager.h" -#include "Common/UI/ScrollView.h" #include "Common/UI/Notice.h" #include "Core/Config.h" -#include "Core/Reporting.h" -#include "Core/System.h" -#include "Core/Util/GameManager.h" #include "Core/Util/MemStick.h" #include "UI/MemStickScreen.h" #include "UI/MainScreen.h" #include "UI/BaseScreens.h" -#include "UI/OnScreenDisplay.h" static std::string FormatSpaceString(int64_t space) { if (space >= 0) { @@ -66,7 +61,9 @@ static std::string FormatSpaceString(int64_t space) { } MemStickScreen::MemStickScreen(bool initialSetup) - : initialSetup_(initialSetup) { + : UITwoPaneBaseDialogScreen(Path(), + TwoPaneFlags::SettingsToTheRight | TwoPaneFlags::ContentsCanScroll | (initialSetup ? TwoPaneFlags::NoBackButton : TwoPaneFlags::Default)), + initialSetup_(initialSetup) { #if PPSSPP_PLATFORM(ANDROID) // Let's only offer the browse-for-folder choice on Android 10 or later. // Earlier versions often don't really have working folder browsers. @@ -81,7 +78,7 @@ MemStickScreen::MemStickScreen(bool initialSetup) if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) { choice_ = CHOICE_BROWSE_FOLDER; } else { - WARN_LOG_REPORT(Log::System, "Scoped storage not enabled - shouldn't be in MemStickScreen at initial setup"); + WARN_LOG(Log::System, "Scoped storage not enabled - shouldn't be in MemStickScreen at initial setup"); choice_ = CHOICE_STORAGE_ROOT; // Shouldn't really be here in initial setup. } @@ -103,112 +100,111 @@ MemStickScreen::MemStickScreen(bool initialSetup) } } -static void AddExplanation(UI::ViewGroup *viewGroup, MemStickScreen::Choice choice, UI::View *extraView = nullptr) { +static std::string GetExplanationString(MemStickScreen::Choice choice) { auto iz = GetI18NCategory(I18NCat::MEMSTICK); using namespace UI; int flags = FLAG_WRAP_TEXT; - UI::ViewGroup *holder = new UI::LinearLayout(ORIENT_VERTICAL); - - UI::ViewGroup *indentHolder = new UI::LinearLayout(ORIENT_HORIZONTAL); - indentHolder->Add(new Spacer(20.0)); - indentHolder->Add(holder); - - viewGroup->Add(indentHolder); - - if (extraView) { - holder->Add(extraView); - } - + std::string str; + std::string_view bullet = "• "; switch (choice) { case MemStickScreen::CHOICE_STORAGE_ROOT: // Old school choice - holder->Add(new TextView(iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP"), flags, false))->SetBullet(true); - holder->Add(new TextView(iz->T("DataCanBeShared", "Data can be shared between PPSSPP regular/Gold"), flags, false))->SetBullet(true); - holder->Add(new TextView(iz->T("EasyUSBAccess", "Easy USB access"), flags, false))->SetBullet(true); + str += bullet; + str += iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP"); + str += "\n"; + str += bullet; + str += iz->T("DataCanBeShared", "Data can be shared between PPSSPP regular/Gold"); + str += "\n"; + str += bullet; + str += iz->T("EasyUSBAccess", "Easy USB access"); + str += "\n"; break; case MemStickScreen::CHOICE_BROWSE_FOLDER: - holder->Add(new TextView(iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP"), flags, false))->SetBullet(true); - holder->Add(new TextView(iz->T("DataCanBeShared", "Data can be shared between PPSSPP regular/Gold"), flags, false))->SetBullet(true); -#if !PPSSPP_PLATFORM(UWP) - holder->Add(new TextView(iz->T("EasyUSBAccess", "Easy USB access"), flags, false))->SetBullet(true); -#endif + str += bullet; + str += iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP"); + str += "\n"; + str += bullet; + str += iz->T("DataCanBeShared", "Data can be shared between PPSSPP regular/Gold"); + str += "\n"; + str += bullet; + str += iz->T("EasyUSBAccess", "Easy USB access"); + str += "\n"; break; case MemStickScreen::CHOICE_PRIVATE_DIRECTORY: // Consider https://www.compart.com/en/unicode/U+26A0 (unicode warning sign?)? or a graphic? - holder->Add(new TextView(iz->T("DataWillBeLostOnUninstall", "Warning! Data will be lost when you uninstall PPSSPP!"), flags, false))->SetBullet(true); - holder->Add(new TextView(iz->T("DataCannotBeShared", "Data CANNOT be shared between PPSSPP regular/Gold!"), flags, false))->SetBullet(true); + str += bullet; + str += iz->T("DataWillBeLostOnUninstall", "Warning! Data will be lost when you uninstall PPSSPP!"); + str += "\n"; + str += bullet; + str += iz->T("DataCannotBeShared", "Data CANNOT be shared between PPSSPP regular/Gold!"); + str += "\n"; #if !PPSSPP_PLATFORM(UWP) if (System_GetPropertyBool(SYSPROP_APP_GOLD)) { - holder->Add(new TextView(iz->T("USBAccessThroughGold", "USB access through Android/data/org.ppsspp.ppssppgold/files"), flags, false))->SetBullet(true); + str += bullet; + str += iz->T("USBAccessThroughGold", "USB access through Android/data/org.ppsspp.ppssppgold/files"); + str += "\n"; } else { - holder->Add(new TextView(iz->T("USBAccessThrough", "USB access through Android/data/org.ppsspp.ppsspp/files"), flags, false))->SetBullet(true); + str += bullet; + str += iz->T("USBAccessThrough", "USB access through Android/data/org.ppsspp.ppsspp/files"); + str += "\n"; } #endif break; case MemStickScreen::CHOICE_SET_MANUAL: - holder->Add(new TextView(iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP"), flags, false))->SetBullet(true); - holder->Add(new TextView(iz->T("EasyUSBAccess", "Easy USB access"), flags, false))->SetBullet(true); + str += bullet; + str += iz->T("DataWillStay", "Data will stay even if you uninstall PPSSPP"); + str += "\n"; + str += bullet; + str += iz->T("EasyUSBAccess", "Easy USB access"); + str += "\n"; break; default: - holder->Add(new TextView(iz->T("EasyUSBAccess", "Easy USB access"), flags, false))->SetBullet(true); + str += bullet; + str += iz->T("EasyUSBAccess", "Easy USB access"); + str += "\n"; break; } + // The last line's newline would leave an empty line at the bottom of the hint. + while (!str.empty() && str.back() == '\n') { + str.pop_back(); + } + return str; } -void MemStickScreen::CreateViews() { +std::string_view MemStickScreen::GetTitle() const { + if (initialSetup_) { + return T(I18NCat::MEMSTICK, "Welcome to PPSSPP!"); + } + return T(I18NCat::SYSTEM, "Memory Stick folder"); +} + +void MemStickScreen::CreateContentViews(UI::ViewGroup *parent) { using namespace UI; - auto di = GetI18NCategory(I18NCat::DIALOG); auto ms = GetI18NCategory(I18NCat::MEMSTICK); - Margins actionMenuMargins(15, 0, 15, 0); + ChoiceGroupList *groups = parent->Add(new ChoiceGroupList()); - root_ = new LinearLayout(ORIENT_HORIZONTAL); + groups->Add(new TextView(ms->T("MemoryStickDescription", "Choose where to keep PSP data (Memory Stick)"), ALIGN_LEFT | FLAG_WRAP_TEXT, false)); - Spacer *spacerColumn = new Spacer(new LinearLayoutParams(20.0, FILL_PARENT, 0.0f)); - ScrollView *mainColumnScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0)); - - ViewGroup *mainColumn = new LinearLayoutList(ORIENT_VERTICAL); - mainColumnScroll->Add(mainColumn); - - root_->Add(spacerColumn); - root_->Add(mainColumnScroll); - - if (initialSetup_) { - mainColumn->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 12.0f, 0.0f))); - mainColumn->Add(new TextView(ms->T("Welcome to PPSSPP!"), ALIGN_LEFT, false)); - } - - mainColumn->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 18.0f, 0.0f))); - - mainColumn->Add(new TextView(ms->T("MemoryStickDescription", "Choose where to keep PSP data (Memory Stick)"), ALIGN_LEFT, false)); - mainColumn->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 18.0f, 0.0f))); - - ViewGroup *subColumns = new LinearLayoutList(ORIENT_HORIZONTAL); - mainColumn->Add(subColumns); - - ViewGroup *leftColumn = new LinearLayoutList(ORIENT_VERTICAL, new LinearLayoutParams(1.0)); - subColumns->Add(leftColumn); - - LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(250, FILL_PARENT, actionMenuMargins)); - subColumns->Add(rightColumnItems); + ChoiceGroup *choiceGroup = groups->Add(new ChoiceGroup()); // For legacy Android systems, so you can switch back to the old ways if you move to SD or something. // Trying to avoid needing a scroll view, so only showing the explanation for one option at a time. #if !PPSSPP_PLATFORM(UWP) if (!System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) { - leftColumn->Add(new RadioButton(&choice_, CHOICE_STORAGE_ROOT, ms->T("Use PSP folder at root of storage")))->OnClick.Handle(this, &MemStickScreen::OnChoiceClick); - if (choice_ == CHOICE_STORAGE_ROOT) { - AddExplanation(leftColumn, (MemStickScreen::Choice)choice_); - } + RadioButton *pspFolder = choiceGroup->Add(new RadioButton(&choice_, CHOICE_STORAGE_ROOT, ms->T("Use PSP folder at root of storage"))); + pspFolder->OnClick.Handle(this, &MemStickScreen::OnChoiceClick); + pspFolder->SetHintText(GetExplanationString(CHOICE_STORAGE_ROOT)); } #endif if (storageBrowserWorking_) { - leftColumn->Add(new RadioButton(&choice_, CHOICE_BROWSE_FOLDER, ms->T("Create or Choose a PSP folder")))->OnClick.Handle(this, &MemStickScreen::OnChoiceClick); - + RadioButton *browseFolder = choiceGroup->Add(new RadioButton(&choice_, CHOICE_BROWSE_FOLDER, ms->T("Create or Choose a PSP folder"))); + browseFolder->OnClick.Handle(this, &MemStickScreen::OnChoiceClick); + browseFolder->SetHintText(GetExplanationString(CHOICE_BROWSE_FOLDER)); // TODO: Show current folder here if we have one set. } @@ -219,16 +215,9 @@ void MemStickScreen::CreateViews() { #endif if (!storageBrowserWorking_ || legacy) { - leftColumn->Add(new RadioButton(&choice_, CHOICE_SET_MANUAL, ms->T("Manually specify PSP folder")))->OnClick.Handle(this, &MemStickScreen::OnChoiceClick); - // TODO: Show current folder here if we have one set. - } - - if (choice_ == CHOICE_BROWSE_FOLDER || choice_ == CHOICE_SET_MANUAL) { - UI::View *extraView = nullptr; - if (!g_Config.memStickDirectory.empty()) { - extraView = new TextView(StringFromFormat(" %s: %s", ms->T_cstr("Current"), g_Config.memStickDirectory.ToVisualString().c_str()), ALIGN_LEFT, false); - } - AddExplanation(leftColumn, (MemStickScreen::Choice)choice_, extraView); + RadioButton *manualFolder = choiceGroup->Add(new RadioButton(&choice_, CHOICE_SET_MANUAL, ms->T("Manually specify PSP folder"))); + manualFolder->OnClick.Handle(this, &MemStickScreen::OnChoiceClick); + manualFolder->SetHintText(GetExplanationString(CHOICE_SET_MANUAL)); } std::string privateString(ms->T("Use App Private Data")); @@ -237,12 +226,22 @@ void MemStickScreen::CreateViews() { privateString = StringFromFormat("%s (%s)", ms->T_cstr("Skip for now"), privateString.c_str()); } - leftColumn->Add(new RadioButton(&choice_, CHOICE_PRIVATE_DIRECTORY, privateString))->OnClick.Handle(this, &MemStickScreen::OnChoiceClick); - if (choice_ == CHOICE_PRIVATE_DIRECTORY) { - AddExplanation(leftColumn, (MemStickScreen::Choice)choice_); + RadioButton *privateFolder = choiceGroup->Add(new RadioButton(&choice_, CHOICE_PRIVATE_DIRECTORY, privateString)); + privateFolder->OnClick.Handle(this, &MemStickScreen::OnChoiceClick); + privateFolder->SetHintText(GetExplanationString(CHOICE_PRIVATE_DIRECTORY)); + + if ((choice_ == CHOICE_BROWSE_FOLDER || choice_ == CHOICE_SET_MANUAL) && !g_Config.memStickDirectory.empty()) { + groups->Add(new TextView(StringFromFormat("%s: %s", ms->T_cstr("Current"), g_Config.memStickDirectory.ToVisualString().c_str()), ALIGN_LEFT, false)); } - leftColumn->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 12.0f, 0.0f))); + INFO_LOG(Log::System, "MemStickScreen: initialSetup=%d", (int)initialSetup_); +} + +void MemStickScreen::CreateSettingsViews(UI::ViewGroup *parent) { + using namespace UI; + + auto di = GetI18NCategory(I18NCat::DIALOG); + auto ms = GetI18NCategory(I18NCat::MEMSTICK); std::string_view confirmButtonText; ImageID confirmButtonImage = ImageID::invalid(); @@ -266,19 +265,16 @@ void MemStickScreen::CreateViews() { break; } - rightColumnItems->Add(new UI::Choice(confirmButtonText, confirmButtonImage))->OnClick.Handle(this, &MemStickScreen::OnConfirmClick); - rightColumnItems->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 12.0f, 0.0f))); + ChoiceGroupList *groups = parent->Add(new ChoiceGroupList()); - if (!initialSetup_) { - rightColumnItems->Add(new UI::Choice(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnBack); - } + ChoiceGroup *confirmGroup = groups->Add(new ChoiceGroup()); + confirmGroup->Add(new UI::Choice(confirmButtonText, confirmButtonImage))->OnClick.Handle(this, &MemStickScreen::OnConfirmClick); + + // The top bar has the back button. if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) != DEVICE_TYPE_TV) { - rightColumnItems->Add(new UI::Choice(ms->T("WhatsThis", "What's this?"), ImageID("I_LINK_OUT_QUESTION")))->OnClick.Handle(this, &MemStickScreen::OnHelp); + ChoiceGroup *helpGroup = groups->Add(new ChoiceGroup()); + helpGroup->Add(new UI::Choice(ms->T("WhatsThis", "What's this?"), ImageID("I_LINK_OUT_QUESTION")))->OnClick.Handle(this, &MemStickScreen::OnHelp); } - - rightColumnItems->ApplyRoundCorners(); - - INFO_LOG(Log::System, "MemStickScreen: initialSetup=%d", (int)initialSetup_); } void MemStickScreen::OnHelp(UI::EventParams ¶ms) { @@ -384,7 +380,7 @@ void MemStickScreen::UseInternalStorage(UI::EventParams ¶ms) { TriggerFinish(DialogResult::DR_OK); } else { // This can't really happen?? Not worth making an error message. - ERROR_LOG_REPORT(Log::System, "Could not switch memstick path in setup (internal)"); + ERROR_LOG(Log::System, "Could not switch memstick path in setup (internal)"); } // Don't have a confirmation dialog that would otherwise do it for us, need to just switch directly to the main screen. screenManager()->switchScreen(new MainScreen()); @@ -407,7 +403,7 @@ void MemStickScreen::UseStorageRoot(UI::EventParams ¶ms) { TriggerFinish(DialogResult::DR_OK); } else { // This can't really happen?? Not worth making an error message. - ERROR_LOG_REPORT(Log::System, "Could not switch memstick path in setup"); + ERROR_LOG(Log::System, "Could not switch memstick path in setup"); } } else if (pendingMemStickFolder != g_Config.memStickDirectory) { // Always ask for confirmation when called from the UI. Likely there's already some data. @@ -466,7 +462,7 @@ void MemStickScreen::dialogFinished(const Screen *dialog, DialogResult result) { } void MemStickScreen::update() { - UIBaseDialogScreen::update(); + UITwoPaneBaseDialogScreen::update(); if (done_) { TriggerFinish(DialogResult::DR_OK); done_ = false; diff --git a/UI/MemStickScreen.h b/UI/MemStickScreen.h index 5242b665be..86a4264fa7 100644 --- a/UI/MemStickScreen.h +++ b/UI/MemStickScreen.h @@ -31,12 +31,13 @@ #include "UI/BaseScreens.h" #include "UI/MiscViews.h" +#include "UI/SimpleDialogScreen.h" class NoticeView; // MemStickScreen - let's you configure your memory stick directory. // Currently only useful for Android. -class MemStickScreen : public UIBaseDialogScreen { +class MemStickScreen : public UITwoPaneBaseDialogScreen { public: MemStickScreen(bool initialSetup); ~MemStickScreen() = default; @@ -51,7 +52,9 @@ public: }; protected: - void CreateViews() override; + void CreateContentViews(UI::ViewGroup *parent) override; + void CreateSettingsViews(UI::ViewGroup *parent) override; + std::string_view GetTitle() const override; void dialogFinished(const Screen *dialog, DialogResult result) override; void update() override; @@ -59,7 +62,7 @@ protected: // Simple anti-flicker due to delayed finish. if (!done_) { // render as usual. - return UIBaseDialogScreen::render(mode); + return UITwoPaneBaseDialogScreen::render(mode); } else { // no render. black frame insertion is better than flicker. } diff --git a/UI/SimpleDialogScreen.cpp b/UI/SimpleDialogScreen.cpp index 0d6d52bf74..f831fedc70 100644 --- a/UI/SimpleDialogScreen.cpp +++ b/UI/SimpleDialogScreen.cpp @@ -73,6 +73,9 @@ void UITwoPaneBaseDialogScreen::CreateViews() { if (flags_ & TwoPaneFlags::SettingsToTheRight) { // If settings are in context menu, we want to avoid double margins on the sides. margins.left = 0; + if (portrait) { + margins.right = 0; + } } else { margins.right = 0; } @@ -96,6 +99,9 @@ void UITwoPaneBaseDialogScreen::CreateViews() { if (flags_ & TwoPaneFlags::DropShadowTitle) { topBarFlags |= TopBarFlags::DropShadowTitle; } + if (flags_ & TwoPaneFlags::NoBackButton) { + topBarFlags |= TopBarFlags::NoBackButton; + } TopBar *topBar = root->Add(new TopBar(*screenManager()->getUIContext(), topBarFlags, GetTitle())); root->SetSpacing(0); createContentViews(root); diff --git a/UI/SimpleDialogScreen.h b/UI/SimpleDialogScreen.h index bfd5ba73cf..58c060547b 100644 --- a/UI/SimpleDialogScreen.h +++ b/UI/SimpleDialogScreen.h @@ -43,6 +43,7 @@ enum class TwoPaneFlags { CustomContextMenu = 16, NoTopbarInLandscape = 32, DropShadowTitle = 64, + NoBackButton = 128, }; ENUM_CLASS_BITOPS(TwoPaneFlags);