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/sceNp2.cpp b/rpcs3/Emu/Cell/Modules/sceNp2.cpp index 3c33375fa3..820fbdf1c0 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp2.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNp2.cpp @@ -1,4 +1,5 @@ #include "stdafx.h" +#include "Emu/Cell/ErrorCodes.h" #include "Emu/Cell/PPUModule.h" #include "Emu/IdManager.h" @@ -540,7 +541,9 @@ error_code sceNpMatching2GetClanLobbyId(SceNpMatching2ContextId ctxId, SceNpClan return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - return CELL_OK; + // Returning this rather than `CELL_OK` allows for games to + // not need Matching2 Clans support to connect, when Clans are enabled. + return SCE_NP_MATCHING2_SERVER_ERROR_SERVICE_UNAVAILABLE; } error_code sceNpMatching2GetLobbyMemberDataInternal( diff --git a/rpcs3/Emu/Cell/Modules/sceNpClans.cpp b/rpcs3/Emu/Cell/Modules/sceNpClans.cpp index 91d22079fe..64ac967d8e 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,36 +143,58 @@ 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->create_request(&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->destroy_request(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->destroy_request(handle); + return CELL_OK; } -error_code sceNpClansCreateClan(vm::ptr handle, vm::cptr name, vm::cptr tag, vm::ptr clanId) +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); + sceNpClans.warning("sceNpClansCreateClan(handle=*0x%x, name=%s, tag=%s, clanId=*0x%x)", handle, name, tag, clanId); if (!g_fxo->get().is_initialized) { @@ -180,31 +211,60 @@ error_code sceNpClansCreateClan(vm::ptr handle, vm::cpt return SCE_NP_CLANS_ERROR_EXCEEDS_MAX; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + std::string name_str; + vm::read_string(name.addr(), SCE_NP_CLANS_CLAN_NAME_MAX_LENGTH, name_str); + + std::string tag_str; + vm::read_string(tag.addr(), SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH, tag_str); + + SceNpClansError res = clans_manager.client->create_clan(nph, handle, name_str, tag_str, clanId); + if (res != SCE_NP_CLANS_SUCCESS) + { + return res; + } + return CELL_OK; } -error_code sceNpClansDisbandClan(vm::ptr handle, SceNpClanId clanId) +error_code sceNpClansDisbandClan(SceNpClansRequestHandle handle, SceNpClanId clanId) { - sceNpClans.todo("sceNpClansDisbandClan(handle=*0x%x, clanId=*0x%x)", handle, clanId); + sceNpClans.warning("sceNpClansDisbandClan(handle=*0x%x, clanId=*0x%x)", handle, clanId); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + if (!clanId) + { + return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; + } + + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansError res = clans_manager.client->disband_dlan(nph, handle, clanId); + if (res != SCE_NP_CLANS_SUCCESS) + { + return res; + } + 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 +277,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->get_clan_list(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 +330,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 +356,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 +378,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->clan_search(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 +422,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->get_clan_info(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 +452,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->update_clan_info(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 +489,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->get_member_list(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 +530,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->get_member_info(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 +560,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->update_member_info(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 +592,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->change_member_role(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 +625,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 +638,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->join_clan(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->leave_clan(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 +702,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->kick_member(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 +745,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->send_invitation(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 +780,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->cancel_invitation(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 +812,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->send_invitation_response(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 +852,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->request_membership(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->cancel_request_membership(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 +914,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->send_membership_response(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 +957,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->get_blacklist(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->add_blacklist_entry(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->remove_blacklist_entry(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 +1064,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->retrieve_announcements(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 +1105,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->post_announcement(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->delete_announcement(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 +1182,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 +1207,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 +1219,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..dd2d926d24 --- /dev/null +++ b/rpcs3/Emu/NP/clans_client.cpp @@ -0,0 +1,1143 @@ +#include "stdafx.h" + +#include +#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/sceNp.h" +#include "Emu/Cell/Modules/sceNpClans.h" +#include "Emu/NP/clans_client.h" +#include "Emu/NP/clans_config.h" +#include "Emu/NP/np_helpers.h" +#include "Emu/system_config.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::CreateClan: return "create_clan"; + case clan::ClanRequestAction::DisbandClan: return "disband_clan"; + 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::curl_write_callback(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::create_request(s32* req_id) + { + if (!g_cfg.net.clans_enabled) + { + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + } + + 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; + } + + *req_id = id; + + return SceNpClansError::SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::destroy_request(u32 req_id) + { + if (idm::remove(req_id)) + return SceNpClansError::SCE_NP_CLANS_SUCCESS; + + return SceNpClansError::SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; + } + + SceNpClansError clans_client::send_request(u32 req_id, ClanRequestAction action, ClanManagerOperationType op_type, pugi::xml_document* xml_body, pugi::xml_document* out_response) + { + auto ctx = idm::get_unlocked(req_id); + + if (!ctx || !ctx->curl) + return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; + + CURL* curl = ctx->curl; + + ClanRequestType req_type = ClanRequestType::FUNC; + pugi::xml_node clan = xml_body->child("clan"); + if (clan && clan.child("ticket")) + { + req_type = 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, op_type, req_type, action); + + std::ostringstream oss; + xml_body->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, curl_write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_buffer); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, err_buf); + + // WARN: This disables certificate verification! + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + + 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) + { + out_response = 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 = out_response->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 clan_result = out_response->child("clan"); + if (!clan_result) + return SCE_NP_CLANS_ERROR_BAD_RESPONSE; + + pugi::xml_attribute result = clan_result.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::get_clan_ticket(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 clan_ticket; + if (!nph.get_clan_ticket_ready()) + { + // If not cached, request a new ticket + nph.req_ticket(0x00020001, &npid, service_id, cookie, cookie_size, entitlement_id, consumed_count); + } + + clan_ticket = nph.get_clan_ticket(); + if (clan_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(clan_ticket.data(), clan_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::get_clan_list(np::np_handler& nph, u32 req_id, SceNpClansPagingRequest* paging, SceNpClansEntry* clan_list, SceNpClansPagingResult* page_result) + { + std::string ticket = get_clan_ticket(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 clan_res = send_request(req_id, ClanRequestAction::GetClanList, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node list = clan_result.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 clan_id = 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 = clan_id, + .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); + + clan_list[i] = entry; + i++; + } + + *page_result = SceNpClansPagingResult{ + .count = results_count, + .total = total_count}; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::get_clan_info(u32 req_id, SceNpClanId clan_id, SceNpClansClanInfo* clan_info) + { + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("id").text().set(clan_id); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetClanInfo, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node info = clan_result.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(); + + *clan_info = SceNpClansClanInfo{ + .info = SceNpClansClanBasicInfo{ + .clanId = clan_id, + .numMembers = members_int, + .name = "", + .tag = "", + }, + .updatable = SceNpClansUpdatableClanInfo{ + .description = "", + }}; + + strcpy_trunc(clan_info->info.name, name_str); + strcpy_trunc(clan_info->info.tag, tag_str); + strcpy_trunc(clan_info->updatable.description, description_str); + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::get_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMemberEntry* mem_info) + { + std::string ticket = get_clan_ticket(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(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetMemberInfo, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node info = clan_result.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(); + + *mem_info = SceNpClansMemberEntry + { + .npid = npid, + .role = static_cast(role_int), + .status = static_cast(status_int), + .updatable = SceNpClansUpdatableMemberInfo{ + .description = "", + } + }; + + strcpy_trunc(mem_info->updatable.description, description_str); + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::get_member_list(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansMemberStatus /*status*/, SceNpClansMemberEntry* mem_list, SceNpClansPagingResult* page_result) + { + std::string ticket = get_clan_ticket(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(clan_id); + clan.append_child("start").text().set(paging->startPos); + clan.append_child("max").text().set(paging->max); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetMemberList, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node list = clan_result.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); + + mem_list[i] = entry; + i++; + } + + *page_result = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::get_blacklist(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansBlacklistEntry* bl, SceNpClansPagingResult* page_result) + { + std::string ticket = get_clan_ticket(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(clan_id); + clan.append_child("start").text().set(paging->startPos); + clan.append_child("max").text().set(paging->max); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetBlacklist, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node list = clan_result.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++; + } + + *page_result = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::add_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id) + { + std::string ticket = get_clan_ticket(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(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::RecordBlacklistEntry, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::remove_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id) + { + std::string ticket = get_clan_ticket(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(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::DeleteBlacklistEntry, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::clan_search(u32 req_id, SceNpClansPagingRequest* paging, SceNpClansSearchableName* search, SceNpClansClanBasicInfo* clan_list, SceNpClansPagingResult* page_result) + { + 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 clan_res = send_request(req_id, ClanRequestAction::ClanSearch, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node list = clan_result.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 clan_id = 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 = clan_id, + .numMembers = members_int, + .name = "", + .tag = "", + .reserved = {0, 0}, + }; + + strcpy_trunc(entry.name, name_str); + strcpy_trunc(entry.tag, tag_str); + + clan_list[i] = entry; + i++; + } + + *page_result = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::create_clan(np::np_handler& nph, u32 req_id, std::string_view name, std::string_view tag, vm::ptr clan_id) + { + std::string ticket = get_clan_ticket(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("name").text().set(name.data()); + clan.append_child("tag").text().set(tag.data()); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::CreateClan, ClanManagerOperationType::UPDATE, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_attribute id = clan_result.attribute("id"); + uint32_t clan_id_int = id.as_uint(); + + *clan_id = clan_id_int; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::disband_dlan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id) + { + std::string ticket = get_clan_ticket(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(clan_id); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::DisbandClan, ClanManagerOperationType::UPDATE, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* /*message*/) + { + std::string ticket = get_clan_ticket(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(clan_id); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::RequestMembership, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::cancel_request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id) + { + std::string ticket = get_clan_ticket(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(clan_id); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::CancelRequestMembership, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::send_membership_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* /*message*/, b8 allow) + { + std::string ticket = get_clan_ticket(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(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, allow ? ClanRequestAction::AcceptMembershipRequest : ClanRequestAction::DeclineMembershipRequest, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::send_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* /*message*/) + { + std::string ticket = get_clan_ticket(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(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::SendInvitation, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::cancel_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id) + { + std::string ticket = get_clan_ticket(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(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::CancelInvitation, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::send_invitation_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* /*message*/, b8 accept) + { + std::string ticket = get_clan_ticket(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(clan_id); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, accept ? ClanRequestAction::AcceptInvitation : ClanRequestAction::DeclineInvitation, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::update_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansUpdatableMemberInfo* info) + { + std::string ticket = get_clan_ticket(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(clan_id); + + 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 bin_attr_1[SCE_NP_CLANS_MEMBER_BINARY_ATTRIBUTE1_MAX_SIZE * 2 + 1] = {0}; + uint32_t bin_attr_1_size = UINT32_MAX; + Base64_Encode_NoNl(info->binAttr1, info->binData1Size, bin_attr_1, &bin_attr_1_size); + + if (bin_attr_1_size == 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(bin_attr_1)); + + pugi::xml_node allow_msg = clan.append_child("allow-msg"); + allow_msg.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 send_request(req_id, ClanRequestAction::UpdateMemberInfo, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::update_clan_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansUpdatableClanInfo* info) + { + std::string ticket = get_clan_ticket(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(clan_id); + + // 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 send_request(req_id, ClanRequestAction::UpdateClanInfo, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::join_clan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id) + { + std::string ticket = get_clan_ticket(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(clan_id); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::JoinClan, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::leave_clan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id) + { + std::string ticket = get_clan_ticket(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(clan_id); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::LeaveClan, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::kick_member(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* /*message*/) + { + std::string ticket = get_clan_ticket(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(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::KickMember, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::change_member_role(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMemberRole role) + { + std::string ticket = get_clan_ticket(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(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_node role_node = clan.append_child("role"); + role_node.text().set(static_cast(role)); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::ChangeMemberRole, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::retrieve_announcements(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansMessageEntry* announcements, SceNpClansPagingResult* page_result) + { + std::string ticket = get_clan_ticket(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(clan_id); + clan.append_child("start").text().set(paging->startPos); + clan.append_child("max").text().set(paging->max); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::RetrieveAnnouncements, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node list = clan_result.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 msg_id = 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 = msg_id, + .message = SceNpClansMessage { + .subject = "", + .body = "", + }, + .npid = npid, + .postedBy = clan_id, + }; + + strcpy_trunc(entry.message.subject, subject_str); + strcpy_trunc(entry.message.body, msg_str); + + announcements[i] = entry; + i++; + } + + *page_result = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::post_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* announcement, SceNpClansMessageData* /*data*/, u32 duration, SceNpClansMessageId* msg_id) + { + std::string ticket = get_clan_ticket(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(clan_id); + + 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 expire_date = clan.append_child("expire-date"); + expire_date.text().set(duration); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::PostAnnouncement, ClanManagerOperationType::UPDATE, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node msg_id_node = clan_result.child("id"); + *msg_id = msg_id_node.text().as_uint(); + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::delete_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessageId announcement_id) + { + std::string ticket = get_clan_ticket(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(clan_id); + clan.append_child("msg-id").text().set(announcement_id); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, 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..68ed4a1cf1 --- /dev/null +++ b/rpcs3/Emu/NP/clans_client.h @@ -0,0 +1,129 @@ +#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, + CreateClan, + DisbandClan, + RequestMembership, + CancelRequestMembership, + AcceptMembershipRequest, + DeclineMembershipRequest, + SendInvitation, + CancelInvitation, + AcceptInvitation, + DeclineInvitation, + UpdateMemberInfo, + UpdateClanInfo, + JoinClan, + LeaveClan, + KickMember, + ChangeMemberRole, + RetrieveAnnouncements, + PostAnnouncement, + DeleteAnnouncement + }; + + class clans_client + { + private: + + + static size_t curl_write_callback(void* data, size_t size, size_t nmemb, void* clientp); + SceNpClansError send_request(u32 reqId, ClanRequestAction action, ClanManagerOperationType type, pugi::xml_document* xml_body, pugi::xml_document* out_response); + + /// @brief Forge and get a V2.1 Ticket for clan operations + std::string get_clan_ticket(np::np_handler& nph); + + public: + clans_client(); + ~clans_client(); + + SceNpClansError create_request(s32* req_id); + SceNpClansError destroy_request(u32 req_id); + + SceNpClansError clan_search(u32 req_id, SceNpClansPagingRequest* paging, SceNpClansSearchableName* search, SceNpClansClanBasicInfo* clan_list, SceNpClansPagingResult* page_result); + + SceNpClansError create_clan(np::np_handler& nph, u32 req_id, std::string_view name, std::string_view tag, vm::ptr clan_id); + SceNpClansError disband_dlan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id); + + SceNpClansError get_clan_list(np::np_handler& nph, u32 req_id, SceNpClansPagingRequest* paging, SceNpClansEntry* clan_list, SceNpClansPagingResult* page_result); + SceNpClansError get_clan_info(u32 req_id, SceNpClanId clan_id, SceNpClansClanInfo* clan_info); + + SceNpClansError get_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMemberEntry* mem_info); + SceNpClansError get_member_list(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansMemberStatus status, SceNpClansMemberEntry* mem_list, SceNpClansPagingResult* page_result); + + SceNpClansError get_blacklist(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansBlacklistEntry* bl, SceNpClansPagingResult* page_result); + SceNpClansError add_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id); + SceNpClansError remove_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id); + + SceNpClansError request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* message); + SceNpClansError cancel_request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id); + SceNpClansError send_membership_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* message, b8 allow); + + SceNpClansError send_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* message); + SceNpClansError cancel_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id); + SceNpClansError send_invitation_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* message, b8 accept); + + SceNpClansError join_clan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id); + SceNpClansError leave_clan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id); + + SceNpClansError update_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansUpdatableMemberInfo* info); + SceNpClansError update_clan_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansUpdatableClanInfo* info); + + SceNpClansError kick_member(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* message); + SceNpClansError change_member_role(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMemberRole role); + + SceNpClansError retrieve_announcements(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansMessageEntry* announcements, SceNpClansPagingResult* page_result); + SceNpClansError post_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* announcement, SceNpClansMessageData* data, u32 duration, SceNpClansMessageId* announcement_id); + SceNpClansError delete_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessageId announcement_id); + }; +} // 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 09770f0908..f76788287b 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()) @@ -1352,6 +1371,24 @@ namespace np return history; } + u32 np_handler::get_clan_ticket_ready() + { + return clan_ticket_ready.load(); + } + + ticket np_handler::get_clan_ticket() + { + clan_ticket_ready.wait(0, 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 999ae7cb86..d3c1213cc6 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,8 @@ 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; + u32 get_clan_ticket_ready(); + 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); @@ -406,6 +410,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 115348ac6d..23e3d7ca75 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(1); + 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/Emu/system_config.h b/rpcs3/Emu/system_config.h index e2ad7cbd8b..7c7b89fcf9 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -322,6 +322,7 @@ struct cfg_root : cfg::node cfg::_enum psn_status{this, "PSN status", np_psn_status::disabled}; cfg::string country{this, "PSN Country", "us"}; + cfg::_bool clans_enabled{this, "Clans Enabled", false}; } net{this}; struct node_savestate : cfg::node 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/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index 3007e0a1d9..cc8ab57b96 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -200,6 +200,7 @@ enum class emu_settings_type BindAddress, EnableUpnp, PSNCountry, + EnableClans, // System LicenseArea, @@ -411,6 +412,7 @@ inline static const std::map settings_location { emu_settings_type::BindAddress, { "Net", "Bind address"}}, { emu_settings_type::EnableUpnp, { "Net", "UPNP Enabled"}}, { emu_settings_type::PSNCountry, { "Net", "PSN Country"}}, + { emu_settings_type::EnableClans, { "Net", "Clans Enabled"}}, // System { emu_settings_type::LicenseArea, { "System", "License Area"}}, diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index c7516dc700..168d08a344 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 diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index 902da72cff..81e85546e9 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -1435,10 +1435,12 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std { ui->psnStatusBox->setCurrentIndex(find_item(ui->psnStatusBox, static_cast(g_cfg.net.psn_status.def))); ui->psnStatusBox->setEnabled(false); + ui->enable_clans->setEnabled(false); } else { ui->psnStatusBox->setEnabled(true); + ui->enable_clans->setEnabled(true); } }); m_emu_settings->EnhanceComboBox(ui->netStatusBox, emu_settings_type::InternetStatus); @@ -1447,6 +1449,9 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceComboBox(ui->psnStatusBox, emu_settings_type::PSNStatus); SubscribeTooltip(ui->gb_psnStatusBox, tooltips.settings.psn_status); + m_emu_settings->EnhanceCheckBox(ui->enable_clans, emu_settings_type::EnableClans); + SubscribeTooltip(ui->enable_clans, tooltips.settings.enable_clans); + settings_dialog::refresh_countrybox(); connect(ui->psnCountryBox, &QComboBox::currentIndexChanged, this, [this](int index) { diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index 4556ae9356..6a27f45be3 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -2319,6 +2319,13 @@ + + + + Enable Clans + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index f7ec927332..abdc60f3dd 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -260,6 +260,7 @@ public: const QString bind = tr("Interface IP Address to bind to.\nOnly available in custom configurations."); const QString enable_upnp = tr("Enable UPNP.\nThis will automatically forward ports bound on 0.0.0.0 if your router has UPNP enabled."); const QString psn_country = tr("Changes the RPCN country."); + const QString enable_clans = tr("Enable connection to the Clans server.\nOnly affects games supporting the Clans feature."); // system