Implement sceNpManagerGetTicketParam

This commit is contained in:
RipleyTom 2022-05-10 17:25:45 +02:00 committed by Megamouse
parent 072c289f5e
commit 4479d99a9a
5 changed files with 394 additions and 33 deletions

View File

@ -3351,7 +3351,7 @@ error_code sceNpManagerGetTicket(vm::ptr<void> buffer, vm::ptr<u32> bufferSize)
error_code sceNpManagerGetTicketParam(s32 paramId, vm::ptr<SceNpTicketParam> param)
{
sceNp.todo("sceNpManagerGetTicketParam(paramId=%d, param=*0x%x)", paramId, param);
sceNp.notice("sceNpManagerGetTicketParam(paramId=%d, param=*0x%x)", paramId, param);
auto& nph = g_fxo->get<named_thread<np::np_handler>>();
@ -3360,12 +3360,23 @@ error_code sceNpManagerGetTicketParam(s32 paramId, vm::ptr<SceNpTicketParam> par
return SCE_NP_ERROR_NOT_INITIALIZED;
}
if (!param)
if (!param || paramId < SCE_NP_TICKET_PARAM_SERIAL_ID || paramId > SCE_NP_TICKET_PARAM_SUBJECT_DOB)
{
// TODO: check paramId
return SCE_NP_ERROR_INVALID_ARGUMENT;
}
const auto& ticket = nph.get_ticket();
if (ticket.empty())
{
return SCE_NP_ERROR_INVALID_STATE;
}
if (!ticket.get_value(paramId, param))
{
return SCE_NP_ERROR_INVALID_STATE;
}
return CELL_OK;
}
@ -3380,7 +3391,7 @@ error_code sceNpManagerGetEntitlementIdList(vm::ptr<SceNpEntitlementId> entIdLis
return SCE_NP_ERROR_NOT_INITIALIZED;
}
return CELL_OK;
return not_an_error(0);
}
error_code sceNpManagerGetEntitlementById(vm::cptr<char> entId, vm::ptr<SceNpEntitlement> ent)

View File

