diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 83e8a28398..defa1ef670 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -442,6 +442,8 @@ target_sources(rpcs3_emu PRIVATE NP/np_requests.cpp NP/signaling_handler.cpp NP/np_structs_extra.cpp + NP/clans_client.cpp + NP/clans_config.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..03c6ce4da3 100644 --- a/rpcs3/Emu/Cell/Modules/sceNpClans.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNpClans.cpp @@ -1,10 +1,14 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" #include "Emu/IdManager.h" +#include "Emu/NP/np_handler.h" +#include "Emu/NP/clans_client.h" #include "sceNp.h" +#include #include "sceNpClans.h" + LOG_CHANNEL(sceNpClans); template<> @@ -14,6 +18,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); @@ -94,6 +99,9 @@ error_code sceNpClansInit(vm::cptr commId, vm::cptr client = std::make_shared(); + clans_manager.client = client; clans_manager.is_initialized = true; return CELL_OK; @@ -110,6 +118,7 @@ error_code sceNpClansTerm() return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + clans_manager.client.reset(); clans_manager.is_initialized = false; return CELL_OK; @@ -117,7 +126,7 @@ error_code sceNpClansTerm() error_code sceNpClansCreateRequest(vm::ptr handle, u64 flags) { - sceNpClans.todo("sceNpClansCreateRequest(handle=*0x%x, flags=0x%llx)", handle, flags); + sceNpClans.warning("sceNpClansCreateRequest(handle=*0x%x, flags=0x%x)", handle, flags); if (!g_fxo->get().is_initialized) { @@ -134,34 +143,57 @@ error_code sceNpClansCreateRequest(vm::ptr handle, u64 return SCE_NP_CLANS_ERROR_NOT_SUPPORTED; } + auto& clans_manager = g_fxo->get(); + + s32 reqId = 0; + SceNpClansError res = clans_manager.client->createRequest(&reqId); + if (res != SCE_NP_CLANS_SUCCESS) + { + return res; + } + + *handle = reqId; + return CELL_OK; } -error_code sceNpClansDestroyRequest(vm::ptr handle) +error_code sceNpClansDestroyRequest(SceNpClansRequestHandle handle) { - sceNpClans.todo("sceNpClansDestroyRequest(handle=*0x%x)", handle); + sceNpClans.warning("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(handle); + if (res != SCE_NP_CLANS_SUCCESS) + { + return res; + } + return CELL_OK; } -error_code sceNpClansAbortRequest(vm::ptr handle) +error_code sceNpClansAbortRequest(SceNpClansRequestHandle handle) { - sceNpClans.todo("sceNpClansAbortRequest(handle=*0x%x)", handle); + sceNpClans.warning("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(handle); + return CELL_OK; } -error_code sceNpClansCreateClan(vm::ptr handle, vm::cptr name, vm::cptr tag, vm::ptr clanId) +// TODO: requires NpCommerce2 +error_code sceNpClansCreateClan(SceNpClansRequestHandle 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,7 +215,9 @@ error_code sceNpClansCreateClan(vm::ptr handle, vm::cpt return CELL_OK; } -error_code sceNpClansDisbandClan(vm::ptr handle, SceNpClanId clanId) +// TODO: should probably not be implemented on RPCS3 until `CreateClan` is, +// to not let people disband a clan by accident +error_code sceNpClansDisbandClan(SceNpClansRequestHandle handle, SceNpClanId clanId) { sceNpClans.todo("sceNpClansDisbandClan(handle=*0x%x, clanId=*0x%x)", handle, clanId); @@ -192,19 +226,22 @@ 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) +error_code sceNpClansGetClanList(SceNpClansRequestHandle 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); + sceNpClans.warning("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,10 +254,35 @@ error_code sceNpClansGetClanList(vm::ptr handle, vm::cp } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansPagingRequest host_paging = {}; + if (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, handle, &host_paging, host_clanList, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + if (clanList && host_pageResult.count > 0) + { + std::memcpy(clanList.get_ptr(), host_clanList, sizeof(SceNpClansEntry) * host_pageResult.count); + } + std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + return CELL_OK; } -error_code sceNpClansGetClanListByNpId(vm::ptr handle, vm::cptr paging, vm::cptr npid, vm::ptr clanList, vm::ptr pageResult) +// TODO: seems to not be needed, even by the PS3..? +error_code sceNpClansGetClanListByNpId(SceNpClansRequestHandle 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,7 +307,8 @@ error_code sceNpClansGetClanListByNpId(vm::ptr handle, return CELL_OK; } -error_code sceNpClansSearchByProfile(vm::ptr handle, vm::cptr paging, vm::cptr search, vm::ptr results, vm::ptr pageResult) +// TODO: seems to not be needed, even by the PS3..? +error_code sceNpClansSearchByProfile(SceNpClansRequestHandle 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); @@ -270,9 +333,9 @@ error_code sceNpClansSearchByProfile(vm::ptr handle, vm return CELL_OK; } -error_code sceNpClansSearchByName(vm::ptr handle, vm::cptr paging, vm::cptr search, vm::ptr results, vm::ptr pageResult) +error_code sceNpClansSearchByName(SceNpClansRequestHandle 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); + sceNpClans.warning("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) { @@ -292,12 +355,38 @@ error_code sceNpClansSearchByName(vm::ptr handle, vm::c } } + auto& clans_manager = g_fxo->get(); + + SceNpClansPagingRequest host_paging = {}; + if (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(handle, &host_paging, &host_search, host_results, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + if (results && host_pageResult.count > 0) + { + 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) +error_code sceNpClansGetClanInfo(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::ptr info) { - sceNpClans.todo("sceNpClansGetClanInfo(handle=*0x%x, clanId=%d, info=*0x%x)", handle, clanId, info); + sceNpClans.warning("sceNpClansGetClanInfo(handle=*0x%x, clanId=*0x%x, info=*0x%x)", handle, clanId, info); if (!g_fxo->get().is_initialized) { @@ -310,12 +399,24 @@ 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(handle, 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) +error_code sceNpClansUpdateClanInfo(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr info) { - sceNpClans.todo("sceNpClansUpdateClanInfo(handle=*0x%x, clanId=%d, info=*0x%x)", handle, clanId, info); + sceNpClans.warning("sceNpClansUpdateClanInfo(handle=*0x%x, clanId=*0x%x, info=*0x%x)", handle, clanId, info); if (!g_fxo->get().is_initialized) { @@ -328,17 +429,24 @@ 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, handle, 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) +error_code sceNpClansGetMemberList(SceNpClansRequestHandle 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); + sceNpClans.warning("sceNpClansGetMemberList(handle=*0x%x, clanId=*0x%x, paging=*0x%x, status=0x%x, memList=*0x%x, pageResult=*0x%x)", handle, clanId, paging, status, memList, pageResult); if (!g_fxo->get().is_initialized) { @@ -358,12 +466,36 @@ error_code sceNpClansGetMemberList(vm::ptr handle, SceN } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansPagingRequest host_paging = {}; + if (paging) + { + std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + } + + SceNpClansMemberEntry host_memList_addr[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + SceNpClansPagingResult host_pageResult = {}; + + SceNpClansError ret = clans_manager.client->getMemberList(nph, handle, clanId, &host_paging, status, host_memList_addr, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + if (memList && host_pageResult.count > 0) + { + 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) +error_code sceNpClansGetMemberInfo(SceNpClansRequestHandle 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); + sceNpClans.warning("sceNpClansGetMemberInfo(handle=*0x%x, clanId=*0x%x, npid=*0x%x, memInfo=*0x%x)", handle, clanId, npid, memInfo); if (!g_fxo->get().is_initialized) { @@ -375,12 +507,28 @@ 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, handle, 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) +error_code sceNpClansUpdateMemberInfo(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr info) { - sceNpClans.todo("sceNpClansUpdateMemberInfo(handle=*0x%x, clanId=%d, memInfo=*0x%x)", handle, clanId, info); + sceNpClans.warning("sceNpClansUpdateMemberInfo(handle=*0x%x, clanId=*0x%x, info=*0x%x)", handle, clanId, info); if (!g_fxo->get().is_initialized) { @@ -389,21 +537,27 @@ 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, handle, 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) +error_code sceNpClansChangeMemberRole(SceNpClansRequestHandle 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); + sceNpClans.warning("sceNpClansChangeMemberRole(handle=*0x%x, clanId=*0x%x, npid=*0x%x, role=0x%x)", handle, clanId, npid, role); if (!g_fxo->get().is_initialized) { @@ -415,10 +569,23 @@ 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, handle, clanId, host_npid, static_cast(role)); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansGetAutoAcceptStatus(vm::ptr handle, SceNpClanId clanId, vm::ptr enable) +// TODO: no struct currently implements `autoAccept` as a field +error_code sceNpClansGetAutoAcceptStatus(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::ptr enable) { sceNpClans.todo("sceNpClansGetAutoAcceptStatus(handle=*0x%x, clanId=%d, enable=*0x%x)", handle, clanId, enable); @@ -435,7 +602,8 @@ error_code sceNpClansGetAutoAcceptStatus(vm::ptr handle return CELL_OK; } -error_code sceNpClansUpdateAutoAcceptStatus(vm::ptr handle, SceNpClanId clanId, b8 enable) +// TODO: no struct currently implements `autoAccept` as a field +error_code sceNpClansUpdateAutoAcceptStatus(SceNpClansRequestHandle handle, SceNpClanId clanId, b8 enable) { sceNpClans.todo("sceNpClansUpdateAutoAcceptStatus(handle=*0x%x, clanId=%d, enable=%d)", handle, clanId, enable); @@ -447,33 +615,51 @@ error_code sceNpClansUpdateAutoAcceptStatus(vm::ptr han return CELL_OK; } -error_code sceNpClansJoinClan(vm::ptr handle, SceNpClanId clanId) +error_code sceNpClansJoinClan(SceNpClansRequestHandle handle, SceNpClanId clanId) { - sceNpClans.todo("sceNpClansJoinClan(handle=*0x%x, clanId=%d)", handle, clanId); + sceNpClans.warning("sceNpClansJoinClan(handle=*0x%x, clanId=*0x%x)", 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, handle, clanId); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansLeaveClan(vm::ptr handle, SceNpClanId clanId) +error_code sceNpClansLeaveClan(SceNpClansRequestHandle handle, SceNpClanId clanId) { - sceNpClans.todo("sceNpClansLeaveClan(handle=*0x%x, clanId=%d)", handle, clanId); + sceNpClans.warning("sceNpClansLeaveClan(handle=*0x%x, clanId=*0x%x)", 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, handle, 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) +error_code sceNpClansKickMember(SceNpClansRequestHandle 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); + sceNpClans.warning("sceNpClansKickMember(handle=*0x%x, clanId=*0x%x, npid=*0x%x, message=*0x%x)", handle, clanId, npid, message); if (!g_fxo->get().is_initialized) { @@ -493,12 +679,30 @@ 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, handle, 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) +error_code sceNpClansSendInvitation(SceNpClansRequestHandle 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); + sceNpClans.warning("sceNpClansSendInvitation(handle=*0x%x, clanId=*0x%x, npid=*0x%x, message=*0x%x)", handle, clanId, npid, message); if (!g_fxo->get().is_initialized) { @@ -518,12 +722,30 @@ 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, handle, 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) +error_code sceNpClansCancelInvitation(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr npid) { - sceNpClans.todo("sceNpClansCancelInvitation(handle=*0x%x, clanId=%d, npid=*0x%x)", handle, clanId, npid); + sceNpClans.warning("sceNpClansCancelInvitation(handle=*0x%x, clanId=*0x%x, npid=*0x%x)", handle, clanId, npid); if (!g_fxo->get().is_initialized) { @@ -535,12 +757,24 @@ 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, handle, 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) +error_code sceNpClansSendInvitationResponse(SceNpClansRequestHandle 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); + sceNpClans.warning("sceNpClansSendInvitationResponse(handle=*0x%x, clanId=*0x%x, message=*0x%x, accept=%d)", handle, clanId, message, accept); if (!g_fxo->get().is_initialized) { @@ -555,12 +789,32 @@ 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)); + } + + if (message) + { + std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + } + + SceNpClansError ret = clans_manager.client->sendInvitationResponse(nph, handle, 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) +error_code sceNpClansSendMembershipRequest(SceNpClansRequestHandle handle, u32 clanId, vm::cptr message) { - sceNpClans.todo("sceNpClansSendMembershipRequest(handle=*0x%x, clanId=%d, message=*0x%x)", handle, clanId, message); + sceNpClans.warning("sceNpClansSendMembershipRequest(handle=*0x%x, clanId=*0x%x, message=*0x%x)", handle, clanId, message); if (!g_fxo->get().is_initialized) { @@ -575,24 +829,49 @@ 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, handle, clanId, &host_message); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansCancelMembershipRequest(vm::ptr handle, SceNpClanId clanId) +error_code sceNpClansCancelMembershipRequest(SceNpClansRequestHandle handle, SceNpClanId clanId) { - sceNpClans.todo("sceNpClansCancelMembershipRequest(handle=*0x%x, clanId=%d)", handle, clanId); + sceNpClans.warning("sceNpClansCancelMembershipRequest(handle=*0x%x, clanId=*0x%x)", 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, handle, 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) +error_code sceNpClansSendMembershipResponse(SceNpClansRequestHandle 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); + sceNpClans.warning("sceNpClansSendMembershipResponse(handle=*0x%x, clanId=*0x%x, npid=*0x%x, message=*0x%x, allow=%d)", handle, clanId, npid, message, allow); if (!g_fxo->get().is_initialized) { @@ -612,12 +891,30 @@ 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, handle, 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) +error_code sceNpClansGetBlacklist(SceNpClansRequestHandle 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); + sceNpClans.warning("sceNpClansGetBlacklist(handle=*0x%x, clanId=*0x%x, paging=*0x%x, bl=*0x%x, pageResult=*0x%x)", handle, clanId, paging, bl, pageResult); if (!g_fxo->get().is_initialized) { @@ -637,53 +934,101 @@ error_code sceNpClansGetBlacklist(vm::ptr handle, SceNp } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansPagingRequest host_paging = {}; + if (paging) + { + std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + } + + SceNpClansBlacklistEntry host_blacklist[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + SceNpClansPagingResult host_pageResult = {}; + + SceNpClansError ret = clans_manager.client->getBlacklist(nph, handle, clanId, &host_paging, host_blacklist, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + if (bl && host_pageResult.count > 0) + { + std::memcpy(bl.get_ptr(), host_blacklist, 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) +error_code sceNpClansAddBlacklistEntry(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr member) { - sceNpClans.todo("sceNpClansAddBlacklistEntry(handle=*0x%x, clanId=%d, npid=*0x%x)", handle, clanId, npid); + sceNpClans.warning("sceNpClansAddBlacklistEntry(handle=*0x%x, clanId=*0x%x, member=*0x%x)", handle, clanId, member); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } - if (!npid) + if (!member) { return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_member = {}; + std::memcpy(&host_member, member.get_ptr(), sizeof(SceNpId)); + + SceNpClansError ret = clans_manager.client->addBlacklistEntry(nph, handle, clanId, host_member); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansRemoveBlacklistEntry(vm::ptr handle, SceNpClanId clanId, vm::cptr npid) +error_code sceNpClansRemoveBlacklistEntry(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr member) { - sceNpClans.todo("sceNpClansRemoveBlacklistEntry(handle=*0x%x, clanId=%d, npid=*0x%x)", handle, clanId, npid); + sceNpClans.warning("sceNpClansRemoveBlacklistEntry(handle=*0x%x, clanId=*0x%x, member=*0x%x)", handle, clanId, member); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } - if (!npid) + if (!member) { return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_member = {}; + std::memcpy(&host_member, member.get_ptr(), sizeof(SceNpId)); + + SceNpClansError ret = clans_manager.client->removeBlacklistEntry(nph, handle, clanId, host_member); + 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) +error_code sceNpClansRetrieveAnnouncements(SceNpClansRequestHandle 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); + sceNpClans.warning("sceNpClansRetrieveAnnouncements(handle=*0x%x, clanId=*0x%x, 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,12 +1041,36 @@ error_code sceNpClansRetrieveAnnouncements(vm::ptr hand } } + auto& clans_manager = g_fxo->get(); + auto& nph = g_fxo->get>(); + + SceNpClansPagingRequest host_paging = {}; + if (paging) + { + std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + } + + SceNpClansMessageEntry host_announcements[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + SceNpClansPagingResult host_pageResult = {}; + + SceNpClansError ret = clans_manager.client->retrieveAnnouncements(nph, handle, clanId, &host_paging, host_announcements, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + if (mlist && host_pageResult.count > 0) + { + std::memcpy(mlist.get_ptr(), host_announcements, 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) +error_code sceNpClansPostAnnouncement(SceNpClansRequestHandle 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); + sceNpClans.warning("sceNpClansPostAnnouncement(handle=*0x%x, clanId=*0x%x, message=*0x%x, data=*0x%x, duration=*0x%x, mId=*0x%x)", handle, clanId, message, data, duration, mId); if (!g_fxo->get().is_initialized) { @@ -713,32 +1082,57 @@ 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_announcement = {}; + std::memcpy(&host_announcement, message.get_ptr(), sizeof(SceNpClansMessage)); + + SceNpClansMessageData host_data = {}; + if (data) + { + std::memcpy(&host_data, data.get_ptr(), sizeof(SceNpClansMessageData)); + } + + SceNpClansMessageId host_announcementId = 0; + SceNpClansError ret = clans_manager.client->postAnnouncement(nph, handle, clanId, &host_announcement, &host_data, duration, &host_announcementId); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + *mId = host_announcementId; + return CELL_OK; } -error_code sceNpClansRemoveAnnouncement(vm::ptr handle, SceNpClanId clanId, SceNpClansMessageId mId) +error_code sceNpClansRemoveAnnouncement(SceNpClansRequestHandle handle, SceNpClanId clanId, SceNpClansMessageId mId) { - sceNpClans.todo("sceNpClansPostAnnouncement(handle=*0x%x, clanId=%d, mId=%d)", handle, clanId, mId); + sceNpClans.warning("sceNpClansRemoveAnnouncement(handle=*0x%x, clanId=*0x%x, mId=*0x%x)", 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, handle, clanId, mId); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansPostChallenge(vm::ptr handle, SceNpClanId clanId, SceNpClanId targetClan, vm::cptr message, vm::cptr data, u32 duration, vm::ptr mId) +error_code sceNpClansPostChallenge(SceNpClansRequestHandle handle, SceNpClanId clanId, SceNpClanId targetClan, vm::cptr message, vm::cptr data, u32 duration, vm::ptr mId) { sceNpClans.todo("sceNpClansPostChallenge(handle=*0x%x, clanId=%d, targetClan=%d, message=*0x%x, data=*0x%x, duration=%d, mId=*0x%x)", handle, clanId, targetClan, message, data, duration, mId); @@ -765,7 +1159,7 @@ error_code sceNpClansPostChallenge(vm::ptr handle, SceN return CELL_OK; } -error_code sceNpClansRetrievePostedChallenges(vm::ptr handle, SceNpClanId clanId, SceNpClanId targetClan, vm::cptr paging, vm::ptr mList, vm::ptr pageResult) +error_code sceNpClansRetrievePostedChallenges(SceNpClansRequestHandle handle, SceNpClanId clanId, SceNpClanId targetClan, vm::cptr paging, vm::ptr mList, vm::ptr pageResult) { sceNpClans.todo("sceNpClansRetrievePostedChallenges(handle=*0x%x, clanId=%d, targetClan=%d, paging=*0x%x, mList=*0x%x, pageResult=*0x%x)", handle, clanId, targetClan, paging, mList, pageResult); @@ -790,7 +1184,7 @@ error_code sceNpClansRetrievePostedChallenges(vm::ptr h return CELL_OK; } -error_code sceNpClansRemovePostedChallenge(vm::ptr handle, SceNpClanId clanId, SceNpClanId targetClan, SceNpClansMessageId mId) +error_code sceNpClansRemovePostedChallenge(SceNpClansRequestHandle handle, SceNpClanId clanId, SceNpClanId targetClan, SceNpClansMessageId mId) { sceNpClans.todo("sceNpClansRemovePostedChallenge(handle=*0x%x, clanId=%d, targetClan=%d, mId=%d)", handle, clanId, targetClan, mId); @@ -802,7 +1196,7 @@ error_code sceNpClansRemovePostedChallenge(vm::ptr hand return CELL_OK; } -error_code sceNpClansRetrieveChallenges(vm::ptr handle, SceNpClanId clanId, vm::cptr paging, vm::ptr mList, vm::ptr pageResult) +error_code sceNpClansRetrieveChallenges(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr paging, vm::ptr mList, vm::ptr pageResult) { sceNpClans.todo("sceNpClansRetrieveChallenges(handle=*0x%x, clanId=%d, paging=*0x%x, mList=*0x%x, pageResult=*0x%x)", handle, clanId, paging, mList, pageResult); diff --git a/rpcs3/Emu/Cell/Modules/sceNpClans.h b/rpcs3/Emu/Cell/Modules/sceNpClans.h index f0d1d54630..9b31c87d25 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, @@ -138,7 +140,7 @@ enum }; // Request handle for clan API -using SceNpClansRequestHandle = vm::ptr; +using SceNpClansRequestHandle = u32; // Paging request structure struct SceNpClansPagingRequest @@ -159,8 +161,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 +199,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 +207,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 +215,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 +235,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]; + char description[SCE_NP_CLANS_MEMBER_DESCRIPTION_MAX_LENGTH + 1]; b8 allowMsg; u8 reserved[3]; }; @@ -271,7 +273,7 @@ struct SceNpClansMessageEntry SceNpClansMessage message; SceNpClansMessageData data; SceNpId npid; - u8 reserved[4]; + SceNpClanId postedBy; }; // Blacklist entry structure @@ -280,10 +282,3 @@ struct SceNpClansBlacklistEntry SceNpId entry; SceNpId registeredBy; }; - -// fxm objects - -struct sce_np_clans_manager -{ - atomic_t is_initialized = false; -}; diff --git a/rpcs3/Emu/NP/clans_client.cpp b/rpcs3/Emu/NP/clans_client.cpp new file mode 100644 index 0000000000..2efcebbb7e --- /dev/null +++ b/rpcs3/Emu/NP/clans_client.cpp @@ -0,0 +1,1083 @@ +#include "Emu/Cell/Modules/sceNp.h" +#include "stdafx.h" + +#include + +// wolfssl uses old-style casts which break clang builds +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wextern-c-compat" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + +#include + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include +#include +#include + +#include "Emu/Cell/Modules/sceNpClans.h" +#include "Emu/NP/clans_client.h" +#include "Emu/NP/clans_config.h" +#include "Emu/NP/np_helpers.h" + +LOG_CHANNEL(clan_log, "clans"); + + +const char* REQ_TYPE_FUNC = "func"; +const char* REQ_TYPE_SEC = "sec"; + +constexpr const char JID_FORMAT[] = "%s@un.br.np.playstation.net"; + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](auto value) + { + switch (value) + { + case clan::ClanRequestType::FUNC: return "func"; + case clan::ClanRequestType::SEC: return "sec"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](auto value) + { + switch (value) + { + case clan::ClanManagerOperationType::VIEW: return "view"; + case clan::ClanManagerOperationType::UPDATE: return "update"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](auto value) + { + switch (value) + { + case clan::ClanSearchFilterOperator::Equal: return "eq"; + case clan::ClanSearchFilterOperator::NotEqual: return "ne"; + case clan::ClanSearchFilterOperator::GreaterThan: return "gt"; + case clan::ClanSearchFilterOperator::GreaterThanOrEqual: return "ge"; + case clan::ClanSearchFilterOperator::LessThan: return "lt"; + case clan::ClanSearchFilterOperator::LessThanOrEqual: return "le"; + case clan::ClanSearchFilterOperator::Like: return "lk"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](auto value) + { + switch (value) + { + case clan::ClanRequestAction::GetClanList: return "get_clan_list"; + case clan::ClanRequestAction::GetClanInfo: return "get_clan_info"; + case clan::ClanRequestAction::GetMemberInfo: return "get_member_info"; + case clan::ClanRequestAction::GetMemberList: return "get_member_list"; + case clan::ClanRequestAction::GetBlacklist: return "get_blacklist"; + case clan::ClanRequestAction::RecordBlacklistEntry: return "record_blacklist_entry"; + case clan::ClanRequestAction::DeleteBlacklistEntry: return "delete_blacklist_entry"; + case clan::ClanRequestAction::ClanSearch: return "clan_search"; + case clan::ClanRequestAction::RequestMembership: return "request_membership"; + case clan::ClanRequestAction::CancelRequestMembership: return "cancel_request_membership"; + case clan::ClanRequestAction::AcceptMembershipRequest: return "accept_membership_request"; + case clan::ClanRequestAction::DeclineMembershipRequest: return "decline_membership_request"; + case clan::ClanRequestAction::SendInvitation: return "send_invitation"; + case clan::ClanRequestAction::CancelInvitation: return "cancel_invitation"; + case clan::ClanRequestAction::AcceptInvitation: return "accept_invitation"; + case clan::ClanRequestAction::DeclineInvitation: return "decline_invitation"; + case clan::ClanRequestAction::UpdateMemberInfo: return "update_member_info"; + case clan::ClanRequestAction::UpdateClanInfo: return "update_clan_info"; + case clan::ClanRequestAction::JoinClan: return "join_clan"; + case clan::ClanRequestAction::LeaveClan: return "leave_clan"; + case clan::ClanRequestAction::KickMember: return "kick_member"; + case clan::ClanRequestAction::ChangeMemberRole: return "change_member_role"; + case clan::ClanRequestAction::RetrieveAnnouncements: return "retrieve_announcements"; + case clan::ClanRequestAction::PostAnnouncement: return "post_announcement"; + case clan::ClanRequestAction::DeleteAnnouncement: return "delete_announcement"; + } + + return unknown; + }); +} + +namespace clan +{ + struct curl_memory + { + char* response; + size_t size; + }; + + size_t clans_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; + } + + struct clan_request_ctx + { + clan_request_ctx() + { + curl = curl_easy_init(); + if (curl) + { + curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); + } + } + + ~clan_request_ctx() + { + if (curl) + { + curl_easy_cleanup(curl); + curl = nullptr; + } + } + + CURL* curl = nullptr; + + // TODO: this was arbitrarily chosen -- see if there's a real amount + static const u32 SCE_NP_CLANS_MAX_CTX_NUM = 16; + + static const u32 id_base = 0xA001; + static const u32 id_step = 1; + static const u32 id_count = SCE_NP_CLANS_MAX_CTX_NUM; + SAVESTATE_INIT_POS(55); + }; + + clans_client::clans_client() + { + g_cfg_clans.load(); + } + + clans_client::~clans_client() + { + idm::clear(); + } + + SceNpClansError clans_client::createRequest(s32* reqId) + { + const s32 id = idm::make(); + + if (id == id_manager::id_traits::invalid) + { + return SceNpClansError::SCE_NP_CLANS_ERROR_EXCEEDS_MAX; + } + + auto ctx = idm::get_unlocked(id); + if (!ctx || !ctx->curl) + { + idm::remove(id); + return SceNpClansError::SCE_NP_CLANS_ERROR_NOT_INITIALIZED; + } + + *reqId = id; + + return SceNpClansError::SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::destroyRequest(u32 reqId) + { + if (idm::remove(reqId)) + return SceNpClansError::SCE_NP_CLANS_SUCCESS; + + return SceNpClansError::SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; + } + + SceNpClansError clans_client::sendRequest(u32 reqId, ClanRequestAction action, ClanManagerOperationType opType, pugi::xml_document* xmlBody, pugi::xml_document* outResponse) + { + auto ctx = idm::get_unlocked(reqId); + + if (!ctx || !ctx->curl) + return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; + + CURL* curl = ctx->curl; + + ClanRequestType reqType = ClanRequestType::FUNC; + pugi::xml_node clan = xmlBody->child("clan"); + if (clan && clan.child("ticket")) + { + reqType = ClanRequestType::SEC; + } + + std::string host = g_cfg_clans.get_host(); + std::string protocol = g_cfg_clans.get_use_https() ? "https" : "http"; + std::string url = fmt::format("%s://%s/clan_manager_%s/%s/%s", protocol, 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_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()); + + CURLcode 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 xml_res = outResponse->load_string(response_buffer.data()); + if (!xml_res) + { + clan_log.error("XML parsing failed: %s", xml_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 clans_client::getClanTicket(np::np_handler& nph) + { + // Pretend we failed to get a ticket if the emulator isn't + // connected to RPCN. + if (nph.get_psn_status() != SCE_NP_MANAGER_STATUS_ONLINE) + return ""; + + 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; + + // Use the cached ticket if available + np::ticket ticket = nph.get_clan_ticket(); + if (ticket.empty()) + { + // If not cached, request a new ticket + nph.req_ticket(0x00020001, &npid, service_id, cookie, cookie_size, entitlement_id, consumed_count); + ticket = nph.get_clan_ticket(); + + // If still empty, return error + 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 clans_client::getClanList(np::np_handler& nph, u32 reqId, SceNpClansPagingRequest* paging, SceNpClansEntry* clanList, SceNpClansPagingResult* pageResult) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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("start").text().set(paging->startPos); + clan.append_child("max").text().set(paging->max); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clanRes = sendRequest(reqId, 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)}; + + strcpy_trunc(entry.info.name, name_str); + strcpy_trunc(entry.info.tag, tag_str); + + clanList[i] = entry; + i++; + } + + *pageResult = SceNpClansPagingResult{ + .count = results_count, + .total = total_count}; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::getClanInfo(u32 reqId, 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(reqId, 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 = "", + }}; + + strcpy_trunc(clanInfo->info.name, name_str); + strcpy_trunc(clanInfo->info.tag, tag_str); + strcpy_trunc(clanInfo->updatable.description, description_str); + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::getMemberInfo(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpId npId, SceNpClansMemberEntry* memInfo) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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 = fmt::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(reqId, 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(); + std::string username = fmt::split(npid_str, {"@"})[0]; + + SceNpId npid; + if (!strcmp(username.c_str(), nph.get_npid().handle.data)) + { + npid = nph.get_npid(); + } + else + { + np::string_to_npid(npid_str, npid); + } + + 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(); + + *memInfo = SceNpClansMemberEntry + { + .npid = npid, + .role = static_cast(role_int), + .status = static_cast(status_int), + .updatable = SceNpClansUpdatableMemberInfo{ + .description = "", + } + }; + + strcpy_trunc(memInfo->updatable.description, description_str); + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::getMemberList(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpClansPagingRequest* paging, SceNpClansMemberStatus /*status*/, SceNpClansMemberEntry* memList, SceNpClansPagingResult* pageResult) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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(reqId, 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(); + std::string username = fmt::split(npid_str, {"@"})[0]; + + SceNpId npid; + if (!strcmp(username.c_str(), nph.get_npid().handle.data)) + { + npid = nph.get_npid(); + } + else + { + np::string_to_npid(npid_str, npid); + } + + 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(); + + SceNpClansMemberEntry entry = SceNpClansMemberEntry + { + .npid = npid, + .role = static_cast(role_int), + .status = static_cast(status_int), + }; + + strcpy_trunc(entry.updatable.description, description_str); + + memList[i] = entry; + i++; + } + + *pageResult = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::getBlacklist(np::np_handler& nph, u32 reqId, 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(reqId, 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(); + + SceNpId npid = {}; + np::string_to_npid(npid_str.c_str(), npid); + + SceNpClansBlacklistEntry entry = SceNpClansBlacklistEntry + { + .entry = npid, + }; + + bl[i] = entry; + i++; + } + + *pageResult = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::addBlacklistEntry(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpId npId) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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 = fmt::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(reqId, ClanRequestAction::RecordBlacklistEntry, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::removeBlacklistEntry(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpId npId) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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 = fmt::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(reqId, ClanRequestAction::DeleteBlacklistEntry, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::clanSearch(u32 reqId, 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 = fmt::format("%s", 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(reqId, 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}, + }; + + strcpy_trunc(entry.name, name_str); + strcpy_trunc(entry.tag, tag_str); + + clanList[i] = entry; + i++; + } + + *pageResult = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::requestMembership(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpClansMessage* /*message*/) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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(reqId, ClanRequestAction::RequestMembership, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::cancelRequestMembership(np::np_handler& nph, u32 reqId, SceNpClanId clanId) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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(reqId, ClanRequestAction::CancelRequestMembership, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::sendMembershipResponse(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpId npId, SceNpClansMessage* /*message*/, b8 allow) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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 = fmt::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(reqId, allow ? ClanRequestAction::AcceptMembershipRequest : ClanRequestAction::DeclineMembershipRequest, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::sendInvitation(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpId npId, SceNpClansMessage* /*message*/) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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 = fmt::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(reqId, ClanRequestAction::SendInvitation, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::cancelInvitation(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpId npId) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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 = fmt::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(reqId, ClanRequestAction::CancelInvitation, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::sendInvitationResponse(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpClansMessage* /*message*/, b8 accept) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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(reqId, accept ? ClanRequestAction::AcceptInvitation : ClanRequestAction::DeclineInvitation, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::updateMemberInfo(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpClansUpdatableMemberInfo* info) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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(reqId, ClanRequestAction::UpdateMemberInfo, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::updateClanInfo(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpClansUpdatableClanInfo* info) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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(reqId, ClanRequestAction::UpdateClanInfo, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::joinClan(np::np_handler& nph, u32 reqId, SceNpClanId clanId) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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(reqId, ClanRequestAction::JoinClan, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::leaveClan(np::np_handler& nph, u32 reqId, SceNpClanId clanId) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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(reqId, ClanRequestAction::LeaveClan, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::kickMember(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpId npId, SceNpClansMessage* /*message*/) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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 = fmt::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(reqId, ClanRequestAction::KickMember, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::changeMemberRole(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpId npId, SceNpClansMemberRole role) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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 = fmt::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(reqId, ClanRequestAction::ChangeMemberRole, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::retrieveAnnouncements(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpClansPagingRequest* paging, SceNpClansMessageEntry* announcements, SceNpClansPagingResult* pageResult) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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(reqId, 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(); + + SceNpId npid; + std::string username = fmt::split(npid_str, {"@"})[0]; + + if (!strcmp(username.c_str(), nph.get_npid().handle.data)) + { + npid = nph.get_npid(); + } + else + { + np::string_to_npid(npid_str, npid); + } + + // TODO: implement `binData` and `fromId` + + SceNpClansMessageEntry entry = SceNpClansMessageEntry + { + .mId = msgId, + .message = SceNpClansMessage { + .subject = "", + .body = "", + }, + .npid = npid, + .postedBy = clanId, + }; + + strcpy_trunc(entry.message.subject, subject_str); + strcpy_trunc(entry.message.body, msg_str); + + announcements[i] = entry; + i++; + } + + *pageResult = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::postAnnouncement(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpClansMessage* announcement, SceNpClansMessageData* /*data*/, u32 duration, SceNpClansMessageId* msgId) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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(reqId, 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 clans_client::deleteAnnouncement(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpClansMessageId announcementId) + { + std::string ticket = getClanTicket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + 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(reqId, ClanRequestAction::DeleteAnnouncement, ClanManagerOperationType::UPDATE, &doc, &response); + } +} +#pragma endregion diff --git a/rpcs3/Emu/NP/clans_client.h b/rpcs3/Emu/NP/clans_client.h new file mode 100644 index 0000000000..15238c4caf --- /dev/null +++ b/rpcs3/Emu/NP/clans_client.h @@ -0,0 +1,124 @@ +#pragma once + +#include +#include + +#include +#include +#include + +inline const char* CLANS_ENTITLEMENT_ID = "NPWR00432_00"; +inline const char* CLANS_SERVICE_ID = "IV0001-NPXS01001_00"; + +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 clans_client + { + private: + + + static size_t curlWriteCallback(void* data, size_t size, size_t nmemb, void* clientp); + SceNpClansError sendRequest(u32 reqId, 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: + clans_client(); + ~clans_client(); + + SceNpClansError createRequest(s32* reqId); + SceNpClansError destroyRequest(u32 reqId); + + SceNpClansError clanSearch(u32 reqId, SceNpClansPagingRequest* paging, SceNpClansSearchableName* search, SceNpClansClanBasicInfo* clanList, SceNpClansPagingResult* pageResult); + + SceNpClansError getClanList(np::np_handler& nph, u32 reqId, SceNpClansPagingRequest* paging, SceNpClansEntry* clanList, SceNpClansPagingResult* pageResult); + SceNpClansError getClanInfo(u32 reqId, SceNpClanId clanId, SceNpClansClanInfo* clanInfo); + + SceNpClansError getMemberInfo(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpId npId, SceNpClansMemberEntry* memInfo); + SceNpClansError getMemberList(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpClansPagingRequest* paging, SceNpClansMemberStatus status, SceNpClansMemberEntry* memList, SceNpClansPagingResult* pageResult); + + SceNpClansError getBlacklist(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpClansPagingRequest* paging, SceNpClansBlacklistEntry* bl, SceNpClansPagingResult* pageResult); + SceNpClansError addBlacklistEntry(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpId npId); + SceNpClansError removeBlacklistEntry(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpId npId); + + SceNpClansError requestMembership(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpClansMessage* message); + SceNpClansError cancelRequestMembership(np::np_handler& nph, u32 reqId, SceNpClanId clanId); + SceNpClansError sendMembershipResponse(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpId npId, SceNpClansMessage* message, b8 allow); + + SceNpClansError sendInvitation(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpId npId, SceNpClansMessage* message); + SceNpClansError cancelInvitation(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpId npId); + SceNpClansError sendInvitationResponse(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpClansMessage* message, b8 accept); + + SceNpClansError joinClan(np::np_handler& nph, u32 reqId, SceNpClanId clanId); + SceNpClansError leaveClan(np::np_handler& nph, u32 reqId, SceNpClanId clanId); + + SceNpClansError updateMemberInfo(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpClansUpdatableMemberInfo* info); + SceNpClansError updateClanInfo(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpClansUpdatableClanInfo* info); + + SceNpClansError kickMember(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpId npId, SceNpClansMessage* message); + SceNpClansError changeMemberRole(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpId npId, SceNpClansMemberRole role); + + SceNpClansError retrieveAnnouncements(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpClansPagingRequest* paging, SceNpClansMessageEntry* announcements, SceNpClansPagingResult* pageResult); + SceNpClansError postAnnouncement(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpClansMessage* announcement, SceNpClansMessageData* data, u32 duration, SceNpClansMessageId* announcementId); + SceNpClansError deleteAnnouncement(np::np_handler& nph, u32 reqId, SceNpClanId clanId, SceNpClansMessageId announcementId); + }; +} // namespace clan + +struct sce_np_clans_manager +{ + atomic_t is_initialized = false; + std::shared_ptr client; +}; diff --git a/rpcs3/Emu/NP/clans_config.cpp b/rpcs3/Emu/NP/clans_config.cpp new file mode 100644 index 0000000000..08fd09c4b2 --- /dev/null +++ b/rpcs3/Emu/NP/clans_config.cpp @@ -0,0 +1,154 @@ +#include "Utilities/StrUtil.h" +#include "stdafx.h" +#include "clans_config.h" +#include "Utilities/File.h" + +cfg_clans g_cfg_clans; + +LOG_CHANNEL(clans_config_log, "clans_config"); + +void cfg_clans::load() +{ + const std::string path = cfg_clans::get_path(); + + fs::file cfg_file(path, fs::read); + if (cfg_file) + { + clans_config_log.notice("Loading Clans config. Path: %s", path); + from_string(cfg_file.to_string()); + } + else + { + clans_config_log.notice("Clans config missing. Using default settings. Path: %s", path); + from_default(); + } +} + +void cfg_clans::save() const +{ +#ifdef _WIN32 + const std::string path_to_cfg = fs::get_config_dir(true); + if (!fs::create_path(path_to_cfg)) + { + clans_config_log.error("Could not create path: %s", path_to_cfg); + } +#endif + + const std::string path = cfg_clans::get_path(); + + if (!cfg::node::save(path)) + { + clans_config_log.error("Could not save config: %s (error=%s)", path, fs::g_tls_error); + } +} + +std::string cfg_clans::get_path() +{ + return fs::get_config_dir(true) + "clans.yml"; +} + +std::string cfg_clans::get_host() const +{ + return host.to_string(); +} + +bool cfg_clans::get_use_https() const +{ + return use_https.get(); +} + +std::vector> cfg_clans::get_hosts() +{ + std::vector> vec_hosts; + const std::string host_str = hosts.to_string(); + const auto hosts_list = fmt::split_sv(host_str, {"|||"}); + + for (const auto& cur_host : hosts_list) + { + const auto desc_and_host = fmt::split(cur_host, {"|"}); + if (desc_and_host.size() != 2) + { + clans_config_log.error("Invalid host in the list of hosts: %s", cur_host); + continue; + } + vec_hosts.push_back(std::make_pair(std::move(desc_and_host[0]), std::move(desc_and_host[1]))); + } + + if (vec_hosts.empty()) + { + hosts.from_default(); + save(); + return get_hosts(); + } + + return vec_hosts; +} + +void cfg_clans::set_host(std::string_view host) +{ + this->host.from_string(host); +} + +void cfg_clans::set_use_https(bool use_https) +{ + this->use_https.set(use_https); +} + +void cfg_clans::set_hosts(const std::vector>& vec_hosts) +{ + std::string final_string; + for (const auto& [cur_desc, cur_host] : vec_hosts) + { + fmt::append(final_string, "%s|%s|||", cur_desc, cur_host); + } + + if (final_string.empty()) + { + hosts.from_default(); + return; + } + + final_string.resize(final_string.size() - 3); + hosts.from_string(final_string); +} + +bool cfg_clans::add_host(std::string_view new_description, std::string_view new_host) +{ + auto cur_hosts = get_hosts(); + + for (const auto& [cur_desc, cur_host] : cur_hosts) + { + if (cur_desc == new_description && cur_host == new_host) + return false; + } + + cur_hosts.push_back(std::make_pair(std::string(new_description), std::string(new_host))); + set_hosts(cur_hosts); + + return true; +} + +bool cfg_clans::del_host(std::string_view del_description, std::string_view del_host) +{ + // Do not delete default servers + const auto def_desc_and_host = fmt::split_sv(hosts.def, {"|"}); + ensure(def_desc_and_host.size() == 2); + if (del_description == def_desc_and_host[0] && del_host == def_desc_and_host[1]) + { + return true; + } + + auto cur_hosts = get_hosts(); + + for (auto it = cur_hosts.begin(); it != cur_hosts.end(); it++) + { + if (it->first == del_description && it->second == del_host) + { + cur_hosts.erase(it); + set_hosts(cur_hosts); + return true; + } + } + + return false; +} diff --git a/rpcs3/Emu/NP/clans_config.h b/rpcs3/Emu/NP/clans_config.h new file mode 100644 index 0000000000..96dcde497d --- /dev/null +++ b/rpcs3/Emu/NP/clans_config.h @@ -0,0 +1,29 @@ +#pragma once + +#include "Utilities/Config.h" + +struct cfg_clans : cfg::node +{ + cfg::uint32 version{this, "Version", 1}; + cfg::string host{this, "Host", "clans.rpcs3.net"}; + cfg::string hosts{this, "Hosts", "Official Clans Server|clans.rpcs3.net"}; + cfg::_bool use_https{this, "Use HTTPS", true}; + + void load(); + void save() const; + + std::string get_host() const; + bool get_use_https() const; + std::vector> get_hosts(); + + void set_host(std::string_view host); + void set_use_https(bool use_https); + bool add_host(std::string_view description, std::string_view host); + bool del_host(std::string_view description, std::string_view host); + +private: + static std::string get_path(); + void set_hosts(const std::vector>& vec_hosts); +}; + +extern cfg_clans g_cfg_clans; diff --git a/rpcs3/Emu/NP/np_handler.cpp b/rpcs3/Emu/NP/np_handler.cpp index 30eac7f470..4aa24a954d 100644 --- a/rpcs3/Emu/NP/np_handler.cpp +++ b/rpcs3/Emu/NP/np_handler.cpp @@ -239,6 +239,25 @@ namespace np return true; } + std::string ticket::get_service_id() const + { + if (!parse_success) + { + return ""; + } + + const auto& node = nodes[0].data.data_nodes[8]; + if (node.len != SCE_NP_SERVICE_ID_SIZE) + { + return ""; + } + + // Trim null characters + const auto& vec = node.data.data_vec; + auto it = std::find(vec.begin(), vec.end(), 0); + return std::string(vec.begin(), it); + } + std::optional ticket::parse_node(std::size_t index) const { if ((index + MIN_TICKET_DATA_SIZE) > size()) @@ -1345,6 +1364,19 @@ namespace np return history; } + ticket np_handler::get_clan_ticket() + { + clan_ticket_ready.wait(false, atomic_wait_timeout{60'000'000'000}); // 60 seconds + + if (!clan_ticket_ready.load()) + { + rpcn_log.error("Failed to get clan ticket within timeout."); + return ticket{}; + } + + return clan_ticket; + } + constexpr usz MAX_HISTORY_ENTRIES = 200; void np_handler::add_player_to_history(const SceNpId* npid, const char* description) diff --git a/rpcs3/Emu/NP/np_handler.h b/rpcs3/Emu/NP/np_handler.h index c7a086f224..d2113688ce 100644 --- a/rpcs3/Emu/NP/np_handler.h +++ b/rpcs3/Emu/NP/np_handler.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "Emu/Memory/vm_ptr.h" #include "Emu/Cell/Modules/sceNp.h" @@ -69,6 +70,7 @@ namespace np bool empty() const; bool get_value(s32 param_id, vm::ptr param) const; + std::string get_service_id() const; private: std::optional parse_node(std::size_t index) const; @@ -253,6 +255,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 ticket& get_ticket() const; + ticket get_clan_ticket(); void add_player_to_history(const SceNpId* npid, const char* description); u32 add_players_to_history(const SceNpId* npids, const char* description, u32 count); u32 get_players_history_count(u32 options); @@ -415,6 +418,10 @@ namespace np ticket current_ticket; + // Clan ticket + atomic_t clan_ticket_ready = 0; + ticket clan_ticket; + // IP & DNS info std::string hostname = "localhost"; std::array ether_address{}; diff --git a/rpcs3/Emu/NP/np_requests.cpp b/rpcs3/Emu/NP/np_requests.cpp index 23e18f1353..d05baa6754 100644 --- a/rpcs3/Emu/NP/np_requests.cpp +++ b/rpcs3/Emu/NP/np_requests.cpp @@ -1,8 +1,9 @@ +#include "stdafx.h" #include "Emu/Cell/Modules/sceNp.h" #include "Emu/Cell/Modules/sceNp2.h" +#include "Emu/NP/clans_client.h" #include "Emu/NP/rpcn_types.h" #include "Utilities/StrFmt.h" -#include "stdafx.h" #include "Emu/Cell/PPUCallback.h" #include "Emu/Cell/lv2/sys_sync.h" #include "Emu/system_config.h" @@ -863,7 +864,20 @@ namespace np auto ticket_raw = reply.get_rawdata(); ensure(!reply.is_error(), "Malformed reply to RequestTicket command"); - current_ticket = ticket(std::move(ticket_raw)); + auto incoming_ticket = ticket(std::move(ticket_raw)); + + // Clans: check if ticket belongs to the clan service. + // If so, hijack the ticket and cache it for future use. + if (incoming_ticket.get_service_id() == CLANS_SERVICE_ID) + { + clan_ticket = incoming_ticket; + clan_ticket_ready.store(true); + clan_ticket_ready.notify_all(); + + return; + } + + current_ticket = incoming_ticket; auto ticket_size = static_cast(current_ticket.size()); if (manager_cb) diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 437d79df8a..63f080c5c9 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -40,7 +40,7 @@ Use - ..\3rdparty\miniupnp\miniupnp\miniupnpc\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\flatbuffers\include;..\3rdparty\libusb\libusb\libusb;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\rtmidi\rtmidi;..\3rdparty\zlib\zlib;$(SolutionDir)build\lib\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(VULKAN_SDK)\Include;..\3rdparty\zstd\zstd\lib;$(SolutionDir)3rdparty\fusion\fusion\Fusion;$(SolutionDir)3rdparty\wolfssl\extra\win32;$(SolutionDir)3rdparty\libsdl-org\SDL\include;$(SolutionDir)3rdparty\glslang\glslang + ..\3rdparty\miniupnp\miniupnp\miniupnpc\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\flatbuffers\include;..\3rdparty\libusb\libusb\libusb;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\rtmidi\rtmidi;..\3rdparty\zlib\zlib;$(SolutionDir)build\lib\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(VULKAN_SDK)\Include;..\3rdparty\zstd\zstd\lib;$(SolutionDir)3rdparty\fusion\fusion\Fusion;$(SolutionDir)3rdparty\wolfssl\extra\win32;$(SolutionDir)3rdparty\libsdl-org\SDL\include;$(SolutionDir)3rdparty\glslang\glslang;$(SolutionDir)3rdparty\curl\curl\include MaxSpeed AL_LIBTYPE_STATIC;MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL3;ZLIB_CONST;WOLFSSL_USER_SETTINGS;%(PreprocessorDefinitions) AL_LIBTYPE_STATIC;MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL3;ZLIB_CONST;WOLFSSL_USER_SETTINGS;%(PreprocessorDefinitions) @@ -112,6 +112,7 @@ + @@ -188,6 +189,7 @@ + diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index b5ffb8ebd8..d8d5053179 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -406,6 +406,9 @@ true + + true + true @@ -697,6 +700,9 @@ true + + true + true @@ -807,6 +813,7 @@ + @@ -1488,6 +1495,16 @@ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" + $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing %(Identity)... diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index aa90e50cd0..2e9340b01b 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -864,6 +864,12 @@ Generated Files\Release + + Generated Files\Debug + + + Generated Files\Release + Generated Files\Debug @@ -909,6 +915,9 @@ Gui\rpcn + + Gui\clans + Gui\message dialog @@ -1699,6 +1708,9 @@ Gui\rpcn + + Gui\rpcn + Gui\message dialog diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index 43c0024905..98be856a25 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -8,6 +8,7 @@ add_library(rpcs3_ui STATIC camera_settings_dialog.cpp cg_disasm_window.cpp cheat_manager.cpp + clans_settings_dialog.cpp config_adapter.cpp config_checker.cpp curl_handle.cpp diff --git a/rpcs3/rpcs3qt/clans_settings_dialog.cpp b/rpcs3/rpcs3qt/clans_settings_dialog.cpp new file mode 100644 index 0000000000..9745d11294 --- /dev/null +++ b/rpcs3/rpcs3qt/clans_settings_dialog.cpp @@ -0,0 +1,187 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "clans_settings_dialog.h" +#include "Emu/NP/clans_config.h" + +clans_settings_dialog::clans_settings_dialog(QWidget* parent) + : QDialog(parent) +{ + g_cfg_clans.load(); + + setWindowTitle(tr("Clans Configuration")); + setObjectName("clans_settings_dialog"); + + QVBoxLayout* vbox_global = new QVBoxLayout(); + + QGroupBox* grp_server = new QGroupBox(tr("Server:")); + QVBoxLayout* vbox_server = new QVBoxLayout(); + + QHBoxLayout* hbox_lbl_combo = new QHBoxLayout(); + QLabel* lbl_server = new QLabel(tr("Server:")); + m_cbx_servers = new QComboBox(); + m_cbx_protocol = new QComboBox(); + + m_cbx_protocol->addItem("HTTPS"); + m_cbx_protocol->addItem("HTTP"); + m_cbx_protocol->setCurrentIndex(g_cfg_clans.get_use_https() ? 0 : 1); + + refresh_combobox(); + + hbox_lbl_combo->addWidget(lbl_server); + hbox_lbl_combo->addWidget(m_cbx_servers); + hbox_lbl_combo->addWidget(m_cbx_protocol); + + QHBoxLayout* hbox_buttons = new QHBoxLayout(); + QPushButton* btn_add_server = new QPushButton(tr("Add")); + QPushButton* btn_del_server = new QPushButton(tr("Del")); + hbox_buttons->addStretch(); + hbox_buttons->addWidget(btn_add_server); + hbox_buttons->addWidget(btn_del_server); + + vbox_server->addLayout(hbox_lbl_combo); + vbox_server->addLayout(hbox_buttons); + + grp_server->setLayout(vbox_server); + vbox_global->addWidget(grp_server); + + setLayout(vbox_global); + + connect(m_cbx_servers, &QComboBox::currentIndexChanged, this, [this](int index) + { + if (index < 0) + return; + + QVariant host = m_cbx_servers->itemData(index); + + if (!host.isValid() || !host.canConvert()) + return; + + g_cfg_clans.set_host(host.toString().toStdString()); + g_cfg_clans.save(); + }); + + connect(m_cbx_protocol, &QComboBox::currentIndexChanged, this, [this](int index) + { + if (index < 0) + return; + + g_cfg_clans.set_use_https(index == 0); + g_cfg_clans.save(); + }); + + connect(btn_add_server, &QAbstractButton::clicked, this, [this]() + { + clans_add_server_dialog dlg(this); + dlg.exec(); + const auto& new_server = dlg.get_new_server(); + if (new_server) + { + if (!g_cfg_clans.add_host(new_server->first, new_server->second)) + { + QMessageBox::critical(this, tr("Existing Server"), tr("You already have a server with this description & hostname in the list."), QMessageBox::Ok); + return; + } + + g_cfg_clans.save(); + refresh_combobox(); + } + }); + + connect(btn_del_server, &QAbstractButton::clicked, this, [this]() + { + const int index = m_cbx_servers->currentIndex(); + + if (index < 0) + return; + + const std::string desc = m_cbx_servers->itemText(index).toStdString(); + const std::string host = m_cbx_servers->itemData(index).toString().toStdString(); + + if (g_cfg_clans.del_host(desc, host)) + { + g_cfg_clans.save(); + refresh_combobox(); + } + else + { + QMessageBox::warning(this, tr("Cannot Delete"), tr("This server cannot be deleted."), QMessageBox::Ok); + } + }); +} + +void clans_settings_dialog::refresh_combobox() +{ + g_cfg_clans.load(); + const auto vec_hosts = g_cfg_clans.get_hosts(); + const auto cur_host = g_cfg_clans.get_host(); + int i = 0, index = 0; + + m_cbx_servers->clear(); + + for (const auto& [desc, host] : vec_hosts) + { + m_cbx_servers->addItem(QString::fromStdString(desc), QString::fromStdString(host)); + if (cur_host == host) + index = i; + + i++; + } + + m_cbx_servers->setCurrentIndex(index); +} + +clans_add_server_dialog::clans_add_server_dialog(QWidget* parent) + : QDialog(parent) +{ + setWindowTitle(tr("Clans: Add Server")); + setObjectName("clans_add_server_dialog"); + setMinimumSize(QSize(400, 200)); + + QVBoxLayout* vbox_global = new QVBoxLayout(); + + QLabel* lbl_description = new QLabel(tr("Description:")); + QLineEdit* edt_description = new QLineEdit(); + QLabel* lbl_host = new QLabel(tr("Host:")); + QLineEdit* edt_host = new QLineEdit(); + QDialogButtonBox* btn_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + vbox_global->addWidget(lbl_description); + vbox_global->addWidget(edt_description); + vbox_global->addWidget(lbl_host); + vbox_global->addWidget(edt_host); + vbox_global->addWidget(btn_box); + + setLayout(vbox_global); + + connect(btn_box, &QDialogButtonBox::accepted, this, [this, edt_description, edt_host]() + { + const QString description = edt_description->text(); + const QString host = edt_host->text(); + + if (description.isEmpty()) + { + QMessageBox::critical(this, tr("Missing Description!"), tr("You must enter a description!"), QMessageBox::Ok); + return; + } + if (host.isEmpty()) + { + QMessageBox::critical(this, tr("Missing Hostname!"), tr("You must enter a hostname for the server!"), QMessageBox::Ok); + return; + } + + m_new_server = std::make_pair(description.toStdString(), host.toStdString()); + QDialog::accept(); + }); + connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +const std::optional>& clans_add_server_dialog::get_new_server() const +{ + return m_new_server; +} diff --git a/rpcs3/rpcs3qt/clans_settings_dialog.h b/rpcs3/rpcs3qt/clans_settings_dialog.h new file mode 100644 index 0000000000..7de6105382 --- /dev/null +++ b/rpcs3/rpcs3qt/clans_settings_dialog.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +class clans_settings_dialog : public QDialog +{ + Q_OBJECT +public: + clans_settings_dialog(QWidget* parent = nullptr); + +private: + void refresh_combobox(); + +private: + QComboBox* m_cbx_servers = nullptr; + QComboBox* m_cbx_protocol = nullptr; +}; + +class clans_add_server_dialog : public QDialog +{ + Q_OBJECT +public: + clans_add_server_dialog(QWidget* parent = nullptr); + const std::optional>& get_new_server() const; + +private: + std::optional> m_new_server; +}; diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 80d71a44c2..122b8d6ff0 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -12,6 +12,7 @@ #include "log_frame.h" #include "settings_dialog.h" #include "rpcn_settings_dialog.h" +#include "clans_settings_dialog.h" #include "auto_pause_settings_dialog.h" #include "cg_disasm_window.h" #include "log_viewer.h" @@ -2991,6 +2992,18 @@ void main_window::CreateConnects() dlg.exec(); }); + connect(ui->confClansAct, &QAction::triggered, this, [this]() + { + if (!Emu.IsStopped()) + { + QMessageBox::critical(this, tr("Error: Emulation Running"), tr("You need to stop the emulator before editing Clans connection information!"), QMessageBox::Ok); + return; + } + + clans_settings_dialog dlg(this); + dlg.exec(); + }); + connect(ui->confIPCAct, &QAction::triggered, this, [this]() { ipc_settings_dialog dlg(this); diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index 72861a5d72..c9c8c0645d 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -282,6 +282,7 @@ + @@ -1252,6 +1253,14 @@ Configure RPCN + + + Clans + + + Configure Clans + + IPC