Merge pull request #10614 from mrfelek/fix/gameupdate-no-update

fix(libSceGameUpdate): answer no update instead of throwing on request creation
This commit is contained in:
Dean authored and GitHub committed 2026-10-26 20:01:56 +13:00
commit 3c2e5dc2ca
3 files changed
+134 -15

No files matched your search

+92 -13
View File
@@ -1,7 +1,9 @@
#include "prx/libSceGameUpdate/include/Api.hpp"
#include <array>
#include <atomic>
#include <cstdint>
#include <cstddef>
#include <mutex>
#include <stdexcept>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
@@ -11,20 +13,58 @@ static constexpr int SCE_GAME_UPDATE_ERROR_ALREADY_INITIALIZED = static_cast<int
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 constexpr int SCE_GAME_UPDATE_ERROR_REQUEST_NOT_FOUND = static_cast<int>(0x80412805);
static constexpr int SCE_GAME_UPDATE_ERROR_TOO_MANY_REQUESTS = static_cast<int>(0x80412806);
static constexpr int SCE_GAME_UPDATE_ERROR_ABORTED = static_cast<int>(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<bool> 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<Request, 8> 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;
}
+37 -1
View File
@@ -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<int>(0x80412806);
constexpr int aborted = static_cast<int>(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], &param, &result) == 0);
Require(!result.found && !result.addcont_found);
Require(std::memcmp(&result, &original, 8) == 0);
Require(std::memcmp(reinterpret_cast<const char*>(&result) + 10, reinterpret_cast<const char*>(&original) + 10, sizeof(result) - 10) == 0);
Require(sceGameUpdateAbortRequest(ids[1]) == 0);
Require(sceGameUpdateCheck(ids[1], &param, &result) == aborted);
Require(sceGameUpdateDeleteRequest(ids[3]) == 0);
Require(sceGameUpdateDeleteRequest(ids[3]) == notFound);
Require(sceGameUpdateCheck(ids[3], &param, &result) == notFound);
Require(sceGameUpdateCreateRequest() >= 0x20000000);
Require(sceGameUpdateTerminate() == 0);
Require(sceGameUpdateInitialize() == 0);
Require(sceGameUpdateCheck(ids[0], &param, &result) == notFound);
}
int main() {
constexpr int notInitialized = static_cast<int>(0x80412801);
constexpr int invalidArgument = static_cast<int>(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);
+5 -1
View File
@@ -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.