From abc19804186a98d0afbbc3218228891b7d3d38da Mon Sep 17 00:00:00 2001 From: lannoene <77375172+lannoene@users.noreply.github.com> Date: Sun, 8 Mar 2026 07:48:09 -0700 Subject: [PATCH] Add DLP:SRVR + misc bug fixes (#1828) * Add DLP:SRVR + add friend code seed hack for LM1 + add multiple filters in IPC debugger + fix cia_container smdh offset not being applied, possible IF statement underflowing + default initialize boss variables + fix IPC header asserts in AM functions + add extra debug info to IPC param assert * Make server & client more resistant to high ping conditions * Remove DLP from list of online recommended modules * Fix license headers + fix clang formatting + fix server create network assert * Fix recorder.cpp license header --- src/citra_qt/debugger/ipc/recorder.cpp | 21 +- src/common/hacks/hack_list.cpp | 12 +- src/common/hacks/hack_list.h | 1 + src/core/file_sys/cia_container.cpp | 5 +- src/core/hle/ipc_helpers.h | 7 +- src/core/hle/service/am/am.cpp | 6 +- src/core/hle/service/boss/boss.h | 10 +- src/core/hle/service/dlp/dlp_base.cpp | 41 +- src/core/hle/service/dlp/dlp_base.h | 104 ++- src/core/hle/service/dlp/dlp_clt_base.cpp | 59 +- src/core/hle/service/dlp/dlp_clt_base.h | 18 +- src/core/hle/service/dlp/dlp_fkcl.cpp | 2 +- src/core/hle/service/dlp/dlp_srvr.cpp | 949 +++++++++++++++++++++- src/core/hle/service/dlp/dlp_srvr.h | 169 +++- src/core/hle/service/fs/fs_user.h | 8 +- src/core/hle/service/nwm/nwm_uds.cpp | 128 +-- src/core/hle/service/nwm/nwm_uds.h | 8 +- src/core/hle/service/service.cpp | 2 +- 18 files changed, 1388 insertions(+), 162 deletions(-) diff --git a/src/citra_qt/debugger/ipc/recorder.cpp b/src/citra_qt/debugger/ipc/recorder.cpp index f0a9973c5..d7a81c3dc 100644 --- a/src/citra_qt/debugger/ipc/recorder.cpp +++ b/src/citra_qt/debugger/ipc/recorder.cpp @@ -1,4 +1,4 @@ -// Copyright Citra Emulator Project / Lime3DS Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -160,16 +160,23 @@ QString IPCRecorderWidget::GetFunctionName(const IPCDebugger::RequestRecord& rec void IPCRecorderWidget::ApplyFilter(int index) { auto* item = ui->main->invisibleRootItem()->child(index); - const QString filter = ui->filter->text(); - if (filter.isEmpty()) { + const QString filter_full = ui->filter->text(); + if (filter_full.isEmpty()) { item->setHidden(false); return; } - for (int i = 0; i < item->columnCount(); ++i) { - if (item->text(i).contains(filter)) { - item->setHidden(false); - return; + auto filters = filter_full.split(u' '); + + for (auto& filter : filters) { + if (filter.isEmpty()) { + continue; + } + for (int i = 0; i < item->columnCount(); ++i) { + if (item->text(i).contains(filter)) { + item->setHidden(false); + return; + } } } diff --git a/src/common/hacks/hack_list.cpp b/src/common/hacks/hack_list.cpp index 4c611f6f6..5f320c673 100644 --- a/src/common/hacks/hack_list.cpp +++ b/src/common/hacks/hack_list.cpp @@ -187,6 +187,16 @@ HackManager hack_manager = { 0x00040000001AA300, // 3 (JPN) }, }}, - + {HackType::SPOOF_FRIEND_CODE_SEED, + HackEntry{ + .mode = HackAllowMode::FORCE, + .affected_title_ids = + { + // Luigi's Mansion 3ds + 0x00040000001D1800, // JPN + 0x00040000001D1900, // USA + 0x00040000001D1A00, // EUR + }, + }}, }}; } \ No newline at end of file diff --git a/src/common/hacks/hack_list.h b/src/common/hacks/hack_list.h index 0617fa51c..61a8cccfa 100644 --- a/src/common/hacks/hack_list.h +++ b/src/common/hacks/hack_list.h @@ -15,6 +15,7 @@ enum class HackType : int { ONLINE_LLE_REQUIRED, REGION_FROM_SECURE, REQUIRES_SHADER_FIXUP, + SPOOF_FRIEND_CODE_SEED, }; class UserHackData {}; diff --git a/src/core/file_sys/cia_container.cpp b/src/core/file_sys/cia_container.cpp index 570cd9b45..f121f2ce5 100644 --- a/src/core/file_sys/cia_container.cpp +++ b/src/core/file_sys/cia_container.cpp @@ -199,13 +199,14 @@ Loader::ResultStatus CIAContainer::LoadMetadata(std::span meta_data, s } Loader::ResultStatus CIAContainer::LoadSMDH(std::span smdh_data, std::size_t offset) { - if (smdh_data.size() - offset < sizeof(Loader::SMDH)) { + if (smdh_data.size() < sizeof(Loader::SMDH) || + offset > smdh_data.size() - sizeof(Loader::SMDH)) { return Loader::ResultStatus::Error; } cia_smdh = std::make_unique(); - std::memcpy(cia_smdh.get(), smdh_data.data(), sizeof(Loader::SMDH)); + std::memcpy(cia_smdh.get(), smdh_data.data() + offset, sizeof(Loader::SMDH)); return Loader::ResultStatus::Success; } diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index 46c87514e..f2547c3b0 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -1,4 +1,4 @@ -// Copyright 2016 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -32,8 +32,9 @@ public: } void ValidateHeader() { - DEBUG_ASSERT_MSG(index == TotalSize(), "Operations do not match the header (cmd {:#x})", - header.raw); + DEBUG_ASSERT_MSG(index == TotalSize(), + "Operations do not match the header (cmd {:#x}, session: {})", header.raw, + context->Session()->GetName()); } void Skip(unsigned size_in_words, bool set_to_null) { diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 9805d55c3..5cad545f7 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -2278,7 +2278,7 @@ void Module::Interface::GetProductCode(Kernel::HLERequestContext& ctx) { ProductCode product_code; - IPC::RequestBuilder rb = rp.MakeBuilder(6, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(5, 0); FileSys::NCCHContainer ncch(path); ncch.Load(); std::memcpy(&product_code.code, &ncch.ncch_header.product_code, 0x10); @@ -2866,7 +2866,7 @@ void Module::Interface::GetNumImportTitleContextsImpl(IPC::RequestParser& rp, bool include_installing, bool include_finalizing) { - IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); u32 count = 0; @@ -2905,7 +2905,7 @@ void Module::Interface::GetImportTitleContextListImpl(IPC::RequestParser& rp, } } - IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); rb.Push(written); } diff --git a/src/core/hle/service/boss/boss.h b/src/core/hle/service/boss/boss.h index ce1df770a..cd523e3b3 100644 --- a/src/core/hle/service/boss/boss.h +++ b/src/core/hle/service/boss/boss.h @@ -1,4 +1,4 @@ -// Copyright 2015 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -980,10 +980,10 @@ private: Core::System& system; std::shared_ptr task_finish_event; - u8 new_arrival_flag; - u8 ns_data_new_flag; - u8 ns_data_new_flag_privileged; - u8 output_flag; + u8 new_arrival_flag{}; + u8 ns_data_new_flag{}; + u8 ns_data_new_flag_privileged{}; + u8 output_flag{}; template void serialize(Archive& ar, const unsigned int); diff --git a/src/core/hle/service/dlp/dlp_base.cpp b/src/core/hle/service/dlp/dlp_base.cpp index a2e22212f..a79776f53 100644 --- a/src/core/hle/service/dlp/dlp_base.cpp +++ b/src/core/hle/service/dlp/dlp_base.cpp @@ -139,7 +139,7 @@ bool DLP_Base::ConnectToNetworkAsync(NWM::NetworkInfo net_info, NWM::ConnectionT return true; } -int DLP_Base::RecvFrom(u16 node_id, std::vector& buffer) { +int DLP_Base::RecvFrom(u16 node_id, std::vector& buffer, u16* out_node) { constexpr u32 max_pullpacket_size = 0x3c00; std::vector buffer_out; @@ -158,6 +158,9 @@ int DLP_Base::RecvFrom(u16 node_id, std::vector& buffer) { } buffer = buffer_out; + if (out_node) { + *out_node = secure_data.src_node_id; + } return *ret; // size } @@ -244,8 +247,40 @@ bool DLP_Base::ValidatePacket(u32 aes, void* pk, size_t sz, bool checksum) { } u32 DLP_Base::GetNumFragmentsFromTitleSize(u32 tsize) { - return Common::AlignUp(tsize - broad_title_size_diff, content_fragment_size) / - content_fragment_size; + return Common::AlignUp(tsize, content_fragment_size) / content_fragment_size; +} + +Loader::SMDH::TitleLanguage DLP_Base::SystemLanguageToSMDHLanguage(CFG::SystemLanguage sys_lang) { + using namespace Loader; + using namespace CFG; + switch (sys_lang) { + case LANGUAGE_JP: + return SMDH::TitleLanguage::Japanese; + case LANGUAGE_EN: + return SMDH::TitleLanguage::English; + case LANGUAGE_FR: + return SMDH::TitleLanguage::French; + case LANGUAGE_DE: + return SMDH::TitleLanguage::German; + case LANGUAGE_IT: + return SMDH::TitleLanguage::Italian; + case LANGUAGE_ES: + return SMDH::TitleLanguage::Spanish; + case LANGUAGE_ZH: + return SMDH::TitleLanguage::SimplifiedChinese; + case LANGUAGE_KO: + return SMDH::TitleLanguage::Korean; + case LANGUAGE_NL: + return SMDH::TitleLanguage::Dutch; + case LANGUAGE_PT: + return SMDH::TitleLanguage::Portuguese; + case LANGUAGE_RU: + return SMDH::TitleLanguage::Russian; + case LANGUAGE_TW: + return SMDH::TitleLanguage::TraditionalChinese; + default:; + } + UNREACHABLE_MSG("Unknown system language: {}", static_cast(sys_lang)); } } // namespace Service::DLP diff --git a/src/core/hle/service/dlp/dlp_base.h b/src/core/hle/service/dlp/dlp_base.h index 6708937f9..2016e97ef 100644 --- a/src/core/hle/service/dlp/dlp_base.h +++ b/src/core/hle/service/dlp/dlp_base.h @@ -8,6 +8,7 @@ #include "core/hle/service/cfg/cfg.h" #include "core/hle/service/nwm/nwm_uds.h" #include "core/hle/service/service.h" +#include "core/loader/smdh.h" #include @@ -17,7 +18,7 @@ namespace Service::DLP { using DLP_Username = std::array; constexpr inline u64 DLP_CHILD_TID_HIGH = 0x0004000100000000; -constexpr inline u32 content_fragment_size = 1440; +constexpr inline u32 content_fragment_size = 1440; // const frag packet size struct DLPTitleInfo { u32 unique_id; // games look at this to make sure it's their title info @@ -25,9 +26,9 @@ struct DLPTitleInfo { Network::MacAddress mac_addr; u16 version; // XX: probably? std::array age_ratings; - std::array short_description; // UTF-16 - std::array long_description; // UTF-16 - std::array icon; // 48x48, RGB565 + std::array title_short; // UTF-16 + std::array title_long; // UTF-16 + std::array icon; // 48x48, RGB565 u32 size; u8 unk2; u8 unk3; @@ -58,6 +59,18 @@ struct DLPEventDescription { static_assert(sizeof(DLPEventDescription) == 0x18); +enum class DLP_Clt_State : u16 { + NotInitialized = 0, // TODO: check on hardware. it probably just errors + Initialized = 1, + Scanning = 2, + Joined = 5, + Downloading = 6, + WaitingForServerReady = 7, + Complete = 9, + WaitingForAccept = + 64, // when a client joins, but is not accepted yet (used on servers w/ manual accept) +}; + // START BIG ENDIAN constexpr inline u8 dl_pk_type_broadcast = 0x01; @@ -79,15 +92,22 @@ struct DLPPacketHeader { std::array magic; struct { u8 type; - u8 mag0x02; - u16 unk; // usually 0x00 0x00 + u8 mag0x02; // usually 0x02, but appears to depend on the internal state of a random + // buffer + u16 padding; + }; + }; + u16_be size; // size of the whole packet, including the header + u16_le must_be_2; // always set to 2 + u32 checksum; // always calculate + u8 packet_index; // starts at 0 + union { + std::array resp_id; // client: copies this from host packet when responding to it + struct { // for server use + u8 pk_seq_num; + std::array resp_id_high; }; }; - u16_be size; // size of the whole packet, including the header - std::array unk1; // always 0x02 0x00 - u32 checksum; // always calculate - u8 packet_index; // starts at 0 - std::array resp_id; // copies this from host packet when responding to it }; static_assert(sizeof(DLPPacketHeader) == 0x10); @@ -114,18 +134,19 @@ struct DLPPacketBool { static_assert(sizeof(DLPPacketBool) == sizeof(u32_be)); -constexpr u32 broad_title_size_diff = 111360; - #pragma pack(push, 2) struct DLPBroadcastPacket1 { DLPPacketHeader head; u64_be child_title_id; // title id of the child being broadcasted - u64 unk1; - u64 unk2; + std::array unk1; + std::array unk6; // need 0x1 0x1 + u32_be content_block_size; // full byte size of content block + u8 max_nodes; + std::array unk2; u64 unk3; - u64 unk4; // all 0s - u32_be size; // size minus broad_title_size_diff - u32 unk5; + u64 unk4; // all 0s + u32_be transfer_size; + u32_be required_size; std::array title_short; std::array title_long; std::array icon_part; @@ -193,7 +214,8 @@ static_assert(sizeof(DLPSrvr_StartDistribution) == 0x14); struct DLPClt_StartDistributionAck_NoContentNeeded { DLPPacketHeader head; DLPPacketBool initialized; // 0x1 - u32 unk2; // 0x0 + u16_be unk2; // 0x0. if 0x20, move to ContentNeeded + u16_be unk3; // 0x0 }; static_assert(sizeof(DLPClt_StartDistributionAck_NoContentNeeded) == 0x18); @@ -212,14 +234,17 @@ static_assert(sizeof(DLPClt_StartDistributionAck_ContentNeeded) == 0x38); // perform distribution of content // packet_index is 1 +#pragma pack(push, 1) struct DLPSrvr_ContentDistributionFragment { DLPPacketHeader head; - u32_be content_magic; // extra magic value - u32_be unk1; // 0x1 BE + u8 unk2; + std::array padding; + u32_be content_block; // content_block index starting at 0 u16_be frag_index; // BE % dlp_content_block_length u16_be frag_size; // BE u8 content_fragment[]; }; +#pragma pack(pop) static_assert(sizeof(DLPSrvr_ContentDistributionFragment) == 28); @@ -237,7 +262,7 @@ static_assert(sizeof(DLPSrvr_FinishContentUpload) == 0x18); struct DLPClt_FinishContentUploadAck { DLPPacketHeader head; DLPPacketBool initialized; // 0x1 - u8 unk2; // 0x1 + u8 finished_cur_block; // 0x1 if finished downloading current block u8 needs_content; // 0x1 if downloading conetnt u32_be seq_ack; // BE client increments this every ack u16 unk4; // 0x0 @@ -279,6 +304,9 @@ static_assert(sizeof(DLPSrvr_BeginGameFinal) == 0x20); // END BIG ENDIAN class DLP_Base { +public: + static Loader::SMDH::TitleLanguage SystemLanguageToSMDHLanguage(CFG::SystemLanguage); + protected: DLP_Base(Core::System& s); virtual ~DLP_Base() = default; @@ -298,17 +326,27 @@ protected: const u32 uds_sharedmem_size = 0x4000; const u32 uds_version = 0x400; - const u32 recv_buffer_size = 0x3c00; - const u32 dlp_channel = 0x10; const u8 num_broadcast_packets = 5; u32 dlp_sharedmem_size{}; + constexpr static inline u32 dlp_poll_rate_normal = 100; + + constexpr static inline u16 dlp_wlan_comm_id = 0x2810; + constexpr static inline u16 dlp_net_info_channel = 0x1; + constexpr static inline u16 dlp_bind_node_id = 0x1; + constexpr static inline u32 dlp_recv_buffer_size = 0x3c00; + constexpr static inline u8 dlp_broadcast_data_channel = 0x1; + constexpr static inline u8 dlp_client_data_channel = 0x2; + constexpr static inline u8 dlp_host_network_node_id = 0x1; + + const u8 first_client_node_id = dlp_host_network_node_id + 1; + DLP_Username username; // stubbed as HLE NWM_UDS does not check this. Should be: 0km@tsa$uhmy1a0sa + nul std::vector dlp_password_buf{}; std::array wireless_reboot_passphrase; - const u32 dlp_content_block_length = 182; + constexpr static inline u32 dlp_content_block_length = 182; std::shared_ptr GetCFG(); std::shared_ptr GetUDS(); @@ -321,7 +359,8 @@ protected: bool ConnectToNetworkAsync(NWM::NetworkInfo net_info, NWM::ConnectionType conn_type, std::vector passphrase); - int RecvFrom(u16 node_id, std::vector& buffer); + // out_node: if node_id is broadcast, check which node we received from + int RecvFrom(u16 node_id, std::vector& buffer, u16* out_node = nullptr); bool SendTo(u16 node_id, u8 data_channel, std::vector& buffer, u8 flags = 0); static std::u16string DLPUsernameAsString16(DLP_Username uname); @@ -349,7 +388,7 @@ protected: template T* PGen_SetPK(std::array magic, u8 packet_index, std::array resp_id) { if (!sm_packet_sender_session.try_acquire()) { - LOG_ERROR(Service_DLP, + LOG_DEBUG(Service_DLP, "Tried to send 2 packets concurrently, causing blocking on this thread"); sm_packet_sender_session.acquire(); } @@ -357,7 +396,7 @@ protected: auto ph = GetPacketHead(send_packet_ctx); ph->magic = magic; ph->size = sizeof(T); - ph->unk1 = {0x02, 0x00}; + ph->must_be_2 = 2; ph->resp_id = resp_id; ph->packet_index = packet_index; return GetPacketBody(send_packet_ctx); @@ -372,6 +411,15 @@ protected: send_packet_ctx.clear(); sm_packet_sender_session.release(); } + // adds x bytes to the packet and refreshes the + // packet body pointer + template + void PGen_AddPacketData(T*& p, size_t num_bytes) { + send_packet_ctx.resize(send_packet_ctx.size() + num_bytes); + auto head = GetPacketHead(send_packet_ctx); + head->size = send_packet_ctx.size(); + p = GetPacketBody(send_packet_ctx); + } // input the host mac address u32 GenDLPChecksumKey(Network::MacAddress mac_addr); static void DLPEncryptCTR(void* out, size_t size, const u8* iv_ctr); diff --git a/src/core/hle/service/dlp/dlp_clt_base.cpp b/src/core/hle/service/dlp/dlp_clt_base.cpp index d127dbd36..cb39c1729 100644 --- a/src/core/hle/service/dlp/dlp_clt_base.cpp +++ b/src/core/hle/service/dlp/dlp_clt_base.cpp @@ -394,8 +394,15 @@ void DLP_Clt_Base::GetConnectingNodes(Kernel::HLERequestContext& ctx) { std::vector connected_nodes_buffer; connected_nodes_buffer.resize(node_array_len * sizeof(u16)); - memcpy(connected_nodes_buffer.data(), conn_status.nodes, - std::min(connected_nodes_buffer.size(), conn_status.total_nodes) * sizeof(u16)); + auto connected_nodes = reinterpret_cast(connected_nodes_buffer.data()); + for (int i = 0, out_idx = 0; i < NWM::UDSMaxNodes; i++) { + auto node_id = conn_status.nodes[i]; + if (!node_id) { + continue; + } + + connected_nodes[out_idx++] = node_id; + } rb.Push(ResultSuccess); rb.Push(conn_status.total_nodes); @@ -476,8 +483,8 @@ void DLP_Clt_Base::BeaconScanCallback(std::uintptr_t user_data, s64 cycles_late) // set our next scan interval system.CoreTiming().ScheduleEvent( - msToCycles(std::max(0, beacon_scan_interval_ms - - beacon_parse_timer_total.GetTimeElapsed().count())) - + msToCycles(std::max(100, beacon_scan_interval_ms - + beacon_parse_timer_total.GetTimeElapsed().count())) - cycles_late, beacon_scan_event, 0); } @@ -578,14 +585,14 @@ void DLP_Clt_Base::CacheBeaconTitleInfo(Network::WifiPacket& beacon) { // copy over title string data std::copy(broad_pk1->title_short.begin(), broad_pk1->title_short.end(), - c_title_info.short_description.begin()); + c_title_info.title_short.begin()); std::copy(broad_pk1->title_long.begin(), broad_pk1->title_long.end(), - c_title_info.long_description.begin()); + c_title_info.title_long.begin()); // unique id should be the title id without the tid high shifted 1 byte right c_title_info.unique_id = (broad_pk1->child_title_id & 0xFFFFFFFF) >> 8; - c_title_info.size = broad_pk1->size + broad_title_size_diff; + c_title_info.size = broad_pk1->transfer_size; // copy over the icon data auto icon_copy_loc = c_title_info.icon.begin(); @@ -661,9 +668,6 @@ void DLP_Clt_Base::ClientConnectionManager() { std::this_thread::sleep_for(std::chrono::milliseconds(poll_rate)); }; - constexpr u32 dlp_poll_rate_normal = 100; - constexpr u32 dlp_poll_rate_distribute = 0; - u32 dlp_poll_rate_ms = dlp_poll_rate_normal; bool got_corrupted_packets = false; @@ -672,7 +676,7 @@ void DLP_Clt_Base::ClientConnectionManager() { while (sleep_poll(dlp_poll_rate_ms), is_connected) { std::vector recv_buf; - if (int sz = RecvFrom(dlp_host_network_node_id, recv_buf)) { + if (int sz = RecvFrom(dlp_bind_node_id, recv_buf)) { auto p_head = GetPacketHead(recv_buf); // validate packet header if (!ValidatePacket(aes, p_head, sz, should_verify_checksum)) { @@ -684,6 +688,7 @@ void DLP_Clt_Base::ClientConnectionManager() { // now we can parse the packet std::scoped_lock lock{clt_state_mutex, title_info_mutex}; if (p_head->type == dl_pk_type_auth) { + LOG_DEBUG(Service_DLP, "Recv auth"); auto s_body = PGen_SetPK(dl_pk_head_auth_header, 0, p_head->resp_id); s_body->initialized = true; @@ -692,6 +697,7 @@ void DLP_Clt_Base::ClientConnectionManager() { s_body->resp_id = {0x01, 0x02}; PGen_SendPK(aes, dlp_host_network_node_id, dlp_client_data_channel); } else if (p_head->type == dl_pk_type_start_dist) { + LOG_DEBUG(Service_DLP, "Recv start dist"); // poll rate on non-downloading clients still needs to // be quick enough to eat broadcast content frag packets dlp_poll_rate_ms = dlp_poll_rate_distribute; @@ -752,6 +758,13 @@ void DLP_Clt_Base::ClientConnectionManager() { is_downloading_content = false; LOG_INFO(Service_DLP, "Finished downloading content. Installing..."); + if (received_fragments.size() != dlp_units_total) { + LOG_WARNING(Service_DLP, + "There was a mismatch in fragment indexes, because we did " + "not receive all fragments {} / {}", + received_fragments.size(), dlp_units_total); + } + if (!InstallEncryptedCIAFromFragments(received_fragments)) { LOG_ERROR(Service_DLP, "Could not install DLP encrypted content"); } else { @@ -759,27 +772,31 @@ void DLP_Clt_Base::ClientConnectionManager() { } clt_state = DLP_Clt_State::WaitingForServerReady; + } else if (FinishedCurrentContentBlock()) { + current_content_block++; } } } else if (p_head->type == dl_pk_type_finish_dist) { if (p_head->packet_index == 1) { - auto r_pbody = GetPacketBody(recv_buf); + [[maybe_unused]] auto r_pbody = + GetPacketBody(recv_buf); auto s_body = PGen_SetPK( dl_pk_head_finish_dist_header, 0, p_head->resp_id); - if (is_downloading_content) { - current_content_block++; - } s_body->initialized = true; - s_body->unk2 = 0x1; + s_body->finished_cur_block = FinishedCurrentContentBlock(); s_body->needs_content = is_downloading_content; - s_body->seq_ack = r_pbody->seq_num + 1; + s_body->seq_ack = current_content_block; s_body->unk4 = 0x0; PGen_SendPK(aes, dlp_host_network_node_id, dlp_client_data_channel); + + LOG_DEBUG(Service_DLP, "Recv finish dist, fc: {}, {} / {}", + FinishedCurrentContentBlock(), dlp_units_downloaded, dlp_units_total); } else { LOG_ERROR(Service_DLP, "Received finish dist packet, but packet index was {}", p_head->packet_index); } } else if (p_head->type == dl_pk_type_start_game) { + LOG_DEBUG(Service_DLP, "Recv start game"); if (p_head->packet_index == 0) { dlp_poll_rate_ms = dlp_poll_rate_normal; auto s_body = PGen_SetPK(dl_pk_head_start_game_header, 0, @@ -801,7 +818,7 @@ void DLP_Clt_Base::ClientConnectionManager() { } } - uds->UnbindHLE(dlp_host_network_node_id); + uds->UnbindHLE(dlp_bind_node_id); uds->DisconnectNetworkHLE(); } @@ -823,6 +840,7 @@ bool DLP_Clt_Base::InstallEncryptedCIAFromFragments(std::set& auto cia_file = std::make_unique(system, FS::MediaType::NAND); cia_file->AuthorizeDecryptionFromHLE(); bool install_errored = false; + for (u64 nb = 0; auto& frag : frags) { constexpr bool flush_data = true; constexpr bool update_timestamp = false; @@ -853,4 +871,9 @@ bool DLP_Clt_Base::IsIdling() { return !is_scanning && !is_connected; } +bool DLP_Clt_Base::FinishedCurrentContentBlock() { + return dlp_units_downloaded % dlp_content_block_length == 0 || + dlp_units_downloaded == dlp_units_total; +} + } // namespace Service::DLP diff --git a/src/core/hle/service/dlp/dlp_clt_base.h b/src/core/hle/service/dlp/dlp_clt_base.h index 10da159b3..3ea6612c3 100644 --- a/src/core/hle/service/dlp/dlp_clt_base.h +++ b/src/core/hle/service/dlp/dlp_clt_base.h @@ -10,16 +10,6 @@ namespace Service::DLP { -enum class DLP_Clt_State : u32 { - NotInitialized = 0, // TODO: check on hardware. it probably just errors - Initialized = 1, - Scanning = 2, - Joined = 5, - Downloading = 6, - WaitingForServerReady = 7, - Complete = 9, -}; - // number of bars enum class DLPSignalStrength : u8 { VeryWeak = 0, @@ -79,18 +69,13 @@ protected: ///< beacons until at least one tinfo buf element is cleared bool is_scanning = false; constexpr static inline int beacon_scan_interval_ms = 1000; + constexpr static inline u32 dlp_poll_rate_distribute = 0; std::vector> scanned_title_info; std::map ignore_servers_list; // ignore servers which give us bad broadcast data u64 scan_title_id_filter; Network::MacAddress scan_mac_address_filter; Network::MacAddress host_mac_address; - constexpr static inline u16 dlp_net_info_channel = 0x1; - constexpr static inline u16 dlp_bind_node_id = 0x1; - constexpr static inline u32 dlp_recv_buffer_size = 0x3c00; - constexpr static inline u8 dlp_broadcast_data_channel = 0x1; - constexpr static inline u8 dlp_client_data_channel = 0x2; - constexpr static inline u8 dlp_host_network_node_id = 0x1; Core::TimingEventType* beacon_scan_event; @@ -125,6 +110,7 @@ protected: bool InstallEncryptedCIAFromFragments(std::set& frags); void DisconnectFromServer(); bool IsIdling(); + bool FinishedCurrentContentBlock(); void GetMyStatus(Kernel::HLERequestContext& ctx); void GetChannels(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/dlp/dlp_fkcl.cpp b/src/core/hle/service/dlp/dlp_fkcl.cpp index 8576d80a3..355de70fc 100644 --- a/src/core/hle/service/dlp/dlp_fkcl.cpp +++ b/src/core/hle/service/dlp/dlp_fkcl.cpp @@ -36,7 +36,7 @@ void DLP_FKCL::InitializeWithName(Kernel::HLERequestContext& ctx) { u32 shared_mem_size = rp.Pop(); u32 max_beacons = rp.Pop(); constexpr u32 constant_mem_size = 0; - auto username = rp.PopRaw>(); + auto username = rp.PopRaw(); rp.Skip(1, false); // possible null terminator or unk flags auto [shared_mem, event] = rp.PopObjects(); diff --git a/src/core/hle/service/dlp/dlp_srvr.cpp b/src/core/hle/service/dlp/dlp_srvr.cpp index 65632ca53..aab3a2145 100644 --- a/src/core/hle/service/dlp/dlp_srvr.cpp +++ b/src/core/hle/service/dlp/dlp_srvr.cpp @@ -5,10 +5,17 @@ #include "common/archives.h" #include "common/common_types.h" #include "common/logging/log.h" +#include "common/string_util.h" #include "core/core.h" +#include "core/file_sys/archive_ncch.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/event.h" +#include "core/hle/kernel/shared_memory.h" #include "core/hle/result.h" +#include "core/hle/romfs.h" +#include "core/hle/service/am/am.h" #include "core/hle/service/dlp/dlp_srvr.h" +#include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/fs_user.h" SERIALIZE_EXPORT_IMPL(Service::DLP::DLP_SRVR) @@ -19,8 +26,148 @@ std::shared_ptr DLP_SRVR::GetServiceFrameworkShar return shared_from_this(); } +std::shared_ptr DLP_SRVR::GetFS() { + return system.ServiceManager().GetService("fs:USER"); +} + +u8 DLP_SRVR::GetSrvrState() { + std::scoped_lock lock(srvr_state_mutex); + return static_cast(srvr_state); +} + +void DLP_SRVR::InitializeSrvrCommon(u32 shared_mem_size, u8 max_clnts, u32 process_id, + DLP_Username uname, + std::shared_ptr shared_mem, + std::shared_ptr event) { + InitializeDlpBase(shared_mem_size, shared_mem, event, uname); + + // this gets our mac address, and we will be the host + host_mac_address = GetUDS()->GetMacAddress(); + max_clients = max_clnts; + client_states.resize(max_clients); + // set up client array + for (u8 i = first_client_node_id; auto& cl : client_states) { + cl.network_node_id = i; + cl.state = ClientState::NotJoined; + cl.pk_seq_num = 0; + i++; + } + + if (!CacheContentFileInMemory(process_id)) { + LOG_WARNING( + Service_DLP, + "Unable to load cia file. You will not be able to perform content distribution!"); + } + + std::scoped_lock lock(srvr_state_mutex); + + srvr_state = DLP_Srvr_State::Initialized; +} + +bool DLP_SRVR::CacheContentFileInMemory(u32 process_id) { + auto fs = GetFS(); + + if (!fs) { + LOG_ERROR(Service_DLP, "Could not get pointer to fs"); + return false; + } + + auto title_info = fs->GetProgramLaunchInfo(process_id); + + // get special content index. could have made a new + // HLE func in FS, but this is so small that it + // doesn't matter + ResultVal index; + if (title_info->media_type == FS::MediaType::GameCard) { + index = fs->GetSpecialContentIndexFromGameCard(title_info->program_id, + FS::SpecialContentType::DLPChild); + } else { + index = fs->GetSpecialContentIndexFromTMD(title_info->media_type, title_info->program_id, + FS::SpecialContentType::DLPChild); + } + + if (!index) { + LOG_ERROR(Service_DLP, "Could not get special content index from program id 0x{:x}", + title_info->program_id); + return false; + } + + // read as ncch to find the content + FileSys::NCCHArchive container(title_info->program_id, title_info->media_type); + + std::array exefs_filepath{}; + FileSys::Path file_path = + FileSys::MakeNCCHFilePath(FileSys::NCCHFileOpenType::NCCHData, *index, + FileSys::NCCHFilePathType::RomFS, exefs_filepath); + FileSys::Mode open_mode = {}; + open_mode.read_flag.Assign(1); + const u32 open_attributes_none = 0; + auto file_result = container.OpenFile(file_path, open_mode, open_attributes_none); + + if (file_result.Failed()) { + LOG_ERROR(Service_DLP, "Could not open DLP child archive"); + return false; + } + + auto romfs = std::move(file_result).Unwrap(); + + std::vector romfs_buffer(romfs->GetSize()); + romfs->Read(0, romfs_buffer.size(), romfs_buffer.data()); + romfs->Close(); + + u64 dlp_child_tid = (title_info->program_id & 0xFFFFFFFF) | DLP_CHILD_TID_HIGH; + auto filename = fmt::format("{:016x}.cia", dlp_child_tid); + + LOG_INFO(Service_DLP, "Loading romfs file: {}", filename.c_str()); + + const RomFS::RomFSFile child_file = + RomFS::GetFile(romfs_buffer.data(), {Common::UTF8ToUTF16(filename)}); + + if (!child_file.Length()) { + LOG_ERROR(Service_DLP, "DLP child is missing from archive"); + return false; + } + + distribution_content.resize(child_file.Length()); + memcpy(distribution_content.data(), child_file.Data(), child_file.Length()); + + FileSys::CIAContainer cia_container; + if (cia_container.Load(distribution_content) != Loader::ResultStatus::Success) { + LOG_ERROR(Service_DLP, "Could not load DLP child header"); + return false; + } + + const auto& smdh = cia_container.GetSMDH(); + + if (!smdh) { + LOG_ERROR(Service_DLP, "Failed to load DLP child SMDH"); + return false; + } + + // load up title_broadcast_info + memset(&title_broadcast_info, 0, sizeof(title_broadcast_info)); + title_broadcast_info.title_id = dlp_child_tid; + title_broadcast_info.title_size = child_file.Length(); + title_broadcast_info.transfer_size = cia_container.GetMetadataOffset(); + title_broadcast_info.required_size = child_file.Length(); // TODO: verify on HW + + memcpy(title_broadcast_info.icon.data(), smdh->large_icon.data(), smdh->large_icon.size()); + + auto request_lang = SystemLanguageToSMDHLanguage(GetCFG()->GetSystemLanguage()); + LOG_DEBUG(Service_DLP, "Requesting lang: {}", static_cast(request_lang)); + + auto t_long = smdh->GetLongTitle(request_lang); + auto t_short = smdh->GetShortTitle(request_lang); + memcpy(title_broadcast_info.title_short.data(), t_short.data(), t_short.size()); + memcpy(title_broadcast_info.title_long.data(), t_long.data(), t_long.size()); + + LOG_INFO(Service_DLP, "Successfully cached DLP child content"); + + return true; +} + void DLP_SRVR::IsChild(Kernel::HLERequestContext& ctx) { - auto fs = system.ServiceManager().GetService("fs:USER"); + auto fs = GetFS(); IPC::RequestParser rp(ctx); u32 process_id = rp.Pop(); @@ -35,8 +182,9 @@ void DLP_SRVR::IsChild(Kernel::HLERequestContext& ctx) { // check if tid corresponds to dlp filter u32 tid[2]; memcpy(tid, &title_info->program_id, sizeof(tid)); - LOG_INFO(Service_DLP, "Checked on tid high: {:x} (low {:x})", tid[1], tid[0]); child = (tid[1] & 0xFFFFC000) == 0x40000 && (tid[1] & 0xFFFF) == 0x1; + LOG_INFO(Service_DLP, "Checked on tid high: {:x} (low {:x}). Is child: {}", tid[1], tid[0], + child); } else { // child not found child = false; LOG_ERROR(Service_DLP, @@ -49,29 +197,796 @@ void DLP_SRVR::IsChild(Kernel::HLERequestContext& ctx) { rb.Push(child); } +void DLP_SRVR::GetDupNoticeNeed(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + // this is not a 3ds. we don't have + // to update anything. + bool need = false; + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); + rb.Push(need); +} + +void DLP_SRVR::GetServerState(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); + rb.Push(GetSrvrState()); +} + +void DLP_SRVR::Initialize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + auto shared_mem_size = rp.Pop(); + auto max_clients = rp.Pop(); + auto process_id = rp.Pop(); + rp.Skip(3, false); // unk + + auto [sharedmem, net_event] = rp.PopObjects(); + + InitializeSrvrCommon(shared_mem_size, max_clients, process_id, + String16AsDLPUsername(GetCFG()->GetUsername()), sharedmem, net_event); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void DLP_SRVR::InitializeWithName(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + auto shared_mem_size = rp.Pop(); + auto max_clients = rp.Pop(); + auto process_id = rp.Pop(); + rp.Skip(3, false); // unk + auto username = rp.PopRaw(); + rp.Skip(1, false); + + auto [sharedmem, net_event] = rp.PopObjects(); + + InitializeSrvrCommon(shared_mem_size, max_clients, process_id, username, sharedmem, net_event); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void DLP_SRVR::Finalize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + std::scoped_lock lock(broadcast_mutex); + is_broadcasting = false; + + EndConnectionManager(); + + distribution_content.clear(); + + FinalizeDlpBase(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void DLP_SRVR::StartHosting(Kernel::HLERequestContext& ctx) { + LOG_INFO(Service_DLP, "called"); + IPC::RequestParser rp(ctx); + + std::scoped_lock lock{srvr_state_mutex, broadcast_mutex}; + + manual_accept = rp.Pop(); + u8 channel = rp.Pop(); + + if (channel != 0 && channel != 1 && channel != 6 && channel != 11) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(Result(ErrorDescription::OutOfRange, ErrorModule::DLP, + ErrorSummary::InvalidArgument, ErrorLevel::Permanent)); + return; + } + + NWM::NetworkInfo net_info; + net_info.max_nodes = max_clients + 1; + net_info.channel = channel; // if 0x0, gets set to default channel: 11 + net_info.wlan_comm_id = dlp_wlan_comm_id; + net_info.application_data_size = 0; + + GetUDS()->BeginHostingNetwork({reinterpret_cast(&net_info), sizeof(NWM::NetworkInfo)}, + dlp_password_buf); + + is_broadcasting = true; + constexpr u32 title_broadcast_delay_ms = 1; + + system.CoreTiming().ScheduleEvent(title_broadcast_delay_ms, title_broadcast_event, 0); + + srvr_state = DLP_Srvr_State::Accepting; + + is_hosting = true; + dlp_srvr_poll_rate_ms = dlp_poll_rate_normal; + server_connection_worker = std::thread([this] { ServerConnectionManager(); }); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void DLP_SRVR::EndHosting(Kernel::HLERequestContext& ctx) { + LOG_INFO(Service_DLP, "called"); + IPC::RequestParser rp(ctx); + + std::scoped_lock lock{srvr_state_mutex, broadcast_mutex}; + is_broadcasting = false; + srvr_state = DLP_Srvr_State::Initialized; + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void DLP_SRVR::GetConnectingClients(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + u32 len = rp.Pop(); + auto mapped_buf = rp.PopMappedBuffer(); + + std::vector buf_connecting; + + std::scoped_lock lock(client_states_mutex); + + for (auto& cl : client_states) { + if (cl.state == ClientState::NotJoined) { + continue; + } + buf_connecting.push_back(cl.network_node_id); + } + + // ignore the host node + mapped_buf.Write(buf_connecting.data(), 0, + std::min((u32)buf_connecting.size(), len) * sizeof(u16)); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + rb.Push(ResultSuccess); + rb.Push(buf_connecting.size()); + rb.PushMappedBuffer(mapped_buf); +} + +void DLP_SRVR::GetClientInfo(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + auto node_id = rp.Pop(); + + auto node_info = GetUDS()->GetNodeInformationHLE(node_id); + if (!node_info) { + LOG_ERROR(Service_DLP, "Could not get node info for network node id 0x{:x}", node_id); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(Result(ErrorDescription::NoData, ErrorModule::DLP, ErrorSummary::NotFound, + ErrorLevel::Status)); + return; + } + + IPC::RequestBuilder rb = rp.MakeBuilder(11, 0); + if (node_id == dlp_host_network_node_id) { + // even though it's not supposed to use + // this info, we will still give it the + // info + rb.Push(Result(ErrorDescription::NoData, ErrorModule::DLP, ErrorSummary::NotFound, + ErrorLevel::Status)); + } else { + rb.Push(ResultSuccess); + } + rb.PushRaw(UDSToDLPNodeInfo(*node_info)); +} + +void DLP_SRVR::GetClientState(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + u8 node_id = rp.Pop(); + + // TODO: check if nodata is returned if the + // node requested isn't joined yet + + std::scoped_lock lock(client_states_mutex); + + bool state_should_error = false; + auto cl = GetClState(node_id, state_should_error); + + if (!cl) { + LOG_WARNING(Service_DLP, "Node id out of range: 0x{:x}", node_id); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(Result(ErrorDescription::NoData, ErrorModule::DLP, ErrorSummary::NotFound, + ErrorLevel::Status)); + return; + } + + IPC::RequestBuilder rb = rp.MakeBuilder(6, 0); + rb.Push(ResultSuccess); + rb.Push(cl->GetMyState()); + rb.Push(cl->dlp_units_total); + rb.Push(cl->GetDlpUnitsDownloaded()); + // unk, see dlp_client_base state + rb.Push(0x0); + rb.Push(0x0); +} + +void DLP_SRVR::StartDistribution(Kernel::HLERequestContext& ctx) { + LOG_INFO(Service_DLP, "called"); + + // when StartDistribution is called, the real + // DLP_SRVR doesn't stop its broadcast thread + // we do because we're better. we're stronger. + std::scoped_lock lock{srvr_state_mutex, broadcast_mutex}; + + is_broadcasting = false; + is_distributing = true; + is_waiting_for_passphrase = false; + dlp_srvr_poll_rate_ms = dlp_poll_rate_distribute; + + srvr_state = DLP_Srvr_State::WaitingForDistribution; + + auto aes = GenDLPChecksumKey(host_mac_address); + + // now send distribute info requests + for (auto& cl : client_states) { + if (cl.state == ClientState::NotJoined) + continue; + if (cl.state != ClientState::Accepted) { + LOG_ERROR(Service_DLP, "Client was not ready start distribution"); + } + cl.state = ClientState::NeedsDistributeAck; + auto s_body = PGen_SetPK(dl_pk_head_start_dist_header, 1, + cl.GetPkRespId()); + s_body->initialized = true; + PGen_SendPK(aes, cl.network_node_id, dlp_client_data_channel); + } + + IPC::RequestParser rp(ctx); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void DLP_SRVR::BeginGame(Kernel::HLERequestContext& ctx) { + LOG_INFO(Service_DLP, "called"); + + IPC::RequestParser rp(ctx); + + auto passphrase = rp.PopRaw>(); + + std::scoped_lock lock{client_states_mutex}; + + auto aes = GenDLPChecksumKey(host_mac_address); + + for (auto& cl : client_states) { + if (cl.state == ClientState::NotJoined) { + continue; + } + if (cl.state == ClientState::SentPassphrase) { + LOG_WARNING(Service_DLP, "Already sent passphrase"); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(-1); // TODO: find real error + return; + } + if (cl.state != ClientState::DistributeDone) { + LOG_ERROR(Service_DLP, "Client is not ready to begin the game (state: {})", + static_cast(cl.state)); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(-1); // TODO: find real error + return; + } + auto s_body = + PGen_SetPK(dl_pk_head_start_game_header, 1, cl.GetPkRespId()); + s_body->unk1 = 0x1; + s_body->wireless_reboot_passphrase = passphrase; + s_body->unk2 = 0x9; // could be our state + PGen_SendPK(aes, cl.network_node_id, dlp_client_data_channel); + cl.state = ClientState::SentPassphrase; + } + + is_waiting_for_passphrase = true; + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void DLP_SRVR::AcceptClient(Kernel::HLERequestContext& ctx) { + LOG_INFO(Service_DLP, "called"); + + IPC::RequestParser rp(ctx); + + auto node_id = rp.Pop(); + + std::scoped_lock lock(client_states_mutex); + + auto cl = GetClState(node_id); + + if (!cl) { + LOG_ERROR(Service_DLP, "Could not find client node id: {}", node_id); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(-1); // TODO: find real error code + return; + } + + SendAuthPacket(*cl); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void DLP_SRVR::DisconnectClient(Kernel::HLERequestContext& ctx) { + LOG_INFO(Service_DLP, "called"); + + IPC::RequestParser rp(ctx); + + auto node_id = rp.Pop(); + + std::scoped_lock lock(client_states_mutex); + + auto cl = GetClState(node_id); + + if (!cl) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(-1); // TODO: find real error code + return; + } + + GetUDS()->EjectClientHLE(node_id); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void DLP_SRVR::TitleBroadcastCallback(std::uintptr_t user_data, s64 cycles_late) { + std::scoped_lock lock(broadcast_mutex); + + if (!is_broadcasting) { + return; + } + + auto aes = GenDLPChecksumKey(host_mac_address); + + std::array broadcast_resp_id = {0x05}; + + auto p1 = PGen_SetPK(dl_pk_head_broadcast_header, 0, broadcast_resp_id); + p1->child_title_id = title_broadcast_info.title_id; + p1->transfer_size = title_broadcast_info.transfer_size; + p1->required_size = title_broadcast_info.required_size; + p1->content_block_size = content_fragment_size * dlp_content_block_length; + p1->max_nodes = max_clients + 1; + std::copy(title_broadcast_info.title_short.begin(), title_broadcast_info.title_short.end(), + p1->title_short.begin()); + std::copy(title_broadcast_info.title_long.begin(), title_broadcast_info.title_long.end(), + p1->title_long.begin()); + + // copy in the icon data + size_t cur_icon_start = 0, cur_icon_end = 0; + + auto copy_pk_icon = [&](std::span&& icon_part) { + cur_icon_start = cur_icon_end; + cur_icon_end += icon_part.size(); + std::copy(title_broadcast_info.icon.begin() + cur_icon_start, + title_broadcast_info.icon.begin() + cur_icon_end, icon_part.begin()); + }; + + copy_pk_icon({p1->icon_part.begin(), p1->icon_part.end()}); + + // i'm not sure what this is. is this regional? + p1->unk1 = {0x0, 0x10}; + p1->unk6 = {0x1, 0x1}; + PGen_SendPK(aes, broadcast_node_id, dlp_broadcast_data_channel); + auto p2 = PGen_SetPK(dl_pk_head_broadcast_header, 1, broadcast_resp_id); + copy_pk_icon({p2->icon_part.begin(), p2->icon_part.end()}); + PGen_SendPK(aes, broadcast_node_id, dlp_broadcast_data_channel); + auto p3 = PGen_SetPK(dl_pk_head_broadcast_header, 2, broadcast_resp_id); + copy_pk_icon({p3->icon_part.begin(), p3->icon_part.end()}); + PGen_SendPK(aes, broadcast_node_id, dlp_broadcast_data_channel); + auto p4 = PGen_SetPK(dl_pk_head_broadcast_header, 3, broadcast_resp_id); + copy_pk_icon({p4->icon_part.begin(), p4->icon_part.end()}); + PGen_SendPK(aes, broadcast_node_id, dlp_broadcast_data_channel); + [[maybe_unused]] auto p5 = + PGen_SetPK(dl_pk_head_broadcast_header, 4, broadcast_resp_id); + PGen_SendPK(aes, broadcast_node_id, dlp_broadcast_data_channel); + + system.CoreTiming().ScheduleEvent(msToCycles(title_broadcast_interval_ms) - cycles_late, + title_broadcast_event, 0); +} + +void DLP_SRVR::ServerConnectionManager() { + auto uds = GetUDS(); + auto aes = GenDLPChecksumKey(host_mac_address); + + auto [ret, data_available_event] = uds->BindHLE(dlp_bind_node_id, dlp_recv_buffer_size, + dlp_client_data_channel, broadcast_node_id); + if (ret != NWM::ResultStatus::ResultSuccess) { + LOG_ERROR(Service_DLP, "Could not bind on node id 0x{:x}", dlp_bind_node_id); + return; + } + + auto sleep_poll = [](size_t poll_rate) -> void { + std::this_thread::sleep_for(std::chrono::milliseconds(poll_rate)); + }; + + LOG_INFO(Service_DLP, "Started"); + + while (sleep_poll(dlp_srvr_poll_rate_ms), is_hosting) { + // these mutexes are preventing the poll_rate from being + // 0 ms (since they are unfair mutexes) + std::scoped_lock lock{client_states_mutex, srvr_state_mutex}; + + auto conn_status = uds->GetConnectionStatusHLE(); + + auto any_needs_distribute = [this]() -> bool { + for (auto& cl : client_states) { + if (cl.state == ClientState::NotJoined) { + continue; + } + if (cl.state == ClientState::NeedsContent || + cl.state == ClientState::DoesNotNeedContent || + cl.state == ClientState::NeedsDistributeAck) { + return true; + } + if (cl.state != ClientState::DistributeDone) { + LOG_WARNING(Service_DLP, + "Invalid client state, should be in content distribution phase"); + } + } + return false; + }; + auto all_got_passphrase = [this]() -> bool { + for (auto& cl : client_states) { + if (cl.state == ClientState::NotJoined) { + continue; + } + if (cl.state != ClientState::SentPassphrase) { + return false; + } + } + return true; + }; + + // send outgoing messages + for (auto& cl : client_states) { + // client disconnected + if (cl.state != ClientState::NotJoined && !conn_status.nodes[cl.network_node_id - 1]) { + cl.state = ClientState::NotJoined; + LOG_INFO(Service_DLP, "Client disconnected"); + } + switch (cl.state) { + case ClientState::NotJoined: { + // check if client should be joined + if (!conn_status.nodes[cl.network_node_id - 1]) { + break; + } + cl.state = ClientState::NeedsAuth; + cl.pk_seq_num = 0; + LOG_INFO(Service_DLP, "Client connected"); + } + // fall through + case ClientState::NeedsAuth: { + if (manual_accept) { + break; + } + } + // fall through + case ClientState::NeedsAuthAck: { + // coded it this way so that it + // matches the hw behavior of constantly + // sending accept packets + SendAuthPacket(cl); + break; + } + case ClientState::Accepted: + break; // can idle + case ClientState::NeedsDistributeAck: + break; + case ClientState::NeedsContent: { + if (!cl.can_download_next_block) { + if (cl.sent_next_block_req) { + continue; + } + // send content block ack request (finish dist) + auto s_body = PGen_SetPK( + dl_pk_head_finish_dist_header, 1, cl.GetPkRespId()); + s_body->initialized = true; + s_body->seq_num = cl.next_req_ack; + PGen_SendPK(aes, cl.network_node_id, dlp_client_data_channel); + cl.sent_next_block_req = true; + continue; + } + // TODO: change to broadcast content instead of + // sending it to individual clients. I'm doing it + // like this for simplicity's sake, but a real 3ds + // needs to broadcast the packets for efficiency. + for (size_t i = 0; i < dlp_content_block_length; i++) { + if (cl.GetTotalFragIndex(i) == cl.dlp_units_total) { + LOG_DEBUG(Service_DLP, "Cut block short"); + break; + } + SendNextCIAFragment(cl, i); + } + cl.can_download_next_block = false; + cl.sent_next_block_req = false; + cl.current_content_block++; + break; + } + case ClientState::DoesNotNeedContent: { + if (cl.can_download_next_block) { + LOG_ERROR(Service_DLP, "Got unexpected content block request when client did " + "not request content previously"); + continue; + } + // since we have a global poll rate, + // we need to rate limit packet sending + // to individual clients + if (cl.ShouldRateLimit(dlp_poll_rate_normal)) { + continue; + } + // send finish dist req + auto s_body = PGen_SetPK(dl_pk_head_finish_dist_header, + 1, cl.GetPkRespId()); + s_body->initialized = true; + s_body->seq_num = 0; + PGen_SendPK(aes, cl.network_node_id, dlp_client_data_channel); + break; + } + case ClientState::DistributeDone: + case ClientState::SentPassphrase: { + if (cl.ShouldRateLimit(dlp_poll_rate_normal)) { + continue; + } + auto s_body = PGen_SetPK(dl_pk_head_start_game_header, 0, + cl.GetPkRespId()); + s_body->unk1 = 0x1; + s_body->unk2 = 0x9; + PGen_SendPK(aes, cl.network_node_id, dlp_client_data_channel); + break; + } + default: + LOG_ERROR(Service_DLP, "Unknown client state: {}", static_cast(cl.state)); + } + } + + // receive incoming response messages + std::vector recv_buf; + u16 recv_node = 0; + while (int sz = RecvFrom(dlp_bind_node_id, recv_buf, &recv_node)) { + auto p_head = GetPacketHead(recv_buf); + auto cl = GetClState(recv_node); + + if (!cl) { + LOG_ERROR(Service_DLP, "Could not get client state from received packet's node id"); + continue; + } + + // validate packet header + if (!ValidatePacket(aes, p_head, sz, should_verify_checksum)) { + LOG_ERROR(Service_DLP, "Could not validate DLP packet header"); + continue; + } + + if (p_head->type == dl_pk_type_auth) { + LOG_DEBUG(Service_DLP, "Recv auth"); + if (cl->state != ClientState::NeedsAuthAck) + continue; + auto r_pbody = GetPacketBody(recv_buf); + cl->state = ClientState::Accepted; + cl->resp_id = r_pbody->resp_id; + cl->IncrSeqNum(); + } else if (p_head->type == dl_pk_type_start_dist) { + LOG_DEBUG(Service_DLP, "Recv start dist"); + if (cl->state != ClientState::NeedsDistributeAck) + continue; + // packet needs to be at least the size of + // no_content_needed + auto r_pbody_nc = + GetPacketBody(recv_buf); + if (r_pbody_nc->unk2 == 0x20) { // check if we should upgrade to content_needed + [[maybe_unused]] auto r_pbody_c = + GetPacketBody(recv_buf); + cl->state = ClientState::NeedsContent; + srvr_state = DLP_Srvr_State::Distributing; + cl->current_content_block = 0; + cl->sent_next_block_req = false; + cl->can_download_next_block = true; + cl->dlp_units_total = + GetNumFragmentsFromTitleSize(title_broadcast_info.title_size); + cl->next_req_ack = 0; + } else { // keep no_content_needed + if (!r_pbody_nc->initialized) { + LOG_WARNING(Service_DLP, "Corrupted packet info"); + } + cl->sent_next_block_req = false; + cl->can_download_next_block = false; + // the reason we have this enum value is because + // we still need it to confirm that it finished + // "downloading" content + cl->state = ClientState::DoesNotNeedContent; + } + cl->IncrSeqNum(); + } else if (p_head->type == dl_pk_type_distribute) { + LOG_ERROR(Service_DLP, "Unexpected content distribution fragment"); + } else if (p_head->type == dl_pk_type_finish_dist) { + LOG_DEBUG(Service_DLP, "Recv finish dist"); + if (cl->state != ClientState::NeedsContent && + cl->state != ClientState::DoesNotNeedContent) { + continue; + } + if (cl->can_download_next_block) { + // got finish dist when we didn't need it + LOG_WARNING( + Service_DLP, + "Got finish dist when client was already queued to download next block"); + continue; + } + auto r_pbody = GetPacketBody(recv_buf); + if (!r_pbody->needs_content) { + LOG_INFO(Service_DLP, "Client has finished downloading content"); + cl->state = ClientState::DistributeDone; + cl->sent_next_block_req = false; + } else { + if (cl->current_content_block != r_pbody->seq_ack) { + if (r_pbody->finished_cur_block) { + LOG_WARNING(Service_DLP, + "Received out of order block request. Ignoring. ({} != {})", + static_cast(r_pbody->seq_ack), + cl->current_content_block); + } + cl->sent_next_block_req = false; + cl->IncrSeqNum(); // client expects us to increment here + continue; + } + } + cl->can_download_next_block = r_pbody->needs_content; + cl->next_req_ack++; + cl->IncrSeqNum(); + } else if (p_head->type == dl_pk_type_start_game) { + LOG_DEBUG(Service_DLP, "Recv start game"); + if (cl->state != ClientState::DistributeDone && + cl->state != ClientState::SentPassphrase) { + continue; + } + auto r_pbody = GetPacketBody(recv_buf); + if (r_pbody->unk2 != 0x9) { + LOG_WARNING(Service_DLP, "Client BeginGameAck unk2 is not 0x9 yet"); + continue; + } + cl->IncrSeqNum(); + } else { + LOG_ERROR(Service_DLP, "Unknown packet type: 0x{:x}", p_head->type); + } + } + + if (is_distributing && !any_needs_distribute()) { + is_distributing = false; + srvr_state = DLP_Srvr_State::NeedToSendPassphrase; // causes begingame to run + is_waiting_for_passphrase = true; + dlp_srvr_poll_rate_ms = dlp_poll_rate_normal; + } else if (is_waiting_for_passphrase && all_got_passphrase()) { + is_waiting_for_passphrase = false; + srvr_state = DLP_Srvr_State::Complete; + // we have to stop the server client manager thread + // right now to avoid a race condition where we're waiting + // for one client to leave, but another client joins our server again + // thinking it's a raw UDS server, so it shows up as a connecting client. + // and if we just ignored it, it wouldn't try to reconnect to our real server, + // meaning that not doing this would case an unrecoverable error. + // a real 3DS gets around this by using different RF channels and + // the built-in DLP server password, which will prevent the client from + // joining our server. + is_hosting = false; + + // force set all clients to disconnected state + for (auto& cl : client_states) { + cl.state = ClientState::NotJoined; + } + } + } + + uds->UnbindHLE(dlp_bind_node_id); + uds->DestroyNetworkHLE(); + + LOG_INFO(Service_DLP, "Ended"); +} + +void DLP_SRVR::SendAuthPacket(ClientState& cl) { + std::scoped_lock lock(client_states_mutex); + auto aes = GenDLPChecksumKey(host_mac_address); + auto s_body = PGen_SetPK(dl_pk_head_auth_header, 1, default_resp_id); + s_body->unk1 = 0x0; + PGen_SendPK(aes, cl.network_node_id, dlp_client_data_channel); + if (cl.state == ClientState::NeedsAuth) { + cl.state = ClientState::NeedsAuthAck; + } +} + +bool DLP_SRVR::SendNextCIAFragment(ClientState& cl, u8 block_frag_index) { + std::scoped_lock lock(client_states_mutex); + size_t cur_frag_index = cl.GetTotalFragIndex(block_frag_index); + size_t frag_offset = content_fragment_size * cur_frag_index; + if (frag_offset > distribution_content.size()) { + LOG_ERROR(Service_DLP, "Frag offset is larger than the CIA size"); + return false; + } + u16 frag_size = + std::min(content_fragment_size, distribution_content.size() - frag_offset); + if (frag_offset + frag_size > distribution_content.size()) { + LOG_ERROR(Service_DLP, "Frag size is too large to properly fit the CIA content"); + return false; + } + auto aes = GenDLPChecksumKey(host_mac_address); + std::span send_frag{distribution_content.begin() + frag_offset, frag_size}; + // uses default resp id because it's supposed to be broadcast + auto s_body = PGen_SetPK(dl_pk_head_distribute_header, 1, + default_resp_id); + PGen_AddPacketData(s_body, frag_size); + + // only checks if the low byte is 0x1 + s_body->unk2 = 0x1; + s_body->padding = {0x8A, 0x4A, 0xE3}; + s_body->content_block = cl.current_content_block; + s_body->frag_index = block_frag_index; + s_body->frag_size = frag_size; + memcpy(s_body->content_fragment, send_frag.data(), frag_size); + + PGen_SendPK(aes, cl.network_node_id, dlp_client_data_channel); + return true; +} + +void DLP_SRVR::EndConnectionManager() { + is_hosting = false; + if (server_connection_worker.joinable()) { + server_connection_worker.join(); + } +} + +// make sure to lock the client states mutex before +// calling this +DLP_SRVR::ClientState* DLP_SRVR::GetClState(u8 node_id, bool should_error) { + u8 cl_state_index = node_id - first_client_node_id; + if (cl_state_index >= client_states.size() && should_error) { + LOG_CRITICAL(Service_DLP, "Out of range node id {}", node_id); + return nullptr; + } + return &client_states[cl_state_index]; +} + +// can cause race conditions, only use this +// in places where the core loop isn't running +void DLP_SRVR::EnsureEndBroadcaster() { + std::scoped_lock lock(broadcast_mutex); + is_broadcasting = false; + system.CoreTiming().UnscheduleEvent(title_broadcast_event, 0); +} + DLP_SRVR::DLP_SRVR() : ServiceFramework("dlp:SRVR", 1), DLP_Base(Core::System::GetInstance()) { static const FunctionInfo functions[] = { // clang-format off - {0x0001, nullptr, "Initialize"}, - {0x0002, nullptr, "Finalize"}, - {0x0003, nullptr, "GetServerState"}, - {0x0004, nullptr, "GetEventDescription"}, - {0x0005, nullptr, "StartAccepting"}, - {0x0006, nullptr, "EndAccepting"}, - {0x0007, nullptr, "StartDistribution"}, - {0x0008, nullptr, "SendWirelessRebootPassphrase"}, - {0x0009, nullptr, "AcceptClient"}, - {0x000A, nullptr, "DisconnectClient"}, - {0x000B, nullptr, "GetConnectingClients"}, - {0x000C, nullptr, "GetClientInfo"}, - {0x000D, nullptr, "GetClientState"}, + {0x0001, &DLP_SRVR::Initialize, "Initialize"}, + {0x0002, &DLP_SRVR::Finalize, "Finalize"}, + {0x0003, &DLP_SRVR::GetServerState, "GetServerState"}, + {0x0004, &DLP_SRVR::GetEventDescription, "GetEventDescription"}, + {0x0005, &DLP_SRVR::StartHosting, "StartHosting"}, + {0x0006, &DLP_SRVR::EndHosting, "EndHosting"}, + {0x0007, &DLP_SRVR::StartDistribution, "StartDistribution"}, + {0x0008, &DLP_SRVR::BeginGame, "BeginGame"}, + {0x0009, &DLP_SRVR::AcceptClient, "AcceptClient"}, + {0x000A, &DLP_SRVR::DisconnectClient, "DisconnectClient"}, + {0x000B, &DLP_SRVR::GetConnectingClients, "GetConnectingClients"}, + {0x000C, &DLP_SRVR::GetClientInfo, "GetClientInfo"}, + {0x000D, &DLP_SRVR::GetClientState, "GetClientState"}, {0x000E, &DLP_SRVR::IsChild, "IsChild"}, - {0x000F, nullptr, "InitializeWithName"}, - {0x0010, nullptr, "GetDupNoticeNeed"}, + {0x000F, &DLP_SRVR::InitializeWithName, "InitializeWithName"}, + {0x0010, &DLP_SRVR::GetDupNoticeNeed, "GetDupNoticeNeed"}, // clang-format on }; RegisterHandlers(functions); + + title_broadcast_event = system.CoreTiming().RegisterEvent( + "dlp:SRVR::TitleBroadcastCallback", [this](std::uintptr_t user_data, s64 cycles_late) { + TitleBroadcastCallback(user_data, cycles_late); + }); +} + +DLP_SRVR::~DLP_SRVR() { + EnsureEndBroadcaster(); + EndConnectionManager(); } } // namespace Service::DLP diff --git a/src/core/hle/service/dlp/dlp_srvr.h b/src/core/hle/service/dlp/dlp_srvr.h index d9b4fdd42..13f2d5e63 100644 --- a/src/core/hle/service/dlp/dlp_srvr.h +++ b/src/core/hle/service/dlp/dlp_srvr.h @@ -4,15 +4,30 @@ #pragma once +#include "common/timer.h" #include "core/hle/service/service.h" #include "dlp_base.h" +namespace Service::FS { +class FS_USER; +}; // namespace Service::FS + namespace Service::DLP { +enum class DLP_Srvr_State : u8 { + NotInitialized = 0, + Initialized = 1, + Accepting = 2, + WaitingForDistribution = 6, // server is processing clients' needs + Distributing = 7, + NeedToSendPassphrase = 8, // finished distribution + Complete = 9, +}; + class DLP_SRVR final : public ServiceFramework, public DLP_Base { public: DLP_SRVR(); - ~DLP_SRVR() = default; + ~DLP_SRVR(); virtual std::shared_ptr GetServiceFrameworkSharedPtr(); virtual bool IsHost() { @@ -20,9 +35,161 @@ public: } private: + std::shared_ptr GetFS(); + + std::recursive_mutex srvr_state_mutex; + DLP_Srvr_State srvr_state = DLP_Srvr_State::NotInitialized; + u8 max_clients; + std::vector distribution_content; + bool manual_accept; + constexpr static inline int title_broadcast_interval_ms = 500; + constexpr static inline u32 dlp_poll_rate_distribute = 1; + const u16 broadcast_node_id = 0xFFFF; + bool is_broadcasting; + std::mutex broadcast_mutex; + const std::array default_resp_id{}; + Network::MacAddress host_mac_address; + + std::atomic_int dlp_srvr_poll_rate_ms; + + struct TitleBroadcastInfo { + u64 title_id; + u64 title_size; + u64 transfer_size; + u64 required_size; + std::array icon; + std::array title_short; + std::array title_long; + } title_broadcast_info; + + std::atomic_bool is_distributing; + std::atomic_bool is_waiting_for_passphrase; + + struct ClientState { + ClientState() { + rate_timer.Start(); + } + bool needs_content_download; + bool can_download_next_block; + bool sent_next_block_req; + bool is_accepted; + u8 pk_seq_num; + void IncrSeqNum() { + pk_seq_num++; + } + Common::Timer rate_timer; + bool ShouldRateLimit(int ms) { + if (rate_timer.GetTimeDifference().count() < ms) { + return true; + } + rate_timer.Update(); + return false; + } + u32 dlp_units_total; + u32 GetDlpUnitsDownloaded() { + return current_content_block * dlp_content_block_length; + } + u32 current_content_block; + u32 GetTotalFragIndex(u8 block_frag_index) { + return current_content_block * dlp_content_block_length + block_frag_index; + } + u8 network_node_id; + std::array resp_id; + u16 next_req_ack; + + std::array GetPkRespId() { + return {pk_seq_num, resp_id[0], resp_id[1]}; + } + u32 GetMyState() { + DLP_Clt_State state_lle; + switch (state) { + case NotJoined: + LOG_WARNING(Service_DLP, "Trying to get LLE state of client that is not joined"); + state_lle = DLP_Clt_State::Initialized; + break; + case NeedsAuth: + case NeedsAuthAck: + state_lle = DLP_Clt_State::WaitingForAccept; + break; + case Accepted: + state_lle = DLP_Clt_State::Joined; + break; + case NeedsDistributeAck: + case NeedsContent: + case DoesNotNeedContent: + state_lle = DLP_Clt_State::Downloading; + break; + case DistributeDone: + state_lle = DLP_Clt_State::WaitingForServerReady; + break; + case SentPassphrase: + state_lle = DLP_Clt_State::Complete; + break; + default: + LOG_ERROR(Service_DLP, "Unknown client state"); + break; + } + + bool is_joined = state != NotJoined && state != NeedsAuth && state != NeedsAuthAck; + + return static_cast(state_lle) << 24 | is_joined << 16 | network_node_id; + } + + // could be used in getclientstate + enum : u8 { + NotJoined = 0, + NeedsAuth, + NeedsAuthAck, // protects us from clients authing themselves + Accepted, + NeedsDistributeAck, + NeedsContent, + DoesNotNeedContent, + DistributeDone, + SentPassphrase, + } state; + }; + + std::vector client_states; + std::recursive_mutex client_states_mutex; + + ClientState* GetClState(u8 node_id, bool should_error = true); + + Core::TimingEventType* title_broadcast_event; + + std::atomic_bool is_hosting; + std::thread server_connection_worker; + + void ServerConnectionManager(); + void EndConnectionManager(); + void SendAuthPacket(ClientState& cl); + bool SendNextCIAFragment(ClientState& cl, u8 block_frag_index); + + u8 GetSrvrState(); + void InitializeSrvrCommon(u32 shared_mem_size, u8 max_clnts, u32 process_id, DLP_Username uname, + std::shared_ptr shared_mem, + std::shared_ptr event); + bool CacheContentFileInMemory(u32 process_id); + void TitleBroadcastCallback(std::uintptr_t user_data, s64 cycles_late); + void EnsureEndBroadcaster(); + void IsChild(Kernel::HLERequestContext& ctx); + void GetDupNoticeNeed(Kernel::HLERequestContext& ctx); + void GetServerState(Kernel::HLERequestContext& ctx); + void Initialize(Kernel::HLERequestContext& ctx); + void InitializeWithName(Kernel::HLERequestContext& ctx); + void Finalize(Kernel::HLERequestContext& ctx); + void StartHosting(Kernel::HLERequestContext& ctx); + void EndHosting(Kernel::HLERequestContext& ctx); + void GetConnectingClients(Kernel::HLERequestContext& ctx); + void GetClientInfo(Kernel::HLERequestContext& ctx); + void GetClientState(Kernel::HLERequestContext& ctx); + void StartDistribution(Kernel::HLERequestContext& ctx); + void BeginGame(Kernel::HLERequestContext& ctx); + void AcceptClient(Kernel::HLERequestContext& ctx); + void DisconnectClient(Kernel::HLERequestContext& ctx); SERVICE_SERIALIZATION_SIMPLE + friend class ClientState; }; } // namespace Service::DLP diff --git a/src/core/hle/service/fs/fs_user.h b/src/core/hle/service/fs/fs_user.h index bdce1b7eb..f94f318f2 100644 --- a/src/core/hle/service/fs/fs_user.h +++ b/src/core/hle/service/fs/fs_user.h @@ -82,6 +82,10 @@ public: secure_value_backend = backend; } + static ResultVal GetSpecialContentIndexFromGameCard(u64 title_id, SpecialContentType type); + static ResultVal GetSpecialContentIndexFromTMD(MediaType media_type, u64 title_id, + SpecialContentType type); + private: void Initialize(Kernel::HLERequestContext& ctx); @@ -740,10 +744,6 @@ private: */ void GetSaveDataSecureValue(Kernel::HLERequestContext& ctx); - static ResultVal GetSpecialContentIndexFromGameCard(u64 title_id, SpecialContentType type); - static ResultVal GetSpecialContentIndexFromTMD(MediaType media_type, u64 title_id, - SpecialContentType type); - std::unordered_map program_info_map; std::string current_gamecard_path; diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index fadbe3f3e..0f7e9c976 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp @@ -9,6 +9,7 @@ #include #include "common/archives.h" #include "common/common_types.h" +#include "common/hacks/hack_manager.h" #include "common/logging/log.h" #include "common/settings.h" #include "core/core.h" @@ -355,7 +356,7 @@ void NWM_UDS::HandleSecureDataPacket(const Network::WifiPacket& packet) { if (connection_status.status != NetworkStatus::ConnectedAsHost && connection_status.status != NetworkStatus::ConnectedAsClient && connection_status.status != NetworkStatus::ConnectedAsSpectator) { - LOG_DEBUG(Service_NWM, "Ignored SecureDataPacket because connection status is {}", + LOG_TRACE(Service_NWM, "Ignored SecureDataPacket because connection status is {}", static_cast(connection_status.status)); return; } @@ -716,6 +717,19 @@ ResultVal> NWM_UDS::Initialize( return connection_status_event; } +// allows people who haven't set up their +// 3ds to play local play on games which +// require a unique friend code seed +void NWM_UDS::CheckSpoofFriendCodeSeed(Kernel::HLERequestContext& ctx, NodeInfo& node) { + u64 caller_tid = ctx.ClientThread()->owner_process.lock()->codeset->program_id; + if (Common::Hacks::hack_manager.GetHackAllowMode( + Common::Hacks::HackType::SPOOF_FRIEND_CODE_SEED, caller_tid, + Common::Hacks::HackAllowMode::DISALLOW) == Common::Hacks::HackAllowMode::FORCE) { + auto mac_address = GetMacAddress(); + memcpy(&node.friend_code_seed, mac_address.data(), mac_address.size()); + } +} + void NWM_UDS::InitializeWithVersion(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 sharedmem_size = rp.Pop(); @@ -723,6 +737,8 @@ void NWM_UDS::InitializeWithVersion(Kernel::HLERequestContext& ctx) { u16 version = rp.Pop(); auto sharedmem = rp.PopObject(); + CheckSpoofFriendCodeSeed(ctx, node); + auto result = Initialize(sharedmem_size, node, version, std::move(sharedmem)); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); @@ -739,6 +755,8 @@ void NWM_UDS::InitializeDeprecated(Kernel::HLERequestContext& ctx) { auto node = rp.PopRaw(); auto sharedmem = rp.PopObject(); + CheckSpoofFriendCodeSeed(ctx, node); + // The deprecated version uses fixed 0x100 as the version auto result = Initialize(sharedmem_size, node, 0x100, std::move(sharedmem)); @@ -960,13 +978,8 @@ Result NWM_UDS::BeginHostingNetwork(std::span network_info_buffer, // Notify the application that the first node was set. connection_status.changed_nodes |= 1; - if (auto room_member = Network::GetRoomMember().lock()) { - if (room_member->IsConnected()) { - network_info.host_mac_address = room_member->GetMacAddress(); - } else { - network_info.host_mac_address = {{0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; - } - } + network_info.host_mac_address = GetMacAddress(); + node_info[0] = current_node; // If the game has a preferred channel, use that instead. @@ -1021,33 +1034,21 @@ void NWM_UDS::BeginHostingNetworkDeprecated(Kernel::HLERequestContext& ctx) { rb.Push(result); } -void NWM_UDS::EjectClient(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx); - const u16 network_node_id = rp.Pop(); - - LOG_WARNING(Service_NWM, "(stubbed) called"); - - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - +Result NWM_UDS::EjectClientHLE(u16 network_node_id) { // The host can not be kicked. if (network_node_id == 1) { - rb.Push(Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, - ErrorSummary::WrongArgument, ErrorLevel::Usage)); - return; + return Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, + ErrorSummary::WrongArgument, ErrorLevel::Usage); } std::scoped_lock lock(connection_status_mutex); if (connection_status.status != NetworkStatus::ConnectedAsHost) { // Only the host can kick people. - rb.Push(Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, - ErrorSummary::InvalidState, ErrorLevel::Usage)); LOG_WARNING(Service_NWM, "called with status {}", connection_status.status); - return; + return Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, ErrorSummary::InvalidState, + ErrorLevel::Usage); } - // This function always returns success if the status is valid. - rb.Push(ResultSuccess); - using Network::WifiPacket; Network::MacAddress dest_address = Network::BroadcastMac; @@ -1056,7 +1057,7 @@ void NWM_UDS::EjectClient(Kernel::HLERequestContext& ctx) { if (!address) { // There is no error if the network node id was not found. - return; + return ResultSuccess; } dest_address = *address; } @@ -1070,6 +1071,21 @@ void NWM_UDS::EjectClient(Kernel::HLERequestContext& ctx) { SendPacket(deauth); SendPacket(deauth); } + + // This function always returns success if the status is valid. + return ResultSuccess; +} + +void NWM_UDS::EjectClient(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const u16 network_node_id = rp.Pop(); + + LOG_WARNING(Service_NWM, "(stubbed) called"); + + auto res = EjectClientHLE(network_node_id); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(res); } Result NWM_UDS::UpdateNetworkAttributeHLE(u16 node_bitmask, u8 flag) { @@ -1092,21 +1108,17 @@ void NWM_UDS::UpdateNetworkAttribute(Kernel::HLERequestContext& ctx) { rb.Push(ResultSuccess); } -void NWM_UDS::DestroyNetwork(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx); - +Result NWM_UDS::DestroyNetworkHLE() { // Unschedule the beacon broadcast event. system.CoreTiming().UnscheduleEvent(beacon_broadcast_event, 0); // Only a host can destroy std::scoped_lock lock(connection_status_mutex); if (connection_status.status != NetworkStatus::ConnectedAsHost) { - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(Result(ErrCodes::WrongStatus, ErrorModule::UDS, ErrorSummary::InvalidState, - ErrorLevel::Status)); LOG_WARNING(Service_NWM, "called with status {}", static_cast(connection_status.status)); - return; + return Result(ErrCodes::WrongStatus, ErrorModule::UDS, ErrorSummary::InvalidState, + ErrorLevel::Status); } // TODO(B3N30): Send 3 Deauth packets @@ -1118,14 +1130,22 @@ void NWM_UDS::DestroyNetwork(Kernel::HLERequestContext& ctx) { node_map.clear(); connection_status_event->Signal(); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - for (auto& bind_node : channel_data) { bind_node.second.event->Signal(); } channel_data.clear(); - rb.Push(ResultSuccess); + return ResultSuccess; +} + +void NWM_UDS::DestroyNetwork(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + auto res = DestroyNetworkHLE(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + rb.Push(res); LOG_DEBUG(Service_NWM, "called"); } @@ -1266,7 +1286,7 @@ void NWM_UDS::PullPacket(Kernel::HLERequestContext& ctx) { IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); rb.Push(ResultSuccess); - rb.Push(*ret); // return is data size if gt/eq to 0 + rb.Push(*ret); rb.Push(secure_data.src_node_id); rb.PushStaticBuffer(std::move(output_buffer), 0); } @@ -1621,6 +1641,25 @@ void NWM_UDS::BeaconBroadcastCallback(std::uintptr_t user_data, s64 cycles_late) beacon_broadcast_event, 0); } +Network::MacAddress NWM_UDS::GetMacAddress() { + MacAddress mac; + + if (auto room_member = Network::GetRoomMember().lock(); + room_member && room_member->IsConnected()) { + mac = room_member->GetMacAddress(); + if (mac != CFG::GetConsoleMacAddress(system)) { + LOG_WARNING(Service_NWM, "Room member mac address is different from the console mac " + "address. Using room member mac address."); + } + } else { + // if we are not connected to the room, we can + // use the system mac address. In hopefully all cases + // this will match the room member mac addr anyways + mac = CFG::GetConsoleMacAddress(system); + } + return mac; +} + NWM_UDS::NWM_UDS(Core::System& system) : ServiceFramework("nwm::UDS"), system(system) { static const FunctionInfo functions[] = { // clang-format off @@ -1665,20 +1704,7 @@ NWM_UDS::NWM_UDS(Core::System& system) : ServiceFramework("nwm::UDS"), system(sy BeaconBroadcastCallback(user_data, cycles_late); }); - MacAddress mac; - - if (auto cfg = system.ServiceManager().GetService("cfg:u")) { - auto cfg_module = cfg->GetModule(); - mac = Service::CFG::MacToArray(cfg_module->GetMacAddress()); - } - - if (auto room_member = Network::GetRoomMember().lock()) { - if (room_member->IsConnected()) { - mac = room_member->GetMacAddress(); - } - } - - system.Kernel().GetSharedPageHandler().SetMacAddress(mac); + system.Kernel().GetSharedPageHandler().SetMacAddress(GetMacAddress()); if (auto room_member = Network::GetRoomMember().lock()) { wifi_packet_received = room_member->BindOnWifiPacketReceived( diff --git a/src/core/hle/service/nwm/nwm_uds.h b/src/core/hle/service/nwm/nwm_uds.h index feefe8a86..560c38582 100644 --- a/src/core/hle/service/nwm/nwm_uds.h +++ b/src/core/hle/service/nwm/nwm_uds.h @@ -117,7 +117,7 @@ struct ConnectionStatus { static_assert(sizeof(ConnectionStatus) == 0x30, "ConnectionStatus has incorrect size."); struct NetworkInfo { - std::array host_mac_address; + MacAddress host_mac_address; u8 channel; u8 unk1; u8 initialized; @@ -479,6 +479,8 @@ private: */ void DecryptBeaconData(Kernel::HLERequestContext& ctx); + void CheckSpoofFriendCodeSeed(Kernel::HLERequestContext& ctx, NodeInfo& node); + ResultVal> Initialize( u32 sharedmem_size, const NodeInfo& node, u16 version, std::shared_ptr sharedmem); @@ -499,6 +501,8 @@ private: ResultStatus SendToHLE(u32 dest_node_id, u8 data_channel, u32 data_size, u8 flags, std::vector input_buffer); Result UpdateNetworkAttributeHLE(u16 bitmask, u8 flag); + Result DestroyNetworkHLE(); + Result EjectClientHLE(u16 node_id); Result BeginHostingNetwork(std::span network_info_buffer, std::vector passphrase); @@ -508,6 +512,8 @@ private: void ConnectToNetworkHLE(NetworkInfo net_info, u8 connection_type, std::vector passphrase); + Network::MacAddress GetMacAddress(); + void BeaconBroadcastCallback(std::uintptr_t user_data, s64 cycles_late); /** diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 26fee2fc4..59971588c 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -78,7 +78,7 @@ const std::array service_module_map{ false}, {"CECD", 0x00040130'00002602, CECD::InstallInterfaces, true}, {"CFG", 0x00040130'00001702, CFG::InstallInterfaces, false}, - {"DLP", 0x00040130'00002802, DLP::InstallInterfaces, true}, + {"DLP", 0x00040130'00002802, DLP::InstallInterfaces, false}, {"DSP", 0x00040130'00001A02, DSP::InstallInterfaces, false}, {"FRD", 0x00040130'00003202, FRD::InstallInterfaces, true}, {"GSP", 0x00040130'00001C02, GSP::InstallInterfaces, false},