Tss support (#4930)

* initial TSS

* improved delete request
This commit is contained in:
georgemoralis
2026-08-27 17:08:47 +03:00
committed by GitHub
parent 54ec7d00a0
commit d7114f9197
7 changed files with 249 additions and 45 deletions
+72
View File
@@ -2122,6 +2122,54 @@ s32 NpHandler::TusSetMultiSlotVariable(s32 user_id, s32 service_label, const std
return ORBIS_OK;
}
s32 NpHandler::TssGetData(s32 user_id, s32 service_label, s32 slotId, bool hasOffset, u64 offset,
bool hasLastByte, u64 lastByte, bool hasIfParam, s32 ifType,
u64 ifLastModified, NpTus::OrbisNpTssDataStatus* statusOut, void* dataOut,
u64 dataCap, std::shared_ptr<NpTus::TusRequestCtx> ctx,
u64* contentLengthOut) {
std::shared_ptr<ShadNet::ShadNetClient> client;
{
std::lock_guard lock(m_mutex_clients);
auto it = m_clients.find(user_id);
if (it == m_clients.end()) {
return ORBIS_NP_ERROR_SIGNED_OUT;
}
client = it->second;
}
shadnet::TssGetDataRequest proto;
proto.set_slotid(slotId);
if (hasOffset) {
proto.set_hasoffset(true);
proto.set_offset(offset);
}
if (hasLastByte) {
proto.set_haslastbyte(true);
proto.set_lastbyte(lastByte);
}
if (hasIfParam) {
proto.set_hasifparam(true);
proto.set_iftype(ifType);
proto.set_iflastmodified(ifLastModified);
}
const std::string com_id = GetNpCommId(service_label);
if (!IsValidNpCommId(com_id)) {
return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
}
const u64 pkt_id = client->SubmitRequest(ShadNet::CommandType::TssGetData,
BuildTusPayload(com_id, proto.SerializeAsString()));
std::lock_guard lock(m_mutex_pending_tus);
PendingTusRequest p;
p.req = std::move(ctx);
p.cmd = ShadNet::CommandType::TssGetData;
p.user_id = user_id;
p.tssStatusOut = statusOut;
p.tssContentLengthOut = contentLengthOut;
p.dataOut = dataOut;
p.dataCap = dataCap;
m_pending_tus.emplace(pkt_id, std::move(p));
return ORBIS_OK;
}
s32 NpHandler::TusGetData(s32 user_id, s32 service_label, const std::string& ownerNpId,
const std::string& virtualUser, s64 ownerAccountId, s32 slotId,
NpTus::OrbisNpTusDataStatusA* statusAOut, u64 statusCap, void* dataOut,
@@ -3297,6 +3345,30 @@ void NpHandler::OnTusReply(s32 user_id, ShadNet::CommandType cmd, u64 pkt_id,
}
break;
}
case ShadNet::CommandType::TssGetData: {
shadnet::TssGetDataResponse resp;
if (!parseTusBody(resp)) {
req->SetResult(ORBIS_NP_COMMUNITY_ERROR_BAD_RESPONSE);
return;
}
u64 recv = 0;
if (pending.dataOut && !resp.data().empty()) {
recv = std::min<u64>(pending.dataCap, resp.data().size());
std::memcpy(pending.dataOut, resp.data().data(), recv);
}
if (pending.tssContentLengthOut) {
*pending.tssContentLengthOut = resp.contentlength();
}
if (pending.tssStatusOut) {
*pending.tssStatusOut = NpTus::OrbisNpTssDataStatus{};
pending.tssStatusOut->modified.tick = resp.lastmodified();
pending.tssStatusOut->status =
static_cast<NpTus::OrbisNpTssStatus>(resp.statuscodetype());
pending.tssStatusOut->contentLength = resp.contentlength();
}
req->SetResult(ORBIS_OK);
return;
}
case ShadNet::CommandType::TusGetData: {
shadnet::TusGetDataResponse resp;
if (!parseTusBody(resp)) {
+10 -6
View File
@@ -229,8 +229,10 @@ public:
const std::string& virtualUser, const std::vector<s32>& slotIds,
const std::vector<s64>& values,
std::shared_ptr<NpTus::TusRequestCtx> ctx, s64 ownerAccountId = 0);
// TUS blob data.
s32 TssGetData(s32 user_id, s32 service_label, s32 slotId, bool hasOffset, u64 offset,
bool hasLastByte, u64 lastByte, bool hasIfParam, s32 ifType, u64 ifLastModified,
NpTus::OrbisNpTssDataStatus* statusOut, void* dataOut, u64 dataCap,
std::shared_ptr<NpTus::TusRequestCtx> ctx, u64* contentLengthOut = nullptr);
s32 TusGetData(s32 user_id, s32 service_label, const std::string& ownerNpId,
const std::string& virtualUser, s64 ownerAccountId, s32 slotId,
NpTus::OrbisNpTusDataStatusA* statusAOut, u64 statusCap, void* dataOut,
@@ -415,10 +417,12 @@ private:
NpTus::OrbisNpTusDataStatusForCrossSave* statusArrayCS = nullptr; // cross-save data status
void* dataOut = nullptr; // GetData payload
u64 dataCap = 0; // GetData buffer capacity
u64 dataOffset = 0; // GetData installment offset into the blob
u64 statusCap = 0; // GetData dataStatus buffer size (0 = struct size)
u32* totalOut = nullptr; // friends total
u64 arrayNum = 0; // expected entry count
u64 dataOffset = 0; // GetData installment offset into the blob
u64 statusCap = 0; // GetData dataStatus buffer size (0 = struct size)
NpTus::OrbisNpTssDataStatus* tssStatusOut = nullptr; // TSS GetData status
u64* tssContentLengthOut = nullptr; // legacy GetSmallStorage byte count
u32* totalOut = nullptr; // friends total
u64 arrayNum = 0; // expected entry count
};
mutable std::mutex m_mutex_pending_tus;
std::map<u64, PendingTusRequest> m_pending_tus;
+130 -35
View File
@@ -55,6 +55,8 @@ constexpr size_t TusMaxTitleCtx = 32;
constexpr size_t TusMaxRequests = 256;
constexpr int TusMaxSlotsPerRequest = 64;
constexpr int TusMaxSelectedFriends = 100;
constexpr u64 TssMaxSize = 64 * 1024U;
constexpr s32 TssMaxSlotId = 15;
constexpr int TusMaxUsersPerRequest = 101;
static std::mutex g_mutex;
@@ -222,7 +224,9 @@ s32 PS4_SYSV_ABI sceNpTusDeleteRequest(int requestId) {
return ORBIS_NP_COMMUNITY_ERROR_INVALID_ID;
}
if (it->second.ctx) {
LOG_ERROR(Lib_NpTus, "Cannot delete request {}: request is still active", requestId);
if (!it->second.ctx->HasResult()) {
LOG_INFO(Lib_NpTus, "request {} still in flight, aborting it", requestId);
}
it->second.ctx->SetResult(ORBIS_NP_COMMUNITY_ERROR_ABORTED);
}
g_requests.erase(it);
@@ -4849,63 +4853,154 @@ s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSaveVUserAsync(
}
//***********************************
// TSS functions - WIP TODO
// TSS functions
//***********************************
s32 PS4_SYSV_ABI sceNpTssGetDataAsync(int reqId, s32 slotId, OrbisNpTssDataStatus* dataStatus,
u64 dataStatusSize, void* data, u64 dataSize,
OrbisNpTssGetDataOptParam* option) { // dummy atm TODO
LOG_INFO(Lib_NpTus, "reqId = {:#x}, slotId = {}, dataStatusSize = {}, dataSize = {}", reqId,
slotId, dataStatusSize, dataSize);
u64 dataStatusSize, void* data, u64 recvSize,
OrbisNpTssGetDataOptParam* option) {
LOG_INFO(Lib_NpTus,
"reqId = {:#x}, slotId = {}, dataStatus = {}, dataStatusSize = {}, data = {}, "
"recvSize = {}, option = {}",
reqId, slotId, fmt::ptr(dataStatus), dataStatusSize, fmt::ptr(data), recvSize,
fmt::ptr(option));
if (!dataStatus) {
LOG_ERROR(Lib_NpTus, "insufficient argument: dataStatus is NULL");
return ORBIS_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT;
}
if (dataStatusSize != sizeof(OrbisNpTssDataStatus)) {
LOG_ERROR(Lib_NpTus, "size mismatch: dataStatusSize={} but sizeof(OrbisNpTssDataStatus)={}",
dataStatusSize, sizeof(OrbisNpTssDataStatus));
return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT;
}
if (slotId < 0 || slotId > TssMaxSlotId) {
LOG_ERROR(Lib_NpTus, "invalid slot id {} (valid 0-{})", slotId, TssMaxSlotId);
return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
}
if (option && option->size != 0x20) {
LOG_ERROR(Lib_NpTus, "Invalid option provided");
return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT;
bool hasOffset = false, hasLastByte = false, hasIfParam = false;
u64 offset = 0, lastByte = 0, ifLastModified = 0;
s32 ifType = 0;
if (option) {
if (option->size != sizeof(OrbisNpTssGetDataOptParam)) {
LOG_ERROR(Lib_NpTus,
"size mismatch: option->size={} but sizeof(OrbisNpTssGetDataOptParam)={}",
option->size, sizeof(OrbisNpTssGetDataOptParam));
return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT;
}
if (option->offset) {
hasOffset = true;
offset = *option->offset;
}
if (option->lastByte) {
hasLastByte = true;
lastByte = *option->lastByte;
}
if (option->ifParam) {
hasIfParam = true;
ifType = static_cast<s32>(option->ifParam->ifType);
ifLastModified = option->ifParam->lastModified.tick;
}
}
if (dataStatus && dataStatusSize != 0x18) {
LOG_ERROR(Lib_NpTus, "Invalid data status size");
return ORBIS_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT;
NpTusRequest* req = nullptr;
u32 svc = 0;
s32 uid = -1;
std::string self;
if (auto ret = ResolveTus(reqId, &req, &svc, &uid, &self); ret < 0) {
return ret;
}
if (slotId < 0 || slotId > 15) {
LOG_ERROR(Lib_NpTus, "Invalid slot ID");
u64 startOffset = offset;
{
std::lock_guard lock(g_mutex);
if (!req->xfer.SameTarget(slotId, 0, std::string())) {
req->xfer.Begin(slotId, 0, std::string());
} else if (!hasOffset) {
hasOffset = true;
startOffset = req->xfer.recvOffset;
}
if (data) {
req->xfer.recvOffset += recvSize;
}
}
auto ctx = std::make_shared<TusRequestCtx>();
req->ctx = ctx;
s32 submit = Libraries::Np::NpHandler::GetInstance().TssGetData(
uid, static_cast<s32>(svc), slotId, hasOffset, startOffset, hasLastByte, lastByte,
hasIfParam, ifType, ifLastModified, dataStatus, data, data ? recvSize : 0, ctx);
if (submit < 0) {
return submit;
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpTssGetData(int reqId, s32 slotId, OrbisNpTssDataStatus* dataStatus,
u64 dataStatusSize, void* data, u64 recvSize,
OrbisNpTssGetDataOptParam* option) {
auto ret =
sceNpTssGetDataAsync(reqId, slotId, dataStatus, dataStatusSize, data, recvSize, option);
if (ret < 0) {
return ret;
}
// Returns 0 on normal termination.
if (auto wait = sceNpTusWaitAsync(reqId, &ret); wait < 0) {
return wait;
}
return ret;
}
s32 PS4_SYSV_ABI sceNpTssGetSmallStorageAsync(int reqId, void* data, u64 maxSize,
u64* contentLength, void* option) {
LOG_INFO(Lib_NpTus, "reqId = {:#x}, data = {}, maxSize = {}, contentLength = {}, option = {}",
reqId, fmt::ptr(data), maxSize, fmt::ptr(contentLength), fmt::ptr(option));
if (option) {
LOG_ERROR(Lib_NpTus, "invalid argument: option must be NULL");
return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
}
if (maxSize > TssMaxSize) {
LOG_ERROR(Lib_NpTus, "maxSize {} exceeds SCE_NP_TSS_MAX_SIZE {}", maxSize, TssMaxSize);
return ORBIS_NP_COMMUNITY_ERROR_INVALID_ARGUMENT;
}
NpTusRequest* req = nullptr;
if (auto ret = GetRequest(reqId, &req); ret < 0) {
u32 svc = 0;
s32 uid = -1;
std::string self;
if (auto ret = ResolveTus(reqId, &req, &svc, &uid, &self); ret < 0) {
return ret;
}
auto ctx = std::make_shared<TusRequestCtx>();
req->ctx = ctx;
if (dataStatus) {
dataStatus->status = OrbisNpTssStatus::Ok;
dataStatus->contentLength = 0;
s32 submit = Libraries::Np::NpHandler::GetInstance().TssGetData(
uid, static_cast<s32>(svc), /*slotId=*/0, /*hasOffset=*/false, 0, /*hasLastByte=*/false, 0,
/*hasIfParam=*/false, 0, 0, /*statusOut=*/nullptr, data, data ? maxSize : 0, ctx,
/*contentLengthOut=*/contentLength);
if (submit < 0) {
return submit;
}
ctx->SetResult(0);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpTssGetSmallStorage(int reqId, void* data, u64 maxSize, u64* contentLength,
void* option) {
auto ret = sceNpTssGetSmallStorageAsync(reqId, data, maxSize, contentLength, option);
if (ret < 0) {
return ret;
}
// Returns 0 on normal termination.
if (auto wait = sceNpTusWaitAsync(reqId, &ret); wait < 0) {
return wait;
}
return ret;
}
s32 PS4_SYSV_ABI sceNpTssGetStorageAsync(int reqId) {
LOG_ERROR(Lib_NpTus, "(STUBBED) called, faking async completion");
return FakeAsyncComplete(reqId);
}
s32 PS4_SYSV_ABI sceNpTssGetData() {
LOG_ERROR(Lib_NpTus, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpTssGetSmallStorage() {
LOG_ERROR(Lib_NpTus, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceNpTssGetSmallStorageAsync(int reqId) {
LOG_ERROR(Lib_NpTus, "(STUBBED) called, faking async completion");
return FakeAsyncComplete(reqId);
}
s32 PS4_SYSV_ABI sceNpTssGetStorage() {
LOG_ERROR(Lib_NpTus, "(STUBBED) called");
return ORBIS_OK;
+7 -3
View File
@@ -169,12 +169,16 @@ struct OrbisNpTusDataStatusForCrossSave {
s32 PS4_SYSV_ABI sceNpTssCreateNpTitleCtx(OrbisNpServiceLabel serviceLabel, OrbisNpId* npId);
s32 PS4_SYSV_ABI sceNpTssCreateNpTitleCtxA(OrbisNpServiceLabel serviceLabel,
Libraries::UserService::OrbisUserServiceUserId userId);
s32 PS4_SYSV_ABI sceNpTssGetData();
s32 PS4_SYSV_ABI sceNpTssGetData(int reqId, s32 slotId, OrbisNpTssDataStatus* dataStatus,
u64 dataStatusSize, void* data, u64 recvSize,
OrbisNpTssGetDataOptParam* option);
s32 PS4_SYSV_ABI sceNpTssGetDataAsync(int reqId, s32 slotId, OrbisNpTssDataStatus* dataStatus,
u64 dataStatusSize, void* data, u64 dataSize,
OrbisNpTssGetDataOptParam* option);
s32 PS4_SYSV_ABI sceNpTssGetSmallStorage();
s32 PS4_SYSV_ABI sceNpTssGetSmallStorageAsync(int reqId);
s32 PS4_SYSV_ABI sceNpTssGetSmallStorage(int reqId, void* data, u64 maxSize, u64* contentLength,
void* option);
s32 PS4_SYSV_ABI sceNpTssGetSmallStorageAsync(int reqId, void* data, u64 maxSize,
u64* contentLength, void* option);
s32 PS4_SYSV_ABI sceNpTssGetStorage();
s32 PS4_SYSV_ABI sceNpTssGetStorageAsync(int reqId);
s32 PS4_SYSV_ABI sceNpTusAbortRequest(int reqId);
@@ -25,6 +25,12 @@ struct TusRequestCtx {
cv.notify_all();
}
// True once the operation has completed (or been aborted).
bool HasResult() {
std::lock_guard lock(mutex);
return result.has_value();
}
// Block until the handler (or an abort/delete) sets the result.
s32 Wait() {
std::unique_lock lock(mutex);
+2
View File
@@ -110,6 +110,7 @@ enum class CommandType : u16 {
TusTryAndSetVariable = 211,
TusGetFriendsVariable = 212,
TusDeleteMultiSlotVariable = 213,
TssGetData = 214,
// Trophies
UnlockTrophy = 301,
SyncTrophies = 302,
@@ -160,6 +161,7 @@ enum class ErrorType : uint8_t {
ScoreHasData = 31,
CondFail = 32,
Unsupported = 33,
TooLarge = 34, // TSS file on the server exceeds the slot's documented maximum
};
enum class ShadNetState {
+22 -1
View File
@@ -722,6 +722,27 @@ message TusGetMultiUserVariableRequest {
repeated string ownerNpIds = 4; // base npid-array path (server resolves each)
}
// Title Small Storage (TSS)
message TssGetDataRequest {
int32 slotId = 1;
bool hasOffset = 2;
uint64 offset = 3;
bool hasLastByte = 4;
uint64 lastByte = 5;
bool hasIfParam = 6;
int32 ifType = 7; // 0 = IF_MODIFIED_SINCE, 1 = IF_RANGE
uint64 ifLastModified = 8; // seconds since epoch
}
message TssGetDataResponse {
int32 statusCodeType = 1; // OrbisNpTssStatusCodeType: 0 = OK, 1 = PARTIAL, 2 = NOT_MODIFIED.
uint64 lastModified = 2; // seconds since epoch, 0 when the file is absent
uint64 contentLength = 3; // size the client should expect for this status
bytes data = 4; // the (possibly ranged) payload
}
// Trophies
message TrophyEntry {
@@ -745,5 +766,5 @@ message SyncTrophiesReply {
}
message SetClientVersionRequest {
string version = 1;
string version = 1;
}