diff --git a/core/libs/prx/libSceGameUpdate/CMakeLists.txt b/core/libs/prx/libSceGameUpdate/CMakeLists.txt new file mode 100644 index 000000000..2ae278027 --- /dev/null +++ b/core/libs/prx/libSceGameUpdate/CMakeLists.txt @@ -0,0 +1,21 @@ +add_library(libSceGameUpdate SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceGameUpdate PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceGameUpdate) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_gameupdate_tests ${LIBS_INCLUDE_DIR}/tests/GuestGameUpdate.cpp) + target_include_directories(guest_gameupdate_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_gameupdate_tests PRIVATE libSceGameUpdate libc) + configure_windows_unwind(guest_gameupdate_tests) + add_test(NAME guest_gameupdate COMMAND guest_gameupdate_tests) + if(WIN32) + set_tests_properties(guest_gameupdate PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceGameUpdate/Export.cpp b/core/libs/prx/libSceGameUpdate/Export.cpp index bc9cbef05..4c0d0bb57 100644 --- a/core/libs/prx/libSceGameUpdate/Export.cpp +++ b/core/libs/prx/libSceGameUpdate/Export.cpp @@ -1,3 +1,4 @@ +#include #include #include #include "SceTypes.hpp" @@ -5,6 +6,11 @@ // Update checks need the network, which is not available; requests fail so the game skips the check. static constexpr int SCE_GAME_UPDATE_ERROR_OFFLINE = static_cast(0x80D50001); +static constexpr int SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED = static_cast(0x80412801); +static constexpr int SCE_GAME_UPDATE_ERROR_INVALID_ARGUMENT = static_cast(0x80412803); +static constexpr int SCE_GAME_UPDATE_ERROR_INVALID_SIZE = static_cast(0x80412804); + +static std::atomic g_initialized{false}; extern "C" { @@ -30,18 +36,33 @@ int APS5_VABI sceGameUpdateDeleteRequest(int request_id) { } int APS5_VABI sceGameUpdateGetAddcontLatestVersion(uint32_t service_label, const void* entitlement_label, GameUpdateAddcontVersionInfo* info) { - (void)service_label; - (void)entitlement_label; - (void)info; - NotImplemented_nid_no_patch(__func__); - return 0; + (void)service_label; + if (!g_initialized.load()) { + return SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED; + } + if (info == nullptr) { + return SCE_GAME_UPDATE_ERROR_INVALID_ARGUMENT; + } + if (entitlement_label == nullptr) { + NotImplemented_nid_no_patch(__func__); + } + if (info->size < sizeof(GameUpdateAddcontVersionInfo)) { + return SCE_GAME_UPDATE_ERROR_INVALID_SIZE; + } + const std::size_t size = info->size; + *info = {}; + info->size = size; + info->found = false; + return 0; } int APS5_VABI sceGameUpdateInitialize(void) { + g_initialized.store(true); return 0; } int APS5_VABI sceGameUpdateTerminate(void) { + g_initialized.store(false); return 0; } diff --git a/core/libs/tests/GuestGameUpdate.cpp b/core/libs/tests/GuestGameUpdate.cpp new file mode 100644 index 000000000..12aa20e59 --- /dev/null +++ b/core/libs/tests/GuestGameUpdate.cpp @@ -0,0 +1,48 @@ +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceGameUpdateInitialize(void); +int APS5_VABI sceGameUpdateTerminate(void); +int APS5_VABI sceGameUpdateGetAddcontLatestVersion(std::uint32_t, const void*, GameUpdateAddcontVersionInfo*); +} + +static void Require(bool value) { if (!value) std::abort(); } + +int main() { + constexpr int notInitialized = static_cast(0x80412801); + constexpr int invalidArgument = static_cast(0x80412803); + constexpr int invalidSize = static_cast(0x80412804); + + static_assert(sizeof(GameUpdateAddcontVersionInfo) == 0x30); + const std::uint8_t label[16] = {}; + GameUpdateAddcontVersionInfo info{}; + info.size = sizeof(info); + + Require(sceGameUpdateGetAddcontLatestVersion(0, label, &info) == notInitialized); + Require(sceGameUpdateInitialize() == 0); + Require(sceGameUpdateGetAddcontLatestVersion(0, label, nullptr) == invalidArgument); + + bool threw = false; + try { sceGameUpdateGetAddcontLatestVersion(0, nullptr, &info); } + catch (const std::runtime_error&) { threw = true; } + Require(threw); + + info.size = sizeof(info) - 1; + Require(sceGameUpdateGetAddcontLatestVersion(0, label, &info) == invalidSize); + + std::memset(&info, 0xAB, sizeof(info)); + info.size = sizeof(info) + 8; + Require(sceGameUpdateGetAddcontLatestVersion(0, label, &info) == 0); + Require(info.size == sizeof(info) + 8 && !info.found); + const auto* bytes = reinterpret_cast(&info); + for (std::size_t i = sizeof(info.size); i < sizeof(info); ++i) Require(bytes[i] == 0); + + Require(sceGameUpdateTerminate() == 0); + info.size = sizeof(info); + Require(sceGameUpdateGetAddcontLatestVersion(0, label, &info) == notInitialized); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index f0fac6f1e..8dc8b611a 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -92,6 +92,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceNpSessionSignalingGetConnectionStatistics](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - unknown signature - [sce::Np::CppWebApi::Common::String](../../core/libs/prx/libSceNpCppWebApi/Export.cpp) (libSceNpCppWebApi) - the 0x20-byte layout (32-bit reference count, buffer at 0x08, buffer size at 0x10, `LibContext*` at 0x18) comes from the module bundled with one title (PPSA21564); other SDK versions are not checked. The destructor throws for a non-null buffer: releasing it needs the `LibContext` allocator, which is not implemented - [sceUserServiceGetAgeLevel](../../core/libs/prx/libSceUserService/Export.cpp) (libSceUserService) - the meaning of the value is unknown; 0 is reported, as for the unset game presets. PPSA12544 (Unity) only stores it for its user profile +- [sceGameUpdateGetAddcontLatestVersion](../../core/libs/prx/libSceGameUpdate/Export.cpp) (libSceGameUpdate) - a null `entitlement_label` throws: KytyPS5 accepts it, prosper returns `0x80412803`. Bytes after `found` are zeroed as in KytyPS5; prosper leaves them untouched - [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