@ -243,26 +243,26 @@ enum SceNpError : u32
SCE_NP_COMMUNITY_SERVER_ERROR_UNSPECIFIED = 0x8002a4ff,
// DRM
SCE_NP_DRM_ERROR_OUT_OF_MEMORY = 0x80029501,
SCE_NP_DRM_ERROR_INVALID_PARAM = 0x80029502,
SCE_NP_DRM_ERROR_SERVER_RESPONSE = 0x80029509,
SCE_NP_DRM_ERROR_NO_ENTITLEMENT = 0x80029513,
SCE_NP_DRM_ERROR_BAD_ACT = 0x80029514,
SCE_NP_DRM_ERROR_BAD_FORMAT = 0x80029515,
SCE_NP_DRM_ERROR_NO_LOGIN = 0x80029516,
SCE_NP_DRM_ERROR_INTERNAL = 0x80029517,
SCE_NP_DRM_ERROR_BAD_PERM = 0x80029519,
SCE_NP_DRM_ERROR_UNKNOWN_VERSION = 0x8002951a,
SCE_NP_DRM_ERROR_TIME_LIMIT = 0x8002951b,
SCE_NP_DRM_ERROR_DIFFERENT_ACCOUNT_ID = 0x8002951c,
SCE_NP_DRM_ERROR_DIFFERENT_DRM_TYPE = 0x8002951d,
SCE_NP_DRM_ERROR_SERVICE_NOT_STARTED = 0x8002951e,
SCE_NP_DRM_ERROR_BUSY = 0x80029520,
SCE_NP_DRM_ERROR_LICENSE_NOT_FOUND = 0x80029521,
SCE_NP_DRM_ERROR_IO = 0x80029525,
SCE_NP_DRM_ERROR_FORMAT = 0x80029530,
SCE_NP_DRM_ERROR_FILENAME = 0x80029533,
SCE_NP_DRM_ERROR_K_LICENSEE = 0x80029534,
SCE_NP_DRM_ERROR_OUT_OF_MEMORY = 0x80029501,
SCE_NP_DRM_ERROR_INVALID_PARAM = 0x80029502,
SCE_NP_DRM_ERROR_SERVER_RESPONSE = 0x80029509,
SCE_NP_DRM_ERROR_NO_ENTITLEMENT = 0x80029513,
SCE_NP_DRM_ERROR_BAD_ACT = 0x80029514,
SCE_NP_DRM_ERROR_BAD_FORMAT = 0x80029515,
SCE_NP_DRM_ERROR_NO_LOGIN = 0x80029516,
SCE_NP_DRM_ERROR_INTERNAL = 0x80029517,
SCE_NP_DRM_ERROR_BAD_PERM = 0x80029519,
SCE_NP_DRM_ERROR_UNKNOWN_VERSION = 0x8002951a,
SCE_NP_DRM_ERROR_TIME_LIMIT = 0x8002951b,
SCE_NP_DRM_ERROR_DIFFERENT_ACCOUNT_ID = 0x8002951c,
SCE_NP_DRM_ERROR_DIFFERENT_DRM_TYPE = 0x8002951d,
SCE_NP_DRM_ERROR_SERVICE_NOT_STARTED = 0x8002951e,
SCE_NP_DRM_ERROR_BUSY = 0x80029520,
SCE_NP_DRM_ERROR_LICENSE_NOT_FOUND = 0x80029521,
SCE_NP_DRM_ERROR_IO = 0x80029525,
SCE_NP_DRM_ERROR_FORMAT = 0x80029530,
SCE_NP_DRM_ERROR_FILENAME = 0x80029533,
SCE_NP_DRM_ERROR_K_LICENSEE = 0x80029534,
// DRM Server
SCE_NP_DRM_SERVER_ERROR_SERVICE_IS_END = 0x80029700,
@ -292,7 +292,7 @@ enum SceNpError : u32
SCE_NP_AUTH_EBUSY = 0x8002a00a,
SCE_NP_AUTH_EABORT = 0x8002a00c,
SCE_NP_AUTH_EEXIST = 0x8002a014,
SCE_NP_AUTH_EINVALID_ARGUMENT = 0x8002a015,
SCE_NP_AUTH_EINVALID_ARGUMENT = 0x8002a015,
// Auth extended
SCE_NP_AUTH_ERROR_SERVICE_END = 0x8002a200,
@ -882,6 +882,30 @@ enum
SCE_NP_MATCHING_GUI_EVENT_GET_ROOM_LIST_LIMIT = 0x000b
};
enum
{
SCE_NP_TICKET_PARAM_SERIAL_ID = 0,
SCE_NP_TICKET_PARAM_ISSUER_ID = 1,
SCE_NP_TICKET_PARAM_ISSUED_DATE = 2,
SCE_NP_TICKET_PARAM_EXPIRE_DATE = 3,
SCE_NP_TICKET_PARAM_SUBJECT_ACCOUNT_ID = 4,
SCE_NP_TICKET_PARAM_SUBJECT_ONLINE_ID = 5,
SCE_NP_TICKET_PARAM_SUBJECT_REGION = 6,
SCE_NP_TICKET_PARAM_SUBJECT_DOMAIN = 7,
SCE_NP_TICKET_PARAM_SERVICE_ID = 8,
SCE_NP_TICKET_PARAM_SUBJECT_STATUS = 9,
SCE_NP_TICKET_PARAM_STATUS_DURATION = 10,
SCE_NP_TICKET_PARAM_SUBJECT_DOB = 11,
};
enum
{
SCE_NP_TICKET_SERIAL_ID_SIZE = 20,
SCE_NP_SUBJECT_REGION_SIZE = 4,
SCE_NP_SUBJECT_DOMAIN_SIZE = 4,
SCE_NP_SERVICE_ID_SIZE = 24,
};
struct SceNpDrmKey
{
u8 keydata[16];

View File

@ -47,9 +47,294 @@ LOG_CHANNEL(sceNp);
LOG_CHANNEL(rpcn_log, "rpcn");
LOG_CHANNEL(nph_log, "NPHandler");
LOG_CHANNEL(ticket_log, "Ticket");
namespace np
{
ticket::ticket(std::vector<u8>&& raw_data)
: raw_data(raw_data)
{
parse();
}
std::size_t ticket::size() const
{
return raw_data.size();
}
const u8* ticket::data() const
{
return raw_data.data();
}
bool ticket::empty() const
{
return raw_data.empty();
}
bool ticket::get_value(s32 param_id, vm::ptr<SceNpTicketParam> param) const
{
if (!parse_success)
{
return false;
}
switch (param_id)
{
case SCE_NP_TICKET_PARAM_SERIAL_ID:
{
const auto& node = nodes[0].data.data_nodes[0];
if (node.len != SCE_NP_TICKET_SERIAL_ID_SIZE)
{
return false;
}
memcpy(param->data, node.data.data_vec.data(), SCE_NP_TICKET_SERIAL_ID_SIZE);
break;
}
case SCE_NP_TICKET_PARAM_ISSUER_ID:
{
const auto& node = nodes[0].data.data_nodes[1];
param->ui32 = node.data.data_u32;
break;
}
case SCE_NP_TICKET_PARAM_ISSUED_DATE:
{
const auto& node = nodes[0].data.data_nodes[2];
param->ui64 = node.data.data_u64;
break;
}
case SCE_NP_TICKET_PARAM_EXPIRE_DATE:
{
const auto& node = nodes[0].data.data_nodes[3];
param->ui64 = node.data.data_u64;
break;
}
case SCE_NP_TICKET_PARAM_SUBJECT_ACCOUNT_ID:
{
const auto& node = nodes[0].data.data_nodes[4];
param->ui64 = node.data.data_u64;
break;
}
case SCE_NP_TICKET_PARAM_SUBJECT_ONLINE_ID:
{
const auto& node = nodes[0].data.data_nodes[5];
if (node.len != 0x20)
{
return false;
}
memcpy(param->data, node.data.data_vec.data(), 0x20);
break;
}
case SCE_NP_TICKET_PARAM_SUBJECT_REGION:
{
const auto& node = nodes[0].data.data_nodes[6];
if (node.len != SCE_NP_SUBJECT_REGION_SIZE)
{
return false;
}
memcpy(param->data, node.data.data_vec.data(), SCE_NP_SUBJECT_REGION_SIZE);
break;
}
case SCE_NP_TICKET_PARAM_SUBJECT_DOMAIN:
{
const auto& node = nodes[0].data.data_nodes[7];
if (node.len != SCE_NP_SUBJECT_DOMAIN_SIZE)
{
return false;
}
memcpy(param->data, node.data.data_vec.data(), SCE_NP_SUBJECT_DOMAIN_SIZE);
break;
}
case SCE_NP_TICKET_PARAM_SERVICE_ID:
{
const auto& node = nodes[0].data.data_nodes[8];
if (node.len != SCE_NP_SERVICE_ID_SIZE)
{
return false;
}
memcpy(param->data, node.data.data_vec.data(), SCE_NP_SERVICE_ID_SIZE);
break;
}
case SCE_NP_TICKET_PARAM_SUBJECT_STATUS:
{
const auto& node = nodes[0].data.data_nodes[9];
param->ui32 = node.data.data_u32;
break;
}
case SCE_NP_TICKET_PARAM_STATUS_DURATION:
case SCE_NP_TICKET_PARAM_SUBJECT_DOB:
{
param->ui64 = 0;
break;
}
default:
sceNp.fatal("Invalid ticket param id requested!");
return false;
}
return true;
}
std::optional<ticket_data> ticket::parse_node(std::size_t index) const
{
if ((index + MIN_TICKET_DATA_SIZE) > size())
{
ticket_log.error("node didn't meet minimum size requirements");
return std::nullopt;
}
ticket_data tdata{};
const auto* ptr = data() + index;
tdata.id = *reinterpret_cast<const be_t<u16>*>(ptr);
tdata.len = *reinterpret_cast<const be_t<u16>*>(ptr + 2);
const auto* data_ptr = data() + 4;
auto check_size = [&](std::size_t expected) -> bool
{
if ((index + MIN_TICKET_DATA_SIZE + expected) > size())
{
return false;
}
return true;
};
switch (tdata.id)
{
case 0:
if (tdata.len != 0)
{
return std::nullopt;
}
break;
case 1:
if (tdata.len != 4 || !check_size(4))
{
return std::nullopt;
}
tdata.data.data_u32 = *reinterpret_cast<const be_t<u32>*>(data_ptr);
break;
case 2:
case 7:
if (tdata.len != 8 || !check_size(8))
{
return std::nullopt;
}
tdata.data.data_u64 = *reinterpret_cast<const be_t<u64>*>(data_ptr);
break;
case 4:
case 8:
if (!check_size(tdata.len))
{
return std::nullopt;
}
tdata.data.data_vec = std::vector<u8>(tdata.len);
memcpy(tdata.data.data_vec.data(), data_ptr, tdata.len);
break;
default:
if ((tdata.id & 0x3000) == 0x3000)
{
if (!check_size(tdata.len))
{
return std::nullopt;
}
std::size_t sub_index = 0;
tdata.data.data_nodes = {};
while (sub_index < tdata.len)
{
auto sub_node = parse_node(sub_index + index + 4);
if (!sub_node)
{
ticket_log.error("Failed to parse subnode at %d", sub_index + index + 4);
return std::nullopt;
}
sub_index += sub_node->len + MIN_TICKET_DATA_SIZE;
tdata.data.data_nodes.push_back(std::move(*sub_node));
}
break;
}
return std::nullopt;
}
return tdata;
}
void ticket::parse()
{
nodes.clear();
parse_success = false;
if (size() < (sizeof(u32) * 2))
{
return;
}
version = *reinterpret_cast<const be_t<u32>*>(data());
if (version != 0x21010000)
{
ticket_log.error("Invalid version: 0x%08x", version);
return;
}
u32 given_size = *reinterpret_cast<const be_t<u32>*>(data() + 4);
if ((given_size + 8) != size())
{
ticket_log.error("Size mismatch (gs: %d vs s: %d)", given_size, size());
return;
}
std::size_t index = 8;
while (index < size())
{
auto node = parse_node(index);
if (!node)
{
ticket_log.error("Failed to parse node at index %d", index);
return;
}
index += (node->len + MIN_TICKET_DATA_SIZE);
nodes.push_back(std::move(*node));
}
// Check that everything expected is there
if (nodes.size() != 2)
{
ticket_log.error("Expected 2 blobs, found %d", nodes.size());
return;
}
if (nodes[0].id != 0x3000 && nodes[1].id != 0x3002)
{
ticket_log.error("The 2 blobs ids are incorrect");
return;
}
if (nodes[0].data.data_nodes.size() < 12)
{
ticket_log.error("Expected at least 12 sub-nodes, found %d", nodes[0].data.data_nodes.size());
return;
}
const auto& subnodes = nodes[0].data.data_nodes;
if (subnodes[0].id != 8 || subnodes[1].id != 1 || subnodes[2].id != 7 || subnodes[3].id != 7 ||
subnodes[4].id != 2 || subnodes[5].id != 4 || subnodes[6].id != 8 || subnodes[7].id != 4 ||
subnodes[8].id != 8 || subnodes[9].id != 1)
{
ticket_log.error("Mismatched node");
return;
}
parse_success = true;
return;
}
np_handler::np_handler()
{
g_fxo->need<named_thread<signaling_handler>>();

View File

@ -17,6 +17,45 @@
namespace np
{
struct ticket_data
{
u16 id{}, len{};
struct
{
u32 data_u32{};
u64 data_u64{};
std::vector<u8> data_vec;
std::vector<ticket_data> data_nodes;
} data;
};
class ticket
{
public:
ticket() = default;
ticket(std::vector<u8>&& raw_data);
std::size_t size() const;
const u8* data() const;
bool empty() const;
bool get_value(s32 param_id, vm::ptr<SceNpTicketParam> param) const;
private:
std::optional<ticket_data> parse_node(std::size_t index) const;
void parse();
private:
static constexpr std::size_t MIN_TICKET_DATA_SIZE = 4;
std::vector<u8> raw_data;
bool parse_success = false;
u32 version{};
std::vector<ticket_data> nodes;
};
struct basic_event
{
s32 event = 0;
@ -121,10 +160,7 @@ namespace np
// Misc stuff
void req_ticket(u32 version, const SceNpId* npid, const char* service_id, const u8* cookie, u32 cookie_size, const char* entitlement_id, u32 consumed_count);
const std::vector<u8>& get_ticket() const
{
return current_ticket;
}
const ticket& get_ticket() const;
u32 add_players_to_history(vm::cptr<SceNpId> npids, u32 count);
// For signaling
@ -207,7 +243,7 @@ namespace np
bool is_connected = false;
bool is_psn_active = false;
std::vector<u8> current_ticket;
ticket current_ticket;
// IP & DNS info
std::string hostname = "localhost";

View File

@ -213,7 +213,7 @@ namespace np
return 0;
});
}
return true;
}
@ -675,7 +675,12 @@ namespace np
return;
}
bool np_handler::reply_req_ticket(u32 /*req_id*/, std::vector<u8>& reply_data)
const ticket& np_handler::get_ticket() const
{
return current_ticket;
}
bool np_handler::reply_req_ticket([[maybe_unused]] u32 req_id, std::vector<u8>& reply_data)
{
vec_stream reply(reply_data, 1);
auto ticket_raw = reply.get_rawdata();
@ -683,7 +688,7 @@ namespace np
if (reply.is_error())
return error_and_disconnect("Malformed reply to RequestTicket command");
current_ticket = std::move(ticket_raw);
current_ticket = ticket(std::move(ticket_raw));
auto ticket_size = static_cast<s32>(current_ticket.size());
if (manager_cb)