Utility: Stand in for the HtmlViewer, offering to open the page in a browser

Instead of the PSP's web browser, a dialog shows the URL the game wants
and opens it in the host's browser on X, or backs out on O. Either way the
game sees the browser closed normally. Platforms that can't open a URL
(the new SYSPROP_CAN_LAUNCH_URL) only offer to back out. Only plain
printable-ASCII http(s) addresses are handed over, and on Linux without a
shell.

What the firmware does (sceUtility_Driver, 6.61, plus
utility/dialog/htmlviewer): the HtmlViewer has its own state apart from the
other dialogs, so they don't block each other, and its calls return
WRONG_TYPE until one has started. The request size picks the 2.00 to 3.00
layout, and InitStart allocates 3.5MB of user memory (4.5MB with options
bit 0x400 from 2.70 on), failing with 800200d9 without it.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-09-25 12:50:23 -06:00
1 parent 3c15b2b112
commit 5f261bd7ff
22 files changed
+373 -11

No files matched your search

+1
View File
@@ -170,6 +170,7 @@ enum SystemProperty {
SYSPROP_CAN_CREATE_SHORTCUT, SYSPROP_CAN_CREATE_SHORTCUT,
SYSPROP_CAN_SHOW_FILE, SYSPROP_CAN_SHOW_FILE,
SYSPROP_CAN_LAUNCH_URL, // System_LaunchUrl can open a web page.
SYSPROP_SUPPORTS_HTTPS, SYSPROP_SUPPORTS_HTTPS,
+2 -2
View File
@@ -209,8 +209,6 @@ if(NOT LIBRETRO)
Debugger/WebSocket/SteppingSubscriber.h Debugger/WebSocket/SteppingSubscriber.h
Debugger/WebSocket/WebSocketUtils.cpp Debugger/WebSocket/WebSocketUtils.cpp
Debugger/WebSocket/WebSocketUtils.h Debugger/WebSocket/WebSocketUtils.h
Dialog/PSPPlaceholderDialog.cpp
Dialog/PSPPlaceholderDialog.h
HW/GranularMixer.cpp HW/GranularMixer.cpp
HW/GranularMixer.h HW/GranularMixer.h
HW/StereoResampler.cpp HW/StereoResampler.cpp
@@ -356,7 +354,9 @@ add_library(Core STATIC
Dialog/PSPNetconfDialog.cpp Dialog/PSPNetconfDialog.cpp
Dialog/PSPNetconfDialog.h Dialog/PSPNetconfDialog.h
Dialog/PSPNpSigninDialog.cpp Dialog/PSPNpSigninDialog.cpp
Dialog/PSPHtmlViewerDialog.cpp
Dialog/PSPNpSigninDialog.h Dialog/PSPNpSigninDialog.h
Dialog/PSPHtmlViewerDialog.h
Dialog/PSPOskDialog.cpp Dialog/PSPOskDialog.cpp
Dialog/PSPOskDialog.h Dialog/PSPOskDialog.h
Dialog/PSPOskConstants.cpp Dialog/PSPOskConstants.cpp
+2
View File
@@ -569,6 +569,7 @@
<ClCompile Include="Dialog\PSPMsgDialog.cpp" /> <ClCompile Include="Dialog\PSPMsgDialog.cpp" />
<ClCompile Include="Dialog\PSPNetconfDialog.cpp" /> <ClCompile Include="Dialog\PSPNetconfDialog.cpp" />
<ClCompile Include="Dialog\PSPNpSigninDialog.cpp" /> <ClCompile Include="Dialog\PSPNpSigninDialog.cpp" />
<ClCompile Include="Dialog\PSPHtmlViewerDialog.cpp" />
<ClCompile Include="Dialog\PSPOskDialog.cpp" /> <ClCompile Include="Dialog\PSPOskDialog.cpp" />
<ClCompile Include="Dialog\PSPPlaceholderDialog.cpp" /> <ClCompile Include="Dialog\PSPPlaceholderDialog.cpp" />
<ClCompile Include="Dialog\PSPSaveDialog.cpp" /> <ClCompile Include="Dialog\PSPSaveDialog.cpp" />
@@ -1087,6 +1088,7 @@
<ClInclude Include="Dialog\PSPMsgDialog.h" /> <ClInclude Include="Dialog\PSPMsgDialog.h" />
<ClInclude Include="Dialog\PSPNetconfDialog.h" /> <ClInclude Include="Dialog\PSPNetconfDialog.h" />
<ClInclude Include="Dialog\PSPNpSigninDialog.h" /> <ClInclude Include="Dialog\PSPNpSigninDialog.h" />
<ClInclude Include="Dialog\PSPHtmlViewerDialog.h" />
<ClInclude Include="Dialog\PSPOskDialog.h" /> <ClInclude Include="Dialog\PSPOskDialog.h" />
<ClInclude Include="Dialog\PSPPlaceholderDialog.h" /> <ClInclude Include="Dialog\PSPPlaceholderDialog.h" />
<ClInclude Include="Dialog\PSPSaveDialog.h" /> <ClInclude Include="Dialog\PSPSaveDialog.h" />
+6
View File
@@ -556,6 +556,9 @@
<ClCompile Include="Dialog\PSPNpSigninDialog.cpp"> <ClCompile Include="Dialog\PSPNpSigninDialog.cpp">
<Filter>Dialog</Filter> <Filter>Dialog</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="Dialog\PSPHtmlViewerDialog.cpp">
<Filter>Dialog</Filter>
</ClCompile>
<ClCompile Include="Debugger\DisassemblyManager.cpp"> <ClCompile Include="Debugger\DisassemblyManager.cpp">
<Filter>Debugger</Filter> <Filter>Debugger</Filter>
</ClCompile> </ClCompile>
@@ -1881,6 +1884,9 @@
<ClInclude Include="Dialog\PSPNpSigninDialog.h"> <ClInclude Include="Dialog\PSPNpSigninDialog.h">
<Filter>Dialog</Filter> <Filter>Dialog</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="Dialog\PSPHtmlViewerDialog.h">
<Filter>Dialog</Filter>
</ClInclude>
<ClInclude Include="MIPS\JitCommon\JitState.h"> <ClInclude Include="MIPS\JitCommon\JitState.h">
<Filter>MIPS\JitCommon</Filter> <Filter>MIPS\JitCommon</Filter>
</ClInclude> </ClInclude>
+3 -2
View File
@@ -50,6 +50,7 @@ const char *UtilityDialogTypeToString(UtilityDialogType type) {
case UtilityDialogType::GAMESHARING: return "GAMESHARING"; case UtilityDialogType::GAMESHARING: return "GAMESHARING";
case UtilityDialogType::GAMEDATAINSTALL: return "GAMEDATAINSTALL"; case UtilityDialogType::GAMEDATAINSTALL: return "GAMEDATAINSTALL";
case UtilityDialogType::NPSIGNIN: return "NPSIGNIN"; case UtilityDialogType::NPSIGNIN: return "NPSIGNIN";
case UtilityDialogType::HTMLVIEWER: return "HTMLVIEWER";
default: return "(unknown)"; default: return "(unknown)";
} }
} }
@@ -101,7 +102,7 @@ void PSPDialog::UpdatePendingStatus() {
if (pendingStatus == SCE_UTILITY_STATUS_NONE && status == SCE_UTILITY_STATUS_SHUTDOWN) { if (pendingStatus == SCE_UTILITY_STATUS_NONE && status == SCE_UTILITY_STATUS_SHUTDOWN) {
FinishVolatile(); FinishVolatile();
} else if (pendingStatus == SCE_UTILITY_STATUS_RUNNING && status == SCE_UTILITY_STATUS_INITIALIZE) { } else if (pendingStatus == SCE_UTILITY_STATUS_RUNNING && status == SCE_UTILITY_STATUS_INITIALIZE) {
if (!volatileLocked_) { if (!volatileLocked_ && LocksVolatileMemory()) {
volatileLocked_ = KernelVolatileMemLock(0, 0, 0) == 0; volatileLocked_ = KernelVolatileMemLock(0, 0, 0) == 0;
changeAllowed = volatileLocked_; changeAllowed = volatileLocked_;
} }
@@ -149,7 +150,7 @@ void PSPDialog::ChangeStatus(DialogStatus newStatus, int delayUs) {
if (newStatus == SCE_UTILITY_STATUS_NONE && status == SCE_UTILITY_STATUS_SHUTDOWN) { if (newStatus == SCE_UTILITY_STATUS_NONE && status == SCE_UTILITY_STATUS_SHUTDOWN) {
FinishVolatile(); FinishVolatile();
} else if (newStatus == SCE_UTILITY_STATUS_RUNNING && status == SCE_UTILITY_STATUS_INITIALIZE) { } 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? // TODO: Should probably make the status pending instead?
volatileLocked_ = KernelVolatileMemLock(0, 0, 0) == 0; volatileLocked_ = KernelVolatileMemLock(0, 0, 0) == 0;
} }
+4
View File
@@ -108,6 +108,10 @@ protected:
// TODO: Remove this once all dialogs are updated. // TODO: Remove this once all dialogs are updated.
virtual bool UseAutoStatus() = 0; virtual bool UseAutoStatus() = 0;
// Whether the dialog holds volatile memory while running.
virtual bool LocksVolatileMemory() const {
return true;
}
static int GetConfirmButton(); static int GetConfirmButton();
static int GetCancelButton(); static int GetCancelButton();
+229
View File
@@ -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;
}
+60
View File
@@ -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 <string>
#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<pspUtilityDialogCommon> params_;
std::string url_;
u32 workMemory_ = 0;
bool started_ = false;
};
+1
View File
@@ -306,6 +306,7 @@ enum PSPErrorCode : u32 {
SCE_ERROR_AV_MODULE_NOT_LOADED = 0x80110F03, SCE_ERROR_AV_MODULE_NOT_LOADED = 0x80110F03,
SCE_ERROR_UTILITY_INVALID_STATUS = 0x80110001, SCE_ERROR_UTILITY_INVALID_STATUS = 0x80110001,
SCE_ERROR_UTILITY_INVALID_ADDRESS = 0x80110002,
SCE_ERROR_UTILITY_INVALID_PARAM_SIZE = 0x80110004, SCE_ERROR_UTILITY_INVALID_PARAM_SIZE = 0x80110004,
SCE_ERROR_UTILITY_WRONG_TYPE = 0x80110005, SCE_ERROR_UTILITY_WRONG_TYPE = 0x80110005,
SCE_ERROR_UTILITY_STRING_TOO_LONG = 0x80110102, SCE_ERROR_UTILITY_STRING_TOO_LONG = 0x80110102,
+39 -6
View File
@@ -55,6 +55,7 @@
#include "Core/Dialog/PSPDialog.h" #include "Core/Dialog/PSPDialog.h"
#include "Core/Dialog/PSPSaveDialog.h" #include "Core/Dialog/PSPSaveDialog.h"
#include "Core/Dialog/PSPMsgDialog.h" #include "Core/Dialog/PSPMsgDialog.h"
#include "Core/Dialog/PSPHtmlViewerDialog.h"
#include "Core/Dialog/PSPPlaceholderDialog.h" #include "Core/Dialog/PSPPlaceholderDialog.h"
#include "Core/Dialog/PSPOskDialog.h" #include "Core/Dialog/PSPOskDialog.h"
#include "Core/Dialog/PSPGamedataInstallDialog.h" #include "Core/Dialog/PSPGamedataInstallDialog.h"
@@ -327,6 +328,8 @@ static PSPScreenshotDialog *screenshotDialog;
static PSPGamedataInstallDialog *gamedataInstallDialog; static PSPGamedataInstallDialog *gamedataInstallDialog;
static PSPNpSigninDialog *npSigninDialog; static PSPNpSigninDialog *npSigninDialog;
static PSPPlaceholderDialog *gameSharingDialog; 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. // A lot of state seems to be shared between the various dialog types.
static int oldStatus = -1; static int oldStatus = -1;
@@ -400,6 +403,8 @@ static PSPDialog *CurrentDialog(UtilityDialogType type) {
return gamedataInstallDialog; return gamedataInstallDialog;
case UtilityDialogType::NPSIGNIN: case UtilityDialogType::NPSIGNIN:
return npSigninDialog; return npSigninDialog;
case UtilityDialogType::HTMLVIEWER:
return htmlViewerDialog;
} }
return nullptr; return nullptr;
} }
@@ -522,6 +527,7 @@ void __UtilityInit() {
gamedataInstallDialog = new PSPGamedataInstallDialog(UtilityDialogType::GAMEDATAINSTALL); gamedataInstallDialog = new PSPGamedataInstallDialog(UtilityDialogType::GAMEDATAINSTALL);
npSigninDialog = new PSPNpSigninDialog(UtilityDialogType::NPSIGNIN); npSigninDialog = new PSPNpSigninDialog(UtilityDialogType::NPSIGNIN);
gameSharingDialog = new PSPPlaceholderDialog(UtilityDialogType::GAMESHARING); gameSharingDialog = new PSPPlaceholderDialog(UtilityDialogType::GAMESHARING);
htmlViewerDialog = new PSPHtmlViewerDialog(UtilityDialogType::HTMLVIEWER);
currentDialogType = UtilityDialogType::NONE; currentDialogType = UtilityDialogType::NONE;
DeactivateDialog(); DeactivateDialog();
@@ -534,7 +540,7 @@ void __UtilityInit() {
} }
void __UtilityDoState(PointerWrap &p) { void __UtilityDoState(PointerWrap &p) {
auto s = p.Section("sceUtility", 1, 8); auto s = p.Section("sceUtility", 1, 9);
if (!s) { if (!s) {
return; return;
} }
@@ -608,6 +614,9 @@ void __UtilityDoState(PointerWrap &p) {
if (s >= 8) { if (s >= 8) {
gameSharingDialog->DoState(p); gameSharingDialog->DoState(p);
} }
if (s >= 9) {
htmlViewerDialog->DoState(p);
}
if (!hasAccessThread && accessThread) { if (!hasAccessThread && accessThread) {
accessThread->Forget(); accessThread->Forget();
@@ -626,6 +635,7 @@ void __UtilityShutdown() {
gamedataInstallDialog->Shutdown(true); gamedataInstallDialog->Shutdown(true);
npSigninDialog->Shutdown(true); npSigninDialog->Shutdown(true);
gameSharingDialog->Shutdown(true); gameSharingDialog->Shutdown(true);
htmlViewerDialog->Shutdown(true);
if (accessThread) { if (accessThread) {
// Don't need to free it during shutdown, may have already been freed. // Don't need to free it during shutdown, may have already been freed.
@@ -645,6 +655,7 @@ void __UtilityShutdown() {
delete gamedataInstallDialog; delete gamedataInstallDialog;
delete npSigninDialog; delete npSigninDialog;
delete gameSharingDialog; delete gameSharingDialog;
delete htmlViewerDialog;
} }
// On a PSP, dialog init and shutdown happen partly at the accessThread priority and partly at the // 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)); 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() { 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) { static u32 sceUtilityLoadUsbModule(u32 module) {
@@ -1741,9 +1774,9 @@ const HLEFunction sceUtility[] = {
{0X88BC7406, nullptr, "sceUtilitySavedataErrGetStatus", '?', "" }, {0X88BC7406, nullptr, "sceUtilitySavedataErrGetStatus", '?', "" },
{0XBDA7D894, &WrapI_V<sceUtilityHtmlViewerGetStatus>, "sceUtilityHtmlViewerGetStatus", 'i', "" }, {0XBDA7D894, &WrapI_V<sceUtilityHtmlViewerGetStatus>, "sceUtilityHtmlViewerGetStatus", 'i', "" },
{0XCDC3AA41, nullptr, "sceUtilityHtmlViewerInitStart", '?', "" }, {0XCDC3AA41, &WrapI_U<sceUtilityHtmlViewerInitStart>, "sceUtilityHtmlViewerInitStart", 'i', "x" },
{0XF5CE1134, nullptr, "sceUtilityHtmlViewerShutdownStart", '?', "" }, {0XF5CE1134, &WrapI_V<sceUtilityHtmlViewerShutdownStart>, "sceUtilityHtmlViewerShutdownStart", 'i', "" },
{0X05AFB9E4, nullptr, "sceUtilityHtmlViewerUpdate", '?', "" }, {0X05AFB9E4, &WrapI_I<sceUtilityHtmlViewerUpdate>, "sceUtilityHtmlViewerUpdate", 'i', "i" },
{0X16A1A8D8, nullptr, "sceUtilityAuthDialogGetStatus", '?', "" }, {0X16A1A8D8, nullptr, "sceUtilityAuthDialogGetStatus", '?', "" },
{0X943CBA46, nullptr, "sceUtilityAuthDialogInitStart", '?', "" }, {0X943CBA46, nullptr, "sceUtilityAuthDialogInitStart", '?', "" },
+1
View File
@@ -117,6 +117,7 @@ enum class UtilityDialogType {
GAMESHARING, GAMESHARING,
GAMEDATAINSTALL, GAMEDATAINSTALL,
NPSIGNIN, NPSIGNIN,
HTMLVIEWER,
}; };
void __UtilityInit(); void __UtilityInit();
+10 -1
View File
@@ -962,8 +962,11 @@ void System_LaunchUrl(LaunchUrlType urlType, std::string_view url) {
ShellExecute(NULL, L"open", wurl.c_str(), NULL, NULL, SW_SHOWNORMAL); ShellExecute(NULL, L"open", wurl.c_str(), NULL, NULL, SW_SHOWNORMAL);
#elif defined(__APPLE__) #elif defined(__APPLE__)
OSXOpenURL(std::string(url).c_str()); 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 #else
std::string command = join("xdg-open ", url); std::string command = "xdg-open '" + ReplaceAll(url, "'", "'\\''") + "'";
int err = system(command.c_str()); int err = system(command.c_str());
if (err) { if (err) {
INFO_LOG(Log::System, "Would have gone to %.*s but xdg-utils seems not to be installed", STR_VIEW(url)); 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; return true;
#else #else
return false; return false;
#endif
case SYSPROP_CAN_LAUNCH_URL:
#if defined(MOBILE_DEVICE) && !PPSSPP_PLATFORM(SWITCH)
return false;
#else
return true;
#endif #endif
case SYSPROP_HAS_OPEN_DIRECTORY: case SYSPROP_HAS_OPEN_DIRECTORY:
#if PPSSPP_PLATFORM(WINDOWS) #if PPSSPP_PLATFORM(WINDOWS)
+2
View File
@@ -129,6 +129,7 @@
<ClInclude Include="..\..\Core\Dialog\PSPMsgDialog.h" /> <ClInclude Include="..\..\Core\Dialog\PSPMsgDialog.h" />
<ClInclude Include="..\..\Core\Dialog\PSPNetconfDialog.h" /> <ClInclude Include="..\..\Core\Dialog\PSPNetconfDialog.h" />
<ClInclude Include="..\..\Core\Dialog\PSPNpSigninDialog.h" /> <ClInclude Include="..\..\Core\Dialog\PSPNpSigninDialog.h" />
<ClInclude Include="..\..\Core\Dialog\PSPHtmlViewerDialog.h" />
<ClInclude Include="..\..\Core\Dialog\PSPOskConstants.h" /> <ClInclude Include="..\..\Core\Dialog\PSPOskConstants.h" />
<ClInclude Include="..\..\Core\Dialog\PSPOskDialog.h" /> <ClInclude Include="..\..\Core\Dialog\PSPOskDialog.h" />
<ClInclude Include="..\..\Core\Dialog\PSPPlaceholderDialog.h" /> <ClInclude Include="..\..\Core\Dialog\PSPPlaceholderDialog.h" />
@@ -416,6 +417,7 @@
<ClCompile Include="..\..\Core\Dialog\PSPMsgDialog.cpp" /> <ClCompile Include="..\..\Core\Dialog\PSPMsgDialog.cpp" />
<ClCompile Include="..\..\Core\Dialog\PSPNetconfDialog.cpp" /> <ClCompile Include="..\..\Core\Dialog\PSPNetconfDialog.cpp" />
<ClCompile Include="..\..\Core\Dialog\PSPNpSigninDialog.cpp" /> <ClCompile Include="..\..\Core\Dialog\PSPNpSigninDialog.cpp" />
<ClCompile Include="..\..\Core\Dialog\PSPHtmlViewerDialog.cpp" />
<ClCompile Include="..\..\Core\Dialog\PSPOskConstants.cpp" /> <ClCompile Include="..\..\Core\Dialog\PSPOskConstants.cpp" />
<ClCompile Include="..\..\Core\Dialog\PSPOskDialog.cpp" /> <ClCompile Include="..\..\Core\Dialog\PSPOskDialog.cpp" />
<ClCompile Include="..\..\Core\Dialog\PSPPlaceholderDialog.cpp" /> <ClCompile Include="..\..\Core\Dialog\PSPPlaceholderDialog.cpp" />
+2
View File
@@ -42,6 +42,7 @@
<ClCompile Include="..\..\Core\Dialog\PSPMsgDialog.cpp" /> <ClCompile Include="..\..\Core\Dialog\PSPMsgDialog.cpp" />
<ClCompile Include="..\..\Core\Dialog\PSPNetconfDialog.cpp" /> <ClCompile Include="..\..\Core\Dialog\PSPNetconfDialog.cpp" />
<ClCompile Include="..\..\Core\Dialog\PSPNpSigninDialog.cpp" /> <ClCompile Include="..\..\Core\Dialog\PSPNpSigninDialog.cpp" />
<ClCompile Include="..\..\Core\Dialog\PSPHtmlViewerDialog.cpp" />
<ClCompile Include="..\..\Core\Dialog\PSPOskConstants.cpp" /> <ClCompile Include="..\..\Core\Dialog\PSPOskConstants.cpp" />
<ClCompile Include="..\..\Core\Dialog\PSPOskDialog.cpp" /> <ClCompile Include="..\..\Core\Dialog\PSPOskDialog.cpp" />
<ClCompile Include="..\..\Core\Dialog\PSPPlaceholderDialog.cpp" /> <ClCompile Include="..\..\Core\Dialog\PSPPlaceholderDialog.cpp" />
@@ -472,6 +473,7 @@
<ClInclude Include="..\..\Core\Dialog\PSPMsgDialog.h" /> <ClInclude Include="..\..\Core\Dialog\PSPMsgDialog.h" />
<ClInclude Include="..\..\Core\Dialog\PSPNetconfDialog.h" /> <ClInclude Include="..\..\Core\Dialog\PSPNetconfDialog.h" />
<ClInclude Include="..\..\Core\Dialog\PSPNpSigninDialog.h" /> <ClInclude Include="..\..\Core\Dialog\PSPNpSigninDialog.h" />
<ClInclude Include="..\..\Core\Dialog\PSPHtmlViewerDialog.h" />
<ClInclude Include="..\..\Core\Dialog\PSPOskConstants.h" /> <ClInclude Include="..\..\Core\Dialog\PSPOskConstants.h" />
<ClInclude Include="..\..\Core\Dialog\PSPOskDialog.h" /> <ClInclude Include="..\..\Core\Dialog\PSPOskDialog.h" />
<ClInclude Include="..\..\Core\Dialog\PSPPlaceholderDialog.h" /> <ClInclude Include="..\..\Core\Dialog\PSPPlaceholderDialog.h" />
+2
View File
@@ -463,6 +463,8 @@ bool System_GetPropertyBool(SystemProperty prop) {
} }
case SYSPROP_HAS_FILE_BROWSER: case SYSPROP_HAS_FILE_BROWSER:
return true; return true;
case SYSPROP_CAN_LAUNCH_URL:
return true;
case SYSPROP_HAS_FOLDER_BROWSER: case SYSPROP_HAS_FOLDER_BROWSER:
return true; return true;
case SYSPROP_HAS_IMAGE_BROWSER: case SYSPROP_HAS_IMAGE_BROWSER:
+1
View File
@@ -450,6 +450,7 @@ bool System_GetPropertyBool(SystemProperty prop) {
case SYSPROP_HAS_TEXT_INPUT_DIALOG: case SYSPROP_HAS_TEXT_INPUT_DIALOG:
case SYSPROP_CAN_CREATE_SHORTCUT: case SYSPROP_CAN_CREATE_SHORTCUT:
case SYSPROP_CAN_SHOW_FILE: case SYSPROP_CAN_SHOW_FILE:
case SYSPROP_CAN_LAUNCH_URL:
case SYSPROP_HAS_TRASH_BIN: case SYSPROP_HAS_TRASH_BIN:
return true; return true;
case SYSPROP_HAS_IMAGE_BROWSER: case SYSPROP_HAS_IMAGE_BROWSER:
+1
View File
@@ -688,6 +688,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Core/Dialog/PSPMsgDialog.cpp \ $(SRC)/Core/Dialog/PSPMsgDialog.cpp \
$(SRC)/Core/Dialog/PSPNetconfDialog.cpp \ $(SRC)/Core/Dialog/PSPNetconfDialog.cpp \
$(SRC)/Core/Dialog/PSPNpSigninDialog.cpp \ $(SRC)/Core/Dialog/PSPNpSigninDialog.cpp \
$(SRC)/Core/Dialog/PSPHtmlViewerDialog.cpp \
$(SRC)/Core/Dialog/PSPOskDialog.cpp \ $(SRC)/Core/Dialog/PSPOskDialog.cpp \
$(SRC)/Core/Dialog/PSPOskConstants.cpp \ $(SRC)/Core/Dialog/PSPOskConstants.cpp \
$(SRC)/Core/Dialog/PSPScreenshotDialog.cpp \ $(SRC)/Core/Dialog/PSPScreenshotDialog.cpp \
+2
View File
@@ -380,6 +380,8 @@ bool System_GetPropertyBool(SystemProperty prop) {
return androidVersion >= 11; // honeycomb return androidVersion >= 11; // honeycomb
case SYSPROP_HAS_TEXT_CLIPBOARD: case SYSPROP_HAS_TEXT_CLIPBOARD:
return true; return true;
case SYSPROP_CAN_LAUNCH_URL:
return true;
case SYSPROP_HAS_OPEN_DIRECTORY: 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. 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: case SYSPROP_HAS_ADDITIONAL_STORAGE:
+2
View File
@@ -360,6 +360,8 @@ bool System_GetPropertyBool(SystemProperty prop) {
switch (prop) { switch (prop) {
case SYSPROP_HAS_DEEP_LINKS: case SYSPROP_HAS_DEEP_LINKS:
return true; return true;
case SYSPROP_CAN_LAUNCH_URL:
return true;
case SYSPROP_DISPLAY_HAS_CAMERA_CUTOUT: case SYSPROP_DISPLAY_HAS_CAMERA_CUTOUT:
return true; return true;
case SYSPROP_HAS_FILE_BROWSER: case SYSPROP_HAS_FILE_BROWSER:
+1
View File
@@ -733,6 +733,7 @@ SOURCES_CXX += \
$(COREDIR)/Dialog/PSPMsgDialog.cpp \ $(COREDIR)/Dialog/PSPMsgDialog.cpp \
$(COREDIR)/Dialog/PSPNetconfDialog.cpp \ $(COREDIR)/Dialog/PSPNetconfDialog.cpp \
$(COREDIR)/Dialog/PSPNpSigninDialog.cpp \ $(COREDIR)/Dialog/PSPNpSigninDialog.cpp \
$(COREDIR)/Dialog/PSPHtmlViewerDialog.cpp \
$(COREDIR)/Dialog/PSPOskDialog.cpp \ $(COREDIR)/Dialog/PSPOskDialog.cpp \
$(COREDIR)/Dialog/PSPOskConstants.cpp \ $(COREDIR)/Dialog/PSPOskConstants.cpp \
$(COREDIR)/Dialog/PSPPlaceholderDialog.cpp \ $(COREDIR)/Dialog/PSPPlaceholderDialog.cpp \
+1
View File
@@ -1991,6 +1991,7 @@ void System_RunOnMainThread(std::function<void()>) {}
void NativeFrame(GraphicsContext *graphicsContext) {} void NativeFrame(GraphicsContext *graphicsContext) {}
void NativeResized() {} void NativeResized() {}
void System_Toast(std::string_view str) {} void System_Toast(std::string_view str) {}
void System_LaunchUrl(LaunchUrlType urlType, std::string_view url) {}
inline int16_t Clamp16(int32_t sample) { inline int16_t Clamp16(int32_t sample) {
if (sample < -32767) return -32767; if (sample < -32767) return -32767;
+1
View File
@@ -425,6 +425,7 @@ tests_good = [
"threads/vtimers/start", "threads/vtimers/start",
"threads/vtimers/stop", "threads/vtimers/stop",
"threads/wakeup/wakeup", "threads/wakeup/wakeup",
"utility/dialog/htmlviewer",
"utility/dialog/priority", "utility/dialog/priority",
"utility/dialog/status", "utility/dialog/status",
"utility/msgdialog/abort", "utility/msgdialog/abort",