Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/libs/prx/libSceSystemService/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
add_library(libSceSystemService SHARED EXCLUDE_FROM_ALL Export.cpp)
target_include_directories(libSceSystemService PRIVATE ${LIBS_INCLUDE_DIR})
if(WIN32)
target_link_libraries(libSceSystemService PRIVATE shell32)
endif()
configure_windows_unwind(libSceSystemService)

if(BUILD_TESTING)
Expand Down
68 changes: 68 additions & 0 deletions core/libs/prx/libSceSystemService/Export.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,82 @@
#include <cctype>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <cstdlib>
#include <stdexcept>
#include <string>
#include <string_view>
#include "prx/libc/include/Shutdown.hpp"
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libSceSystemService/SystemService.hpp"

#ifdef _WIN32
#include <windows.h>
#include <shellapi.h>
#else
#include <spawn.h>
#include <sys/wait.h>
#include <thread>
extern char** environ;
#endif

namespace {

bool hasWebScheme(std::string_view uri) {
for (std::string_view scheme : {std::string_view("http://"), std::string_view("https://")}) {
if (uri.size() < scheme.size()) continue;
bool matches = true;
for (std::size_t i = 0; i < scheme.size() && matches; ++i) {
matches = std::tolower(static_cast<unsigned char>(uri[i])) == scheme[i];
}
if (matches) return true;
}
return false;
}

void openInHostBrowser(const char* uri) {
#ifdef _WIN32
const int length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, uri, -1, nullptr, 0);
if (length <= 0) throw std::runtime_error("sceSystemServiceLaunchWebBrowser: the URI is not valid UTF-8");
std::wstring wide(static_cast<std::size_t>(length), L'\0');
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, uri, -1, wide.data(), length);
const auto result = reinterpret_cast<std::intptr_t>(ShellExecuteW(nullptr, L"open", wide.c_str(), nullptr, nullptr, SW_SHOWNORMAL));
if (result <= 32) {
throw std::runtime_error("sceSystemServiceLaunchWebBrowser: ShellExecuteW failed with " + std::to_string(result));
}
#else
#ifdef __APPLE__
const char* opener = "open";
#else
const char* opener = "xdg-open";
#endif
char* const arguments[] = {const_cast<char*>(opener), const_cast<char*>(uri), nullptr};
pid_t pid = 0;
const int error = posix_spawnp(&pid, opener, nullptr, nullptr, arguments, environ);
if (error != 0) {
throw std::runtime_error(std::string("sceSystemServiceLaunchWebBrowser: cannot start ") + opener + ": " + std::strerror(error));
}
std::thread([pid] {
int status = 0;
waitpid(pid, &status, 0);
}).detach();
#endif
}

}

