diff --git a/Common/System/System.h b/Common/System/System.h index d2ce08d675..3016c4d7e6 100644 --- a/Common/System/System.h +++ b/Common/System/System.h @@ -170,6 +170,7 @@ enum SystemProperty { SYSPROP_CAN_CREATE_SHORTCUT, SYSPROP_CAN_SHOW_FILE, + SYSPROP_CAN_LAUNCH_URL, // System_LaunchUrl can open a web page. SYSPROP_SUPPORTS_HTTPS, diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index 8148f28d09..556c6a7221 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -209,8 +209,6 @@ if(NOT LIBRETRO) Debugger/WebSocket/SteppingSubscriber.h Debugger/WebSocket/WebSocketUtils.cpp Debugger/WebSocket/WebSocketUtils.h - Dialog/PSPPlaceholderDialog.cpp - Dialog/PSPPlaceholderDialog.h HW/GranularMixer.cpp HW/GranularMixer.h HW/StereoResampler.cpp @@ -356,7 +354,9 @@ add_library(Core STATIC Dialog/PSPNetconfDialog.cpp Dialog/PSPNetconfDialog.h Dialog/PSPNpSigninDialog.cpp + Dialog/PSPHtmlViewerDialog.cpp Dialog/PSPNpSigninDialog.h + Dialog/PSPHtmlViewerDialog.h Dialog/PSPOskDialog.cpp Dialog/PSPOskDialog.h Dialog/PSPOskConstants.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index afc31a0f5f..58f6c8b408 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -569,6 +569,7 @@ + @@ -1087,6 +1088,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 8baa918c5e..4e8a93738b 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -556,6 +556,9 @@ Dialog + + Dialog + Debugger @@ -1881,6 +1884,9 @@ Dialog + + Dialog + MIPS\JitCommon diff --git a/Core/Dialog/PSPDialog.cpp b/Core/Dialog/PSPDialog.cpp index 6ca7a64bfb..ac554c6e4f 100644 --- a/Core/Dialog/PSPDialog.cpp +++ b/Core/Dialog/PSPDialog.cpp @@ -50,6 +50,7 @@ const char *UtilityDialogTypeToString(UtilityDialogType type) { case UtilityDialogType::GAMESHARING: return "GAMESHARING"; case UtilityDialogType::GAMEDATAINSTALL: return "GAMEDATAINSTALL"; case UtilityDialogType::NPSIGNIN: return "NPSIGNIN"; + case UtilityDialogType::HTMLVIEWER: return "HTMLVIEWER"; default: return "(unknown)"; } } @@ -101,7 +102,7 @@ void PSPDialog::UpdatePendingStatus() { if (pendingStatus == SCE_UTILITY_STATUS_NONE && status == SCE_UTILITY_STATUS_SHUTDOWN) { FinishVolatile(); } else if (pendingStatus == SCE_UTILITY_STATUS_RUNNING && status == SCE_UTILITY_STATUS_INITIALIZE) { - if (!volatileLocked_) { + if (!volatileLocked_ && LocksVolatileMemory()) { volatileLocked_ = KernelVolatileMemLock(0, 0, 0) == 0; changeAllowed = volatileLocked_; } @@ -149,7 +150,7 @@ void PSPDialog::ChangeStatus(DialogStatus newStatus, int delayUs) { if (newStatus == SCE_UTILITY_STATUS_NONE && status == SCE_UTILITY_STATUS_SHUTDOWN) { FinishVolatile(); } else if (newStatus == SCE_UTILITY_STATUS_RUNNING && status == SCE_UTILITY_STATUS_INITIALIZE) { - if (!volatileLocked_) { + if (!volatileLocked_ && LocksVolatileMemory()) { // TODO: Should probably make the status pending instead? volatileLocked_ = KernelVolatileMemLock(0, 0, 0) == 0; } diff --git a/Core/Dialog/PSPDialog.h b/Core/Dialog/PSPDialog.h index 0380d7a548..9f0320305e 100644 --- a/Core/Dialog/PSPDialog.h +++ b/Core/Dialog/PSPDialog.h @@ -108,6 +108,10 @@ protected: // TODO: Remove this once all dialogs are updated. virtual bool UseAutoStatus() = 0; + // Whether the dialog holds volatile memory while running. + virtual bool LocksVolatileMemory() const { + return true; + } static int GetConfirmButton(); static int GetCancelButton(); diff --git a/Core/Dialog/PSPHtmlViewerDialog.cpp b/Core/Dialog/PSPHtmlViewerDialog.cpp new file mode 100644 index 0000000000..7f83874e54 --- /dev/null +++ b/Core/Dialog/PSPHtmlViewerDialog.cpp @@ -0,0 +1,229 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program 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 Foundation, version 2.0 or later versions. + +// This program 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 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Common/Data/Text/I18n.h" +#include "Common/Serialize/Serializer.h" +#include "Common/Serialize/SerializeFuncs.h" +#include "Common/StringUtils.h" +#include "Common/System/System.h" +#include "Core/Dialog/PSPHtmlViewerDialog.h" +#include "Core/HLE/ErrorCodes.h" +#include "Core/HLE/sceCtrl.h" +#include "Core/HLE/sceKernelMemory.h" +#include "Core/Util/PPGeDraw.h" + +// Guesses. The real browser takes much longer to come up. +static const int HTMLVIEWER_INIT_DELAY_US = 200000; +static const int HTMLVIEWER_SHUTDOWN_DELAY_US = 40000; + +struct SceUtilityHtmlViewerParam { + pspUtilityDialogCommon common; + u32_le memAddr; + u32_le memSize; + s32_le unknown1; + s32_le unknown2; + u32_le initialUrl; + u32_le numTabs; + u32_le interfaceMode; + u32_le options; + u32_le dlDirName; + u32_le dlFileName; + u32_le ulDirName; + u32_le ulFileName; + u32_le cookieMode; + u32_le unknown3; + u32_le homeUrl; + u32_le textSize; + // The smallest request (0x70) ends here. +}; + +// From sceUtility_Driver's InitStart for the HtmlViewer, firmware 6.61: the request size says which +// firmware's layout it is, and the working memory it allocates for the browser depends on that and +// on bit 0x400 of options. Any other size is INVALID_PARAM_SIZE. +static bool HtmlViewerWorkSize(u32 size, u32 options, u32 *workSize) { + switch (size) { + case 0x70: // 2.00 + case 0x78: // 2.50 + case 0x80: // 2.60 + *workSize = 0x380000; + return true; + case 0x98: // 2.70 + case 0xA4: // 2.80 + case 0xA8: // 3.00 + *workSize = (options & 0x400) ? 0x480000 : 0x380000; + return true; + default: + return false; + } +} + +static std::string ReadUrl(u32 addr) { + if (!Memory::IsValidNullTerminatedString(addr)) { + return ""; + } + return Memory::GetCharPointerUnchecked(addr); +} + +// Only plain web addresses get handed to the host: printable ASCII, nothing a shell or URL parser +// could take for something else. +static bool IsWebUrl(std::string_view url) { + if (!startsWithNoCase(url, "http://") && !startsWithNoCase(url, "https://")) { + return false; + } + for (char c : url) { + if (c <= 0x20 || c >= 0x7F) { + return false; + } + } + return true; +} + +PSPHtmlViewerDialog::PSPHtmlViewerDialog(UtilityDialogType type) : PSPDialog(type) { +} + +int PSPHtmlViewerDialog::Init(u32 paramAddr) { + if (GetStatus() != SCE_UTILITY_STATUS_NONE) { + return SCE_ERROR_UTILITY_INVALID_STATUS; + } + if (!Memory::IsValidRange(paramAddr, sizeof(pspUtilityDialogCommon))) { + return SCE_ERROR_UTILITY_INVALID_ADDRESS; + } + const u32 size = Memory::ReadUnchecked_U32(paramAddr); + const SceUtilityHtmlViewerParam *param = (const SceUtilityHtmlViewerParam *)Memory::GetPointerUnchecked(paramAddr); + u32 workSize = 0; + if (!HtmlViewerWorkSize(size, param->options, &workSize)) { + return SCE_ERROR_UTILITY_INVALID_PARAM_SIZE; + } + if (!Memory::IsValidRange(paramAddr, size)) { + return SCE_ERROR_UTILITY_INVALID_ADDRESS; + } + + u32 addr = userMemory.Alloc(workSize, false, "HtmlViewer"); + if (addr == (u32)-1) { + return SCE_KERNEL_ERROR_MEMBLOCK_ALLOC_FAILED; + } + workMemory_ = addr; + + params_ = paramAddr; + url_ = ReadUrl(param->initialUrl); + if (url_.empty()) { + url_ = ReadUrl(param->homeUrl); + } + started_ = true; + scrollPos_ = 0.0f; + framesUpHeld_ = 0; + framesDownHeld_ = 0; + + ChangeStatus(SCE_UTILITY_STATUS_INITIALIZE, 0); + ChangeStatus(SCE_UTILITY_STATUS_RUNNING, HTMLVIEWER_INIT_DELAY_US); + InitCommon(); + // Presses from before (like the one that opened this) mustn't open the page. + UpdateButtons(); + StartFade(true); + return 0; +} + +int PSPHtmlViewerDialog::Update(int animSpeed) { + if (GetStatus() != SCE_UTILITY_STATUS_RUNNING) { + return SCE_ERROR_UTILITY_INVALID_STATUS; + } + + auto di = GetI18NCategory(I18NCat::DIALOG); + const bool hasUrl = IsWebUrl(url_); + const bool canOpen = hasUrl && System_GetPropertyBool(SYSPROP_CAN_LAUNCH_URL); + + UpdateButtons(); + UpdateCommon(); + UpdateFade(animSpeed); + + StartDraw(); + PPGeDrawRect(0, 0, 480, 272, CalcFadedColor(0xC0C8B2AC)); + if (hasUrl) { + DisplayMessage2(std::string(di->T("The game wants to open a web page:")) + "\n\n" + url_); + } else { + DisplayMessage2(di->T("The game wants to open a web page, but gave no address.")); + } + + // Fixed buttons, whatever the game's confirm button is: X opens, O backs out. + PPGeStyle textStyle = FadedStyle(PPGeAlign::BOX_LEFT, 0.55f); + if (canOpen) { + PPGeDrawImage(ImageID("I_CROSS"), 183.5f, 256, 11.5f, 11.5f, textStyle); + PPGeDrawText(di->T("Open in browser"), 183.5f + 14.5f, 252, textStyle); + } + PPGeDrawImage(ImageID("I_CIRCLE"), 321.5f, 256, 11.5f, 11.5f, textStyle); + PPGeDrawText(di->T("Back"), 321.5f + 14.5f, 252, textStyle); + + bool done = false; + if (canOpen && IsButtonPressed(CTRL_CROSS)) { + System_LaunchUrl(LaunchUrlType::BROWSER_URL, url_); + done = true; + } else if (IsButtonPressed(CTRL_CIRCLE)) { + done = true; + } + if (done) { + // As if the player had browsed and then left the browser. + params_->result = 0; + params_.NotifyWrite("DialogResult"); + StartFade(false); + } + + EndDraw(); + return 0; +} + +void PSPHtmlViewerDialog::FreeWorkMemory() { + if (workMemory_ != 0) { + userMemory.Free(workMemory_); + workMemory_ = 0; + } +} + +int PSPHtmlViewerDialog::Shutdown(bool force) { + if (ReadStatus() != SCE_UTILITY_STATUS_FINISHED && !force) { + return SCE_ERROR_UTILITY_INVALID_STATUS; + } + + if (force) { + // Only when the emulator shuts down, and user memory goes with it. + workMemory_ = 0; + PSPDialog::Shutdown(true); + } else { + FreeWorkMemory(); + ChangeStatus(SCE_UTILITY_STATUS_SHUTDOWN, 0); + ChangeStatus(SCE_UTILITY_STATUS_NONE, HTMLVIEWER_SHUTDOWN_DELAY_US); + } + return 0; +} + +void PSPHtmlViewerDialog::DoState(PointerWrap &p) { + PSPDialog::DoState(p); + + auto s = p.Section("PSPHtmlViewerDialog", 1); + if (!s) + return; + + Do(p, params_); + Do(p, url_); + Do(p, workMemory_); + Do(p, started_); +} + +pspUtilityDialogCommon *PSPHtmlViewerDialog::GetCommonParam() { + if (params_.IsValid()) + return params_; + return nullptr; +} diff --git a/Core/Dialog/PSPHtmlViewerDialog.h b/Core/Dialog/PSPHtmlViewerDialog.h new file mode 100644 index 0000000000..aa57f54ac4 --- /dev/null +++ b/Core/Dialog/PSPHtmlViewerDialog.h @@ -0,0 +1,60 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program 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 Foundation, version 2.0 or later versions. + +// This program 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 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include + +#include "Core/Dialog/PSPDialog.h" +#include "Core/MemMap.h" + +// Stands in for the PSP's web browser: shows the URL the game wants to open and offers to open it in +// the host's browser instead. +// +// On a PSP the HtmlViewer has its own state, apart from the other dialogs: it doesn't wait for them, +// and they don't wait for it (utility/dialog/htmlviewer). +class PSPHtmlViewerDialog : public PSPDialog { +public: + PSPHtmlViewerDialog(UtilityDialogType type); + + int Init(u32 paramAddr); + int Update(int animSpeed) override; + int Shutdown(bool force = false) override; + void DoState(PointerWrap &p) override; + pspUtilityDialogCommon *GetCommonParam() override; + + // Until one has started, all the HtmlViewer calls return WRONG_TYPE. + bool HasStarted() const { + return started_; + } + +protected: + bool UseAutoStatus() override { + return false; + } + bool LocksVolatileMemory() const override { + return false; + } + +private: + void FreeWorkMemory(); + + PSPPointer params_; + std::string url_; + u32 workMemory_ = 0; + bool started_ = false; +}; diff --git a/Core/HLE/ErrorCodes.h b/Core/HLE/ErrorCodes.h index 9d345ed2de..f15d353d5f 100644 --- a/Core/HLE/ErrorCodes.h +++ b/Core/HLE/ErrorCodes.h @@ -306,6 +306,7 @@ enum PSPErrorCode : u32 { SCE_ERROR_AV_MODULE_NOT_LOADED = 0x80110F03, SCE_ERROR_UTILITY_INVALID_STATUS = 0x80110001, + SCE_ERROR_UTILITY_INVALID_ADDRESS = 0x80110002, SCE_ERROR_UTILITY_INVALID_PARAM_SIZE = 0x80110004, SCE_ERROR_UTILITY_WRONG_TYPE = 0x80110005, SCE_ERROR_UTILITY_STRING_TOO_LONG = 0x80110102, diff --git a/Core/HLE/sceUtility.cpp b/Core/HLE/sceUtility.cpp index 9356d32f59..67251a22a6 100644 --- a/Core/HLE/sceUtility.cpp +++ b/Core/HLE/sceUtility.cpp @@ -55,6 +55,7 @@ #include "Core/Dialog/PSPDialog.h" #include "Core/Dialog/PSPSaveDialog.h" #include "Core/Dialog/PSPMsgDialog.h" +#include "Core/Dialog/PSPHtmlViewerDialog.h" #include "Core/Dialog/PSPPlaceholderDialog.h" #include "Core/Dialog/PSPOskDialog.h" #include "Core/Dialog/PSPGamedataInstallDialog.h" @@ -327,6 +328,8 @@ static PSPScreenshotDialog *screenshotDialog; static PSPGamedataInstallDialog *gamedataInstallDialog; static PSPNpSigninDialog *npSigninDialog; static PSPPlaceholderDialog *gameSharingDialog; +// Not one of the dialogs above: it has its own state on a PSP, and doesn't count as the current dialog. +static PSPHtmlViewerDialog *htmlViewerDialog; // A lot of state seems to be shared between the various dialog types. static int oldStatus = -1; @@ -400,6 +403,8 @@ static PSPDialog *CurrentDialog(UtilityDialogType type) { return gamedataInstallDialog; case UtilityDialogType::NPSIGNIN: return npSigninDialog; + case UtilityDialogType::HTMLVIEWER: + return htmlViewerDialog; } return nullptr; } @@ -522,6 +527,7 @@ void __UtilityInit() { gamedataInstallDialog = new PSPGamedataInstallDialog(UtilityDialogType::GAMEDATAINSTALL); npSigninDialog = new PSPNpSigninDialog(UtilityDialogType::NPSIGNIN); gameSharingDialog = new PSPPlaceholderDialog(UtilityDialogType::GAMESHARING); + htmlViewerDialog = new PSPHtmlViewerDialog(UtilityDialogType::HTMLVIEWER); currentDialogType = UtilityDialogType::NONE; DeactivateDialog(); @@ -534,7 +540,7 @@ void __UtilityInit() { } void __UtilityDoState(PointerWrap &p) { - auto s = p.Section("sceUtility", 1, 8); + auto s = p.Section("sceUtility", 1, 9); if (!s) { return; } @@ -608,6 +614,9 @@ void __UtilityDoState(PointerWrap &p) { if (s >= 8) { gameSharingDialog->DoState(p); } + if (s >= 9) { + htmlViewerDialog->DoState(p); + } if (!hasAccessThread && accessThread) { accessThread->Forget(); @@ -626,6 +635,7 @@ void __UtilityShutdown() { gamedataInstallDialog->Shutdown(true); npSigninDialog->Shutdown(true); gameSharingDialog->Shutdown(true); + htmlViewerDialog->Shutdown(true); if (accessThread) { // Don't need to free it during shutdown, may have already been freed. @@ -645,6 +655,7 @@ void __UtilityShutdown() { delete gamedataInstallDialog; delete npSigninDialog; delete gameSharingDialog; + delete htmlViewerDialog; } // On a PSP, dialog init and shutdown happen partly at the accessThread priority and partly at the @@ -1677,9 +1688,31 @@ static int sceUtilityGameSharingGetStatus() { return hleLogVerbose(Log::sceUtility, status, "status: %s", UtilityDialogStatusToString(status)); } -// We never run an HtmlViewer, so it's never the current type. +// The HtmlViewer keeps its own state on a PSP: other dialogs don't make it busy or change its type, +// and until one has been started its calls return WRONG_TYPE (utility/dialog/htmlviewer). +static int sceUtilityHtmlViewerInitStart(u32 paramsPtr) { + return hleLogInfo(Log::sceUtility, htmlViewerDialog->Init(paramsPtr)); +} + +static int sceUtilityHtmlViewerShutdownStart() { + if (!htmlViewerDialog->HasStarted()) { + return hleLogDebug(Log::sceUtility, SCE_ERROR_UTILITY_WRONG_TYPE, "wrong dialog type"); + } + return hleLogDebug(Log::sceUtility, htmlViewerDialog->Shutdown()); +} + +static int sceUtilityHtmlViewerUpdate(int animSpeed) { + if (!htmlViewerDialog->HasStarted()) { + return hleLogDebug(Log::sceUtility, SCE_ERROR_UTILITY_WRONG_TYPE, "wrong dialog type"); + } + return hleLogDebug(Log::sceUtility, htmlViewerDialog->Update(animSpeed)); +} + static int sceUtilityHtmlViewerGetStatus() { - return hleLogDebug(Log::sceUtility, SCE_ERROR_UTILITY_WRONG_TYPE, "wrong dialog type"); + if (!htmlViewerDialog->HasStarted()) { + return hleLogDebug(Log::sceUtility, SCE_ERROR_UTILITY_WRONG_TYPE, "wrong dialog type"); + } + return hleLogVerbose(Log::sceUtility, htmlViewerDialog->GetStatus()); } static u32 sceUtilityLoadUsbModule(u32 module) { @@ -1741,9 +1774,9 @@ const HLEFunction sceUtility[] = { {0X88BC7406, nullptr, "sceUtilitySavedataErrGetStatus", '?', "" }, {0XBDA7D894, &WrapI_V, "sceUtilityHtmlViewerGetStatus", 'i', "" }, - {0XCDC3AA41, nullptr, "sceUtilityHtmlViewerInitStart", '?', "" }, - {0XF5CE1134, nullptr, "sceUtilityHtmlViewerShutdownStart", '?', "" }, - {0X05AFB9E4, nullptr, "sceUtilityHtmlViewerUpdate", '?', "" }, + {0XCDC3AA41, &WrapI_U, "sceUtilityHtmlViewerInitStart", 'i', "x" }, + {0XF5CE1134, &WrapI_V, "sceUtilityHtmlViewerShutdownStart", 'i', "" }, + {0X05AFB9E4, &WrapI_I, "sceUtilityHtmlViewerUpdate", 'i', "i" }, {0X16A1A8D8, nullptr, "sceUtilityAuthDialogGetStatus", '?', "" }, {0X943CBA46, nullptr, "sceUtilityAuthDialogInitStart", '?', "" }, diff --git a/Core/HLE/sceUtility.h b/Core/HLE/sceUtility.h index 7a3b0e8c65..2240708796 100644 --- a/Core/HLE/sceUtility.h +++ b/Core/HLE/sceUtility.h @@ -117,6 +117,7 @@ enum class UtilityDialogType { GAMESHARING, GAMEDATAINSTALL, NPSIGNIN, + HTMLVIEWER, }; void __UtilityInit(); diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index b3a89ff63d..0cea4a1273 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -962,8 +962,11 @@ void System_LaunchUrl(LaunchUrlType urlType, std::string_view url) { ShellExecute(NULL, L"open", wurl.c_str(), NULL, NULL, SW_SHOWNORMAL); #elif defined(__APPLE__) OSXOpenURL(std::string(url).c_str()); +#elif PPSSPP_PLATFORM(LINUX) + // Not through a shell: the URL can come from the game. + LaunchXdgOpen(std::string(url)); #else - std::string command = join("xdg-open ", url); + std::string command = "xdg-open '" + ReplaceAll(url, "'", "'\\''") + "'"; int err = system(command.c_str()); if (err) { INFO_LOG(Log::System, "Would have gone to %.*s but xdg-utils seems not to be installed", STR_VIEW(url)); @@ -1176,6 +1179,12 @@ bool System_GetPropertyBool(SystemProperty prop) { return true; #else return false; +#endif + case SYSPROP_CAN_LAUNCH_URL: +#if defined(MOBILE_DEVICE) && !PPSSPP_PLATFORM(SWITCH) + return false; +#else + return true; #endif case SYSPROP_HAS_OPEN_DIRECTORY: #if PPSSPP_PLATFORM(WINDOWS) diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index d48a75adc2..080025e76f 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -129,6 +129,7 @@ + @@ -416,6 +417,7 @@ + diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index 31536e4ae4..3d445e6e01 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -42,6 +42,7 @@ + @@ -472,6 +473,7 @@ + diff --git a/UWP/PPSSPP_UWPMain.cpp b/UWP/PPSSPP_UWPMain.cpp index 7f585c1ea5..2b64b1f191 100644 --- a/UWP/PPSSPP_UWPMain.cpp +++ b/UWP/PPSSPP_UWPMain.cpp @@ -463,6 +463,8 @@ bool System_GetPropertyBool(SystemProperty prop) { } case SYSPROP_HAS_FILE_BROWSER: return true; + case SYSPROP_CAN_LAUNCH_URL: + return true; case SYSPROP_HAS_FOLDER_BROWSER: return true; case SYSPROP_HAS_IMAGE_BROWSER: diff --git a/Windows/main.cpp b/Windows/main.cpp index 1de6919fd8..cca22423a8 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -450,6 +450,7 @@ bool System_GetPropertyBool(SystemProperty prop) { case SYSPROP_HAS_TEXT_INPUT_DIALOG: case SYSPROP_CAN_CREATE_SHORTCUT: case SYSPROP_CAN_SHOW_FILE: + case SYSPROP_CAN_LAUNCH_URL: case SYSPROP_HAS_TRASH_BIN: return true; case SYSPROP_HAS_IMAGE_BROWSER: diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 7f6610d2fe..f13ee4a087 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -688,6 +688,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/Dialog/PSPMsgDialog.cpp \ $(SRC)/Core/Dialog/PSPNetconfDialog.cpp \ $(SRC)/Core/Dialog/PSPNpSigninDialog.cpp \ + $(SRC)/Core/Dialog/PSPHtmlViewerDialog.cpp \ $(SRC)/Core/Dialog/PSPOskDialog.cpp \ $(SRC)/Core/Dialog/PSPOskConstants.cpp \ $(SRC)/Core/Dialog/PSPScreenshotDialog.cpp \ diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 6ec11565c2..69c4af8588 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -380,6 +380,8 @@ bool System_GetPropertyBool(SystemProperty prop) { return androidVersion >= 11; // honeycomb case SYSPROP_HAS_TEXT_CLIPBOARD: return true; + case SYSPROP_CAN_LAUNCH_URL: + return true; case SYSPROP_HAS_OPEN_DIRECTORY: return false; // We have this implemented but it may or may not work depending on if a file explorer is installed. case SYSPROP_HAS_ADDITIONAL_STORAGE: diff --git a/ios/main.mm b/ios/main.mm index ccbcee2576..e720fc0782 100644 --- a/ios/main.mm +++ b/ios/main.mm @@ -360,6 +360,8 @@ bool System_GetPropertyBool(SystemProperty prop) { switch (prop) { case SYSPROP_HAS_DEEP_LINKS: return true; + case SYSPROP_CAN_LAUNCH_URL: + return true; case SYSPROP_DISPLAY_HAS_CAMERA_CUTOUT: return true; case SYSPROP_HAS_FILE_BROWSER: diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 264d7050a1..ad4b061e70 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -733,6 +733,7 @@ SOURCES_CXX += \ $(COREDIR)/Dialog/PSPMsgDialog.cpp \ $(COREDIR)/Dialog/PSPNetconfDialog.cpp \ $(COREDIR)/Dialog/PSPNpSigninDialog.cpp \ + $(COREDIR)/Dialog/PSPHtmlViewerDialog.cpp \ $(COREDIR)/Dialog/PSPOskDialog.cpp \ $(COREDIR)/Dialog/PSPOskConstants.cpp \ $(COREDIR)/Dialog/PSPPlaceholderDialog.cpp \ diff --git a/libretro/libretro.cpp b/libretro/libretro.cpp index a217af86f6..e871544165 100644 --- a/libretro/libretro.cpp +++ b/libretro/libretro.cpp @@ -1991,6 +1991,7 @@ void System_RunOnMainThread(std::function) {} void NativeFrame(GraphicsContext *graphicsContext) {} void NativeResized() {} void System_Toast(std::string_view str) {} +void System_LaunchUrl(LaunchUrlType urlType, std::string_view url) {} inline int16_t Clamp16(int32_t sample) { if (sample < -32767) return -32767; diff --git a/test.py b/test.py index 0f4915e38e..f3e7bca1d2 100755 --- a/test.py +++ b/test.py @@ -425,6 +425,7 @@ tests_good = [ "threads/vtimers/start", "threads/vtimers/stop", "threads/wakeup/wakeup", + "utility/dialog/htmlviewer", "utility/dialog/priority", "utility/dialog/status", "utility/msgdialog/abort",