mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-10-12 02:22:27 +00:00
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user