feat(libs): implement sceGameUpdateGetAddcontLatestVersion

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Adrià Franch 2026-10-05 19:18:50 +02:00
parent 0c4b80dc61
commit 58fce8ab1c
4 changed files with 96 additions and 5 deletions

View File

@ -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:$<TARGET_FILE_DIR:libSceGameUpdate>")
endif()
endif()

View File

@ -1,3 +1,4 @@
#include <atomic>
#include <cstdint>
#include <cstddef>
#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<int>(0x80D50001);
static constexpr int SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED = static_cast<int>(0x80412801);
static constexpr int SCE_GAME_UPDATE_ERROR_INVALID_ARGUMENT = static_cast<int>(0x80412803);
static constexpr int SCE_GAME_UPDATE_ERROR_INVALID_SIZE = static_cast<int>(0x80412804);
static std::atomic<bool> 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;
}

View File

@ -0,0 +1,48 @@
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
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<int>(0x80412801);
constexpr int invalidArgument = static_cast<int>(0x80412803);
constexpr int invalidSize = static_cast<int>(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<const std::uint8_t*>(&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);
}

View File

@ -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