extern "C" {

int APS5_VABI sceSystemServiceLaunchWebBrowser(const char* uri, const void* param) {
if (uri == nullptr || *uri == '\0') return SYSTEM_SERVICE_ERROR_PARAMETER;
if (param != nullptr) NotImplemented_nid_no_patch("sceSystemServiceLaunchWebBrowser: launch parameter");
if (!hasWebScheme(uri)) NotImplemented_nid_no_patch("sceSystemServiceLaunchWebBrowser: URI scheme other than http or https");
openInHostBrowser(uri);
return SYSTEM_SERVICE_OK;
}


int APS5_VABI sceSystemServiceLoadExec(const char* path, const char* const* arguments) {
if (!path || !*path) return SYSTEM_SERVICE_ERROR_PARAMETER;
if (std::strcmp(path, "exit") != 0) {
Expand Down
26 changes: 26 additions & 0 deletions core/libs/tests/GuestSystemService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,34 @@ extern "C" int APS5_VABI sceSystemServicePowerTick(void);
extern "C" int APS5_VABI sceSystemServiceReportAbnormalTermination(const void* info);
extern "C" int APS5_VABI sceSystemServiceDisableMusicPlayer(void);
extern "C" int APS5_VABI sceSystemServiceReenableMusicPlayer(void);
extern "C" int APS5_VABI sceSystemServiceLaunchWebBrowser(const char* uri, const void* param);

namespace {

bool LaunchWebBrowserThrows(const char* uri, const void* param, const char* what) {
try {
static_cast<void>(sceSystemServiceLaunchWebBrowser(uri, param));
} catch (const std::runtime_error& error) {
return std::strcmp(error.what(), what) == 0;
}
return false;
}

}

int main() {
Require(sceSystemServiceLaunchWebBrowser(nullptr, nullptr) == SYSTEM_SERVICE_ERROR_PARAMETER);
Require(sceSystemServiceLaunchWebBrowser("", nullptr) == SYSTEM_SERVICE_ERROR_PARAMETER);
int launchParam = 0;
Require(LaunchWebBrowserThrows("https://example.com/", &launchParam,
"sceSystemServiceLaunchWebBrowser: launch parameter not implemented"));
constexpr const char* schemeNotImplemented =
"sceSystemServiceLaunchWebBrowser: URI scheme other than http or https not implemented";
Require(LaunchWebBrowserThrows("ftp://example.com/", nullptr, schemeNotImplemented));
Require(LaunchWebBrowserThrows("file:///etc/passwd", nullptr, schemeNotImplemented));
Require(LaunchWebBrowserThrows("example.com", nullptr, schemeNotImplemented));
Require(LaunchWebBrowserThrows("http:/example.com", nullptr, schemeNotImplemented));

Require(sceSystemServicePowerTick() == SYSTEM_SERVICE_OK);
Require(sceSystemServicePowerTick() == SYSTEM_SERVICE_OK);
Require(sceSystemServiceReportAbnormalTermination(nullptr) == SYSTEM_SERVICE_OK);
Expand Down
1 change: 1 addition & 0 deletions docs/dev/TechnicalDebt.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ Throughout the project, every function at every stage either **does exactly what
- [sceSystemServiceDisableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature
- [sceSystemServiceReenableMediaPlay](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - unknown signature
- [sceSystemServiceParamGetString](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - the system name (parameter 6) is the fixed `PS5`, not the console's name; other parameters and buffers of 1 to 64 bytes throw
- [sceSystemServiceLaunchWebBrowser](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - the `(uri, param)` signature follows the public homebrew callers ([CTurt's PS4 userland write-up](https://cturt.github.io/ps4-2.html), [elf-launcher](https://github.com/X-F1REBALL-X/elf-launcher/blob/main/host/hbinstall/main.c), [stremio-ps4](https://github.com/bielstar123-source/stremio-ps4/blob/main/main.cpp)), which all pass a null `param`; the parameter's layout is unknown, so a non-null one throws. The URI opens in the host's default browser (`ShellExecuteW` on Windows, `xdg-open` on Linux, `open` on macOS) and the call returns at once; only `http` and `https` URIs are passed on, other schemes throw. The console's error for a bad URI, whether the call waits for the browser, and the overlay status and events a title sees while the browser is up are unverified: `sceSystemServiceGetStatus` keeps reporting no overlay and no event is queued. PPSA69420 (ScreenTester5 01.000.000) imports it; no title has been seen calling it
- [sceNgs2SystemSetSampleRate, sceNgs2SystemSetUserData, sceNgs2SystemGetUserData, sceNgs2SystemLock, sceNgs2SystemUnlock](../../core/libs/prx/libSceNgs2.native/src/System.cpp) (libSceNgs2.native) - shadPS4 only checks the system handle (`INVALID_SYSTEM_HANDLE`) and SharpProspero names what each call does; neither gives the other limits. A sample rate of 0 and a null user data pointer throw, any other rate is accepted, and changing the rate while a sampler filter is enabled throws because the filter coefficients were computed for the old rate. Lock and unlock take the same lock as `sceNgs2RackLock`
- [sceNgs2RackGetInfo](../../core/libs/prx/libSceNgs2.native/src/Rack.cpp) (libSceNgs2.native) - field order from [shadPS4](https://github.com/shadps4-emu/shadPS4)'s `OrbisNgs2RackInfo` with the 64-byte name of the other info structures (216 bytes); `type` is assumed to be the rack id divided by 0x1000, `min_grain_samples` 64 (the system minimum) and `state_flags` 1; `uid` comes from a per-process rack counter, `render_count` is the owning system's render count and `active_voice_count` counts voices that are not empty (paused and stopped voices included). The last process ratio and tick and `active_channel_work_count` are left zero
- [sceNgs2VoiceQueryInfo](../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - only info id 0x4001 is known: PPSA22520 passes an 8-byte buffer and branches on the first word being 1 or 2, so it is assumed to be the voice channel count (0 before the voice is set up); the second word is zeroed and other ids throw
Expand Down