From e47f224bc21cddcc64a702ef9afbdfc2a94c4de4 Mon Sep 17 00:00:00 2001 From: zeph <35661622+ZephyrCodesStuff@users.noreply.github.com> Date: Sun, 7 Dec 2025 17:52:22 +0100 Subject: [PATCH] Clans: initial implementation - Needs GUI implementation to pick the server host - Needs code to select the server host from config Signed-off-by: zeph <35661622+ZephyrCodesStuff@users.noreply.github.com> --- rpcs3/Emu/CMakeLists.txt | 1 + rpcs3/Emu/Cell/Modules/sceNp.h | 1 + rpcs3/Emu/Cell/Modules/sceNpClans.cpp | 449 +++++++++-- rpcs3/Emu/Cell/Modules/sceNpClans.h | 32 +- rpcs3/Emu/NP/clan_client.cpp | 1035 +++++++++++++++++++++++++ rpcs3/Emu/NP/clan_client.h | 120 +++ 6 files changed, 1546 insertions(+), 92 deletions(-) create mode 100644 rpcs3/Emu/NP/clan_client.cpp create mode 100644 rpcs3/Emu/NP/clan_client.h diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 39e145900a..44210909d8 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -441,6 +441,7 @@ target_sources(rpcs3_emu PRIVATE NP/np_requests.cpp NP/signaling_handler.cpp NP/np_structs_extra.cpp + NP/clan_client.cpp NP/rpcn_client.cpp NP/rpcn_config.cpp NP/rpcn_countries.cpp diff --git a/rpcs3/Emu/Cell/Modules/sceNp.h b/rpcs3/Emu/Cell/Modules/sceNp.h index 12ca388ba2..6f29a2f8a9 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp.h +++ b/rpcs3/Emu/Cell/Modules/sceNp.h @@ -32,6 +32,7 @@ using SceNpBasicMessageRecvAction = u32; using SceNpClanId = u32; using SceNpClansMessageId = u32; +using SceNpClansMemberRole = u32; using SceNpClansMemberStatus = s32; using SceNpCustomMenuIndexMask = u32; diff --git a/rpcs3/Emu/Cell/Modules/sceNpClans.cpp b/rpcs3/Emu/Cell/Modules/sceNpClans.cpp index 91d22079fe..29f26d9e2b 100644 --- a/rpcs3/Emu/Cell/Modules/sceNpClans.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNpClans.cpp @@ -1,10 +1,13 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" #include "Emu/IdManager.h" +#include "Emu/NP/np_handler.h" +#include "Emu/NP/clan_client.h" #include "sceNp.h" #include "sceNpClans.h" + LOG_CHANNEL(sceNpClans); template<> @@ -14,6 +17,7 @@ void fmt_class_string::format(std::string& out, u64 arg) { switch (error) { + STR_CASE(SCE_NP_CLANS_SUCCESS); STR_CASE(SCE_NP_CLANS_ERROR_ALREADY_INITIALIZED); STR_CASE(SCE_NP_CLANS_ERROR_NOT_INITIALIZED); STR_CASE(SCE_NP_CLANS_ERROR_NOT_SUPPORTED); @@ -75,8 +79,6 @@ void fmt_class_string::format(std::string& out, u64 arg) error_code sceNpClansInit(vm::cptr commId, vm::cptr passphrase, vm::ptr pool, vm::ptr poolSize, u32 flags) { - sceNpClans.warning("sceNpClansInit(commId=*0x%x, passphrase=*0x%x, pool=*0x%x, poolSize=*0x%x, flags=0x%x)", commId, passphrase, pool, poolSize, flags); - auto& clans_manager = g_fxo->get(); if (clans_manager.is_initialized) @@ -94,6 +96,9 @@ error_code sceNpClansInit(vm::cptr commId, vm::cptr commId, vm::cptrget(); if (!clans_manager.is_initialized) @@ -110,6 +113,7 @@ error_code sceNpClansTerm() return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + delete clans_manager.client; clans_manager.is_initialized = false; return CELL_OK; @@ -117,8 +121,6 @@ error_code sceNpClansTerm() error_code sceNpClansCreateRequest(vm::ptr handle, u64 flags) { - sceNpClans.todo("sceNpClansCreateRequest(handle=*0x%x, flags=0x%llx)", handle, flags); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; @@ -134,33 +136,50 @@ error_code sceNpClansCreateRequest(vm::ptr handle, u64 return SCE_NP_CLANS_ERROR_NOT_SUPPORTED; } + vm::var req; + vm::write32(handle.addr(), req.addr()); + + auto& clans_manager = g_fxo->get(); + SceNpClansError res = clans_manager.client->createRequest(); + if (res != SCE_NP_CLANS_SUCCESS) + { + return res; + } + return CELL_OK; } error_code sceNpClansDestroyRequest(vm::ptr handle) { - sceNpClans.todo("sceNpClansDestroyRequest(handle=*0x%x)", handle); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + auto& clans_manager = g_fxo->get(); + SceNpClansError res = clans_manager.client->destroyRequest(); + if (res != SCE_NP_CLANS_SUCCESS) + { + return res; + } + return CELL_OK; } error_code sceNpClansAbortRequest(vm::ptr handle) { - sceNpClans.todo("sceNpClansAbortRequest(handle=*0x%x)", handle); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + auto& clans_manager = g_fxo->get(); + clans_manager.client->destroyRequest(); + return CELL_OK; } +// TODO: requires NpCommerce2 error_code sceNpClansCreateClan(vm::ptr handle, vm::cptr name, vm::cptr tag, vm::ptr clanId) { sceNpClans.todo("sceNpClansCreateClan(handle=*0x%x, name=%s, tag=%s, clanId=*0x%x)", handle, name, tag, clanId); @@ -183,6 +202,8 @@ error_code sceNpClansCreateClan(vm::ptr handle, vm::cpt return CELL_OK; } +// TODO: should probably not be implemented on RPCS3 until `CreateClan` is, +// to not let people disband a clan by accident error_code sceNpClansDisbandClan(vm::ptr handle, SceNpClanId clanId) { sceNpClans.todo("sceNpClansDisbandClan(handle=*0x%x, clanId=*0x%x)", handle, clanId); @@ -192,19 +213,20 @@ error_code sceNpClansDisbandClan(vm::ptr handle, SceNpC return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } - return CELL_OK; + // TEMP: don't let people disband + return SCE_NP_CLANS_SERVER_ERROR_PERMISSION_DENIED; + + // return CELL_OK; } error_code sceNpClansGetClanList(vm::ptr handle, vm::cptr paging, vm::ptr clanList, vm::ptr pageResult) { - sceNpClans.todo("sceNpClansGetClanList(handle=*0x%x, paging=*0x%x, clanList=*0x%x, pageResult=*0x%x)", handle, paging, clanList, pageResult); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } - if (!pageResult || (paging && !clanList)) // TODO: confirm + if (!pageResult || (paging && !clanList)) { return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } @@ -217,9 +239,28 @@ error_code sceNpClansGetClanList(vm::ptr handle, vm::cp } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansPagingRequest host_paging = {}; + std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + + SceNpClansEntry host_clanList[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + SceNpClansPagingResult host_pageResult = {}; + + SceNpClansError ret = clans_manager.client->getClanList(nph, &host_paging, host_clanList, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + std::memcpy(clanList.get_ptr(), host_clanList, sizeof(SceNpClansEntry) * host_pageResult.count); + std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + return CELL_OK; } +// TODO: seems to not be needed, even by the PS3..? error_code sceNpClansGetClanListByNpId(vm::ptr handle, vm::cptr paging, vm::cptr npid, vm::ptr clanList, vm::ptr pageResult) { sceNpClans.todo("sceNpClansGetClanListByNpId(handle=*0x%x, paging=*0x%x, npid=*0x%x, clanList=*0x%x, pageResult=*0x%x)", handle, paging, npid, clanList, pageResult); @@ -245,6 +286,7 @@ error_code sceNpClansGetClanListByNpId(vm::ptr handle, return CELL_OK; } +// TODO: seems to not be needed, even by the PS3..? error_code sceNpClansSearchByProfile(vm::ptr handle, vm::cptr paging, vm::cptr search, vm::ptr results, vm::ptr pageResult) { sceNpClans.todo("sceNpClansSearchByProfile(handle=*0x%x, paging=*0x%x, search=*0x%x, results=*0x%x, pageResult=*0x%x)", handle, paging, search, results, pageResult); @@ -272,8 +314,6 @@ error_code sceNpClansSearchByProfile(vm::ptr handle, vm error_code sceNpClansSearchByName(vm::ptr handle, vm::cptr paging, vm::cptr search, vm::ptr results, vm::ptr pageResult) { - sceNpClans.todo("sceNpClansSearchByName(handle=*0x%x, paging=*0x%x, search=*0x%x, results=*0x%x, pageResult=*0x%x)", handle, paging, search, results, pageResult); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; @@ -292,13 +332,31 @@ error_code sceNpClansSearchByName(vm::ptr handle, vm::c } } + auto& clans_manager = g_fxo->get(); + + SceNpClansPagingRequest host_paging = {}; + std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + + SceNpClansSearchableName host_search = {}; + std::memcpy(&host_search, search.get_ptr(), sizeof(SceNpClansSearchableName)); + + SceNpClansClanBasicInfo host_results[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + SceNpClansPagingResult host_pageResult = {}; + + SceNpClansError ret = clans_manager.client->clanSearch(&host_paging, &host_search, host_results, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + std::memcpy(results.get_ptr(), host_results, sizeof(SceNpClansClanBasicInfo) * host_pageResult.count); + std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + return CELL_OK; } error_code sceNpClansGetClanInfo(vm::ptr handle, SceNpClanId clanId, vm::ptr info) { - sceNpClans.todo("sceNpClansGetClanInfo(handle=*0x%x, clanId=%d, info=*0x%x)", handle, clanId, info); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; @@ -310,13 +368,23 @@ error_code sceNpClansGetClanInfo(vm::ptr handle, SceNpC return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& clans_manager = g_fxo->get(); + + SceNpClansClanInfo host_info = {}; + + SceNpClansError ret = clans_manager.client->getClanInfo(clanId, &host_info); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + std::memcpy(info.get_ptr(), &host_info, sizeof(SceNpClansClanInfo)); + return CELL_OK; } error_code sceNpClansUpdateClanInfo(vm::ptr handle, SceNpClanId clanId, vm::cptr info) { - sceNpClans.todo("sceNpClansUpdateClanInfo(handle=*0x%x, clanId=%d, info=*0x%x)", handle, clanId, info); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; @@ -328,18 +396,23 @@ error_code sceNpClansUpdateClanInfo(vm::ptr handle, Sce return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } - //if (info->something > X) - //{ - // return SCE_NP_CLANS_ERROR_EXCEEDS_MAX; - //} + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansUpdatableClanInfo host_info = {}; + std::memcpy(&host_info, info.get_ptr(), sizeof(SceNpClansUpdatableClanInfo)); + + SceNpClansError ret = clans_manager.client->updateClanInfo(nph, clanId, &host_info); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } return CELL_OK; } error_code sceNpClansGetMemberList(vm::ptr handle, SceNpClanId clanId, vm::cptr paging, SceNpClansMemberStatus status, vm::ptr memList, vm::ptr pageResult) { - sceNpClans.todo("sceNpClansGetMemberList(handle=*0x%x, clanId=%d, paging=*0x%x, status=%d, memList=*0x%x, pageResult=*0x%x)", handle, clanId, paging, status, memList, pageResult); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; @@ -358,13 +431,29 @@ error_code sceNpClansGetMemberList(vm::ptr handle, SceN } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansPagingRequest host_paging = {}; + std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + + SceNpClansMemberEntry* host_memList_addr = new SceNpClansMemberEntry[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX]; + SceNpClansPagingResult host_pageResult = {}; + + SceNpClansError ret = clans_manager.client->getMemberList(nph, clanId, &host_paging, status, host_memList_addr, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + std::memcpy(memList.get_ptr(), host_memList_addr, sizeof(SceNpClansMemberEntry) * host_pageResult.count); + std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + return CELL_OK; } error_code sceNpClansGetMemberInfo(vm::ptr handle, SceNpClanId clanId, vm::cptr npid, vm::ptr memInfo) { - sceNpClans.todo("sceNpClansGetMemberInfo(handle=*0x%x, clanId=%d, npid=*0x%x, memInfo=*0x%x)", handle, clanId, npid, memInfo); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; @@ -375,13 +464,27 @@ error_code sceNpClansGetMemberInfo(vm::ptr handle, SceN return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansMemberEntry host_memInfo = {}; + + SceNpClansError ret = clans_manager.client->getMemberInfo(nph, clanId, host_npid, &host_memInfo); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + std::memcpy(memInfo.get_ptr(), &host_memInfo, sizeof(SceNpClansMemberEntry)); + return CELL_OK; } error_code sceNpClansUpdateMemberInfo(vm::ptr handle, SceNpClanId clanId, vm::cptr info) { - sceNpClans.todo("sceNpClansUpdateMemberInfo(handle=*0x%x, clanId=%d, memInfo=*0x%x)", handle, clanId, info); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; @@ -389,22 +492,26 @@ error_code sceNpClansUpdateMemberInfo(vm::ptr handle, S if (!info) { - // TODO: add more checks for info return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } - //if (info->something > X) - //{ - // return SCE_NP_CLANS_ERROR_EXCEEDS_MAX; - //} + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansUpdatableMemberInfo host_info = {}; + std::memcpy(&host_info, info.get_ptr(), sizeof(SceNpClansUpdatableMemberInfo)); + + SceNpClansError ret = clans_manager.client->updateMemberInfo(nph, clanId, &host_info); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } return CELL_OK; } error_code sceNpClansChangeMemberRole(vm::ptr handle, SceNpClanId clanId, vm::cptr npid, u32 role) { - sceNpClans.todo("sceNpClansChangeMemberRole(handle=*0x%x, clanId=%d, npid=*0x%x, role=%d)", handle, clanId, npid, role); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; @@ -415,9 +522,22 @@ error_code sceNpClansChangeMemberRole(vm::ptr handle, S return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansError ret = clans_manager.client->changeMemberRole(nph, clanId, host_npid, role); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } +// TODO: no struct currently implements `autoAccept` as a field error_code sceNpClansGetAutoAcceptStatus(vm::ptr handle, SceNpClanId clanId, vm::ptr enable) { sceNpClans.todo("sceNpClansGetAutoAcceptStatus(handle=*0x%x, clanId=%d, enable=*0x%x)", handle, clanId, enable); @@ -435,6 +555,7 @@ error_code sceNpClansGetAutoAcceptStatus(vm::ptr handle return CELL_OK; } +// TODO: no struct currently implements `autoAccept` as a field error_code sceNpClansUpdateAutoAcceptStatus(vm::ptr handle, SceNpClanId clanId, b8 enable) { sceNpClans.todo("sceNpClansUpdateAutoAcceptStatus(handle=*0x%x, clanId=%d, enable=%d)", handle, clanId, enable); @@ -449,32 +570,44 @@ error_code sceNpClansUpdateAutoAcceptStatus(vm::ptr han error_code sceNpClansJoinClan(vm::ptr handle, SceNpClanId clanId) { - sceNpClans.todo("sceNpClansJoinClan(handle=*0x%x, clanId=%d)", handle, clanId); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansError ret = clans_manager.client->joinClan(nph, clanId); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } error_code sceNpClansLeaveClan(vm::ptr handle, SceNpClanId clanId) { - sceNpClans.todo("sceNpClansLeaveClan(handle=*0x%x, clanId=%d)", handle, clanId); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansError ret = clans_manager.client->leaveClan(nph, clanId); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } error_code sceNpClansKickMember(vm::ptr handle, SceNpClanId clanId, vm::cptr npid, vm::cptr message) { - sceNpClans.todo("sceNpClansKickMember(handle=*0x%x, clanId=%d, npid=*0x%x, message=*0x%x)", handle, clanId, npid, message); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; @@ -493,13 +626,29 @@ error_code sceNpClansKickMember(vm::ptr handle, SceNpCl } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansMessage host_message = {}; + if (message) + { + std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + } + + SceNpClansError ret = clans_manager.client->kickMember(nph, clanId, host_npid, &host_message); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } error_code sceNpClansSendInvitation(vm::ptr handle, SceNpClanId clanId, vm::cptr npid, vm::cptr message) { - sceNpClans.todo("sceNpClansSendInvitation(handle=*0x%x, clanId=%d, npid=*0x%x, message=*0x%x)", handle, clanId, npid, message); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; @@ -518,13 +667,29 @@ error_code sceNpClansSendInvitation(vm::ptr handle, Sce } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansMessage host_message = {}; + if (message) + { + std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + } + + SceNpClansError ret = clans_manager.client->sendInvitation(nph, clanId, host_npid, &host_message); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } error_code sceNpClansCancelInvitation(vm::ptr handle, SceNpClanId clanId, vm::cptr npid) { - sceNpClans.todo("sceNpClansCancelInvitation(handle=*0x%x, clanId=%d, npid=*0x%x)", handle, clanId, npid); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; @@ -535,13 +700,23 @@ error_code sceNpClansCancelInvitation(vm::ptr handle, S return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansError ret = clans_manager.client->cancelInvitation(nph, clanId, host_npid); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } error_code sceNpClansSendInvitationResponse(vm::ptr handle, SceNpClanId clanId, vm::cptr message, b8 accept) { - sceNpClans.todo("sceNpClansSendInvitationResponse(handle=*0x%x, clanId=%d, message=*0x%x, accept=%d)", handle, clanId, message, accept); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; @@ -555,13 +730,26 @@ error_code sceNpClansSendInvitationResponse(vm::ptr han } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansMessage host_message = {}; + if (message) + { + std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + } + + SceNpClansError ret = clans_manager.client->sendInvitationResponse(nph, clanId, &host_message, accept); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } error_code sceNpClansSendMembershipRequest(vm::ptr handle, u32 clanId, vm::cptr message) { - sceNpClans.todo("sceNpClansSendMembershipRequest(handle=*0x%x, clanId=%d, message=*0x%x)", handle, clanId, message); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; @@ -575,25 +763,46 @@ error_code sceNpClansSendMembershipRequest(vm::ptr hand } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansMessage host_message = {}; + if (message) + { + std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + } + + SceNpClansError ret = clans_manager.client->requestMembership(nph, clanId, &host_message); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } error_code sceNpClansCancelMembershipRequest(vm::ptr handle, SceNpClanId clanId) { - sceNpClans.todo("sceNpClansCancelMembershipRequest(handle=*0x%x, clanId=%d)", handle, clanId); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansError ret = clans_manager.client->cancelRequestMembership(nph, clanId); + + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } error_code sceNpClansSendMembershipResponse(vm::ptr handle, SceNpClanId clanId, vm::cptr npid, vm::cptr message, b8 allow) { - sceNpClans.todo("sceNpClansSendMembershipResponse(handle=*0x%x, clanId=%d, npid=*0x%x, message=*0x%x, allow=%d)", handle, clanId, npid, message, allow); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; @@ -612,13 +821,29 @@ error_code sceNpClansSendMembershipResponse(vm::ptr han } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansMessage host_message = {}; + if (message) + { + std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + } + + SceNpClansError ret = clans_manager.client->sendMembershipResponse(nph, clanId, host_npid, &host_message, allow); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } error_code sceNpClansGetBlacklist(vm::ptr handle, SceNpClanId clanId, vm::cptr paging, vm::ptr bl, vm::ptr pageResult) { - sceNpClans.todo("sceNpClansGetBlacklist(handle=*0x%x, clanId=%d, paging=*0x%x, bl=*0x%x, pageResult=*0x%x)", handle, clanId, paging, bl, pageResult); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; @@ -637,13 +862,29 @@ error_code sceNpClansGetBlacklist(vm::ptr handle, SceNp } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansPagingRequest host_paging = {}; + std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + + SceNpClansBlacklistEntry host_bl[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + SceNpClansPagingResult host_pageResult = {}; + + SceNpClansError ret = clans_manager.client->getBlacklist(nph, clanId, &host_paging, host_bl, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + std::memcpy(bl.get_ptr(), host_bl, sizeof(SceNpClansBlacklistEntry) * host_pageResult.count); + std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + return CELL_OK; } error_code sceNpClansAddBlacklistEntry(vm::ptr handle, SceNpClanId clanId, vm::cptr npid) { - sceNpClans.todo("sceNpClansAddBlacklistEntry(handle=*0x%x, clanId=%d, npid=*0x%x)", handle, clanId, npid); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; @@ -654,13 +895,23 @@ error_code sceNpClansAddBlacklistEntry(vm::ptr handle, return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansError ret = clans_manager.client->addBlacklistEntry(nph, clanId, host_npid); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } error_code sceNpClansRemoveBlacklistEntry(vm::ptr handle, SceNpClanId clanId, vm::cptr npid) { - sceNpClans.todo("sceNpClansRemoveBlacklistEntry(handle=*0x%x, clanId=%d, npid=*0x%x)", handle, clanId, npid); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; @@ -671,19 +922,29 @@ error_code sceNpClansRemoveBlacklistEntry(vm::ptr handl return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansError ret = clans_manager.client->removeBlacklistEntry(nph, clanId, host_npid); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } error_code sceNpClansRetrieveAnnouncements(vm::ptr handle, SceNpClanId clanId, vm::cptr paging, vm::ptr mlist, vm::ptr pageResult) { - sceNpClans.todo("sceNpClansRetrieveAnnouncements(handle=*0x%x, clanId=%d, paging=*0x%x, mlist=*0x%x, pageResult=*0x%x)", handle, clanId, paging, mlist, pageResult); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } - if (!pageResult || (paging && !mlist)) // TODO: confirm + if (!pageResult || (paging && !mlist) || clanId == UINT32_MAX) // TODO: confirm { return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } @@ -696,13 +957,29 @@ error_code sceNpClansRetrieveAnnouncements(vm::ptr hand } } + auto& clans_manager = g_fxo->get(); + auto& nph = g_fxo->get>(); + + SceNpClansPagingRequest host_paging = {}; + std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + + SceNpClansMessageEntry host_mlist[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + SceNpClansPagingResult host_pageResult = {}; + + SceNpClansError ret = clans_manager.client->retrieveAnnouncements(nph, clanId, &host_paging, host_mlist, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + std::memcpy(mlist.get_ptr(), host_mlist, sizeof(SceNpClansMessageEntry) * host_pageResult.count); + std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + return CELL_OK; } error_code sceNpClansPostAnnouncement(vm::ptr handle, SceNpClanId clanId, vm::cptr message, vm::cptr data, u32 duration, vm::ptr mId) { - sceNpClans.todo("sceNpClansPostAnnouncement(handle=*0x%x, clanId=%d, message=*0x%x, data=*0x%x, duration=%d, mId=*0x%x)", handle, clanId, message, data, duration, mId); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; @@ -713,28 +990,52 @@ error_code sceNpClansPostAnnouncement(vm::ptr handle, S return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } - if (!data) // TODO verify - { - return SCE_NP_CLANS_ERROR_NOT_SUPPORTED; - } - if (strlen(message->body) > SCE_NP_CLANS_ANNOUNCEMENT_MESSAGE_BODY_MAX_LENGTH || strlen(message->subject) > SCE_NP_CLANS_MESSAGE_SUBJECT_MAX_LENGTH) // TODO: correct max? { return SCE_NP_CLANS_ERROR_EXCEEDS_MAX; } + auto& clans_manager = g_fxo->get(); + auto& nph = g_fxo->get>(); + + SceNpClansMessage host_message = {}; + std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + + SceNpClansMessageData host_data = {}; + if (data) + { + std::memcpy(&host_data, data.get_ptr(), sizeof(SceNpClansMessageData)); + } + + SceNpClansMessageId host_mId = 0; + + SceNpClansError ret = clans_manager.client->postAnnouncement(nph, clanId, &host_message, &host_data, duration, &host_mId); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + *mId = host_mId; + return CELL_OK; } error_code sceNpClansRemoveAnnouncement(vm::ptr handle, SceNpClanId clanId, SceNpClansMessageId mId) { - sceNpClans.todo("sceNpClansPostAnnouncement(handle=*0x%x, clanId=%d, mId=%d)", handle, clanId, mId); - if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + auto& clans_manager = g_fxo->get(); + auto& nph = g_fxo->get>(); + + SceNpClansError ret = clans_manager.client->deleteAnnouncement(nph, clanId, mId); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } @@ -880,4 +1181,4 @@ DECLARE(ppu_module_manager::sceNpClans)("sceNpClans", []() REG_FUNC(sceNpClans, sceNpClansRemovePostedChallenge); REG_FUNC(sceNpClans, sceNpClansRetrieveChallenges); REG_FUNC(sceNpClans, sceNpClansRemoveChallenge); -}); +}); \ No newline at end of file diff --git a/rpcs3/Emu/Cell/Modules/sceNpClans.h b/rpcs3/Emu/Cell/Modules/sceNpClans.h index f0d1d54630..8e686bbeab 100644 --- a/rpcs3/Emu/Cell/Modules/sceNpClans.h +++ b/rpcs3/Emu/Cell/Modules/sceNpClans.h @@ -5,6 +5,8 @@ // Return codes enum SceNpClansError : u32 { + SCE_NP_CLANS_SUCCESS = CELL_OK, + SCE_NP_CLANS_ERROR_ALREADY_INITIALIZED = 0x80022701, SCE_NP_CLANS_ERROR_NOT_INITIALIZED = 0x80022702, SCE_NP_CLANS_ERROR_NOT_SUPPORTED = 0x80022703, @@ -97,7 +99,7 @@ enum SCE_NP_CLANS_ANNOUNCEMENT_MESSAGE_BODY_MAX_LENGTH = 1536, SCE_NP_CLANS_CLAN_BINARY_ATTRIBUTE1_MAX_SIZE = 190, SCE_NP_CLANS_CLAN_BINARY_DATA_MAX_SIZE = 10240, - SCE_NP_CLANS_MEMBER_BINARY_ATTRIBUTE1_MAX_SIZE = 16, + SCE_NP_CLANS_MEMBER_BINARY_ATTRIBUTE1_MAX_SIZE = 15, SCE_NP_CLANS_MEMBER_DESCRIPTION_MAX_LENGTH = 255, SCE_NP_CLANS_MEMBER_BINARY_DATA_MAX_SIZE = 1024, SCE_NP_CLANS_MESSAGE_BODY_MAX_LENGTH = 1536, @@ -138,7 +140,8 @@ enum }; // Request handle for clan API -using SceNpClansRequestHandle = vm::ptr; +struct SceNpClansRequest {}; +using SceNpClansRequestHandle = vm::ptr; // Paging request structure struct SceNpClansPagingRequest @@ -159,8 +162,8 @@ struct SceNpClansClanBasicInfo { be_t clanId; be_t numMembers; - s8 name[SCE_NP_CLANS_CLAN_NAME_MAX_LENGTH + 1]; - s8 tag[SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH + 1]; + char name[SCE_NP_CLANS_CLAN_NAME_MAX_LENGTH + 1]; + char tag[SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH + 1]; u8 reserved[2]; }; @@ -197,7 +200,7 @@ struct SceNpClansSearchableProfile be_t intAttr2SearchOp; be_t intAttr3SearchOp; be_t binAttr1SearchOp; - s8 tag[SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH + 1]; + char tag[SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH + 1]; u8 reserved[3]; }; @@ -205,7 +208,7 @@ struct SceNpClansSearchableProfile struct SceNpClansSearchableName { be_t nameSearchOp; - s8 name[SCE_NP_CLANS_CLAN_NAME_MAX_LENGTH + 1]; + char name[SCE_NP_CLANS_CLAN_NAME_MAX_LENGTH + 1]; u8 reserved[3]; }; @@ -213,7 +216,7 @@ struct SceNpClansSearchableName struct SceNpClansUpdatableClanInfo { be_t fields; - s8 description[SCE_NP_CLANS_CLAN_DESCRIPTION_MAX_LENGTH + 1]; + char description[SCE_NP_CLANS_CLAN_DESCRIPTION_MAX_LENGTH + 1]; SceNpClansSearchableAttr attr; u8 binData1; be_t binData1Size; @@ -233,8 +236,8 @@ struct SceNpClansUpdatableMemberInfo be_t fields; u8 binData1; be_t binData1Size; - u8 binAttr1[SCE_NP_CLANS_CLAN_BINARY_ATTRIBUTE1_MAX_SIZE + 1]; - s8 description[SCE_NP_CLANS_MEMBER_DESCRIPTION_MAX_LENGTH + 1]; + u8 binAttr1[SCE_NP_CLANS_MEMBER_BINARY_ATTRIBUTE1_MAX_SIZE + 1]; + char description[SCE_NP_CLANS_MEMBER_DESCRIPTION_MAX_LENGTH + 1]; b8 allowMsg; u8 reserved[3]; }; @@ -271,7 +274,7 @@ struct SceNpClansMessageEntry SceNpClansMessage message; SceNpClansMessageData data; SceNpId npid; - u8 reserved[4]; + SceNpClanId postedBy; }; // Blacklist entry structure @@ -279,11 +282,4 @@ struct SceNpClansBlacklistEntry { SceNpId entry; SceNpId registeredBy; -}; - -// fxm objects - -struct sce_np_clans_manager -{ - atomic_t is_initialized = false; -}; +}; \ No newline at end of file diff --git a/rpcs3/Emu/NP/clan_client.cpp b/rpcs3/Emu/NP/clan_client.cpp new file mode 100644 index 0000000000..283b524132 --- /dev/null +++ b/rpcs3/Emu/NP/clan_client.cpp @@ -0,0 +1,1035 @@ +#include "stdafx.h" +#include "util/types.hpp" + +#include +#include +#include +#include + +LOG_CHANNEL(clan_log, "clans"); + +const char* HOST_VIEW = "clans-view01.ww.np.community.playstation.net"; +const char* HOST_UPDATE = "clans-rec01.ww.np.community.playstation.net"; + +const char* REQ_TYPE_FUNC = "func"; +const char* REQ_TYPE_SEC = "sec"; + +constexpr const char* JID_FORMAT = "%s@un.br.np.playstation.net"; + +const char* CLANS_SERVICE_ID = "IV0001-NPXS01001_00"; +const char* CLANS_ENTITLEMENT_ID = "NPWR00432_00"; + +namespace std +{ + template <> + struct formatter : formatter + { + auto format(clan::ClanRequestType value, format_context& ctx) const + { + string_view name = "unknown"; + switch (value) + { + case clan::ClanRequestType::FUNC: name = "func"; break; + case clan::ClanRequestType::SEC: name = "sec"; break; + } + return formatter::format(string(name), ctx); + } + }; + + template <> + struct formatter : formatter + { + auto format(clan::ClanManagerOperationType value, format_context& ctx) const + { + string_view name = "unknown"; + switch (value) + { + case clan::ClanManagerOperationType::VIEW: name = "view"; break; + case clan::ClanManagerOperationType::UPDATE: name = "update"; break; + } + return formatter::format(string(name), ctx); + } + }; + + template <> + struct formatter : formatter + { + auto format(clan::ClanSearchFilterOperator value, format_context& ctx) const + { + string_view name = "unknown"; + switch (value) + { + case clan::ClanSearchFilterOperator::Equal: name = "eq"; break; + case clan::ClanSearchFilterOperator::NotEqual: name = "ne"; break; + case clan::ClanSearchFilterOperator::GreaterThan: name = "gt"; break; + case clan::ClanSearchFilterOperator::GreaterThanOrEqual: name = "ge"; break; + case clan::ClanSearchFilterOperator::LessThan: name = "lt"; break; + case clan::ClanSearchFilterOperator::LessThanOrEqual: name = "le"; break; + case clan::ClanSearchFilterOperator::Like: name = "lk"; break; + } + return formatter::format(string(name), ctx); + } + }; + + template <> + struct formatter : formatter + { + auto format(clan::ClanRequestAction value, format_context& ctx) const + { + string_view name = "unknown"; + switch (value) + { + case clan::ClanRequestAction::GetClanList: name = "get_clan_list"; break; + case clan::ClanRequestAction::GetClanInfo: name = "get_clan_info"; break; + case clan::ClanRequestAction::GetMemberInfo: name = "get_member_info"; break; + case clan::ClanRequestAction::GetMemberList: name = "get_member_list"; break; + case clan::ClanRequestAction::GetBlacklist: name = "get_blacklist"; break; + case clan::ClanRequestAction::RecordBlacklistEntry: name = "record_blacklist_entry"; break; + case clan::ClanRequestAction::DeleteBlacklistEntry: name = "delete_blacklist_entry"; break; + case clan::ClanRequestAction::ClanSearch: name = "clan_search"; break; + case clan::ClanRequestAction::RequestMembership: name = "request_membership"; break; + case clan::ClanRequestAction::CancelRequestMembership: name = "cancel_request_membership"; break; + case clan::ClanRequestAction::AcceptMembershipRequest: name = "accept_membership_request"; break; + case clan::ClanRequestAction::DeclineMembershipRequest: name = "decline_membership_request"; break; + case clan::ClanRequestAction::SendInvitation: name = "send_invitation"; break; + case clan::ClanRequestAction::CancelInvitation: name = "cancel_invitation"; break; + case clan::ClanRequestAction::AcceptInvitation: name = "accept_invitation"; break; + case clan::ClanRequestAction::DeclineInvitation: name = "decline_invitation"; break; + case clan::ClanRequestAction::UpdateMemberInfo: name = "update_member_info"; break; + case clan::ClanRequestAction::UpdateClanInfo: name = "update_clan_info"; break; + case clan::ClanRequestAction::JoinClan: name = "join_clan"; break; + case clan::ClanRequestAction::LeaveClan: name = "leave_clan"; break; + case clan::ClanRequestAction::KickMember: name = "kick_member"; break; + case clan::ClanRequestAction::ChangeMemberRole: name = "change_member_role"; break; + case clan::ClanRequestAction::RetrieveAnnouncements: name = "retrieve_announcements"; break; + case clan::ClanRequestAction::PostAnnouncement: name = "post_announcement"; break; + case clan::ClanRequestAction::DeleteAnnouncement: name = "delete_announcement"; break; + } + return formatter::format(string(name), ctx); + } + }; +} + +namespace clan +{ + struct curl_memory + { + char* response; + size_t size; + }; + + size_t clan_client::curlWriteCallback(void* data, size_t size, size_t nmemb, void* clientp) + { + size_t realsize = size * nmemb; + std::vector* mem = static_cast*>(clientp); + + size_t old_size = mem->size(); + mem->resize(old_size + realsize); + memcpy(mem->data() + old_size, data, realsize); + + return realsize; + } + + clan_client::clan_client() + { + createRequest(); + } + + clan_client::~clan_client() + { + destroyRequest(); + } + + SceNpClansError clan_client::createRequest() + { + if (curl) + return SceNpClansError::SCE_NP_CLANS_ERROR_ALREADY_INITIALIZED; + + curl = curl_easy_init(); + if (!curl) + { + return SceNpClansError::SCE_NP_CLANS_ERROR_NOT_INITIALIZED; + } + + curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); + + return SceNpClansError::SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clan_client::destroyRequest() + { + if (curl) + { + curl_easy_cleanup(curl); + curl = nullptr; + } + + return SceNpClansError::SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clan_client::sendRequest(ClanRequestAction action, ClanManagerOperationType opType, pugi::xml_document* xmlBody, pugi::xml_document* outResponse) + { + if (!curl) + return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; + + ClanRequestType reqType = ClanRequestType::FUNC; + pugi::xml_node clan = xmlBody->child("clan"); + if (clan && clan.child("ticket")) + { + reqType = ClanRequestType::SEC; + } + + std::string host = opType == ClanManagerOperationType::VIEW ? HOST_VIEW : HOST_UPDATE; + std::string url = std::format("https://{}/clan_manager_{}/{}/{}", host, opType, reqType, action); + + std::ostringstream oss; + xmlBody->save(oss, "\t", 8U); + + std::string xml = oss.str(); + + char err_buf[CURL_ERROR_SIZE]; + err_buf[0] = '\0'; + + std::vector response_buffer; + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_buffer); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, err_buf); + + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, xml.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, xml.size()); + + res = curl_easy_perform(curl); + + if (res != CURLE_OK) + { + outResponse = nullptr; + clan_log.error("curl_easy_perform() failed: %s", curl_easy_strerror(res)); + clan_log.error("Error buffer: %s", err_buf); + return SCE_NP_CLANS_ERROR_BAD_REQUEST; + } + + response_buffer.push_back('\0'); + + pugi::xml_parse_result res = outResponse->load_string(response_buffer.data()); + if (!res) + { + clan_log.error("XML parsing failed: %s", res.description()); + return SCE_NP_CLANS_ERROR_BAD_RESPONSE; + } + + pugi::xml_node clanResult = outResponse->child("clan"); + if (!clanResult) + return SCE_NP_CLANS_ERROR_BAD_RESPONSE; + + pugi::xml_attribute result = clanResult.attribute("result"); + if (!result) + return SCE_NP_CLANS_ERROR_BAD_RESPONSE; + + std::string result_str = result.as_string(); + if (result_str != "00") + return static_cast(0x80022800 | std::stoul(result_str, nullptr, 16)); + + return SCE_NP_CLANS_SUCCESS; + } + + std::string clan_client::getClanTicket(np::np_handler& nph) + { + if (nph.get_ticket().size() > 0) { + std::vector ticket_bytes(1024); + uint32_t ticket_size = UINT32_MAX; + + Base64_Encode_NoNl(nph.get_ticket().data(), nph.get_ticket().size(), ticket_bytes.data(), &ticket_size); + return std::string(reinterpret_cast(ticket_bytes.data()), ticket_size); + } + + const auto& npid = nph.get_npid(); + + const char* service_id = CLANS_SERVICE_ID; + const unsigned char* cookie = nullptr; + const u32 cookie_size = 0; + const char* entitlement_id = CLANS_ENTITLEMENT_ID; + const u32 consumed_count = 0; + + nph.req_ticket(0x00020001, &npid, service_id, cookie, cookie_size, entitlement_id, consumed_count); + + np::ticket ticket; + + // TODO: convert this to use events? + int retries = 0; + while (ticket.empty() && retries < 100) + { + ticket = nph.get_ticket(); + if (ticket.empty()) + { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + retries++; + } + } + + if (ticket.empty()) + { + clan_log.error("Failed to get clan ticket"); + return ""; + } + + std::vector ticket_bytes(1024); + uint32_t ticket_size = UINT32_MAX; + + Base64_Encode_NoNl(ticket.data(), ticket.size(), ticket_bytes.data(), &ticket_size); + std::string ticket_str = std::string(reinterpret_cast(ticket_bytes.data()), ticket_size); + + return ticket_str; + } + +#pragma region Outgoing API Requests + SceNpClansError clan_client::getClanList(np::np_handler& nph, SceNpClansPagingRequest* paging, SceNpClansEntry* clanList, SceNpClansPagingResult* pageResult) + { + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + + std::string ticket = getClanTicket(nph); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("start").text().set(paging->startPos); + clan.append_child("max").text().set(paging->max); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clanRes = sendRequest(ClanRequestAction::GetClanList, ClanManagerOperationType::VIEW, &doc, &response); + + if (clanRes != SCE_NP_CLANS_SUCCESS) + return clanRes; + + pugi::xml_node clanResult = response.child("clan"); + pugi::xml_node list = clanResult.child("list"); + + pugi::xml_attribute results = list.attribute("results"); + uint32_t results_count = results.as_uint(); + + pugi::xml_attribute total = list.attribute("total"); + uint32_t total_count = total.as_uint(); + + int i = 0; + for (pugi::xml_node info = list.child("info"); info; info = info.next_sibling("info"), i++) + { + pugi::xml_attribute id = info.attribute("id"); + uint32_t clanId = id.as_uint(); + + pugi::xml_node name = info.child("name"); + std::string name_str = name.text().as_string(); + + pugi::xml_node tag = info.child("tag"); + std::string tag_str = tag.text().as_string(); + + pugi::xml_node role = info.child("role"); + int32_t role_int = role.text().as_uint(); + + pugi::xml_node status = info.child("status"); + uint32_t status_int = status.text().as_uint(); + + pugi::xml_node onlinename = info.child("onlinename"); + std::string onlinename_str = onlinename.text().as_string(); + + pugi::xml_node members = info.child("members"); + uint32_t members_int = members.text().as_uint(); + + SceNpClansEntry entry = SceNpClansEntry{ + .info = SceNpClansClanBasicInfo{ + .clanId = clanId, + .numMembers = members_int, + .name = "", + .tag = "", + .reserved = {0, 0}, + }, + .role = static_cast(role_int), + .status = static_cast(status_int)}; + + snprintf(entry.info.name, sizeof(entry.info.name), "%s", name_str.c_str()); + snprintf(entry.info.tag, sizeof(entry.info.tag), "%s", tag_str.c_str()); + + clanList[i] = entry; + i++; + } + + *pageResult = SceNpClansPagingResult{ + .count = results_count, + .total = total_count}; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clan_client::getClanInfo(SceNpClanId clanId, SceNpClansClanInfo* clanInfo) + { + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("id").text().set(clanId); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clanRes = sendRequest(ClanRequestAction::GetClanInfo, ClanManagerOperationType::VIEW, &doc, &response); + + if (clanRes != SCE_NP_CLANS_SUCCESS) + return clanRes; + + pugi::xml_node clanResult = response.child("clan"); + pugi::xml_node info = clanResult.child("info"); + + std::string name_str = info.child("name").text().as_string(); + std::string tag_str = info.child("tag").text().as_string(); + uint32_t members_int = info.child("members").text().as_uint(); + std::string date_created_str = info.child("date-created").text().as_string(); + std::string description_str = info.child("description").text().as_string(); + + *clanInfo = SceNpClansClanInfo{ + .info = SceNpClansClanBasicInfo{ + .clanId = clanId, + .numMembers = members_int, + .name = "", + .tag = "", + }, + .updatable = SceNpClansUpdatableClanInfo{ + .description = "", + }}; + + snprintf(clanInfo->info.name, sizeof(clanInfo->info.name), "%s", name_str.c_str()); + snprintf(clanInfo->info.tag, sizeof(clanInfo->info.tag), "%s", tag_str.c_str()); + snprintf(clanInfo->updatable.description, sizeof(clanInfo->updatable.description), "%s", description_str.c_str()); + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clan_client::getMemberInfo(np::np_handler& nph, SceNpClanId clanId, SceNpId npId, SceNpClansMemberEntry* memInfo) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + + std::string jid_str = std::format(JID_FORMAT, npId.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clanRes = sendRequest(ClanRequestAction::GetMemberInfo, ClanManagerOperationType::VIEW, &doc, &response); + + if (clanRes != SCE_NP_CLANS_SUCCESS) + return clanRes; + + pugi::xml_node clanResult = response.child("clan"); + pugi::xml_node info = clanResult.child("info"); + + pugi::xml_attribute jid = info.attribute("jid"); + std::string npid_str = jid.as_string(); + + char username[SCE_NET_NP_ONLINEID_MAX_LENGTH + 1] = {0}; + + sscanf(npid_str.c_str(), "%16[^@]", username); + + SceNpId npid; + + if (!strcmp(username, nph.get_npid().handle.data)) + { + npid = nph.get_npid(); + } + else + { + npid = SceNpId {}; + std::strncpy(npid.handle.data, username, SCE_NET_NP_ONLINEID_MAX_LENGTH + 1); + } + + pugi::xml_node role = info.child("role"); + uint32_t role_int = role.text().as_uint(); + + pugi::xml_node status = info.child("status"); + uint32_t status_int = status.text().as_uint(); + + pugi::xml_node description = info.child("description"); + std::string description_str = description.text().as_string(); + + char description_char[256] = {0}; + snprintf(description_char, sizeof(description_char), "%s", description_str.c_str()); + + *memInfo = SceNpClansMemberEntry + { + .npid = npid, + .role = static_cast(role_int), + .status = static_cast(status_int), + .updatable = SceNpClansUpdatableMemberInfo{ + .description = "", + } + }; + + snprintf(memInfo->npid.handle.data, sizeof(memInfo->npid.handle.data), "%s", username); + snprintf(memInfo->updatable.description, sizeof(memInfo->updatable.description), "%s", description_char); + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clan_client::getMemberList(np::np_handler& nph, SceNpClanId clanId, SceNpClansPagingRequest* paging, SceNpClansMemberStatus /*status*/, SceNpClansMemberEntry* memList, SceNpClansPagingResult* pageResult) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + clan.append_child("start").text().set(paging->startPos); + clan.append_child("max").text().set(paging->max); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clanRes = sendRequest(ClanRequestAction::GetMemberList, ClanManagerOperationType::VIEW, &doc, &response); + + if (clanRes != SCE_NP_CLANS_SUCCESS) + return clanRes; + + pugi::xml_node clanResult = response.child("clan"); + pugi::xml_node list = clanResult.child("list"); + + pugi::xml_attribute results = list.attribute("results"); + uint32_t results_count = results.as_uint(); + + pugi::xml_attribute total = list.attribute("total"); + uint32_t total_count = total.as_uint(); + + int i = 0; + for (pugi::xml_node info = list.child("info"); info; info = info.next_sibling("info")) + { + std::string npid_str = info.attribute("jid").as_string(); + + char username[SCE_NET_NP_ONLINEID_MAX_LENGTH + 1] = {0}; + + sscanf(npid_str.c_str(), "%16[^@]", username); + + SceNpId npid; + + if (!strcmp(username, nph.get_npid().handle.data)) + { + npid = nph.get_npid(); + } + else + { + npid = SceNpId {}; + std::strncpy(npid.handle.data, username, SCE_NET_NP_ONLINEID_MAX_LENGTH + 1); + } + + uint32_t role_int = info.child("role").text().as_uint(); + uint32_t status_int = info.child("status").text().as_uint(); + std::string description_str = info.child("description").text().as_string(); + + char description_char[256] = {0}; + snprintf(description_char, sizeof(description_char), "%s", description_str.c_str()); + + SceNpClansMemberEntry entry = SceNpClansMemberEntry + { + .npid = npid, + .role = static_cast(role_int), + .status = static_cast(status_int), + }; + + snprintf(entry.updatable.description, sizeof(entry.updatable.description), "%s", description_char); + + memList[i] = entry; + i++; + } + + *pageResult = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clan_client::getBlacklist(np::np_handler& nph, SceNpClanId clanId, SceNpClansPagingRequest* paging, SceNpClansBlacklistEntry* bl, SceNpClansPagingResult* pageResult) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + clan.append_child("start").text().set(paging->startPos); + clan.append_child("max").text().set(paging->max); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clanRes = sendRequest(ClanRequestAction::GetBlacklist, ClanManagerOperationType::VIEW, &doc, &response); + + if (clanRes != SCE_NP_CLANS_SUCCESS) + return clanRes; + + pugi::xml_node clanResult = response.child("clan"); + pugi::xml_node list = clanResult.child("list"); + + pugi::xml_attribute results = list.attribute("results"); + uint32_t results_count = results.as_uint(); + + pugi::xml_attribute total = list.attribute("total"); + uint32_t total_count = total.as_uint(); + + int i = 0; + for (pugi::xml_node node = list.child("entry"); node; node = node.next_sibling("entry")) + { + pugi::xml_node id = node.child("jid"); + std::string npid_str = id.text().as_string(); + + char username[SCE_NET_NP_ONLINEID_MAX_LENGTH + 1] = {0}; + + sscanf(npid_str.c_str(), "%16[^@]", username); + + SceNpId npid = SceNpId + { + .handle = SceNpOnlineId + { + .data = "" + } + }; + + snprintf(npid.handle.data, sizeof(npid.handle.data), "%s", username); + + SceNpClansBlacklistEntry entry = SceNpClansBlacklistEntry + { + .entry = npid, + }; + + bl[i] = entry; + i++; + } + + *pageResult = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clan_client::addBlacklistEntry(np::np_handler& nph, SceNpClanId clanId, SceNpId npId) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + + std::string jid_str = std::format(JID_FORMAT, npId.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return sendRequest(ClanRequestAction::RecordBlacklistEntry, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clan_client::removeBlacklistEntry(np::np_handler& nph, SceNpClanId clanId, SceNpId npId) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + + std::string jid_str = std::format(JID_FORMAT, npId.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return sendRequest(ClanRequestAction::DeleteBlacklistEntry, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clan_client::clanSearch(SceNpClansPagingRequest* paging, SceNpClansSearchableName* search, SceNpClansClanBasicInfo* clanList, SceNpClansPagingResult* pageResult) + { + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("start").text().set(paging->startPos); + clan.append_child("max").text().set(paging->max); + + pugi::xml_node filter = clan.append_child("filter"); + pugi::xml_node name = filter.append_child("name"); + + std::string op_name = std::format("{}", static_cast(static_cast(search->nameSearchOp))); + name.append_attribute("op").set_value(op_name.c_str()); + name.append_attribute("value").set_value(search->name); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clanRes = sendRequest(ClanRequestAction::ClanSearch, ClanManagerOperationType::VIEW, &doc, &response); + + if (clanRes != SCE_NP_CLANS_SUCCESS) + return clanRes; + + pugi::xml_node clanResult = response.child("clan"); + pugi::xml_node list = clanResult.child("list"); + + pugi::xml_attribute results = list.attribute("results"); + uint32_t results_count = results.as_uint(); + + pugi::xml_attribute total = list.attribute("total"); + uint32_t total_count = total.as_uint(); + + int i = 0; + for (pugi::xml_node node = list.child("info"); node; node = node.next_sibling("info")) + { + uint32_t clanId = node.attribute("id").as_uint(); + std::string name_str = node.child("name").text().as_string(); + std::string tag_str = node.child("tag").text().as_string(); + uint32_t members_int = node.child("members").text().as_uint(); + + SceNpClansClanBasicInfo entry = SceNpClansClanBasicInfo + { + .clanId = clanId, + .numMembers = members_int, + .name = "", + .tag = "", + .reserved = {0, 0}, + }; + + snprintf(entry.name, sizeof(entry.name), "%s", name_str.c_str()); + snprintf(entry.tag, sizeof(entry.tag), "%s", tag_str.c_str()); + + clanList[i] = entry; + i++; + } + + *pageResult = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clan_client::requestMembership(np::np_handler& nph, SceNpClanId clanId, SceNpClansMessage* /*message*/) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + + pugi::xml_document response = pugi::xml_document(); + return sendRequest(ClanRequestAction::RequestMembership, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clan_client::cancelRequestMembership(np::np_handler& nph, SceNpClanId clanId) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + + pugi::xml_document response = pugi::xml_document(); + return sendRequest(ClanRequestAction::CancelRequestMembership, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clan_client::sendMembershipResponse(np::np_handler& nph, SceNpClanId clanId, SceNpId npId, SceNpClansMessage* /*message*/, b8 allow) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + + std::string jid_str = std::format(JID_FORMAT, npId.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return sendRequest(allow ? ClanRequestAction::AcceptMembershipRequest : ClanRequestAction::DeclineMembershipRequest, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clan_client::sendInvitation(np::np_handler& nph, SceNpClanId clanId, SceNpId npId, SceNpClansMessage* /*message*/) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + + std::string jid_str = std::format(JID_FORMAT, npId.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return sendRequest(ClanRequestAction::SendInvitation, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clan_client::cancelInvitation(np::np_handler& nph, SceNpClanId clanId, SceNpId npId) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + + std::string jid_str = std::format(JID_FORMAT, npId.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return sendRequest(ClanRequestAction::CancelInvitation, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clan_client::sendInvitationResponse(np::np_handler& nph, SceNpClanId clanId, SceNpClansMessage* /*message*/, b8 accept) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + + pugi::xml_document response = pugi::xml_document(); + return sendRequest(accept ? ClanRequestAction::AcceptInvitation : ClanRequestAction::DeclineInvitation, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clan_client::updateMemberInfo(np::np_handler& nph, SceNpClanId clanId, SceNpClansUpdatableMemberInfo* info) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + + pugi::xml_node role = clan.append_child("onlinename"); + role.text().set(nph.get_npid().handle.data); + + pugi::xml_node description = clan.append_child("description"); + description.text().set(info->description); + + pugi::xml_node status = clan.append_child("bin-attr1"); + + byte binAttr1[SCE_NP_CLANS_MEMBER_BINARY_ATTRIBUTE1_MAX_SIZE * 2 + 1] = {0}; + uint32_t binAttr1Size = UINT32_MAX; + Base64_Encode_NoNl(info->binAttr1, info->binData1Size, binAttr1, &binAttr1Size); + + if (binAttr1Size == UINT32_MAX) + return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; + + // `reinterpret_cast` used to let the compiler select the correct overload of `set` + status.text().set(reinterpret_cast(binAttr1)); + + pugi::xml_node allowMsg = clan.append_child("allow-msg"); + allowMsg.text().set(static_cast(info->allowMsg)); + + pugi::xml_node size = clan.append_child("size"); + size.text().set(info->binData1Size); + + pugi::xml_document response = pugi::xml_document(); + return sendRequest(ClanRequestAction::UpdateMemberInfo, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clan_client::updateClanInfo(np::np_handler& nph, SceNpClanId clanId, SceNpClansUpdatableClanInfo* info) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + + // TODO: implement binary and integer attributes (not implemented in server yet) + + pugi::xml_node description = clan.append_child("description"); + description.text().set(info->description); + + pugi::xml_document response = pugi::xml_document(); + return sendRequest(ClanRequestAction::UpdateClanInfo, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clan_client::joinClan(np::np_handler& nph, SceNpClanId clanId) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + + pugi::xml_document response = pugi::xml_document(); + return sendRequest(ClanRequestAction::JoinClan, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clan_client::leaveClan(np::np_handler& nph, SceNpClanId clanId) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + + pugi::xml_document response = pugi::xml_document(); + return sendRequest(ClanRequestAction::LeaveClan, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clan_client::kickMember(np::np_handler& nph, SceNpClanId clanId, SceNpId npId, SceNpClansMessage* /*message*/) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + + std::string jid_str = std::format(JID_FORMAT, npId.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return sendRequest(ClanRequestAction::KickMember, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clan_client::changeMemberRole(np::np_handler& nph, SceNpClanId clanId, SceNpId npId, SceNpClansMemberRole role) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + + std::string jid_str = std::format(JID_FORMAT, npId.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_node roleNode = clan.append_child("role"); + roleNode.text().set(static_cast(role)); + + pugi::xml_document response = pugi::xml_document(); + return sendRequest(ClanRequestAction::ChangeMemberRole, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clan_client::retrieveAnnouncements(np::np_handler& nph, SceNpClanId clanId, SceNpClansPagingRequest* paging, SceNpClansMessageEntry* announcements, SceNpClansPagingResult* pageResult) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + clan.append_child("start").text().set(paging->startPos); + clan.append_child("max").text().set(paging->max); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clanRes = sendRequest(ClanRequestAction::RetrieveAnnouncements, ClanManagerOperationType::VIEW, &doc, &response); + + if (clanRes != SCE_NP_CLANS_SUCCESS) + return clanRes; + + pugi::xml_node clanResult = response.child("clan"); + pugi::xml_node list = clanResult.child("list"); + + pugi::xml_attribute results = list.attribute("results"); + uint32_t results_count = results.as_uint(); + + pugi::xml_attribute total = list.attribute("total"); + uint32_t total_count = total.as_uint(); + + int i = 0; + for (pugi::xml_node node = list.child("msg-info"); node; node = node.next_sibling("msg-info")) + { + pugi::xml_attribute id = node.attribute("id"); + uint32_t msgId = id.as_uint(); + + std::string subject_str = node.child("subject").text().as_string(); + std::string msg_str = node.child("msg").text().as_string(); + std::string npid_str = node.child("jid").text().as_string(); + std::string msg_date = node.child("msg-date").text().as_string(); + + char username[SCE_NET_NP_ONLINEID_MAX_LENGTH + 1] = {0}; + sscanf(npid_str.c_str(), "%16[^@]", username); + + SceNpId npid; + + if (!strcmp(username, nph.get_npid().handle.data)) + { + npid = nph.get_npid(); + } + else + { + npid = SceNpId {}; + std::strncpy(npid.handle.data, username, SCE_NET_NP_ONLINEID_MAX_LENGTH + 1); + } + + // TODO: implement `binData` and `fromId` + + SceNpClansMessageEntry entry = SceNpClansMessageEntry + { + .mId = msgId, + .message = SceNpClansMessage { + .subject = "", + .body = "", + }, + .npid = npid, + .postedBy = clanId, + }; + + + strncpy(entry.message.subject, subject_str.c_str(), subject_str.size()); + strncpy(entry.message.body, msg_str.c_str(), msg_str.size()); + + announcements[i] = entry; + i++; + } + + *pageResult = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clan_client::postAnnouncement(np::np_handler& nph, SceNpClanId clanId, SceNpClansMessage* announcement, SceNpClansMessageData* /*data*/, u32 duration, SceNpClansMessageId* msgId) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + + pugi::xml_node subject = clan.append_child("subject"); + subject.text().set(announcement->subject); + + pugi::xml_node msg = clan.append_child("msg"); + msg.text().set(announcement->body); + + pugi::xml_node expireDate = clan.append_child("expire-date"); + expireDate.text().set(duration); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clanRes = sendRequest(ClanRequestAction::PostAnnouncement, ClanManagerOperationType::UPDATE, &doc, &response); + + if (clanRes != SCE_NP_CLANS_SUCCESS) + return clanRes; + + pugi::xml_node clanResult = response.child("clan"); + pugi::xml_node msgIdNode = clanResult.child("id"); + *msgId = msgIdNode.text().as_uint(); + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clan_client::deleteAnnouncement(np::np_handler& nph, SceNpClanId clanId, SceNpClansMessageId announcementId) + { + std::string ticket = getClanTicket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clanId); + clan.append_child("msg-id").text().set(announcementId); + + pugi::xml_document response = pugi::xml_document(); + return sendRequest(ClanRequestAction::DeleteAnnouncement, ClanManagerOperationType::UPDATE, &doc, &response); + } +} +#pragma endregion \ No newline at end of file diff --git a/rpcs3/Emu/NP/clan_client.h b/rpcs3/Emu/NP/clan_client.h new file mode 100644 index 0000000000..1703c59c78 --- /dev/null +++ b/rpcs3/Emu/NP/clan_client.h @@ -0,0 +1,120 @@ +#pragma once + +#include <3rdparty/curl/curl/include/curl/curl.h> +#include +#include +#include + +namespace clan +{ + enum class ClanManagerOperationType + { + VIEW, + UPDATE + }; + + enum class ClanRequestType + { + FUNC, + SEC + }; + + enum class ClanSearchFilterOperator : u8 + { + Equal, + NotEqual, + GreaterThan, + GreaterThanOrEqual, + LessThan, + LessThanOrEqual, + Like, + }; + + enum class ClanRequestAction + { + GetClanList, + GetClanInfo, + GetMemberInfo, + GetMemberList, + GetBlacklist, + RecordBlacklistEntry, + DeleteBlacklistEntry, + ClanSearch, + RequestMembership, + CancelRequestMembership, + AcceptMembershipRequest, + DeclineMembershipRequest, + SendInvitation, + CancelInvitation, + AcceptInvitation, + DeclineInvitation, + UpdateMemberInfo, + UpdateClanInfo, + JoinClan, + LeaveClan, + KickMember, + ChangeMemberRole, + RetrieveAnnouncements, + PostAnnouncement, + DeleteAnnouncement + }; + + class clan_client + { + private: + CURL* curl = nullptr; + CURLcode res = CURLE_OK; + + static size_t curlWriteCallback(void* data, size_t size, size_t nmemb, void* clientp); + SceNpClansError sendRequest(ClanRequestAction action, ClanManagerOperationType type, pugi::xml_document* xmlBody, pugi::xml_document* outResponse); + + /// @brief Forge and get a V2.1 Ticket for clan operations + std::string getClanTicket(np::np_handler& nph); + + public: + clan_client(); + ~clan_client(); + + SceNpClansError createRequest(); + SceNpClansError destroyRequest(); + + SceNpClansError clanSearch(SceNpClansPagingRequest* paging, SceNpClansSearchableName* search, SceNpClansClanBasicInfo* clanList, SceNpClansPagingResult* pageResult); + + SceNpClansError getClanList(np::np_handler& nph, SceNpClansPagingRequest* paging, SceNpClansEntry* clanList, SceNpClansPagingResult* pageResult); + SceNpClansError getClanInfo(SceNpClanId clanId, SceNpClansClanInfo* clanInfo); + + SceNpClansError getMemberInfo(np::np_handler& nph, SceNpClanId clanId, SceNpId npId, SceNpClansMemberEntry* memInfo); + SceNpClansError getMemberList(np::np_handler& nph, SceNpClanId clanId, SceNpClansPagingRequest* paging, SceNpClansMemberStatus status, SceNpClansMemberEntry* memList, SceNpClansPagingResult* pageResult); + + SceNpClansError getBlacklist(np::np_handler& nph, SceNpClanId clanId, SceNpClansPagingRequest* paging, SceNpClansBlacklistEntry* bl, SceNpClansPagingResult* pageResult); + SceNpClansError addBlacklistEntry(np::np_handler& nph, SceNpClanId clanId, SceNpId npId); + SceNpClansError removeBlacklistEntry(np::np_handler& nph, SceNpClanId clanId, SceNpId npId); + + SceNpClansError requestMembership(np::np_handler& nph, SceNpClanId clanId, SceNpClansMessage* message); + SceNpClansError cancelRequestMembership(np::np_handler& nph, SceNpClanId clanId); + SceNpClansError sendMembershipResponse(np::np_handler& nph, SceNpClanId clanId, SceNpId npId, SceNpClansMessage* message, b8 allow); + + SceNpClansError sendInvitation(np::np_handler& nph, SceNpClanId clanId, SceNpId npId, SceNpClansMessage* message); + SceNpClansError cancelInvitation(np::np_handler& nph, SceNpClanId clanId, SceNpId npId); + SceNpClansError sendInvitationResponse(np::np_handler& nph, SceNpClanId clanId, SceNpClansMessage* message, b8 accept); + + SceNpClansError joinClan(np::np_handler& nph, SceNpClanId clanId); + SceNpClansError leaveClan(np::np_handler& nph, SceNpClanId clanId); + + SceNpClansError updateMemberInfo(np::np_handler& nph, SceNpClanId clanId, SceNpClansUpdatableMemberInfo* info); + SceNpClansError updateClanInfo(np::np_handler& nph, SceNpClanId clanId, SceNpClansUpdatableClanInfo* info); + + SceNpClansError kickMember(np::np_handler& nph, SceNpClanId clanId, SceNpId npId, SceNpClansMessage* message); + SceNpClansError changeMemberRole(np::np_handler& nph, SceNpClanId clanId, SceNpId npId, SceNpClansMemberRole role); + + SceNpClansError retrieveAnnouncements(np::np_handler& nph, SceNpClanId clanId, SceNpClansPagingRequest* paging, SceNpClansMessageEntry* announcements, SceNpClansPagingResult* pageResult); + SceNpClansError postAnnouncement(np::np_handler& nph, SceNpClanId clanId, SceNpClansMessage* announcement, SceNpClansMessageData* data, u32 duration, SceNpClansMessageId* announcementId); + SceNpClansError deleteAnnouncement(np::np_handler& nph, SceNpClanId clanId, SceNpClansMessageId announcementId); + }; +} // namespace clan + +struct sce_np_clans_manager +{ + atomic_t is_initialized = false; + clan::clan_client* client; +}; \ No newline at end of file