diff --git a/core/libs/prx/libSceGameUpdate/Export.cpp b/core/libs/prx/libSceGameUpdate/Export.cpp index dfcb3d1383..0e3ef3835e 100644 --- a/core/libs/prx/libSceGameUpdate/Export.cpp +++ b/core/libs/prx/libSceGameUpdate/Export.cpp @@ -1,7 +1,9 @@ #include "prx/libSceGameUpdate/include/Api.hpp" +#include #include #include #include +#include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -11,20 +13,58 @@ static constexpr int SCE_GAME_UPDATE_ERROR_ALREADY_INITIALIZED = static_cast(0x80412803); static constexpr int SCE_GAME_UPDATE_ERROR_INVALID_SIZE = static_cast(0x80412804); static constexpr int SCE_GAME_UPDATE_ERROR_REQUEST_NOT_FOUND = static_cast(0x80412805); +static constexpr int SCE_GAME_UPDATE_ERROR_TOO_MANY_REQUESTS = static_cast(0x80412806); +static constexpr int SCE_GAME_UPDATE_ERROR_ABORTED = static_cast(0x80412807); +static constexpr int FIRST_REQUEST_ID = 0x20000000; +static constexpr int LAST_REQUEST_ID = 0x2FFFFFFF; static_assert(sizeof(GameUpdateCheckParam) == 0x30); static_assert(sizeof(GameUpdateCheckResult) == 0x30); -static std::atomic initialized{false}; +struct Request { + int id; + bool aborted; +}; -static int checkRequestId(int requestId) { +static std::mutex stateMutex; +static bool initialized = false; +static int nextRequestId = FIRST_REQUEST_ID; +static std::array requests{}; + +static Request* findRequest(int requestId) { + for (Request& request : requests) { + if (request.id != 0 && request.id == requestId) { + return &request; + } + } + return nullptr; +} + +static int followingRequestId(int requestId) { + return requestId == LAST_REQUEST_ID ? FIRST_REQUEST_ID : requestId + 1; +} + +static int takeRequestId() { + int requestId = nextRequestId; + while (findRequest(requestId) != nullptr) { + requestId = followingRequestId(requestId); + } + nextRequestId = followingRequestId(requestId); + return requestId; +} + +static int checkRequestId(int requestId, Request** request) { if (requestId <= 0) { return SCE_GAME_UPDATE_ERROR_REQUEST_NOT_FOUND; } - if (!initialized.load()) { + if (!initialized) { return SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED; } - return SCE_GAME_UPDATE_ERROR_REQUEST_NOT_FOUND; + *request = findRequest(requestId); + if (*request == nullptr) { + return SCE_GAME_UPDATE_ERROR_REQUEST_NOT_FOUND; + } + return 0; } static bool hasNonZeroReserved(const GameUpdateCheckParam* param) { @@ -39,7 +79,13 @@ static bool hasNonZeroReserved(const GameUpdateCheckParam* param) { extern "C" { int APS5_VABI sceGameUpdateAbortRequest(int requestId) { - return checkRequestId(requestId); + std::lock_guard lock(stateMutex); + Request* request = nullptr; + if (int error = checkRequestId(requestId, &request); error != 0) { + return error; + } + request->aborted = true; + return 0; } int APS5_VABI sceGameUpdateCheck(int requestId, const GameUpdateCheckParam* param, GameUpdateCheckResult* result) { @@ -55,18 +101,43 @@ int APS5_VABI sceGameUpdateCheck(int requestId, const GameUpdateCheckParam* para if (param->option != 0 || hasNonZeroReserved(param)) { return SCE_GAME_UPDATE_ERROR_INVALID_ARGUMENT; } - return checkRequestId(requestId); + std::lock_guard lock(stateMutex); + Request* request = nullptr; + if (int error = checkRequestId(requestId, &request); error != 0) { + return error; + } + if (request->aborted) { + return SCE_GAME_UPDATE_ERROR_ABORTED; + } + result->found = false; + result->addcont_found = false; + return 0; } int APS5_VABI sceGameUpdateCreateRequest(void) { - if (!initialized.load()) { + std::lock_guard lock(stateMutex); + if (!initialized) { return SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED; } - throw std::runtime_error("sceGameUpdateCreateRequest: patch service request creation is not implemented"); + for (Request& request : requests) { + if (request.id != 0) { + continue; + } + const int requestId = takeRequestId(); + request = {requestId, false}; + return requestId; + } + return SCE_GAME_UPDATE_ERROR_TOO_MANY_REQUESTS; } int APS5_VABI sceGameUpdateDeleteRequest(int requestId) { - return checkRequestId(requestId); + std::lock_guard lock(stateMutex); + Request* request = nullptr; + if (int error = checkRequestId(requestId, &request); error != 0) { + return error; + } + *request = {}; + return 0; } int APS5_VABI sceGameUpdateGetAddcontLatestVersion(uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, GameUpdateAddcontVersionInfo* info) { @@ -77,23 +148,31 @@ int APS5_VABI sceGameUpdateGetAddcontLatestVersion(uint32_t serviceLabel, const if (entitlementLabel == nullptr) { return SCE_GAME_UPDATE_ERROR_INVALID_ARGUMENT; } - if (!initialized.load()) { - return SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED; + { + std::lock_guard lock(stateMutex); + if (!initialized) { + return SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED; + } } throw std::runtime_error("sceGameUpdateGetAddcontLatestVersion: entitlement version lookup is not implemented"); } int APS5_VABI sceGameUpdateInitialize(void) { - if (initialized.exchange(true)) { + std::lock_guard lock(stateMutex); + if (initialized) { return SCE_GAME_UPDATE_ERROR_ALREADY_INITIALIZED; } + initialized = true; return 0; } int APS5_VABI sceGameUpdateTerminate(void) { - if (!initialized.exchange(false)) { + std::lock_guard lock(stateMutex); + if (!initialized) { return SCE_GAME_UPDATE_ERROR_NOT_INITIALIZED; } + initialized = false; + requests = {}; return 0; } diff --git a/core/libs/tests/GuestGameUpdate.cpp b/core/libs/tests/GuestGameUpdate.cpp index b3df072055..34c5c5308b 100644 --- a/core/libs/tests/GuestGameUpdate.cpp +++ b/core/libs/tests/GuestGameUpdate.cpp @@ -58,6 +58,42 @@ static void TestRequestArguments(int notInitialized, int invalidArgument, int in Require(sceGameUpdateTerminate() == 0); } +static void TestRequestLifecycle(int notFound) { + constexpr int tooManyRequests = static_cast(0x80412806); + constexpr int aborted = static_cast(0x80412807); + GameUpdateCheckParam param{}; + param.size = sizeof(param); + GameUpdateCheckResult result{}; + std::memset(&result, 0xAB, sizeof(result)); + result.size = sizeof(result); + const auto original = result; + + int ids[8]; + for (int& id : ids) { + id = sceGameUpdateCreateRequest(); + Require(id >= 0x20000000 && id <= 0x2FFFFFFF); + } + Require(ids[0] != ids[1]); + Require(sceGameUpdateCreateRequest() == tooManyRequests); + + Require(sceGameUpdateCheck(ids[0], ¶m, &result) == 0); + Require(!result.found && !result.addcont_found); + Require(std::memcmp(&result, &original, 8) == 0); + Require(std::memcmp(reinterpret_cast(&result) + 10, reinterpret_cast(&original) + 10, sizeof(result) - 10) == 0); + + Require(sceGameUpdateAbortRequest(ids[1]) == 0); + Require(sceGameUpdateCheck(ids[1], ¶m, &result) == aborted); + + Require(sceGameUpdateDeleteRequest(ids[3]) == 0); + Require(sceGameUpdateDeleteRequest(ids[3]) == notFound); + Require(sceGameUpdateCheck(ids[3], ¶m, &result) == notFound); + Require(sceGameUpdateCreateRequest() >= 0x20000000); + + Require(sceGameUpdateTerminate() == 0); + Require(sceGameUpdateInitialize() == 0); + Require(sceGameUpdateCheck(ids[0], ¶m, &result) == notFound); +} + int main() { constexpr int notInitialized = static_cast(0x80412801); constexpr int invalidArgument = static_cast(0x80412803); @@ -91,7 +127,7 @@ int main() { const auto original = info; RequireThrows([&] { sceGameUpdateGetAddcontLatestVersion(0, &label, &info); }); Require(std::memcmp(&info, &original, sizeof(info)) == 0); - RequireThrows([] { sceGameUpdateCreateRequest(); }); + TestRequestLifecycle(notFound); Require(sceGameUpdateTerminate() == 0); Require(sceGameUpdateTerminate() == notInitialized); diff --git a/docs/dev/debt/libSceGameUpdate.md b/docs/dev/debt/libSceGameUpdate.md index fd8f0f341a..75626c8e75 100644 --- a/docs/dev/debt/libSceGameUpdate.md +++ b/docs/dev/debt/libSceGameUpdate.md @@ -1,7 +1,11 @@ # libSceGameUpdate technical debt +### Silent stubs + +- [sceGameUpdateCreateRequest, sceGameUpdateCheck](../../../core/libs/prx/libSceGameUpdate/Export.cpp) (libSceGameUpdate) - requests are kept in the module's 8 slots with ids from `0x20000000` (a ninth returns `0x80412806`, an aborted request's Check `0x80412807`), as [prosper](https://github.com/mattias800/prosper) reads the PS5 3.20 `libSceGameUpdate.sprx`. Check never asks a patch service: it always answers no update (`found` and the byte after it cleared, the rest of the result untouched, return 0), which is what the module returns when its service reports `0x80412883`; KytyPS5 also answers no update. What the service returns on an offline console is not measured. The answer only decides whether a title offers a patch. PPSA22408 aborted on the throw in sceGameUpdateCreateRequest at its title screen. Closes when: measured on a PS5 + ### Functional -- [libSceGameUpdate](../../../core/libs/prx/libSceGameUpdate/Export.cpp): initialization tracks host state but does not connect the patch service. Request creation and entitlement version lookup throw instead of fabricating an offline error or a successful no-update result. The original maintains eight service-backed requests with IDs starting at `0x20000000`; the service's offline response is still unknown. Check, Abort and Delete implement argument/state checks but have no live requests to operate on. +- [libSceGameUpdate](../../../core/libs/prx/libSceGameUpdate/Export.cpp): initialization tracks host state but does not connect the patch service. Entitlement version lookup throws. - [sceGameUpdateCheck](../../../core/libs/prx/libSceGameUpdate/Export.cpp): option zero is supported for argument validation. The original additionally permits option one when a kernel platform predicate succeeds; that platform mode is not modeled. - [sceGameUpdateGetAddcontLatestVersion](../../../core/libs/prx/libSceGameUpdate/Export.cpp): the supplied module confirms a 20-byte entitlement-label input and a 48-byte service output. Null pointers are rejected before initialization is checked. There is no local output-size validation or blanket clearing of the output; the host leaves it untouched when the missing service causes an exception.