diff --git a/core/libs/prx/libSceSystemService/CMakeLists.txt b/core/libs/prx/libSceSystemService/CMakeLists.txt index 645da47bd0..d32fd36f5c 100644 --- a/core/libs/prx/libSceSystemService/CMakeLists.txt +++ b/core/libs/prx/libSceSystemService/CMakeLists.txt @@ -92,6 +92,29 @@ if(BUILD_TESTING) set_tests_properties(guest_system_service_time_zone PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_system_service_app_title_id_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemServiceAppTitleId.cpp) + target_include_directories(guest_system_service_app_title_id_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_system_service_app_title_id_tests PRIVATE libSceSystemService libkernel libc) + configure_windows_unwind(guest_system_service_app_title_id_tests) + foreach(appTitleIdCase IN ITEMS known:PPSA00000 long:PPSA000000 missing:none) + string(REPLACE ":" ";" appTitleIdCase ${appTitleIdCase}) + list(GET appTitleIdCase 0 appTitleIdName) + list(GET appTitleIdCase 1 appTitleIdValue) + set(appTitleIdRun ${CMAKE_CURRENT_BINARY_DIR}/guest_system_service_app_title_id_${appTitleIdName}) + if(appTitleIdName STREQUAL "missing") + file(MAKE_DIRECTORY ${appTitleIdRun}) + else() + file(WRITE ${appTitleIdRun}/app0/sce_sys/param.json + "{\"titleId\":\"${appTitleIdValue}\",\"localizedParameters\":{\"en-US\":{\"titleName\":\"Example\"}}}") + endif() + add_test(NAME guest_system_service_app_title_id_${appTitleIdName} COMMAND guest_system_service_app_title_id_tests ${appTitleIdName} + WORKING_DIRECTORY ${appTitleIdRun}) + set_tests_properties(guest_system_service_app_title_id_${appTitleIdName} PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_system_service_app_title_id_${appTitleIdName} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() + endforeach() add_test_executable(guest_system_kill_app_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemKillApp.cpp) target_include_directories(guest_system_kill_app_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_system_kill_app_tests PRIVATE libSceSystemService libc) diff --git a/core/libs/prx/libSceSystemService/Export.cpp b/core/libs/prx/libSceSystemService/Export.cpp index f99a9cc62f..88e9208e7b 100644 --- a/core/libs/prx/libSceSystemService/Export.cpp +++ b/core/libs/prx/libSceSystemService/Export.cpp @@ -25,6 +25,7 @@ #include "LoadExec.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/KernelErrors.hpp" +#include "prx/libkernel/AppMetadata/include/AppMetadata.hpp" #include "prx/libSceSystemService/SystemService.hpp" #include "prx/libSceAgcDriver/Execution/include/HostHdr.hpp" @@ -231,6 +232,21 @@ HostLaunch PrepareLaunch(const char*, const char* const*) { void StartHostExecutable(HostLaunch&) {} #endif +constexpr std::size_t TitleIdBufferSize = 10; + +std::string RunningTitleId() { + const AppTitleId titleId = GetAppTitleId_nid_postfix(); + return std::string(titleId.value); +} + +void CopyRunningTitleId(const std::string& caller, bool running, char* destination) { + if (!running) NotImplemented_nid_no_patch((caller + ": application other than the running title").c_str()); + if (destination == nullptr) NotImplemented_nid_no_patch((caller + ": null title ID buffer").c_str()); + const std::string titleId = RunningTitleId(); + if (titleId.size() >= TitleIdBufferSize) NotImplemented_nid_no_patch((caller + ": title ID that does not fit 10 bytes").c_str()); + std::memcpy(destination, titleId.c_str(), titleId.size() + 1); +} + } extern "C" { @@ -414,6 +430,25 @@ int APS5_VABI sceSystemServiceGetAppIdOfRunningBigApp(void) { return SYSTEM_SERVICE_RUNNING_APP_ID; } +int APS5_VABI sceSystemServiceGetAppId(const char* titleId) { + if (titleId == nullptr) { + NotImplemented_nid_no_patch("sceSystemServiceGetAppId: null title ID"); + } + if (RunningTitleId() != titleId) { + NotImplemented_nid_no_patch("sceSystemServiceGetAppId: title other than the running title"); + } + return SYSTEM_SERVICE_RUNNING_APP_ID; +} + +int APS5_VABI sceSystemServiceGetAppTitleId(int appId, char* titleId) { + CopyRunningTitleId("sceSystemServiceGetAppTitleId", appId == SYSTEM_SERVICE_RUNNING_APP_ID, titleId); + return SYSTEM_SERVICE_OK; +} + +void APS5_VABI sceLncUtilGetAppTitleId(uint32_t appId, char* titleId) { + CopyRunningTitleId("sceLncUtilGetAppTitleId", appId == static_cast(SYSTEM_SERVICE_RUNNING_APP_ID), titleId); +} + int APS5_VABI sceSystemServiceKillApp(int appId, int how, int reason, int coreDump) { if (appId != SYSTEM_SERVICE_RUNNING_APP_ID) { NotImplemented_nid_no_patch("sceSystemServiceKillApp: application other than the running title"); diff --git a/core/libs/prx/libSceSystemService/PendingExports.cpp b/core/libs/prx/libSceSystemService/PendingExports.cpp index 3fe8d5dfc5..bb331dd692 100644 --- a/core/libs/prx/libSceSystemService/PendingExports.cpp +++ b/core/libs/prx/libSceSystemService/PendingExports.cpp @@ -172,11 +172,6 @@ int APS5_VABI sceLncUtilGetAppStatusListForShellUIReboot() { return 0; } -int APS5_VABI sceLncUtilGetAppTitleId() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceLncUtilGetAppType() { NotImplemented_nid_no_patch(__func__); return 0; @@ -1777,11 +1772,6 @@ int APS5_VABI sceSystemServiceGetAppFocusedAppStatus() { return 0; } -int APS5_VABI sceSystemServiceGetAppId() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceSystemServiceGetAppIdListOfBigApp() { NotImplemented_nid_no_patch(__func__); return 0; @@ -1797,11 +1787,6 @@ int APS5_VABI sceSystemServiceGetAppStatus() { return 0; } -int APS5_VABI sceSystemServiceGetAppTitleId() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceSystemServiceGetAppType() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/tests/GuestSystemServiceAppTitleId.cpp b/core/libs/tests/GuestSystemServiceAppTitleId.cpp new file mode 100644 index 0000000000..47393285ba --- /dev/null +++ b/core/libs/tests/GuestSystemServiceAppTitleId.cpp @@ -0,0 +1,136 @@ +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include + +extern "C" int APS5_VABI sceSystemServiceGetAppIdOfRunningBigApp(void); +extern "C" int APS5_VABI sceSystemServiceGetAppId(const char* titleId); +extern "C" int APS5_VABI sceSystemServiceGetAppTitleId(int appId, char* titleId); +extern "C" void APS5_VABI sceLncUtilGetAppTitleId(std::uint32_t appId, char* titleId); + +namespace { + +constexpr char Sentinel = 0x5A; + +struct GuardedBuffer { + char bytes[10 + 6]; + GuardedBuffer() { std::memset(bytes, Sentinel, sizeof(bytes)); } + bool Untouched() const { + for (const char byte : bytes) { + if (byte != Sentinel) return false; + } + return true; + } + bool GuardIntact() const { + for (std::size_t index = 10; index < sizeof(bytes); ++index) { + if (bytes[index] != Sentinel) return false; + } + return true; + } +}; + +void Require(bool value) { if (!value) std::abort(); } + +template +bool Throws(TCall call) { + try { + call(); + } catch (const std::exception&) { + return true; + } + return false; +} + +bool GetAppIdThrows(const char* titleId) { + return Throws([&] { static_cast(sceSystemServiceGetAppId(titleId)); }); +} + +bool GetAppTitleIdThrows(int appId, GuardedBuffer& buffer) { + return Throws([&] { static_cast(sceSystemServiceGetAppTitleId(appId, buffer.bytes)); }) && buffer.Untouched(); +} + +bool LncGetAppTitleIdThrows(std::uint32_t appId, GuardedBuffer& buffer) { + return Throws([&] { sceLncUtilGetAppTitleId(appId, buffer.bytes); }) && buffer.Untouched(); +} + +void CheckOtherApplications(int runningAppId) { + const int otherAppIds[] = {runningAppId + 1, runningAppId - 1, 0, -1, std::numeric_limits::min(), std::numeric_limits::max()}; + for (const int appId : otherAppIds) { + GuardedBuffer buffer; + Require(GetAppTitleIdThrows(appId, buffer)); + Require(LncGetAppTitleIdThrows(static_cast(appId), buffer)); + } + GuardedBuffer buffer; + Require(LncGetAppTitleIdThrows(static_cast(runningAppId) | 0x80000000u, buffer)); + Require(LncGetAppTitleIdThrows(std::numeric_limits::max(), buffer)); +} + +void CheckNullBuffers(int runningAppId) { + Require(Throws([&] { static_cast(sceSystemServiceGetAppTitleId(runningAppId, nullptr)); })); + Require(Throws([&] { sceLncUtilGetAppTitleId(static_cast(runningAppId), nullptr); })); + Require(Throws([&] { static_cast(sceSystemServiceGetAppTitleId(runningAppId + 1, nullptr)); })); + Require(GetAppIdThrows(nullptr)); +} + +void CheckKnownTitle(int runningAppId) { + constexpr std::string_view titleId = "PPSA00000"; + Require(runningAppId >= 0); + Require(sceSystemServiceGetAppId("PPSA00000") == runningAppId); + Require(sceSystemServiceGetAppId("PPSA00000") == runningAppId); + const char* const otherTitles[] = {"PPSA00001", "PPSA00010", "ppsa00000", "PPSA0000", "PPSA000000", "PPSA00000 ", " PPSA00000", "CUSA00000", ""}; + for (const char* other : otherTitles) { + Require(GetAppIdThrows(other)); + } + + GuardedBuffer buffer; + Require(sceSystemServiceGetAppTitleId(runningAppId, buffer.bytes) == 0); + Require(std::memcmp(buffer.bytes, "PPSA00000", titleId.size() + 1) == 0); + Require(buffer.GuardIntact()); + Require(sceSystemServiceGetAppId(buffer.bytes) == runningAppId); + + GuardedBuffer lncBuffer; + sceLncUtilGetAppTitleId(static_cast(runningAppId), lncBuffer.bytes); + Require(std::memcmp(lncBuffer.bytes, "PPSA00000", titleId.size() + 1) == 0); + Require(lncBuffer.GuardIntact()); + + GuardedBuffer dirty; + std::memset(dirty.bytes, 'x', 10); + Require(sceSystemServiceGetAppTitleId(runningAppId, dirty.bytes) == 0); + Require(std::strcmp(dirty.bytes, "PPSA00000") == 0 && dirty.GuardIntact()); + + CheckOtherApplications(runningAppId); + CheckNullBuffers(runningAppId); +} + +void CheckLongTitle(int runningAppId) { + Require(sceSystemServiceGetAppId("PPSA000000") == runningAppId); + Require(GetAppIdThrows("PPSA00000")); + Require(GetAppIdThrows("PPSA0000000")); + GuardedBuffer buffer; + Require(GetAppTitleIdThrows(runningAppId, buffer)); + Require(LncGetAppTitleIdThrows(static_cast(runningAppId), buffer)); +} + +void CheckMissingMetadata(int runningAppId) { + Require(GetAppIdThrows("PPSA00000")); + Require(GetAppIdThrows(nullptr)); + GuardedBuffer buffer; + Require(GetAppTitleIdThrows(runningAppId, buffer)); + Require(LncGetAppTitleIdThrows(static_cast(runningAppId), buffer)); +} + +} + +int main(int argc, char** argv) { + Require(argc == 2); + const std::string_view mode = argv[1]; + const int runningAppId = sceSystemServiceGetAppIdOfRunningBigApp(); + if (mode == "known") CheckKnownTitle(runningAppId); + else if (mode == "long") CheckLongTitle(runningAppId); + else if (mode == "missing") CheckMissingMetadata(runningAppId); + else Require(false); +} diff --git a/docs/dev/debt/libSceSystemService.md b/docs/dev/debt/libSceSystemService.md index 9339410b2f..d99dd3dc9d 100644 --- a/docs/dev/debt/libSceSystemService.md +++ b/docs/dev/debt/libSceSystemService.md @@ -10,13 +10,14 @@ ### Unknown function info - [sceSystemServiceAddLocalProcessForPsmKit, sceSystemServiceKillLocalProcessForPsmKit](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - AnyPS5 runs one process and starts or stops no other, so both throw without reading their arguments. The parameters follow Forever Launcher's own firmware 5.50 probes (`ps5/probes/localproc` in [bizkut/ps5-forever](https://github.com/bizkut/ps5-forever)): a path, a null-terminated argv and a 0x90-byte parameter block in, a handle out; the kill takes that handle. Their results on the console are unknown. Forever Launcher (PPSA99030) imports both and stops at load without them. +- [sceSystemServiceGetAppId, sceSystemServiceGetAppTitleId, sceLncUtilGetAppTitleId](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - the console's results for a title ID or app ID other than the running title are unknown, and so are its errors for null pointers, so those calls throw. `sceSystemServiceGetAppId` compares the title ID exactly with the one in `param.json` or `param.sfo`. The title ID buffer is 10 bytes, so a running title ID of 10 or more characters throws too. The signatures follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/SystemService/SystemService.cs#L236-L260), which names `ErrorNoAppInfo` (0x80A10009) only for `sceSystemServiceGetAppFocusedAppStatus`. - [sceSystemServiceLaunchApp](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - AnyPS5 runs one title and cannot start another, so every call throws without reading its arguments. The (title ID, null-terminated argv, launch parameters) signature follows [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/SystemService/SystemService.cs#L141-L148), whose `AppLauncher` Prospero Explorer (PPSA99109) and Prospero Multi Tools (PPSA99110) import it through and which passes a 0x20-byte launch context or null; the console's argument checks and errors are unknown - [sceSystemServiceLaunchWebBrowser](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - the console's error for an unavailable web browser is unknown; it returns the kernel's `EOPNOTSUPP` (0x8002002D) for any arguments. The (uri, param) signature follows [RommPS](https://github.com/s0liton/RommPS/blob/754d4d60f25938a5136b9e2efcd074313c4386c5/platform/ps5/app/rommps/examples/rommps/kit/credits.cpp#L86-L95), which passes a URL and a null param and only tests the result for zero; the param layout and the console's argument checks are unknown - [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. PPSA26344 at `0xA87935` passes parameter 6 with a 65-byte buffer - [sceSystemServiceParamGetInt](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - console language numbers 0 to 29 follow the PS4 SDK enum in [OpenOrbis](https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/06fcf7fd108fa080035cdd3259ed22eb07e0508c/include/orbis/_types/sys_service.h#L54-L83) (21 Arabic). 30 (Ukrainian) is not in it and follows PPSA99505 ([Jelly5](https://github.com/02dnot/Jelly5/blob/552b866d89b2a58b27f7f7b0484dc743ce235526/app/src/app/i18n.cpp#L29-L60)) and PPSA99002 ([ProsperoLight](https://github.com/blackbearreloaded/ProsperoLight/blob/b708bbde3457bef1073a9ab6959957cb691e8e2f/include/i18n.hpp#L252-L309)), whose numbers agree with the enum. Not confirmed on a console, and neither title starts on `main` yet. Parameter 400 returns the same number as parameter 1: PPSA01325 asks for 400 first, asks for 1 when that fails, and uses either answer as an index into the same 31 languages; in the PS5 10.01 and 12.60 `libSceSystemService` modules parameter 400 reads the same registry value as parameter 1, without parameter 1's mapping of newer language numbers by SDK version, and returns 0x80a10003 when an imported check that is not identified returns 1; the 4.03 module has no parameter 400. Neither the mapping nor that error is modelled, and the parameter's name is not known. Without `ANYPS5_LANGUAGE` the language is English (United States); the date format, time format and enter button stay fixed - [sceSystemServiceParamGetInt](../../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - the time zone (parameter 4) and summer time (parameter 5) follow the host's local time zone at the moment of the call. Titles compute the local time as UTC plus the time zone in minutes plus 60 minutes when summer time is 1 ([Apollo](https://github.com/bucanero/apollo-ps4/blob/252a9d5a187c463994263009851a179f4f910c49/source/exec_cmd.c#L70-L92), [GoldHEN's frame logger](https://github.com/GoldHEN/GoldHEN_Plugins_Repository/blob/6c3a06f53d9caebadfb0aeed9bb94119012010a7/plugin_src/frame_logger/source/main.c#L60-L82), and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/413d2e3518fb435f40971d238fe0ecae7ee551da/src/SharpProspero/Platform/SystemParameters.cs#L75-L79) calls them the offset from UTC in minutes and whether summer time is in effect), so the time zone is the host's current offset east of UTC less the summer time shift while summer time is in effect: 60 minutes on Linux, and on Windows the C runtime's own summer time bias (`_get_dstbias`), which is also what shifted its local time. For a host zone whose summer time is not one hour, Linux keeps the sum right and the standard offset wrong, and Windows the opposite; how the console reports such a zone is unknown. libkernel's `sceKernelConvertUtcToLocaltime`, `sceKernelConvertLocaltimeToUtc` and `sceKernelGettimezone` still use UTC -- [libSceSystemService firmware 3.20 pending exports](../../../core/libs/prx/libSceSystemService/PendingExports.cpp) - 477 symbol names are present in [the PS5 3.20 libSceSystemService export list](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSystemService.c). Their signatures and console behaviour are unverified. The declarations use the existing no-parameter unknown-signature convention solely to resolve imports, and every call throws through `NotImplemented_nid_no_patch`. +- [libSceSystemService firmware 3.20 pending exports](../../../core/libs/prx/libSceSystemService/PendingExports.cpp) - 474 symbol names are present in [the PS5 3.20 libSceSystemService export list](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceSystemService.c). Their signatures and console behaviour are unverified. The declarations use the existing no-parameter unknown-signature convention solely to resolve imports, and every call throws through `NotImplemented_nid_no_patch`. ### Functional