diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f069ff33d..a5abbc585 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -293,6 +293,11 @@ add_library(citra_core STATIC hle/service/dlp/dlp_fkcl.h hle/service/dlp/dlp_srvr.cpp hle/service/dlp/dlp_srvr.h + hle/service/dlp/dlp_clt_base.cpp + hle/service/dlp/dlp_clt_base.h + hle/service/dlp/dlp_base.cpp + hle/service/dlp/dlp_base.h + hle/service/dlp/dlp_crypto.cpp hle/service/dsp/dsp_dsp.cpp hle/service/dsp/dsp_dsp.h hle/service/err/err_f.cpp diff --git a/src/core/file_sys/ncch_container.cpp b/src/core/file_sys/ncch_container.cpp index 1438a766a..382c8943e 100644 --- a/src/core/file_sys/ncch_container.cpp +++ b/src/core/file_sys/ncch_container.cpp @@ -138,7 +138,7 @@ Loader::ResultStatus NCCHContainer::LoadHeader() { return Loader::ResultStatus::Success; } - if (!file->IsOpen()) { + if (!file || !file->IsOpen()) { return Loader::ResultStatus::Error; } @@ -205,6 +205,9 @@ Loader::ResultStatus NCCHContainer::Load() { if (is_loaded) return Loader::ResultStatus::Success; + if (!file) + return Loader::ResultStatus::Error; + int block_size = kBlockSize; if (file->IsOpen()) { @@ -621,7 +624,7 @@ Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr& romf return Loader::ResultStatus::ErrorNotUsed; } - if (!file->IsOpen()) + if (!file || !file->IsOpen()) return Loader::ResultStatus::Error; u32 romfs_offset = ncch_offset + (ncch_header.romfs_offset * block_size) + 0x1000; @@ -767,6 +770,9 @@ bool NCCHContainer::HasExHeader() { std::unique_ptr NCCHContainer::Reopen( const std::unique_ptr& orig_file, const std::string& new_filename) { + if (!orig_file) + return nullptr; + const bool is_compressed = orig_file->IsCompressed(); const bool is_crypto = orig_file->IsCrypto(); const std::string filename = new_filename.empty() ? orig_file->Filename() : new_filename; diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 645a015b6..af2739590 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -417,6 +417,10 @@ void AuthorizeCIAFileDecryption(CIAFile* cia_file, Kernel::HLERequestContext& ct } } +void CIAFile::AuthorizeDecryptionFromHLE() { + decryption_authorized = true; +} + CIAFile::CIAFile(Core::System& system_, Service::FS::MediaType media_type, bool from_cdn_) : system(system_), from_cdn(from_cdn_), decryption_authorized(false), media_type(media_type), decryption_state(std::make_unique()) { @@ -873,6 +877,7 @@ bool CIAFile::Close() { // Only delete the content folder as there may be user save data in the title folder. const std::string title_content_path = GetTitlePath(media_type, container.GetTitleMetadata().GetTitleID()) + "content/"; + current_content_file.reset(); FileUtil::DeleteDirRecursively(title_content_path); } return true; diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 4dec69e80..96258ecf9 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -233,8 +233,11 @@ public: return install_results; } + void AuthorizeDecryptionFromHLE(); + private: friend void AuthorizeCIAFileDecryption(CIAFile* cia_file, Kernel::HLERequestContext& ctx); + Core::System& system; // Sections (tik, tmd, contents) are being imported individually diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index fcc9b2a89..7b2dfeb19 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -39,6 +39,8 @@ SERVICE_CONSTRUCT_IMPL(Service::APT::Module) namespace Service::APT { +constexpr u32 max_wireless_reboot_info_size = 0x10; + template void Module::serialize(Archive& ar, const unsigned int file_version) { DEBUG_SERIALIZATION_POINT; @@ -64,7 +66,7 @@ std::shared_ptr Module::NSInterface::GetModule() const { void Module::NSInterface::SetWirelessRebootInfo(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - const auto size = rp.Pop(); + const auto size = std::min(rp.Pop(), max_wireless_reboot_info_size); const auto buffer = rp.PopStaticBuffer(); apt->wireless_reboot_info = std::move(buffer); diff --git a/src/core/hle/service/dlp/dlp.cpp b/src/core/hle/service/dlp/dlp.cpp index edbd22321..c0565d330 100644 --- a/src/core/hle/service/dlp/dlp.cpp +++ b/src/core/hle/service/dlp/dlp.cpp @@ -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. diff --git a/src/core/hle/service/dlp/dlp.h b/src/core/hle/service/dlp/dlp.h index 28fb73718..ea34e83e2 100644 --- a/src/core/hle/service/dlp/dlp.h +++ b/src/core/hle/service/dlp/dlp.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. diff --git a/src/core/hle/service/dlp/dlp_base.cpp b/src/core/hle/service/dlp/dlp_base.cpp new file mode 100644 index 000000000..a2e22212f --- /dev/null +++ b/src/core/hle/service/dlp/dlp_base.cpp @@ -0,0 +1,251 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "dlp_base.h" + +#include +#include +#include "common/alignment.h" +#include "common/swap.h" +#include "common/timer.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/nwm/uds_data.h" +#include "core/hle/service/service.h" +#include "core/hw/aes/key.h" +#include "core/hw/unique_data.h" + +namespace Service::DLP { + +DLP_Base::DLP_Base(Core::System& s) : system(s) {} + +std::shared_ptr DLP_Base::GetCFG() { + return Service::CFG::GetModule(system); +} + +std::shared_ptr DLP_Base::GetUDS() { + return system.ServiceManager().GetService("nwm::UDS"); +} + +std::u16string DLP_Base::DLPUsernameAsString16(DLP_Username uname) { + std::u16string strUsername; + for (auto c : uname) { + strUsername.push_back(c); + } + return strUsername; +} + +DLP_Username DLP_Base::String16AsDLPUsername(std::u16string str) { + DLP_Username out{}; + u32 num_chars_copy = std::min(out.size(), str.size()); + memcpy(out.data(), str.data(), num_chars_copy * sizeof(u16_le)); + return out; +} + +DLPNodeInfo DLP_Base::UDSToDLPNodeInfo(NWM::NodeInfo node_info) { + DLPNodeInfo out{}; + out.username = node_info.username; + out.network_node_id = node_info.network_node_id; + out.friend_code_seed = node_info.friend_code_seed; + return out; +} + +void DLP_Base::GetEventDescription(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + LOG_WARNING(Service_DLP, "(STUBBED) called"); + + DLPEventDescription desc{}; + + IPC::RequestBuilder rb = rp.MakeBuilder(8, 0); + + rb.Push(ResultSuccess); + rb.PushRaw(desc); +} + +void DLP_Base::InitializeDlpBase(u32 shared_mem_size, + std::shared_ptr shared_mem, + std::shared_ptr event, DLP_Username uname) { + dlp_sharedmem_size = shared_mem_size; + dlp_sharedmem = shared_mem; + dlp_status_event = event; + username = uname; + + uds_sharedmem = + system.Kernel() + .CreateSharedMemory(nullptr, uds_sharedmem_size, Kernel::MemoryPermission::ReadWrite, + Kernel::MemoryPermission::ReadWrite, 0, Kernel::MemoryRegion::BASE, + "NWM::UDS:SharedMemory") + .Unwrap(); + + NWM::NodeInfo cnode_info{ + .friend_code_seed = HW::UniqueData::GetLocalFriendCodeSeedB().body.friend_code_seed, + .username = uname, + }; + GetUDS()->Initialize(uds_sharedmem_size, cnode_info, uds_version, uds_sharedmem); +} + +void DLP_Base::FinalizeDlpBase() { + GetUDS()->ShutdownHLE(); + dlp_sharedmem.reset(); + uds_sharedmem.reset(); + dlp_status_event.reset(); + username = DLP_Username{}; +} + +bool DLP_Base::ConnectToNetworkAsync(NWM::NetworkInfo net_info, NWM::ConnectionType conn_type, + std::vector passphrase) { + auto uds = GetUDS(); + + // we need to make this event manually + uds->connection_event = + system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "dlp_connect_to_beacon"); + + uds->ConnectToNetworkHLE(net_info, static_cast(conn_type), passphrase); + + // wait for connection + Common::Timer t_time_out; + t_time_out.Start(); + bool timed_out = false; + while (true) { // busy wait, TODO: change to not busy wait? + if (uds->GetConnectionStatusHLE().status == NWM::NetworkStatus::ConnectedAsSpectator || + uds->GetConnectionStatusHLE().status == NWM::NetworkStatus::ConnectedAsClient) { + // connected + break; + } + constexpr u32 connect_network_timeout_ms = 3000; + if (t_time_out.GetTimeElapsed().count() > connect_network_timeout_ms) { + timed_out = true; + break; + } + } + + if (timed_out) { + // TODO: fix unlikely race cond, timeout happens, we disconnect, then server registers our + // connection + uds->DisconnectNetworkHLE(); + LOG_ERROR(Service_DLP, "Timed out when trying to connect to beacon"); + return false; + } + + if (uds->GetConnectionStatusHLE().status != NWM::NetworkStatus::ConnectedAsSpectator && + uds->GetConnectionStatusHLE().status != NWM::NetworkStatus::ConnectedAsClient) { + // error! + LOG_ERROR(Service_DLP, "Could not connect to network, connected as 0x{:x}", + static_cast(uds->GetConnectionStatusHLE().status)); + return false; + } + + return true; +} + +int DLP_Base::RecvFrom(u16 node_id, std::vector& buffer) { + constexpr u32 max_pullpacket_size = 0x3c00; + std::vector buffer_out; + + NWM::SecureDataHeader secure_data; + auto uds = GetUDS(); + if (!uds) { + LOG_ERROR(Service_DLP, "Could not get get pointer to UDS service!"); + return 0; + } + auto ret = + uds->PullPacketHLE(node_id, max_pullpacket_size, static_cast(max_pullpacket_size) >> 2, + buffer_out, &secure_data); + + if (!ret) { + return 0; + } + + buffer = buffer_out; + return *ret; // size +} + +bool DLP_Base::SendTo(u16 node_id, u8 data_channel, std::vector& buffer, u8 flags) { + constexpr u32 max_sendto_size = 0x3c00; + + if (buffer.size() > max_sendto_size) { + LOG_WARNING(Service_DLP, "Packet size is larger than 0x{:x}", max_sendto_size); + } + + return GetUDS()->SendToHLE(node_id, data_channel, buffer.size(), flags, buffer) == + NWM::ResultStatus::ResultSuccess; +} + +u32 DLP_Base::GeneratePKChecksum(u32 aes_value, void* _input_buffer, u32 packet_size) { + auto input_buffer = reinterpret_cast(_input_buffer); + + u32 working_hash = 0; + // add all word aligned bytes + for (u32 i = 0; i < packet_size / sizeof(u32); i++) { + u32 inp_buf_word = reinterpret_cast(input_buffer)[i]; + working_hash += Common::swap32(inp_buf_word); + } + // add any remaining non word-aligned bytes + if (u32 num_bytes_non_aligned = packet_size & 3; num_bytes_non_aligned != 0) { + u32 non_aligned = 0; + memcpy(&non_aligned, input_buffer + packet_size - num_bytes_non_aligned, + num_bytes_non_aligned); + working_hash += Common::swap32(non_aligned); + } + // hash by the aes value + u8 num_extra_hash = (reinterpret_cast(&aes_value)[3] & 0b0111) + 2; + u8 num_shift_extra_hash = (reinterpret_cast(&aes_value)[2] & 0b1111) + 4; + u32 aes_swap = Common::swap32(aes_value); + for (u8 i = 0; i < num_extra_hash; i++) { + working_hash = + (working_hash >> num_shift_extra_hash | working_hash << num_shift_extra_hash) ^ + aes_swap; + } + return Common::swap32(working_hash); +} + +u32 DLP_Base::GenDLPChecksumKey(Network::MacAddress mac_addr) { + auto dlp_iv_ctr_buf = HW::AES::GetDlpChecksumModIv(); + + std::array ctr_encrypt_buf{}; + for (u32 i = 0; i < 0x10; i++) { + ctr_encrypt_buf[i] = mac_addr[i % 6] ^ dlp_iv_ctr_buf[i]; + } + + u32 val_out = 0; + DLPEncryptCTR(&val_out, sizeof(val_out), ctr_encrypt_buf.data()); + return val_out; +} + +bool DLP_Base::ValidatePacket(u32 aes, void* pk, size_t sz, bool checksum) { + if (sz < sizeof(DLPPacketHeader)) { + LOG_ERROR(Service_DLP, "Packet size is too small"); + return false; + } + + auto ph = reinterpret_cast(pk); + + if (ph->size != sz) { + LOG_ERROR(Service_DLP, "Packet size in header does not match size received"); + return false; + } + + if (checksum) { + std::vector pk_copy; + pk_copy.resize(sz); + memcpy(pk_copy.data(), pk, sz); + + auto ph_cpy = reinterpret_cast(pk_copy.data()); + ph_cpy->checksum = 0; + u32 new_checksum = GeneratePKChecksum(aes, pk_copy.data(), pk_copy.size()); + if (new_checksum != ph->checksum) { + LOG_ERROR(Service_DLP, "Could not verify packet checksum 0x{:x} != 0x{:x}", + new_checksum, ph->checksum); + return false; + } + } + return true; +} + +u32 DLP_Base::GetNumFragmentsFromTitleSize(u32 tsize) { + return Common::AlignUp(tsize - broad_title_size_diff, content_fragment_size) / + content_fragment_size; +} + +} // namespace Service::DLP diff --git a/src/core/hle/service/dlp/dlp_base.h b/src/core/hle/service/dlp/dlp_base.h new file mode 100644 index 000000000..6708937f9 --- /dev/null +++ b/src/core/hle/service/dlp/dlp_base.h @@ -0,0 +1,387 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/core.h" +#include "core/hle/service/cfg/cfg.h" +#include "core/hle/service/nwm/nwm_uds.h" +#include "core/hle/service/service.h" + +#include + +// DLP save states are not supported + +namespace Service::DLP { + +using DLP_Username = std::array; +constexpr inline u64 DLP_CHILD_TID_HIGH = 0x0004000100000000; +constexpr inline u32 content_fragment_size = 1440; + +struct DLPTitleInfo { + u32 unique_id; // games look at this to make sure it's their title info + u32 variation; + 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 + u32 size; + u8 unk2; + u8 unk3; + u16 padding; + std::vector ToBuffer() { + std::vector out; + out.resize(sizeof(DLPTitleInfo)); + memcpy(out.data(), this, sizeof(DLPTitleInfo)); + return out; + } +}; + +static_assert(sizeof(DLPTitleInfo) == 5032, "DLPTitleInfo is the wrong size"); + +struct DLPNodeInfo { + u64 friend_code_seed; + std::array pad; + DLP_Username username; + u32 unk1; + u32 network_node_id; +}; + +static_assert(sizeof(DLPNodeInfo) == 0x28); + +struct DLPEventDescription { + std::array unk; +}; + +static_assert(sizeof(DLPEventDescription) == 0x18); + +// START BIG ENDIAN + +constexpr inline u8 dl_pk_type_broadcast = 0x01; +constexpr inline u8 dl_pk_type_auth = 0x02; +constexpr inline u8 dl_pk_type_start_dist = 0x03; +constexpr inline u8 dl_pk_type_distribute = 0x04; +constexpr inline u8 dl_pk_type_finish_dist = 0x05; +constexpr inline u8 dl_pk_type_start_game = 0x06; + +constexpr inline std::array dl_pk_head_broadcast_header = {dl_pk_type_broadcast, 0x02}; +constexpr inline std::array dl_pk_head_auth_header = {dl_pk_type_auth, 0x02}; +constexpr inline std::array dl_pk_head_start_dist_header = {dl_pk_type_start_dist, 0x02}; +constexpr inline std::array dl_pk_head_distribute_header = {dl_pk_type_distribute, 0x02}; +constexpr inline std::array dl_pk_head_finish_dist_header = {dl_pk_type_finish_dist, 0x02}; +constexpr inline std::array dl_pk_head_start_game_header = {dl_pk_type_start_game, 0x02}; + +struct DLPPacketHeader { + union { + std::array magic; + struct { + u8 type; + u8 mag0x02; + u16 unk; // usually 0x00 0x00 + }; + }; + 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); + +// bool with 3ds padding included +struct DLPPacketBool { + union { + u32 raw; + struct { + u8 active_value : 1; + u8 padding : 7; + std::array padding2; + }; + }; + operator bool() { + return active_value; + } + DLPPacketBool& operator=(const bool& o) { + raw = 0x0; + active_value = o; + return *this; + } +}; + +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; + u64 unk3; + u64 unk4; // all 0s + u32_be size; // size minus broad_title_size_diff + u32 unk5; + std::array title_short; + std::array title_long; + std::array icon_part; + u64 unk; +}; +#pragma pack(pop) + +static_assert(sizeof(DLPBroadcastPacket1) == 768); + +struct DLPBroadcastPacket2 { + DLPPacketHeader head; + std::array icon_part; +}; + +static_assert(sizeof(DLPBroadcastPacket2) == 1448); + +struct DLPBroadcastPacket3 { + DLPPacketHeader head; + std::array icon_part; +}; + +static_assert(sizeof(DLPBroadcastPacket3) == 1448); + +struct DLPBroadcastPacket4 { + DLPPacketHeader head; + std::array icon_part; +}; + +static_assert(sizeof(DLPBroadcastPacket4) == 1448); + +struct DLPBroadcastPacket5 { + DLPPacketHeader head; + std::array unk1; + std::array unk2; + std::array unk3; +}; + +static_assert(sizeof(DLPBroadcastPacket5) == 1464); + +// auth session +struct DLPSrvr_Auth { + DLPPacketHeader head; + u32 unk1; // 0x0 +}; + +static_assert(sizeof(DLPSrvr_Auth) == 0x14); + +struct DLPClt_AuthAck { + DLPPacketHeader head; + DLPPacketBool initialized; // true + std::array padding; + std::array resp_id; // very important! game specific? +}; + +static_assert(sizeof(DLPClt_AuthAck) == 0x18); + +// start distribution +struct DLPSrvr_StartDistribution { + DLPPacketHeader head; + DLPPacketBool initialized; // 0x1 +}; + +static_assert(sizeof(DLPSrvr_StartDistribution) == 0x14); + +struct DLPClt_StartDistributionAck_NoContentNeeded { + DLPPacketHeader head; + DLPPacketBool initialized; // 0x1 + u32 unk2; // 0x0 +}; + +static_assert(sizeof(DLPClt_StartDistributionAck_NoContentNeeded) == 0x18); + +struct DLPClt_StartDistributionAck_ContentNeeded { + DLPPacketHeader head; + DLPPacketBool initialized; // 0x1 + u16_be unk2; // BE 0x20 unk important! + u16_be unk3; // 0x0 + DLPPacketBool unk4; // 0x1 + u32_be unk5; // 0x0 + std::array unk_body; +}; + +static_assert(sizeof(DLPClt_StartDistributionAck_ContentNeeded) == 0x38); + +// perform distribution of content +// packet_index is 1 +struct DLPSrvr_ContentDistributionFragment { + DLPPacketHeader head; + u32_be content_magic; // extra magic value + u32_be unk1; // 0x1 BE + u16_be frag_index; // BE % dlp_content_block_length + u16_be frag_size; // BE + u8 content_fragment[]; +}; + +static_assert(sizeof(DLPSrvr_ContentDistributionFragment) == 28); + +// finish receiving content +struct DLPSrvr_FinishContentUpload { + DLPPacketHeader head; + DLPPacketBool initialized; // 0x1 + u32_be seq_num; // BE starts at 0x0 and copies whatever number the ack gives it +}; + +static_assert(sizeof(DLPSrvr_FinishContentUpload) == 0x18); + +// it sends this to clients during distribution +#pragma pack(push, 2) +struct DLPClt_FinishContentUploadAck { + DLPPacketHeader head; + DLPPacketBool initialized; // 0x1 + u8 unk2; // 0x1 + u8 needs_content; // 0x1 if downloading conetnt + u32_be seq_ack; // BE client increments this every ack + u16 unk4; // 0x0 +}; +#pragma pack(pop) + +static_assert(sizeof(DLPClt_FinishContentUploadAck) == 0x1C); + +// start game +// these will keep sending until +// the final command is given +struct DLPSrvr_BeginGame { + DLPPacketHeader head; + u32_le unk1; // 0x1 + u32_le unk2; // 0x9 could be DLP_Srvr_State +}; + +static_assert(sizeof(DLPSrvr_BeginGame) == 0x18); + +struct DLPClt_BeginGameAck { + DLPPacketHeader head; + u32_le unk1; // 0x1 + u32_le unk2; // 0x9 could be DLP_Clt_State +}; + +static_assert(sizeof(DLPClt_BeginGameAck) == 0x18); + +// packet_index is 1. this is not acked +struct DLPSrvr_BeginGameFinal { + DLPPacketHeader head; + u32_le unk1; // 0x1 + std::array wireless_reboot_passphrase; + u8 unk2; // 0x09 could be server state + u16 padding; // 0x00 0x00 +}; + +static_assert(sizeof(DLPSrvr_BeginGameFinal) == 0x20); + +// END BIG ENDIAN + +class DLP_Base { +protected: + DLP_Base(Core::System& s); + virtual ~DLP_Base() = default; + + virtual std::shared_ptr GetServiceFrameworkSharedPtr() = 0; + virtual bool IsHost() = 0; + + Core::System& system; + + std::shared_ptr dlp_sharedmem; + std::shared_ptr uds_sharedmem; + + std::shared_ptr dlp_status_event; // out + std::shared_ptr uds_status_event; // in + + bool should_verify_checksum = false; + + 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{}; + + 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; + + std::shared_ptr GetCFG(); + std::shared_ptr GetUDS(); + + void GetEventDescription(Kernel::HLERequestContext& ctx); + + void InitializeDlpBase(u32 shared_mem_size, std::shared_ptr shared_mem, + std::shared_ptr event, DLP_Username username); + void FinalizeDlpBase(); + + bool ConnectToNetworkAsync(NWM::NetworkInfo net_info, NWM::ConnectionType conn_type, + std::vector passphrase); + int RecvFrom(u16 node_id, std::vector& buffer); + bool SendTo(u16 node_id, u8 data_channel, std::vector& buffer, u8 flags = 0); + + static std::u16string DLPUsernameAsString16(DLP_Username uname); + static DLP_Username String16AsDLPUsername(std::u16string str); + static DLPNodeInfo UDSToDLPNodeInfo(NWM::NodeInfo node_info); + template + static T* GetPacketBody(std::vector& b) { + if (b.size() < sizeof(T)) { + LOG_CRITICAL(Service_DLP, "Packet size is too small to fit content {} < {}", b.size(), + sizeof(T)); + return nullptr; + } + return reinterpret_cast(b.data()); + } + static DLPPacketHeader* GetPacketHead(std::vector& b) { + if (b.size() < sizeof(DLPPacketHeader)) { + LOG_CRITICAL(Service_DLP, "Packet is too small to fit a DLP header"); + return nullptr; + } + return reinterpret_cast(b.data()); + } + + static u32 GeneratePKChecksum(u32 aes_value, void* input_buffer, u32 packet_size); + + 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, + "Tried to send 2 packets concurrently, causing blocking on this thread"); + sm_packet_sender_session.acquire(); + } + send_packet_ctx.resize(sizeof(T)); + auto ph = GetPacketHead(send_packet_ctx); + ph->magic = magic; + ph->size = sizeof(T); + ph->unk1 = {0x02, 0x00}; + ph->resp_id = resp_id; + ph->packet_index = packet_index; + return GetPacketBody(send_packet_ctx); + } + void PGen_SendPK(u32 aes, u16 node_id, u8 data_channel, u8 flags = 0) { + ASSERT(send_packet_ctx.size() >= sizeof(DLPPacketHeader)); + auto ph = GetPacketHead(send_packet_ctx); + ASSERT(ph->size == send_packet_ctx.size()); + ph->checksum = 0; + ph->checksum = GeneratePKChecksum(aes, ph, ph->size); + SendTo(node_id, data_channel, send_packet_ctx, flags); + send_packet_ctx.clear(); + sm_packet_sender_session.release(); + } + // input the host mac address + u32 GenDLPChecksumKey(Network::MacAddress mac_addr); + static void DLPEncryptCTR(void* out, size_t size, const u8* iv_ctr); + static bool ValidatePacket(u32 aes, void* pk, size_t sz, bool checksum = true); + + static u32 GetNumFragmentsFromTitleSize(u32 tsize); + +private: + std::binary_semaphore sm_packet_sender_session{1}; + std::vector send_packet_ctx; +}; + +} // namespace Service::DLP diff --git a/src/core/hle/service/dlp/dlp_clnt.cpp b/src/core/hle/service/dlp/dlp_clnt.cpp index 8d79e9709..c4121a27c 100644 --- a/src/core/hle/service/dlp/dlp_clnt.cpp +++ b/src/core/hle/service/dlp/dlp_clnt.cpp @@ -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. @@ -10,29 +10,150 @@ SERIALIZE_EXPORT_IMPL(Service::DLP::DLP_CLNT) namespace Service::DLP { -DLP_CLNT::DLP_CLNT() : ServiceFramework("dlp:CLNT", 1) { +std::shared_ptr DLP_CLNT::GetServiceFrameworkSharedPtr() { + return shared_from_this(); +} + +u32 DLP_CLNT::ClientNeedsDup() { + [[maybe_unused]] constexpr u32 res_needs_system_update = 0x1; + constexpr u32 res_does_not_need_update = 0x0; + return res_does_not_need_update; +} + +void DLP_CLNT::Initialize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + u32 shared_mem_size = rp.Pop(); + u32 max_beacons = rp.Pop(); + u32 constant_mem_size = rp.Pop(); + auto [shared_mem, event] = rp.PopObjects(); + + InitializeCltBase(shared_mem_size, max_beacons, constant_mem_size, shared_mem, event, + String16AsDLPUsername(GetCFG()->GetUsername())); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void DLP_CLNT::Finalize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + FinalizeCltBase(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +// returns the version of the currently joined server +void DLP_CLNT::GetCupVersion(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + [[maybe_unused]] auto mac_addr = rp.PopRaw(); + [[maybe_unused]] u32 tid_low = rp.PopRaw(); + [[maybe_unused]] u32 tid_high = rp.PopRaw(); + + LOG_WARNING(Service_DLP, "(STUBBED) called"); + + IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); + + // TODO: someone decipher this version code + u64 version_num = 0x0; + + rb.Push(ResultSuccess); + rb.Push(version_num); +} + +// tells us which server to connect to and download an update from +// the dlp app uses this to check whether or not we need the update data +void DLP_CLNT::PrepareForSystemDownload(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + mac_addr_update = rp.PopRaw(); + [[maybe_unused]] u32 tid_low = rp.PopRaw(); + [[maybe_unused]] u32 tid_high = rp.PopRaw(); + + if (ClientNeedsDup()) { + is_preparing_for_update = true; + } + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + + rb.Push(ResultSuccess); + rb.Push(ClientNeedsDup()); +} + +// runs after the user accepts the license agreement to +// download the update +void DLP_CLNT::StartSystemDownload(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + LOG_WARNING(Service_DLP, "(STUBBED) called"); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + if (!is_preparing_for_update) { + // error + LOG_ERROR(Service_DLP, "Called without preparing first. We don't have a mac address!"); + // TODO: verify this on hw + rb.Push(0xD960AC02); + return; + } + + is_preparing_for_update = false; + is_updating = true; + + // TODO: figure out what comes after when + // hw starts downloading update data via dlp. + // it could set some missing client states + // in GetCltState + + rb.Push(ResultSuccess); +} + +// i'm assuming this is a secondary check whether or not we +// can download the update data? +void DLP_CLNT::GetDupAvailability(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + mac_addr_update = rp.PopRaw(); + [[maybe_unused]] u32 tid_low = rp.PopRaw(); + [[maybe_unused]] u32 tid_high = rp.PopRaw(); + + LOG_WARNING(Service_DLP, "(STUBBED) called"); + + [[maybe_unused]] constexpr u32 dup_is_available = 0x1; + constexpr u32 dup_is_not_available = 0x0; + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + + rb.Push(ResultSuccess); + rb.Push(dup_is_not_available); +} + +DLP_CLNT::DLP_CLNT() + : ServiceFramework("dlp:CLNT", 1), DLP_Clt_Base(Core::System::GetInstance(), "CLNT") { static const FunctionInfo functions[] = { // clang-format off - {0x0001, nullptr, "Initialize"}, - {0x0002, nullptr, "Finalize"}, - {0x0003, nullptr, "GetEventDesc"}, - {0x0004, nullptr, "GetChannel"}, - {0x0005, nullptr, "StartScan"}, - {0x0006, nullptr, "StopScan"}, - {0x0007, nullptr, "GetServerInfo"}, - {0x0008, nullptr, "GetTitleInfo"}, - {0x0009, nullptr, "GetTitleInfoInOrder"}, - {0x000A, nullptr, "DeleteScanInfo"}, - {0x000B, nullptr, "PrepareForSystemDownload"}, - {0x000C, nullptr, "StartSystemDownload"}, - {0x000D, nullptr, "StartTitleDownload"}, - {0x000E, nullptr, "GetMyStatus"}, - {0x000F, nullptr, "GetConnectingNodes"}, - {0x0010, nullptr, "GetNodeInfo"}, - {0x0011, nullptr, "GetWirelessRebootPassphrase"}, - {0x0012, nullptr, "StopSession"}, - {0x0013, nullptr, "GetCupVersion"}, - {0x0014, nullptr, "GetDupAvailability"}, + {0x0001, &DLP_CLNT::Initialize, "Initialize"}, + {0x0002, &DLP_CLNT::Finalize, "Finalize"}, + {0x0003, &DLP_CLNT::GetEventDescription, "GetEventDescription"}, + {0x0004, &DLP_CLNT::GetChannels, "GetChannel"}, + {0x0005, &DLP_CLNT::StartScan, "StartScan"}, + {0x0006, &DLP_CLNT::StopScan, "StopScan"}, + {0x0007, &DLP_CLNT::GetServerInfo, "GetServerInfo"}, + {0x0008, &DLP_CLNT::GetTitleInfo, "GetTitleInfo"}, + {0x0009, &DLP_CLNT::GetTitleInfoInOrder, "GetTitleInfoInOrder"}, + {0x000A, &DLP_CLNT::DeleteScanInfo, "DeleteScanInfo"}, + {0x000B, &DLP_CLNT::PrepareForSystemDownload, "PrepareForSystemDownload"}, + {0x000C, &DLP_CLNT::StartSystemDownload, "StartSystemDownload"}, + {0x000D, &DLP_CLNT::StartSession, "StartTitleDownload"}, + {0x000E, &DLP_CLNT::GetMyStatus, "GetMyStatus"}, + {0x000F, &DLP_CLNT::GetConnectingNodes, "GetConnectingNodes"}, + {0x0010, &DLP_CLNT::GetNodeInfo, "GetNodeInfo"}, + {0x0011, &DLP_CLNT::GetWirelessRebootPassphrase, "GetWirelessRebootPassphrase"}, + {0x0012, &DLP_CLNT::StopSession, "StopSession"}, + {0x0013, &DLP_CLNT::GetCupVersion, "GetCupVersion"}, + {0x0014, &DLP_CLNT::GetDupAvailability, "GetDupAvailability"}, // clang-format on }; diff --git a/src/core/hle/service/dlp/dlp_clnt.h b/src/core/hle/service/dlp/dlp_clnt.h index ac6933e7e..8b9a56300 100644 --- a/src/core/hle/service/dlp/dlp_clnt.h +++ b/src/core/hle/service/dlp/dlp_clnt.h @@ -1,20 +1,41 @@ -// 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. #pragma once #include "core/hle/service/service.h" +#include "dlp_clt_base.h" namespace Service::DLP { -class DLP_CLNT final : public ServiceFramework { +class DLP_CLNT final : public ServiceFramework, public DLP_Clt_Base { public: DLP_CLNT(); - ~DLP_CLNT() = default; + virtual ~DLP_CLNT() = default; + + virtual std::shared_ptr GetServiceFrameworkSharedPtr(); private: SERVICE_SERIALIZATION_SIMPLE + + virtual bool IsFKCL() { + return false; + } + + bool is_preparing_for_update = false; + bool is_updating = false; + Network::MacAddress mac_addr_update; + + u32 ClientNeedsDup(); + + void Initialize(Kernel::HLERequestContext& ctx); + void Finalize(Kernel::HLERequestContext& ctx); + void GetCupVersion(Kernel::HLERequestContext& ctx); + void StartTitleDownload(Kernel::HLERequestContext& ctx); + void PrepareForSystemDownload(Kernel::HLERequestContext& ctx); + void StartSystemDownload(Kernel::HLERequestContext& ctx); + void GetDupAvailability(Kernel::HLERequestContext& ctx); }; } // namespace Service::DLP diff --git a/src/core/hle/service/dlp/dlp_clt_base.cpp b/src/core/hle/service/dlp/dlp_clt_base.cpp new file mode 100644 index 000000000..d127dbd36 --- /dev/null +++ b/src/core/hle/service/dlp/dlp_clt_base.cpp @@ -0,0 +1,856 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "dlp_clt_base.h" + +#include "common/alignment.h" +#include "common/string_util.h" +#include "common/timer.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/nwm/uds_beacon.h" + +namespace Service::DLP { + +DLP_Clt_Base::DLP_Clt_Base(Core::System& s, std::string unique_string_id) : DLP_Base(s) { + std::string unique_scan_event_id = fmt::format("DLP::{}::BeaconScanCallback", unique_string_id); + beacon_scan_event = system.CoreTiming().RegisterEvent( + unique_scan_event_id, [this](std::uintptr_t user_data, s64 cycles_late) { + BeaconScanCallback(user_data, cycles_late); + }); +} + +DLP_Clt_Base::~DLP_Clt_Base() { + { + std::scoped_lock lock(beacon_mutex); + is_scanning = false; + system.CoreTiming().UnscheduleEvent(beacon_scan_event, 0); + } + + DisconnectFromServer(); +} + +void DLP_Clt_Base::InitializeCltBase(u32 shared_mem_size, u32 max_beacons, u32 constant_mem_size, + std::shared_ptr shared_mem, + std::shared_ptr event, DLP_Username username) { + InitializeDlpBase(shared_mem_size, shared_mem, event, username); + + clt_state = DLP_Clt_State::Initialized; + max_title_info = max_beacons; + + LOG_INFO(Service_DLP, + "shared mem size: 0x{:x}, max beacons: {}, constant mem size: 0x{:x}, username: {}", + shared_mem_size, max_beacons, constant_mem_size, + Common::UTF16ToUTF8(DLPUsernameAsString16(username)).c_str()); +} + +void DLP_Clt_Base::FinalizeCltBase() { + clt_state = DLP_Clt_State::Initialized; + + if (is_connected) { + DisconnectFromServer(); + } + + FinalizeDlpBase(); + + LOG_INFO(Service_DLP, "called"); +} + +void DLP_Clt_Base::GenerateChannelHandle() { + dlp_channel_handle = 0x0421; // it seems to always be this value on hardware +} + +u32 DLP_Clt_Base::GetCltState() { + std::scoped_lock lock(clt_state_mutex); + u16 node_id = 0x0; + if (is_connected) { + node_id = GetUDS()->GetConnectionStatusHLE().network_node_id; + } + return static_cast(clt_state) << 24 | is_connected << 16 | node_id; +} + +void DLP_Clt_Base::GetChannels(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + GenerateChannelHandle(); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(ResultSuccess); + rb.Push(dlp_channel_handle); +} + +void DLP_Clt_Base::GetMyStatus(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + IPC::RequestBuilder rb = rp.MakeBuilder(6, 0); + rb.Push(ResultSuccess); + rb.Push(GetCltState()); + rb.Push(dlp_units_total); + rb.Push(dlp_units_downloaded); + // TODO: find out what these are + rb.Push(0x0); + rb.Push(0x0); +} + +int DLP_Clt_Base::GetCachedTitleInfoIdx(Network::MacAddress mac_addr) { + std::scoped_lock lock(title_info_mutex); + + for (int i = 0; auto& t : scanned_title_info) { + if (t.first.mac_addr == mac_addr) { + return i; + } + i++; + } + return -1; +} + +bool DLP_Clt_Base::TitleInfoIsCached(Network::MacAddress mac_addr) { + return GetCachedTitleInfoIdx(mac_addr) != -1; +} + +void DLP_Clt_Base::StartScan(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + u16 scan_handle = rp.Pop(); + scan_title_id_filter = rp.Pop(); + scan_mac_address_filter = rp.PopRaw(); + ASSERT_MSG( + scan_handle == dlp_channel_handle, + "Scan handle and dlp channel handle do not match. Did you input the wrong ipc params?"); + [[maybe_unused]] u32 unk1 = rp.Pop(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + // start beacon worker + if (!IsIdling()) { + rb.Push(Result(0x1, ErrorModule::DLP, ErrorSummary::InvalidState, ErrorLevel::Usage)); + return; + } + + std::scoped_lock lock{beacon_mutex, title_info_mutex, clt_state_mutex}; + + // reset scan dependent variables + scanned_title_info.clear(); + ignore_servers_list.clear(); + title_info_index = 0; + + clt_state = DLP_Clt_State::Scanning; + is_scanning = true; + + // clear out received beacons + GetUDS()->GetReceivedBeacons(Network::BroadcastMac); + + LOG_INFO(Service_DLP, "Starting scan worker"); + + constexpr int first_scan_delay_ms = 0; + + system.CoreTiming().ScheduleEvent(msToCycles(first_scan_delay_ms), beacon_scan_event, 0); + + rb.Push(ResultSuccess); +} + +void DLP_Clt_Base::StopScan(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + // end beacon worker + { + std::scoped_lock lock{beacon_mutex, clt_state_mutex}; + clt_state = DLP_Clt_State::Initialized; + is_scanning = false; + + LOG_INFO(Service_DLP, "Ending scan worker"); + + system.CoreTiming().UnscheduleEvent(beacon_scan_event, 0); + } + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void DLP_Clt_Base::GetTitleInfo(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + auto mac_addr = rp.PopRaw(); + [[maybe_unused]] u32 tid_low = rp.Pop(); + [[maybe_unused]] u32 tid_high = rp.Pop(); + + std::scoped_lock lock(title_info_mutex); + + if (!TitleInfoIsCached(mac_addr)) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(Result(ErrorDescription::NoData, ErrorModule::DLP, ErrorSummary::NotFound, + ErrorLevel::Status)); + return; + } + + auto c_title_idx = GetCachedTitleInfoIdx(mac_addr); + std::vector buffer = scanned_title_info[c_title_idx].first.ToBuffer(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(ResultSuccess); + rb.PushStaticBuffer(std::move(buffer), 0); +} + +void DLP_Clt_Base::GetTitleInfoInOrder(Kernel::HLERequestContext& ctx) { + constexpr u8 cmd_reset_iterator = 0x1; + + IPC::RequestParser rp(ctx); + + u8 command = rp.Pop(); + if (command == cmd_reset_iterator) { + title_info_index = 0; + } + + std::scoped_lock lock(title_info_mutex); + + if (title_info_index >= scanned_title_info.size()) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(Result(ErrorDescription::NoData, ErrorModule::DLP, ErrorSummary::NotFound, + ErrorLevel::Status)); + return; + } + + std::vector buffer = scanned_title_info[title_info_index].first.ToBuffer(); + + ++title_info_index; + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(ResultSuccess); + rb.PushStaticBuffer(std::move(buffer), 0); +} + +void DLP_Clt_Base::DeleteScanInfo(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + LOG_INFO(Service_DLP, "Called"); + + auto mac_addr = rp.PopRaw(); + + std::scoped_lock lock(title_info_mutex); + + if (!TitleInfoIsCached(mac_addr)) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(Result(ErrorDescription::NoData, ErrorModule::DLP, ErrorSummary::NotFound, + ErrorLevel::Status)); + return; + } + + scanned_title_info.erase(scanned_title_info.begin() + GetCachedTitleInfoIdx(mac_addr)); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void DLP_Clt_Base::GetServerInfo(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + auto mac_addr = rp.PopRaw(); + + std::scoped_lock lock(title_info_mutex); + + if (!TitleInfoIsCached(mac_addr)) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(Result(ErrorDescription::NoData, ErrorModule::DLP, ErrorSummary::NotFound, + ErrorLevel::Status)); + return; + } + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + + auto buffer = scanned_title_info[GetCachedTitleInfoIdx(mac_addr)].second.ToBuffer(); + + rb.Push(ResultSuccess); + rb.PushStaticBuffer(std::move(buffer), 0); +} + +class DLP_Clt_Base::ThreadCallback : public Kernel::HLERequestContext::WakeupCallback { +public: + explicit ThreadCallback(std::shared_ptr p) : p_obj(p) {} + + void WakeUp(std::shared_ptr thread, Kernel::HLERequestContext& ctx, + Kernel::ThreadWakeupReason reason) { + IPC::RequestBuilder rb(ctx, 1, 0); + + if (!p_obj->OnConnectCallback()) { + rb.Push(Result(ErrorDescription::Timeout, ErrorModule::DLP, ErrorSummary::Canceled, + ErrorLevel::Status)); + return; + } + rb.Push(ResultSuccess); + } + +private: + ThreadCallback() = default; + std::shared_ptr p_obj; + + template + void serialize(Archive& ar, const unsigned int) { + ar& boost::serialization::base_object(*this); + } + friend class boost::serialization::access; +}; + +bool DLP_Clt_Base::OnConnectCallback() { + auto uds = GetUDS(); + if (uds->GetConnectionStatusHLE().status != NWM::NetworkStatus::ConnectedAsClient) { + LOG_ERROR(Service_DLP, "Could not connect to dlp server (timed out)"); + return false; + } + + is_connected = true; + + client_connection_worker = std::thread([this] { ClientConnectionManager(); }); + + return true; +} + +void DLP_Clt_Base::StartSession(Kernel::HLERequestContext& ctx) { + std::scoped_lock lock(clt_state_mutex); + IPC::RequestParser rp(ctx); + + auto mac_addr = rp.PopRaw(); + + LOG_INFO(Service_DLP, "called"); + + // tells us which child we want to use for this session + // only used for dlp::CLNT + u32 dlp_child_low = rp.Pop(); + u32 dlp_child_high = rp.Pop(); + + if (!IsIdling()) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(Result(0x1, ErrorModule::DLP, ErrorSummary::InvalidState, ErrorLevel::Usage)); + return; + } + if (!TitleInfoIsCached(mac_addr)) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(Result(ErrorDescription::NoData, ErrorModule::DLP, ErrorSummary::NotFound, + ErrorLevel::Status)); + return; + } + + dlp_download_child_tid = static_cast(dlp_child_high) << 32 | dlp_child_low; + + // ConnectToNetworkAsync won't work here beacuse this is + // synchronous + + auto shared_this = std::dynamic_pointer_cast(GetServiceFrameworkSharedPtr()); + if (!shared_this) { + LOG_CRITICAL(Service_DLP, + "Could not dynamic_cast service framework shared_ptr to DLP_Clt_Base"); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(-1); + return; + } + + host_mac_address = mac_addr; + clt_state = DLP_Clt_State::Joined; + + auto uds = GetUDS(); + NWM::NetworkInfo net_info; + net_info.host_mac_address = mac_addr; + net_info.channel = dlp_net_info_channel; + net_info.initialized = true; + net_info.oui_value = NWM::NintendoOUI; + + uds->ConnectToNetworkHLE(net_info, static_cast(NWM::ConnectionType::Client), + dlp_password_buf); + + // 3 second timeout + constexpr std::chrono::nanoseconds UDSConnectionTimeout{3000000000}; + uds->connection_event = + ctx.SleepClientThread("DLP_Clt_Base::StartSession", UDSConnectionTimeout, + std::make_shared(shared_this)); +} + +void DLP_Clt_Base::StopSession(Kernel::HLERequestContext& ctx) { + LOG_INFO(Service_DLP, "called"); + std::scoped_lock lock(clt_state_mutex); + IPC::RequestParser rp(ctx); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + if (is_connected) { + DisconnectFromServer(); + } + + // this call returns success no matter what + rb.Push(ResultSuccess); +} + +void DLP_Clt_Base::GetConnectingNodes(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + u16 node_array_len = rp.Pop(); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + + auto conn_status = GetUDS()->GetConnectionStatusHLE(); + + if (!is_connected || conn_status.status != NWM::NetworkStatus::ConnectedAsClient) { + LOG_ERROR(Service_DLP, "called when we are not connected to a server"); + } + + 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)); + + rb.Push(ResultSuccess); + rb.Push(conn_status.total_nodes); + rb.PushStaticBuffer(std::move(connected_nodes_buffer), 0); +} + +void DLP_Clt_Base::GetNodeInfo(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + u16 network_node_id = rp.Pop(); + + auto node_info = GetUDS()->GetNodeInformationHLE(network_node_id); + if (!node_info) { + LOG_ERROR(Service_DLP, "Could not get node info for network node id 0x{:x}", + network_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); + + rb.Push(ResultSuccess); + rb.PushRaw(UDSToDLPNodeInfo(*node_info)); +} + +void DLP_Clt_Base::GetWirelessRebootPassphrase(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + LOG_INFO(Service_DLP, "called"); + + std::scoped_lock lock(clt_state_mutex); + if (clt_state != DLP_Clt_State::Complete) { + LOG_WARNING(Service_DLP, "we have not gotten the passphrase yet"); + } + + IPC::RequestBuilder rb = rp.MakeBuilder(4, 0); + rb.Push(ResultSuccess); + rb.PushRaw(wireless_reboot_passphrase); +} + +void DLP_Clt_Base::BeaconScanCallback(std::uintptr_t user_data, s64 cycles_late) { + std::scoped_lock lock{beacon_mutex, title_info_mutex}; + + if (!is_scanning) { + return; + } + + auto uds = GetUDS(); + Common::Timer beacon_parse_timer_total; + + // sadly, we have to impl the scan code ourselves + // because the nwm recvbeaconbroadcastdata function + // has a timeout in it, which won't work here because + // we don't have a uds server/client session + auto beacons = uds->GetReceivedBeacons(Network::BroadcastMac); + + beacon_parse_timer_total.Start(); + + for (auto& beacon : beacons) { + if (auto idx = GetCachedTitleInfoIdx(beacon.transmitter_address); idx != -1) { + // update server info from beacon + auto b = GetDLPServerInfoFromRawBeacon(beacon); + scanned_title_info[idx].second.clients_joined = + b.clients_joined; // we only want to update clients joined + continue; + } + if (scanned_title_info.size() >= max_title_info) { + break; + } + if (ignore_servers_list[beacon.transmitter_address]) { + continue; + } + + CacheBeaconTitleInfo(beacon); + } + + // set our next scan interval + system.CoreTiming().ScheduleEvent( + msToCycles(std::max(0, beacon_scan_interval_ms - + beacon_parse_timer_total.GetTimeElapsed().count())) - + cycles_late, + beacon_scan_event, 0); +} + +void DLP_Clt_Base::CacheBeaconTitleInfo(Network::WifiPacket& beacon) { + // connect to the network as a spectator + // and receive dlp data + + auto uds = GetUDS(); + + NWM::NetworkInfo net_info; + net_info.host_mac_address = beacon.transmitter_address; + net_info.channel = dlp_net_info_channel; + net_info.initialized = true; + net_info.oui_value = NWM::NintendoOUI; + + if (!ConnectToNetworkAsync(net_info, NWM::ConnectionType::Spectator, dlp_password_buf)) { + LOG_ERROR(Service_DLP, "Could not connect to network."); + return; + } + + LOG_INFO(Service_DLP, "Connected to spec to network"); + + auto [ret, data_available_event] = + uds->BindHLE(dlp_bind_node_id, dlp_recv_buffer_size, dlp_broadcast_data_channel, + dlp_host_network_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 aes = GenDLPChecksumKey(beacon.transmitter_address); + + constexpr u32 max_beacon_recv_time_out_ms = 1000; + + Common::Timer beacon_parse_timer; + beacon_parse_timer.Start(); + + std::unordered_map got_broadcast_packet; + std::unordered_map> broadcast_packet_idx_buf; + DLP_Username server_username; // workaround before I decrypt the beacon data + std::vector recv_buf; + bool got_all_packets = false; + while (beacon_parse_timer.GetTimeElapsed().count() < max_beacon_recv_time_out_ms) { + if (int sz = RecvFrom(dlp_host_network_node_id, recv_buf)) { + auto p_head = reinterpret_cast(recv_buf.data()); + if (!ValidatePacket(aes, p_head, sz, should_verify_checksum) || + p_head->packet_index >= num_broadcast_packets) { + ignore_servers_list[beacon.transmitter_address] = true; + break; // corrupted info + } + got_broadcast_packet[p_head->packet_index] = true; + broadcast_packet_idx_buf[p_head->packet_index] = recv_buf; + if (got_broadcast_packet.size() == num_broadcast_packets) { + got_all_packets = true; + constexpr u16 nwm_host_node_network_id = 0x1; + server_username = uds->GetNodeInformationHLE(nwm_host_node_network_id)->username; + break; // we got all 5! + } + } + } + + uds->UnbindHLE(dlp_bind_node_id); + uds->DisconnectNetworkHLE(); + + if (!got_all_packets) { + if (!got_broadcast_packet.size()) { + // we didn't get ANY packet info from this server + // so we add it to the ignore list + ignore_servers_list[beacon.transmitter_address] = true; + } + LOG_ERROR(Service_DLP, "Connected to beacon, but could not receive all dlp packets"); + return; + } + + // parse packets into cached DLPServerInfo and DLPTitleInfo + auto broad_pk1 = reinterpret_cast(broadcast_packet_idx_buf[0].data()); + auto broad_pk2 = reinterpret_cast(broadcast_packet_idx_buf[1].data()); + auto broad_pk3 = reinterpret_cast(broadcast_packet_idx_buf[2].data()); + auto broad_pk4 = reinterpret_cast(broadcast_packet_idx_buf[3].data()); + [[maybe_unused]] auto broad_pk5 = + reinterpret_cast(broadcast_packet_idx_buf[4].data()); + + // apply title filter + if (scan_title_id_filter && broad_pk1->child_title_id != scan_title_id_filter) { + LOG_WARNING(Service_DLP, "Got title info, but it did not match title id filter"); + return; + } + + DLPServerInfo c_server_info = GetDLPServerInfoFromRawBeacon(beacon); + { + // workaround: load username in host node manually + c_server_info.node_info[0].username = server_username; + } + + DLPTitleInfo c_title_info{}; + c_title_info.mac_addr = beacon.transmitter_address; + + // copy over title string data + std::copy(broad_pk1->title_short.begin(), broad_pk1->title_short.end(), + c_title_info.short_description.begin()); + std::copy(broad_pk1->title_long.begin(), broad_pk1->title_long.end(), + c_title_info.long_description.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; + + // copy over the icon data + auto icon_copy_loc = c_title_info.icon.begin(); + icon_copy_loc = + std::copy(broad_pk1->icon_part.begin(), broad_pk1->icon_part.end(), icon_copy_loc); + icon_copy_loc = + std::copy(broad_pk2->icon_part.begin(), broad_pk2->icon_part.end(), icon_copy_loc); + icon_copy_loc = + std::copy(broad_pk3->icon_part.begin(), broad_pk3->icon_part.end(), icon_copy_loc); + icon_copy_loc = + std::copy(broad_pk4->icon_part.begin(), broad_pk4->icon_part.end(), icon_copy_loc); + + LOG_INFO(Service_DLP, "Got title info"); + + scanned_title_info.emplace_back(c_title_info, c_server_info); + + dlp_status_event->Signal(); +} + +DLPServerInfo DLP_Clt_Base::GetDLPServerInfoFromRawBeacon(Network::WifiPacket& beacon) { + // get networkinfo from beacon + auto p_beacon = beacon.data.data(); + + bool found_net_info = false; + NWM::NetworkInfo net_info; + + // find networkinfo tag + for (auto place = p_beacon + sizeof(NWM::BeaconFrameHeader); place < place + beacon.data.size(); + place += reinterpret_cast(place)->length + sizeof(NWM::TagHeader)) { + auto th = reinterpret_cast(place); + if (th->tag_id == static_cast(NWM::TagId::VendorSpecific) && + th->length <= sizeof(NWM::NetworkInfoTag) - sizeof(NWM::TagHeader)) { + // cast to network info and check if correct + auto ni_tag = reinterpret_cast(place); + memcpy(&net_info.oui_value, ni_tag->network_info.data(), ni_tag->network_info.size()); + // make sure this is really a network info tag + if (net_info.oui_value == NWM::NintendoOUI && + net_info.oui_type == static_cast(NWM::NintendoTagId::NetworkInfo)) { + found_net_info = true; + break; + } + } + } + + if (!found_net_info) { + LOG_ERROR(Service_DLP, "Unable to find network info in beacon payload"); + return DLPServerInfo{}; + } + + DLPServerInfo srv_info{}; + srv_info.mac_addr = beacon.transmitter_address; + srv_info.max_clients = net_info.max_nodes; + srv_info.clients_joined = net_info.total_nodes; + srv_info.signal_strength = DLPSignalStrength::Strong; + srv_info.unk5 = 0x6; + // TODO: decrypt node info and load it in here + return srv_info; +} + +void DLP_Clt_Base::ClientConnectionManager() { + auto uds = GetUDS(); + + auto [ret, data_available_event] = uds->BindHLE( + dlp_bind_node_id, dlp_recv_buffer_size, dlp_client_data_channel, dlp_host_network_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 aes = GenDLPChecksumKey(host_mac_address); + + auto sleep_poll = [](size_t poll_rate) -> void { + 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; + + std::set received_fragments; + + while (sleep_poll(dlp_poll_rate_ms), is_connected) { + std::vector recv_buf; + + if (int sz = RecvFrom(dlp_host_network_node_id, recv_buf)) { + auto p_head = GetPacketHead(recv_buf); + // validate packet header + if (!ValidatePacket(aes, p_head, sz, should_verify_checksum)) { + got_corrupted_packets = true; + LOG_ERROR(Service_DLP, "Could not validate DLP packet header"); + break; + } + + // now we can parse the packet + std::scoped_lock lock{clt_state_mutex, title_info_mutex}; + if (p_head->type == dl_pk_type_auth) { + auto s_body = + PGen_SetPK(dl_pk_head_auth_header, 0, p_head->resp_id); + s_body->initialized = true; + // TODO: find out what this is. this changes each session. + // placeholder + 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) { + // 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; + + if (IsFKCL() || !NeedsContentDownload(host_mac_address)) { + auto s_body = PGen_SetPK( + dl_pk_head_start_dist_header, 0, p_head->resp_id); + s_body->initialized = true; + s_body->unk2 = 0x0; + is_downloading_content = false; + clt_state = DLP_Clt_State::WaitingForServerReady; + } else { + // send content needed ack + auto s_body = PGen_SetPK( + dl_pk_head_start_dist_header, 0, p_head->resp_id); + s_body->initialized = true; + // TODO: figure out what these are. seems like magic values + s_body->unk2 = 0x20; + s_body->unk3 = 0x0; + s_body->unk4 = true; + s_body->unk5 = 0x0; + s_body->unk_body = {}; // all zeros + is_downloading_content = true; + clt_state = DLP_Clt_State::Downloading; + + if (!TitleInfoIsCached(host_mac_address)) { + LOG_CRITICAL( + Service_DLP, + "Tried to request content download, but title info was not cached"); + break; + } + + auto tinfo = scanned_title_info[GetCachedTitleInfoIdx(host_mac_address)].first; + + dlp_units_downloaded = 0; + dlp_units_total = GetNumFragmentsFromTitleSize(tinfo.size); + current_content_block = 0; + LOG_INFO(Service_DLP, "Requesting game content"); + } + PGen_SendPK(aes, dlp_host_network_node_id, dlp_client_data_channel); + } else if (p_head->type == dl_pk_type_distribute) { + if (is_downloading_content) { + auto r_pbody = GetPacketBody(recv_buf); + if (r_pbody->frag_size > sz - sizeof(DLPSrvr_ContentDistributionFragment)) { + LOG_CRITICAL(Service_DLP, + "Embedded fragment size is too large. Ignoring fragment."); + continue; + } + std::span cf(r_pbody->content_fragment, + static_cast(r_pbody->frag_size)); + ReceivedFragment frag{ + .index = static_cast(r_pbody->frag_index + + dlp_content_block_length * current_content_block), + .content{cf.begin(), cf.end()}}; + received_fragments.insert(frag); + dlp_units_downloaded++; + if (dlp_units_downloaded == dlp_units_total) { + is_downloading_content = false; + LOG_INFO(Service_DLP, "Finished downloading content. Installing..."); + + if (!InstallEncryptedCIAFromFragments(received_fragments)) { + LOG_ERROR(Service_DLP, "Could not install DLP encrypted content"); + } else { + LOG_INFO(Service_DLP, "Successfully installed DLP encrypted content"); + } + + clt_state = DLP_Clt_State::WaitingForServerReady; + } + } + } else if (p_head->type == dl_pk_type_finish_dist) { + if (p_head->packet_index == 1) { + 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->needs_content = is_downloading_content; + s_body->seq_ack = r_pbody->seq_num + 1; + s_body->unk4 = 0x0; + PGen_SendPK(aes, dlp_host_network_node_id, dlp_client_data_channel); + } 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) { + 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, + p_head->resp_id); + s_body->unk1 = 0x1; + s_body->unk2 = 0x9; + PGen_SendPK(aes, dlp_host_network_node_id, dlp_client_data_channel); + } else if (p_head->packet_index == 1) { + clt_state = DLP_Clt_State::Complete; + auto r_pbody = GetPacketBody(recv_buf); + wireless_reboot_passphrase = r_pbody->wireless_reboot_passphrase; + } else { + LOG_ERROR(Service_DLP, "Unknown packet index {}", p_head->packet_index); + } + } else { + LOG_ERROR(Service_DLP, "Unknown DLP Magic 0x{:x} 0x{:x} 0x{:x} 0x{:x}", + p_head->magic[0], p_head->magic[1], p_head->magic[2], p_head->magic[3]); + } + } + } + + uds->UnbindHLE(dlp_host_network_node_id); + uds->DisconnectNetworkHLE(); +} + +bool DLP_Clt_Base::NeedsContentDownload(Network::MacAddress mac_addr) { + std::scoped_lock lock(title_info_mutex); + if (!TitleInfoIsCached(mac_addr)) { + LOG_ERROR(Service_DLP, "title info was not cached"); + return false; + } + auto tinfo = scanned_title_info[GetCachedTitleInfoIdx(mac_addr)].first; + u64 title_id = DLP_CHILD_TID_HIGH | (tinfo.unique_id << 8); + return !FileUtil::Exists(AM::GetTitleContentPath(FS::MediaType::NAND, title_id)); +} + +// DLP Fragments contain encrypted CIA content by design. +// It is required to decrypt them in order to achieve +// interoperability between HLE & LLE service modules. +bool DLP_Clt_Base::InstallEncryptedCIAFromFragments(std::set& frags) { + 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; + auto res = cia_file->Write(nb, frag.content.size(), flush_data, update_timestamp, + frag.content.data()); + + if (res.Failed()) { + LOG_ERROR(Service_DLP, "Could not install CIA. Error code {:08x}", res.Code().raw); + install_errored = true; + break; + } + + nb += frag.content.size(); + } + cia_file->Close(); + return !install_errored; +} + +void DLP_Clt_Base::DisconnectFromServer() { + is_connected = false; + if (client_connection_worker.joinable()) { + client_connection_worker.join(); + } +} + +bool DLP_Clt_Base::IsIdling() { + std::scoped_lock lock(beacon_mutex); + return !is_scanning && !is_connected; +} + +} // 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 new file mode 100644 index 000000000..10da159b3 --- /dev/null +++ b/src/core/hle/service/dlp/dlp_clt_base.h @@ -0,0 +1,144 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/kernel/event.h" +#include "core/hle/kernel/shared_memory.h" +#include "dlp_base.h" + +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, + Weak = 1, + Medium = 2, + Strong = 3, +}; + +// info from a server that +// can be obtained from its beacon only +struct DLPServerInfo { + Network::MacAddress mac_addr; + u8 unk1; + DLPSignalStrength signal_strength; + u8 max_clients; + u8 clients_joined; + u16 unk3; // node bitmask? + u32 padding; // all zeros + std::array node_info; + u32 unk4; + u32 unk5; + std::vector ToBuffer() { + std::vector out; + out.resize(sizeof(DLPServerInfo)); + memcpy(out.data(), this, sizeof(DLPServerInfo)); + return out; + } +}; + +static_assert(sizeof(DLPServerInfo) == 0x298); + +class DLP_Clt_Base : public DLP_Base { +protected: + DLP_Clt_Base(Core::System& s, std::string unique_string_id); + virtual ~DLP_Clt_Base(); + + virtual bool IsHost() { + return false; + } + + class ThreadCallback; + bool OnConnectCallback(); + void ClientConnectionManager(); + + virtual bool IsFKCL() = 0; + bool IsCLNT() { + return !IsFKCL(); + } + + DLP_Clt_State clt_state = DLP_Clt_State::NotInitialized; + u16 dlp_channel_handle{}; + std::atomic_bool is_connected = false; + u32 dlp_units_downloaded = 0x0, dlp_units_total = 0x0; + u64 dlp_download_child_tid = 0x0; + u32 title_info_index = 0; + u32 max_title_info = 0; ///< once we receive x beacons, we will no longer parse any other + ///< beacons until at least one tinfo buf element is cleared + bool is_scanning = false; + constexpr static inline int beacon_scan_interval_ms = 1000; + 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; + + std::mutex beacon_mutex; + std::recursive_mutex title_info_mutex; + std::recursive_mutex clt_state_mutex; + + std::thread client_connection_worker; + + bool is_downloading_content; + struct ReceivedFragment { + u32 index; + std::vector content; + bool operator<(const ReceivedFragment& o) const { + return index < o.index; + } + }; + u16 current_content_block; + + void InitializeCltBase(u32 shared_mem_size, u32 max_beacons, u32 constant_mem_size, + std::shared_ptr shared_mem, + std::shared_ptr event, DLP_Username username); + void FinalizeCltBase(); + void GenerateChannelHandle(); + u32 GetCltState(); + void BeaconScanCallback(std::uintptr_t user_data, s64 cycles_late); + void CacheBeaconTitleInfo(Network::WifiPacket& beacon); + int GetCachedTitleInfoIdx(Network::MacAddress mac_addr); + bool TitleInfoIsCached(Network::MacAddress mac_addr); + DLPServerInfo GetDLPServerInfoFromRawBeacon(Network::WifiPacket& beacon); + bool NeedsContentDownload(Network::MacAddress mac_addr); + bool InstallEncryptedCIAFromFragments(std::set& frags); + void DisconnectFromServer(); + bool IsIdling(); + + void GetMyStatus(Kernel::HLERequestContext& ctx); + void GetChannels(Kernel::HLERequestContext& ctx); + void GetTitleInfo(Kernel::HLERequestContext& ctx); + void GetTitleInfoInOrder(Kernel::HLERequestContext& ctx); + void StartScan(Kernel::HLERequestContext& ctx); + void StopScan(Kernel::HLERequestContext& ctx); + void DeleteScanInfo(Kernel::HLERequestContext& ctx); + void GetServerInfo(Kernel::HLERequestContext& ctx); + void StartSession(Kernel::HLERequestContext& ctx); + void StopSession(Kernel::HLERequestContext& ctx); + void GetConnectingNodes(Kernel::HLERequestContext& ctx); + void GetNodeInfo(Kernel::HLERequestContext& ctx); + void GetWirelessRebootPassphrase(Kernel::HLERequestContext& ctx); +}; + +} // namespace Service::DLP diff --git a/src/core/hle/service/dlp/dlp_crypto.cpp b/src/core/hle/service/dlp/dlp_crypto.cpp new file mode 100644 index 000000000..4cefd3516 --- /dev/null +++ b/src/core/hle/service/dlp/dlp_crypto.cpp @@ -0,0 +1,31 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "common/archives.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/dlp/dlp_base.h" +#include "core/hle/service/ssl/ssl_c.h" +#include "core/hw/aes/arithmetic128.h" +#include "core/hw/aes/key.h" + +namespace Service::DLP { + +void DLP_Base::DLPEncryptCTR(void* _out, size_t size, const u8* iv_ctr) { + auto out = reinterpret_cast(_out); + memset(out, 0, size); + + HW::AES::SelectDlpNfcKeyYIndex(HW::AES::DlpNfcKeyY::Dlp); + HW::AES::AESKey key = HW::AES::GetNormalKey(HW::AES::DLPNFCDataKey); + + // AlgorithmType::CTR_Encrypt + CryptoPP::CTR_Mode::Encryption aes; + aes.SetKeyWithIV(key.data(), CryptoPP::AES::BLOCKSIZE, iv_ctr); + aes.ProcessData(out, out, size); +} + +} // namespace Service::DLP diff --git a/src/core/hle/service/dlp/dlp_fkcl.cpp b/src/core/hle/service/dlp/dlp_fkcl.cpp index ed247427d..8576d80a3 100644 --- a/src/core/hle/service/dlp/dlp_fkcl.cpp +++ b/src/core/hle/service/dlp/dlp_fkcl.cpp @@ -1,8 +1,9 @@ -// 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. #include "common/archives.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/dlp/dlp_fkcl.h" @@ -10,26 +11,71 @@ SERIALIZE_EXPORT_IMPL(Service::DLP::DLP_FKCL) namespace Service::DLP { -DLP_FKCL::DLP_FKCL() : ServiceFramework("dlp:FKCL", 1) { +std::shared_ptr DLP_FKCL::GetServiceFrameworkSharedPtr() { + return shared_from_this(); +} + +void DLP_FKCL::Initialize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + u32 shared_mem_size = rp.Pop(); + u32 max_beacons = rp.Pop(); + constexpr u32 constant_mem_size = 0; + auto [shared_mem, event] = rp.PopObjects(); + + InitializeCltBase(shared_mem_size, max_beacons, constant_mem_size, shared_mem, event, + String16AsDLPUsername(GetCFG()->GetUsername())); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void DLP_FKCL::InitializeWithName(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + u32 shared_mem_size = rp.Pop(); + u32 max_beacons = rp.Pop(); + constexpr u32 constant_mem_size = 0; + auto username = rp.PopRaw>(); + rp.Skip(1, false); // possible null terminator or unk flags + auto [shared_mem, event] = rp.PopObjects(); + + InitializeCltBase(shared_mem_size, max_beacons, constant_mem_size, shared_mem, event, username); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void DLP_FKCL::Finalize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + FinalizeCltBase(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +DLP_FKCL::DLP_FKCL() + : ServiceFramework("dlp:FKCL", 1), DLP_Clt_Base(Core::System::GetInstance(), "FKCL") { static const FunctionInfo functions[] = { // clang-format off - {0x0001, nullptr, "Initialize"}, - {0x0002, nullptr, "Finalize"}, - {0x0003, nullptr, "GetEventDesc"}, - {0x0004, nullptr, "GetChannels"}, - {0x0005, nullptr, "StartScan"}, - {0x0006, nullptr, "StopScan"}, - {0x0007, nullptr, "GetServerInfo"}, - {0x0008, nullptr, "GetTitleInfo"}, - {0x0009, nullptr, "GetTitleInfoInOrder"}, - {0x000A, nullptr, "DeleteScanInfo"}, - {0x000B, nullptr, "StartFakeSession"}, - {0x000C, nullptr, "GetMyStatus"}, - {0x000D, nullptr, "GetConnectingNodes"}, - {0x000E, nullptr, "GetNodeInfo"}, - {0x000F, nullptr, "GetWirelessRebootPassphrase"}, - {0x0010, nullptr, "StopSession"}, - {0x0011, nullptr, "Initialize2"}, + {0x0001, &DLP_FKCL::Initialize, "Initialize"}, + {0x0002, &DLP_FKCL::Finalize, "Finalize"}, + {0x0003, &DLP_FKCL::GetEventDescription, "GetEventDescription"}, + {0x0004, &DLP_FKCL::GetChannels, "GetChannels"}, + {0x0005, &DLP_FKCL::StartScan, "StartScan"}, + {0x0006, &DLP_FKCL::StopScan, "StopScan"}, + {0x0007, &DLP_FKCL::GetServerInfo, "GetServerInfo"}, + {0x0008, &DLP_FKCL::GetTitleInfo, "GetTitleInfo"}, + {0x0009, &DLP_FKCL::GetTitleInfoInOrder, "GetTitleInfoInOrder"}, + {0x000A, &DLP_FKCL::DeleteScanInfo, "DeleteScanInfo"}, + {0x000B, &DLP_FKCL::StartSession, "StartFakeSession"}, + {0x000C, &DLP_FKCL::GetMyStatus, "GetMyStatus"}, + {0x000D, &DLP_FKCL::GetConnectingNodes, "GetConnectingNodes"}, + {0x000E, &DLP_FKCL::GetNodeInfo, "GetNodeInfo"}, + {0x000F, &DLP_FKCL::GetWirelessRebootPassphrase, "GetWirelessRebootPassphrase"}, + {0x0010, &DLP_FKCL::StopSession, "StopSession"}, + {0x0011, &DLP_FKCL::InitializeWithName, "InitializeWithName"}, // clang-format on }; diff --git a/src/core/hle/service/dlp/dlp_fkcl.h b/src/core/hle/service/dlp/dlp_fkcl.h index c05a77b49..74d41f4ac 100644 --- a/src/core/hle/service/dlp/dlp_fkcl.h +++ b/src/core/hle/service/dlp/dlp_fkcl.h @@ -1,20 +1,31 @@ -// 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. #pragma once #include "core/hle/service/service.h" +#include "dlp_clt_base.h" namespace Service::DLP { -class DLP_FKCL final : public ServiceFramework { +class DLP_FKCL final : public ServiceFramework, public DLP_Clt_Base { public: DLP_FKCL(); ~DLP_FKCL() = default; + virtual std::shared_ptr GetServiceFrameworkSharedPtr(); + private: SERVICE_SERIALIZATION_SIMPLE + + virtual bool IsFKCL() { + return true; + } + + void Initialize(Kernel::HLERequestContext& ctx); + void Finalize(Kernel::HLERequestContext& ctx); + void InitializeWithName(Kernel::HLERequestContext& ctx); }; } // namespace Service::DLP diff --git a/src/core/hle/service/dlp/dlp_srvr.cpp b/src/core/hle/service/dlp/dlp_srvr.cpp index b82d3e286..65632ca53 100644 --- a/src/core/hle/service/dlp/dlp_srvr.cpp +++ b/src/core/hle/service/dlp/dlp_srvr.cpp @@ -1,30 +1,55 @@ -// 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. #include "common/archives.h" #include "common/common_types.h" #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/result.h" #include "core/hle/service/dlp/dlp_srvr.h" +#include "core/hle/service/fs/fs_user.h" SERIALIZE_EXPORT_IMPL(Service::DLP::DLP_SRVR) namespace Service::DLP { +std::shared_ptr DLP_SRVR::GetServiceFrameworkSharedPtr() { + return shared_from_this(); +} + void DLP_SRVR::IsChild(Kernel::HLERequestContext& ctx) { + auto fs = system.ServiceManager().GetService("fs:USER"); + IPC::RequestParser rp(ctx); - rp.Skip(1, false); + u32 process_id = rp.Pop(); + + bool child; + if (!fs) { + LOG_CRITICAL(Service_DLP, "Could not get direct pointer fs:USER (sm returned null)"); + } + auto title_info = fs->GetProgramLaunchInfo(process_id); + + if (title_info) { + // 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; + } else { // child not found + child = false; + LOG_ERROR(Service_DLP, + "Could not determine program id from process id. (process id not found: {:x})", + process_id); + } IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); - rb.Push(false); - - LOG_WARNING(Service_DLP, "(STUBBED) called"); + rb.Push(child); } -DLP_SRVR::DLP_SRVR() : ServiceFramework("dlp:SRVR", 1) { +DLP_SRVR::DLP_SRVR() : ServiceFramework("dlp:SRVR", 1), DLP_Base(Core::System::GetInstance()) { static const FunctionInfo functions[] = { // clang-format off {0x0001, nullptr, "Initialize"}, diff --git a/src/core/hle/service/dlp/dlp_srvr.h b/src/core/hle/service/dlp/dlp_srvr.h index 625740d2f..d9b4fdd42 100644 --- a/src/core/hle/service/dlp/dlp_srvr.h +++ b/src/core/hle/service/dlp/dlp_srvr.h @@ -1,18 +1,24 @@ -// 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. #pragma once #include "core/hle/service/service.h" +#include "dlp_base.h" namespace Service::DLP { -class DLP_SRVR final : public ServiceFramework { +class DLP_SRVR final : public ServiceFramework, public DLP_Base { public: DLP_SRVR(); ~DLP_SRVR() = default; + virtual std::shared_ptr GetServiceFrameworkSharedPtr(); + virtual bool IsHost() { + return true; + } + private: void IsChild(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/http/http_c.cpp b/src/core/hle/service/http/http_c.cpp index 3a1dc1410..b1f47373b 100644 --- a/src/core/hle/service/http/http_c.cpp +++ b/src/core/hle/service/http/http_c.cpp @@ -1773,9 +1773,8 @@ void HTTP_C::SetClientCertContext(Kernel::HLERequestContext& ctx) { void HTTP_C::GetSSLError(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 context_handle = rp.Pop(); - const u32 unk = rp.Pop(); - LOG_WARNING(Service_HTTP, "(STUBBED) called, context_handle={}, unk={}", context_handle, unk); + LOG_WARNING(Service_HTTP, "(STUBBED) called, context_handle={}", context_handle); [[maybe_unused]] Context& http_context = GetContext(context_handle); diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index 43547ee42..fadbe3f3e 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp @@ -226,7 +226,7 @@ void NWM_UDS::HandleEAPoLPacket(const Network::WifiPacket& packet) { auto node = DeserializeNodeInfo(eapol_start.node); - if (eapol_start.conn_type == ConnectionType::Client) { + if (eapol_start.connection_type == ConnectionType::Client) { // Get an unused network node id u16 node_id = GetNextAvailableNodeId(); node.network_node_id = node_id; @@ -244,13 +244,13 @@ void NWM_UDS::HandleEAPoLPacket(const Network::WifiPacket& packet) { node_map[packet.transmitter_address].spec = false; BroadcastNodeMap(); - } else if (eapol_start.conn_type == ConnectionType::Spectator) { + } else if (eapol_start.connection_type == ConnectionType::Spectator) { node_map[packet.transmitter_address].node_id = NodeIDSpec; node_map[packet.transmitter_address].connected = true; node_map[packet.transmitter_address].spec = true; } else { LOG_ERROR(Service_NWM, "Client tried connecting with unknown connection type: 0x{:x}", - static_cast(eapol_start.conn_type)); + static_cast(eapol_start.connection_type)); } // Send the EAPoL-Logoff packet. @@ -595,9 +595,7 @@ boost::optional NWM_UDS::GetNodeMacAddress(u16 dest_node_id return destination->first; } -void NWM_UDS::Shutdown(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx); - +void NWM_UDS::ShutdownHLE() { initialized = false; for (auto& bind_node : channel_data) { @@ -607,12 +605,92 @@ void NWM_UDS::Shutdown(Kernel::HLERequestContext& ctx) { node_map.clear(); recv_buffer_memory.reset(); +} + +void NWM_UDS::Shutdown(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + ShutdownHLE(); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ResultSuccess); LOG_DEBUG(Service_NWM, "called"); } +void NWM_UDS::RecvBeaconBroadcastData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + u32 out_buffer_size = rp.Pop(); + + // scan input struct + u32 unk1 = rp.Pop(); + u32 unk2 = rp.Pop(); + + MacAddress mac_address; + rp.PopRaw(mac_address); + + // uninitialized data in scan input struct + rp.Skip(9, false); + + // end scan input struct + + u32 wlan_comm_id = rp.Pop(); + u32 id = rp.Pop(); + // From 3dbrew: + // 'Official user processes create a new event handle which is then passed to this command. + // However, those user processes don't save that handle anywhere afterwards.' + // So we don't save/use that event too. + std::shared_ptr input_event = rp.PopObject(); + + Kernel::MappedBuffer& out_buffer = rp.PopMappedBuffer(); + ASSERT(out_buffer.GetSize() == out_buffer_size); + + std::size_t cur_buffer_size = sizeof(BeaconDataReplyHeader); + + auto beacons = GetReceivedBeacons(mac_address); + + BeaconDataReplyHeader data_reply_header{}; + data_reply_header.total_entries = static_cast(beacons.size()); + data_reply_header.max_output_size = out_buffer_size; + + // Write each of the received beacons into the buffer + for (const auto& beacon : beacons) { + BeaconEntryHeader entry{}; + // TODO(Subv): Figure out what this size is used for. + entry.unk_size = static_cast(sizeof(BeaconEntryHeader) + beacon.data.size()); + entry.total_size = static_cast(sizeof(BeaconEntryHeader) + beacon.data.size()); + entry.wifi_channel = beacon.channel; + entry.header_size = sizeof(BeaconEntryHeader); + entry.mac_address = beacon.transmitter_address; + + ASSERT(cur_buffer_size < out_buffer_size); + + out_buffer.Write(&entry, cur_buffer_size, sizeof(BeaconEntryHeader)); + cur_buffer_size += sizeof(BeaconEntryHeader); + const unsigned char* beacon_data = beacon.data.data(); + out_buffer.Write(beacon_data, cur_buffer_size, beacon.data.size()); + cur_buffer_size += beacon.data.size(); + } + + // Update the total size in the structure and write it to the buffer again. + data_reply_header.total_size = static_cast(cur_buffer_size); + out_buffer.Write(&data_reply_header, 0, sizeof(BeaconDataReplyHeader)); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(ResultSuccess); + rb.PushMappedBuffer(out_buffer); + + // on a real 3ds this is about 0.38 seconds + static constexpr std::chrono::nanoseconds UDSBeaconScanInterval{300000000}; + + ctx.SleepClientThread("uds::RecvBeaconBroadcastData", UDSBeaconScanInterval, nullptr); + + LOG_DEBUG(Service_NWM, + "called out_buffer_size=0x{:08X}, wlan_comm_id=0x{:08X}, id=0x{:08X}," + "unk1=0x{:08X}, unk2=0x{:08X}, offset={}", + out_buffer_size, wlan_comm_id, id, unk1, unk2, cur_buffer_size); +} + ResultVal> NWM_UDS::Initialize( u32 sharedmem_size, const NodeInfo& node, u16 version, std::shared_ptr sharedmem) { @@ -671,25 +749,41 @@ void NWM_UDS::InitializeDeprecated(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NWM, "called sharedmem_size=0x{:08X}", sharedmem_size); } +ConnectionStatus NWM_UDS::GetConnectionStatusHLE() { + std::scoped_lock lock(connection_status_mutex); + ConnectionStatus cs_out = connection_status; + + // Reset the bitmask of changed nodes after each call to this + // function to prevent falsely informing games of outstanding + // changes in subsequent calls. + // TODO(Subv): Find exactly where the NWM module resets this value. + connection_status.changed_nodes = 0; + + return cs_out; +} + void NWM_UDS::GetConnectionStatus(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(13, 0); rb.Push(ResultSuccess); - { - std::scoped_lock lock(connection_status_mutex); - rb.PushRaw(connection_status); - - // Reset the bitmask of changed nodes after each call to this - // function to prevent falsely informing games of outstanding - // changes in subsequent calls. - // TODO(Subv): Find exactly where the NWM module resets this value. - connection_status.changed_nodes = 0; - } + rb.PushRaw(GetConnectionStatusHLE()); LOG_DEBUG(Service_NWM, "called"); } +std::unique_ptr NWM_UDS::GetNodeInformationHLE(u16 network_node_id) { + std::scoped_lock lock(connection_status_mutex); + auto itr = + std::find_if(node_info.begin(), node_info.end(), [network_node_id](const NodeInfo& node) { + return node.network_node_id == network_node_id; + }); + if (itr == node_info.end()) { + return nullptr; + } + return std::make_unique(*itr); +} + void NWM_UDS::GetNodeInformation(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u16 network_node_id = rp.Pop(); @@ -702,12 +796,8 @@ void NWM_UDS::GetNodeInformation(Kernel::HLERequestContext& ctx) { } { - std::scoped_lock lock(connection_status_mutex); - auto itr = std::find_if(node_info.begin(), node_info.end(), - [network_node_id](const NodeInfo& node) { - return node.network_node_id == network_node_id; - }); - if (itr == node_info.end()) { + auto node = GetNodeInformationHLE(network_node_id); + if (!node) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(Result(ErrorDescription::NotFound, ErrorModule::UDS, ErrorSummary::WrongArgument, ErrorLevel::Status)); @@ -716,46 +806,31 @@ void NWM_UDS::GetNodeInformation(Kernel::HLERequestContext& ctx) { IPC::RequestBuilder rb = rp.MakeBuilder(11, 0); rb.Push(ResultSuccess); - rb.PushRaw(*itr); + rb.PushRaw(*node); } LOG_DEBUG(Service_NWM, "called"); } -void NWM_UDS::Bind(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx); - - u32 bind_node_id = rp.Pop(); - u32 recv_buffer_size = rp.Pop(); - u8 data_channel = rp.Pop(); - u16 network_node_id = rp.Pop(); - - LOG_DEBUG(Service_NWM, "called"); - +std::pair> NWM_UDS::BindHLE(u32 bind_node_id, + u32 recv_buffer_size, + u8 data_channel, + u16 network_node_id) { if (data_channel == 0 || bind_node_id == 0) { - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, - ErrorSummary::WrongArgument, ErrorLevel::Usage)); LOG_WARNING(Service_NWM, "data_channel = {}, bind_node_id = {}", data_channel, bind_node_id); - return; + return std::make_pair(ResultStatus::BindError_ArgsZero, nullptr); } constexpr std::size_t MaxBindNodes = 16; if (channel_data.size() >= MaxBindNodes) { - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(Result(ErrorDescription::OutOfMemory, ErrorModule::UDS, ErrorSummary::OutOfResource, - ErrorLevel::Status)); LOG_WARNING(Service_NWM, "max bind nodes"); - return; + return std::make_pair(ResultStatus::BindError_MaxBinds, nullptr); } constexpr u32 MinRecvBufferSize = 0x5F4; if (recv_buffer_size < MinRecvBufferSize) { - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(Result(ErrorDescription::TooLarge, ErrorModule::UDS, ErrorSummary::WrongArgument, - ErrorLevel::Usage)); LOG_WARNING(Service_NWM, "MinRecvBufferSize"); - return; + return std::make_pair(ResultStatus::BindError_RecvBufferTooLarge, nullptr); } // Create a new event for this bind node. @@ -766,12 +841,61 @@ void NWM_UDS::Bind(Kernel::HLERequestContext& ctx) { ASSERT(channel_data.find(data_channel) == channel_data.end()); // TODO(B3N30): Support more than one bind node per channel. channel_data[data_channel] = {bind_node_id, data_channel, network_node_id, event}; + return std::make_pair(ResultStatus::ResultSuccess, std::move(event)); +} + +void NWM_UDS::Bind(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + u32 bind_node_id = rp.Pop(); + u32 recv_buffer_size = rp.Pop(); + u8 data_channel = rp.Pop(); + u16 network_node_id = rp.Pop(); + + auto [ret, event] = BindHLE(bind_node_id, recv_buffer_size, data_channel, network_node_id); + + switch (ret) { + case ResultStatus::BindError_ArgsZero: { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, + ErrorSummary::WrongArgument, ErrorLevel::Usage)); + return; + } + case ResultStatus::BindError_MaxBinds: { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(Result(ErrorDescription::OutOfMemory, ErrorModule::UDS, ErrorSummary::OutOfResource, + ErrorLevel::Status)); + return; + } + case ResultStatus::BindError_RecvBufferTooLarge: { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(Result(ErrorDescription::TooLarge, ErrorModule::UDS, ErrorSummary::WrongArgument, + ErrorLevel::Usage)); + return; + } + default:; + } IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(ResultSuccess); rb.PushCopyObjects(event); } +void NWM_UDS::UnbindHLE(u32 bind_node_id) { + std::scoped_lock lock(connection_status_mutex); + + auto itr = + std::find_if(channel_data.begin(), channel_data.end(), [bind_node_id](const auto& data) { + return data.second.bind_node_id == bind_node_id; + }); + + if (itr != channel_data.end()) { + // TODO(B3N30): Check out what Unbind does if the bind_node_id wasn't in the map + itr->second.event->Signal(); + channel_data.erase(itr); + } +} + void NWM_UDS::Unbind(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); @@ -783,18 +907,7 @@ void NWM_UDS::Unbind(Kernel::HLERequestContext& ctx) { return; } - std::scoped_lock lock(connection_status_mutex); - - auto itr = - std::find_if(channel_data.begin(), channel_data.end(), [bind_node_id](const auto& data) { - return data.second.bind_node_id == bind_node_id; - }); - - if (itr != channel_data.end()) { - // TODO(B3N30): Check out what Unbind does if the bind_node_id wasn't in the map - itr->second.event->Signal(); - channel_data.erase(itr); - } + UnbindHLE(bind_node_id); IPC::RequestBuilder rb = rp.MakeBuilder(5, 0); rb.Push(ResultSuccess); @@ -959,10 +1072,22 @@ void NWM_UDS::EjectClient(Kernel::HLERequestContext& ctx) { } } +Result NWM_UDS::UpdateNetworkAttributeHLE(u16 node_bitmask, u8 flag) { + [[maybe_unused]] constexpr u8 flag_disconnect_and_block_non_bitmasked_nodes = 0x1; + + // stubbed + + return ResultSuccess; +} + void NWM_UDS::UpdateNetworkAttribute(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - rp.Skip(2, false); - LOG_WARNING(Service_NWM, "stubbed"); + + u16 bitmask = rp.Pop(); + u8 flag = rp.Pop(); + + auto res = UpdateNetworkAttributeHLE(bitmask, flag); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(ResultSuccess); } @@ -1005,52 +1130,6 @@ void NWM_UDS::DestroyNetwork(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NWM, "called"); } -void NWM_UDS::DisconnectNetwork(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_NWM, "disconnecting from network"); - IPC::RequestParser rp(ctx); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - - using Network::WifiPacket; - WifiPacket deauth; - { - std::scoped_lock lock(connection_status_mutex); - if (connection_status.status == NetworkStatus::ConnectedAsHost) { - // A real 3ds makes strange things here. We do the same - u16_le tmp_node_id = connection_status.network_node_id; - connection_status = {}; - connection_status.status = NetworkStatus::ConnectedAsHost; - connection_status.network_node_id = tmp_node_id; - node_map.clear(); - LOG_DEBUG(Service_NWM, "called as a host"); - rb.Push(Result(ErrCodes::WrongStatus, ErrorModule::UDS, ErrorSummary::InvalidState, - ErrorLevel::Status)); - return; - } - u16_le tmp_node_id = connection_status.network_node_id; - connection_status = {}; - connection_status.status = NetworkStatus::NotConnected; - connection_status.network_node_id = tmp_node_id; - node_map.clear(); - connection_status_event->Signal(); - - deauth.channel = network_channel; - // TODO(B3N30): Add disconnect reason - deauth.data = {}; - deauth.destination_address = network_info.host_mac_address; - deauth.type = WifiPacket::PacketType::Deauthentication; - } - - SendPacket(deauth); - - for (auto& bind_node : channel_data) { - bind_node.second.event->Signal(); - } - channel_data.clear(); - - rb.Push(ResultSuccess); - LOG_DEBUG(Service_NWM, "called"); -} - void NWM_UDS::SendTo(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); @@ -1062,32 +1141,54 @@ void NWM_UDS::SendTo(Kernel::HLERequestContext& ctx) { u8 flags = rp.Pop(); std::vector input_buffer = rp.PopStaticBuffer(); - ASSERT(input_buffer.size() >= data_size); - input_buffer.resize(data_size); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + auto res = SendToHLE(dest_node_id, data_channel, data_size, flags, input_buffer); + + switch (res) { + case ResultStatus::SendError_PacketSizeTooLarge: + rb.Push(Result(ErrorDescription::TooLarge, ErrorModule::UDS, ErrorSummary::WrongArgument, + ErrorLevel::Usage)); + return; + case ResultStatus::SendError_NotConnected: + rb.Push(Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, + ErrorSummary::InvalidState, ErrorLevel::Status)); + return; + case ResultStatus::SendError_BadNode: + case ResultStatus::SendError_BadMacAddress: + rb.Push(Result(ErrorDescription::NotFound, ErrorModule::UDS, ErrorSummary::WrongArgument, + ErrorLevel::Status)); + return; + default:; + } + + rb.Push(ResultSuccess); +} + +ResultStatus NWM_UDS::SendToHLE(u32 dest_node_id, u8 data_channel, u32 data_size, u8 flags, + std::vector input_buffer) { + ASSERT(input_buffer.size() >= data_size); + input_buffer.resize(data_size); + std::scoped_lock lock(connection_status_mutex); if (connection_status.status != NetworkStatus::ConnectedAsClient && connection_status.status != NetworkStatus::ConnectedAsHost) { - rb.Push(Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, - ErrorSummary::InvalidState, ErrorLevel::Status)); - return; + LOG_ERROR(Service_NWM, + "You are not connected as a client or a host. (you are connected as type {})", + connection_status.status); + return ResultStatus::SendError_NotConnected; } // There should never be a dest_node_id of 0 if (dest_node_id == 0) { - rb.Push(Result(ErrorDescription::NotFound, ErrorModule::UDS, ErrorSummary::WrongArgument, - ErrorLevel::Status)); LOG_ERROR(Service_NWM, "dest_node_id is 0"); - return; + return ResultStatus::SendError_BadNode; } if (dest_node_id == connection_status.network_node_id) { LOG_ERROR(Service_NWM, "tried to send packet to itself"); - rb.Push(Result(ErrorDescription::NotFound, ErrorModule::UDS, ErrorSummary::WrongArgument, - ErrorLevel::Status)); - return; + return ResultStatus::SendError_BadNode; } if (flags >> 2) { @@ -1096,18 +1197,16 @@ void NWM_UDS::SendTo(Kernel::HLERequestContext& ctx) { auto dest_address = GetNodeMacAddress(dest_node_id, flags); if (!dest_address) { - rb.Push(Result(ErrorDescription::NotFound, ErrorModule::UDS, ErrorSummary::WrongArgument, - ErrorLevel::Status)); - return; + LOG_ERROR(Service_NWM, "Destination address was 0"); + return ResultStatus::SendError_BadMacAddress; } constexpr std::size_t MaxSize = 0x5C6; if (data_size > MaxSize) { - rb.Push(Result(ErrorDescription::TooLarge, ErrorModule::UDS, ErrorSummary::WrongArgument, - ErrorLevel::Usage)); - return; + LOG_ERROR(Service_NWM, "Data size was greater than the max packet size {} > {}", data_size, + MaxSize); + return ResultStatus::SendError_PacketSizeTooLarge; } - // TODO(B3N30): Increment the sequence number after each sent packet. u16 sequence_number = 0; std::vector data_payload = @@ -1126,7 +1225,7 @@ void NWM_UDS::SendTo(Kernel::HLERequestContext& ctx) { SendPacket(packet); - rb.Push(ResultSuccess); + return ResultStatus::ResultSuccess; } void NWM_UDS::PullPacket(Kernel::HLERequestContext& ctx) { @@ -1135,7 +1234,47 @@ void NWM_UDS::PullPacket(Kernel::HLERequestContext& ctx) { u32 bind_node_id = rp.Pop(); u32 max_out_buff_size_aligned = rp.Pop(); u32 max_out_buff_size = rp.Pop(); + std::vector output_buffer; + SecureDataHeader secure_data; + + auto ret = PullPacketHLE(bind_node_id, max_out_buff_size, max_out_buff_size_aligned, + output_buffer, &secure_data); + + switch (ret.error()) { + case ResultStatus::RecvError_NotConnected: { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, + ErrorSummary::InvalidState, ErrorLevel::Status)); + return; + } + case ResultStatus::RecvError_BadNode: { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, + ErrorSummary::InvalidState, ErrorLevel::Status)); + return; + } + case ResultStatus::RecvError_PacketSizeTooLarge: { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(Result(ErrorDescription::TooLarge, ErrorModule::UDS, ErrorSummary::WrongArgument, + ErrorLevel::Usage)); + return; + } + default:; + } + + IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); + + rb.Push(ResultSuccess); + rb.Push(*ret); // return is data size if gt/eq to 0 + rb.Push(secure_data.src_node_id); + rb.PushStaticBuffer(std::move(output_buffer), 0); +} + +Common::Expected NWM_UDS::PullPacketHLE(u32 bind_node_id, u32 max_out_buff_size, + u32 max_out_buff_size_aligned, + std::vector& output_buffer, + void* secure_data_out) { // This size is hard coded into the uds module. We don't know the meaning yet. u32 buff_size = std::min(max_out_buff_size_aligned, 0x172) << 2; @@ -1143,10 +1282,8 @@ void NWM_UDS::PullPacket(Kernel::HLERequestContext& ctx) { if (connection_status.status != NetworkStatus::ConnectedAsHost && connection_status.status != NetworkStatus::ConnectedAsClient && connection_status.status != NetworkStatus::ConnectedAsSpectator) { - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, - ErrorSummary::InvalidState, ErrorLevel::Status)); - return; + LOG_ERROR(Service_NWM, "Not connected yet."); + return Common::Unexpected(ResultStatus::RecvError_NotConnected); } auto channel = @@ -1155,20 +1292,13 @@ void NWM_UDS::PullPacket(Kernel::HLERequestContext& ctx) { }); if (channel == channel_data.end()) { - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, - ErrorSummary::WrongArgument, ErrorLevel::Usage)); - return; + LOG_ERROR(Service_NWM, "Could not find channel bn 0x{:x}.", bind_node_id); + return Common::Unexpected(ResultStatus::RecvError_BadNode); } if (channel->second.received_packets.empty()) { - std::vector output_buffer(buff_size); - IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); - rb.Push(ResultSuccess); - rb.Push(0); - rb.Push(0); - rb.PushStaticBuffer(std::move(output_buffer), 0); - return; + output_buffer.resize(buff_size); + return int(0); } const auto& next_packet = channel->second.received_packets.front(); @@ -1176,26 +1306,22 @@ void NWM_UDS::PullPacket(Kernel::HLERequestContext& ctx) { auto secure_data = ParseSecureDataHeader(next_packet); auto data_size = secure_data.GetActualDataSize(); - if (data_size > max_out_buff_size) { - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(Result(ErrorDescription::TooLarge, ErrorModule::UDS, ErrorSummary::WrongArgument, - ErrorLevel::Usage)); - return; + if (secure_data_out) { + *reinterpret_cast(secure_data_out) = secure_data; } - IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); + if (data_size > max_out_buff_size) { + LOG_ERROR(Service_NWM, "Data size was too large."); + return Common::Unexpected(ResultStatus::RecvError_PacketSizeTooLarge); + } + output_buffer.resize(buff_size); - std::vector output_buffer(buff_size); // Write the actual data. std::memcpy(output_buffer.data(), next_packet.data() + sizeof(LLCHeader) + sizeof(SecureDataHeader), data_size); - rb.Push(ResultSuccess); - rb.Push(data_size); - rb.Push(secure_data.src_node_id); - rb.PushStaticBuffer(std::move(output_buffer), 0); - channel->second.received_packets.pop_front(); + return int(data_size); } void NWM_UDS::GetChannel(Kernel::HLERequestContext& ctx) { @@ -1219,8 +1345,13 @@ public: void WakeUp(std::shared_ptr thread, Kernel::HLERequestContext& ctx, Kernel::ThreadWakeupReason reason) { - // TODO(B3N30): Add error handling for host full and timeout IPC::RequestBuilder rb(ctx, command_id, 1, 0); + if (reason == Kernel::ThreadWakeupReason::Timeout) { + LOG_ERROR(Service_NWM, "timed out when trying to connect to UDS server"); + rb.Push(Result(ErrorDescription::Timeout, ErrorModule::UDS, ErrorSummary::Canceled, + ErrorLevel::Status)); + return; + } rb.Push(ResultSuccess); LOG_DEBUG(Service_NWM, "connection sequence finished"); } @@ -1237,95 +1368,25 @@ private: friend class boost::serialization::access; }; -void NWM_UDS::RecvBeaconBroadcastData(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx); +void NWM_UDS::ConnectToNetworkHLE(NetworkInfo net_info, u8 connection_type, + std::vector passphrase) { + network_info = net_info; - u32 out_buffer_size = rp.Pop(); + conn_type = static_cast(connection_type); - // scan input struct - u32 unk1 = rp.Pop(); - u32 unk2 = rp.Pop(); - - MacAddress mac_address; - rp.PopRaw(mac_address); - - // uninitialized data in scan input struct - rp.Skip(9, false); - - // end scan input struct - - u32 wlan_comm_id = rp.Pop(); - u32 id = rp.Pop(); - // From 3dbrew: - // 'Official user processes create a new event handle which is then passed to this command. - // However, those user processes don't save that handle anywhere afterwards.' - // So we don't save/use that event too. - std::shared_ptr input_event = rp.PopObject(); - - Kernel::MappedBuffer out_buffer = rp.PopMappedBuffer(); - ASSERT(out_buffer.GetSize() == out_buffer_size); - - std::size_t cur_buffer_size = sizeof(BeaconDataReplyHeader); - - // on a real 3ds this is about 0.38 seconds - static constexpr std::chrono::nanoseconds UDSBeaconScanInterval{300000000}; - - ctx.SleepClientThread("uds::RecvBeaconBroadcastData", UDSBeaconScanInterval, - std::make_shared(0xF)); - - // Retrieve all beacon frames that were received from the desired mac address. - auto beacons = GetReceivedBeacons(mac_address); - - BeaconDataReplyHeader data_reply_header{}; - data_reply_header.total_entries = static_cast(beacons.size()); - data_reply_header.max_output_size = out_buffer_size; - - // Write each of the received beacons into the buffer - for (const auto& beacon : beacons) { - BeaconEntryHeader entry{}; - // TODO(Subv): Figure out what this size is used for. - entry.unk_size = static_cast(sizeof(BeaconEntryHeader) + beacon.data.size()); - entry.total_size = static_cast(sizeof(BeaconEntryHeader) + beacon.data.size()); - entry.wifi_channel = beacon.channel; - entry.header_size = sizeof(BeaconEntryHeader); - entry.mac_address = beacon.transmitter_address; - - ASSERT(cur_buffer_size < out_buffer_size); - - out_buffer.Write(&entry, cur_buffer_size, sizeof(BeaconEntryHeader)); - cur_buffer_size += sizeof(BeaconEntryHeader); - const unsigned char* beacon_data = beacon.data.data(); - out_buffer.Write(beacon_data, cur_buffer_size, beacon.data.size()); - cur_buffer_size += beacon.data.size(); - } - - // Update the total size in the structure and write it to the buffer again. - data_reply_header.total_size = static_cast(cur_buffer_size); - out_buffer.Write(&data_reply_header, 0, sizeof(BeaconDataReplyHeader)); - - IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); - rb.Push(ResultSuccess); - rb.PushMappedBuffer(out_buffer); - - LOG_DEBUG(Service_NWM, - "called out_buffer_size=0x{:08X}, wlan_comm_id=0x{:08X}, id=0x{:08X}," - "unk1=0x{:08X}, unk2=0x{:08X}, offset={}", - out_buffer_size, wlan_comm_id, id, unk1, unk2, cur_buffer_size); + // Start the connection sequence + StartConnectionSequence(network_info.host_mac_address); } void NWM_UDS::ConnectToNetwork(Kernel::HLERequestContext& ctx, u16 command_id, std::span network_info_buffer, u8 connection_type, std::vector passphrase) { - network_info = {}; - std::memcpy(&network_info, network_info_buffer.data(), network_info_buffer.size()); - conn_type = static_cast(connection_type); - - // Start the connection sequence - StartConnectionSequence(network_info.host_mac_address); - - // 300 ms + NetworkInfo net_info; + std::memcpy(&net_info, network_info_buffer.data(), network_info_buffer.size()); + ConnectToNetworkHLE(net_info, connection_type, passphrase); + // Originally 300 ms, but was changed to 5s to accommodate high ping // Since this timing is handled by core_timing it could differ from the 'real world' time - static constexpr std::chrono::nanoseconds UDSConnectionTimeout{300000000}; + static constexpr std::chrono::nanoseconds UDSConnectionTimeout{5000000000}; connection_event = ctx.SleepClientThread("uds::ConnectToNetwork", UDSConnectionTimeout, std::make_shared(command_id)); @@ -1364,6 +1425,60 @@ void NWM_UDS::ConnectToNetworkDeprecated(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NWM, "called"); } +ResultStatus NWM_UDS::DisconnectNetworkHLE() { + using Network::WifiPacket; + WifiPacket deauth; + { + std::scoped_lock lock(connection_status_mutex); + if (connection_status.status == NetworkStatus::ConnectedAsHost) { + // A real 3ds makes strange things here. We do the same + u16_le tmp_node_id = connection_status.network_node_id; + connection_status = {}; + connection_status.status = NetworkStatus::ConnectedAsHost; + connection_status.network_node_id = tmp_node_id; + node_map.clear(); + return ResultStatus::DisconError_CalledAsHost; + } + u16_le tmp_node_id = connection_status.network_node_id; + connection_status = {}; + connection_status.status = NetworkStatus::NotConnected; + connection_status.network_node_id = tmp_node_id; + node_map.clear(); + connection_status_event->Signal(); + + deauth.channel = network_channel; + // TODO(B3N30): Add disconnect reason + deauth.data = {}; + deauth.destination_address = network_info.host_mac_address; + deauth.type = WifiPacket::PacketType::Deauthentication; + } + + SendPacket(deauth); + + for (auto& bind_node : channel_data) { + bind_node.second.event->Signal(); + } + channel_data.clear(); + + return ResultStatus::ResultSuccess; +} + +void NWM_UDS::DisconnectNetwork(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NWM, "disconnecting from network"); + IPC::RequestParser rp(ctx); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + auto res = DisconnectNetworkHLE(); + if (res == ResultStatus::DisconError_CalledAsHost) { + LOG_DEBUG(Service_NWM, "called as a host"); + rb.Push(Result(ErrCodes::WrongStatus, ErrorModule::UDS, ErrorSummary::InvalidState, + ErrorLevel::Status)); + return; + } + + rb.Push(ResultSuccess); +} + void NWM_UDS::SetApplicationData(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); @@ -1473,6 +1588,16 @@ void NWM_UDS::DecryptBeaconData(Kernel::HLERequestContext& ctx) { rb.PushStaticBuffer(std::move(output_buffer), 0); } +void NWM_UDS::EjectSpectators(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + LOG_WARNING(Service_NWM, "(STUBBED) called"); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + rb.Push(ResultSuccess); +} + // Sends a 802.11 beacon frame with information about the current network. void NWM_UDS::BeaconBroadcastCallback(std::uintptr_t user_data, s64 cycles_late) { // Don't do anything if we're not actually hosting a network @@ -1504,7 +1629,7 @@ NWM_UDS::NWM_UDS(Core::System& system) : ServiceFramework("nwm::UDS"), system(sy {0x0003, &NWM_UDS::Shutdown, "Shutdown"}, {0x0004, &NWM_UDS::BeginHostingNetworkDeprecated, "BeginHostingNetwork (deprecated)"}, {0x0005, &NWM_UDS::EjectClient, "EjectClient"}, - {0x0006, nullptr, "EjectSpectator"}, + {0x0006, &NWM_UDS::EjectSpectators, "EjectSpectators"}, {0x0007, &NWM_UDS::UpdateNetworkAttribute, "UpdateNetworkAttribute"}, {0x0008, &NWM_UDS::DestroyNetwork, "DestroyNetwork"}, {0x0009, &NWM_UDS::ConnectToNetworkDeprecated, "ConnectToNetwork (deprecated)"}, @@ -1542,8 +1667,7 @@ NWM_UDS::NWM_UDS(Core::System& system) : ServiceFramework("nwm::UDS"), system(sy MacAddress mac; - auto cfg = system.ServiceManager().GetService("cfg:u"); - if (cfg.get()) { + if (auto cfg = system.ServiceManager().GetService("cfg:u")) { auto cfg_module = cfg->GetModule(); mac = Service::CFG::MacToArray(cfg_module->GetMacAddress()); } diff --git a/src/core/hle/service/nwm/nwm_uds.h b/src/core/hle/service/nwm/nwm_uds.h index 045d4ba96..feefe8a86 100644 --- a/src/core/hle/service/nwm/nwm_uds.h +++ b/src/core/hle/service/nwm/nwm_uds.h @@ -31,10 +31,31 @@ class Event; class SharedMemory; } // namespace Kernel +namespace Service::DLP { +class DLP_Base; +class DLP_Clt_Base; +class DLP_SRVR; +} // namespace Service::DLP + // Local-WLAN service namespace Service::NWM { +enum class ResultStatus { + ResultSuccess = 0, + BindError_ArgsZero, + BindError_MaxBinds, + BindError_RecvBufferTooLarge, + DisconError_CalledAsHost, + SendError_NotConnected, + SendError_BadNode, + SendError_BadMacAddress, + SendError_PacketSizeTooLarge, + RecvError_NotConnected, + RecvError_BadNode, + RecvError_PacketSizeTooLarge, +}; + using MacAddress = std::array; const std::size_t ApplicationDataSize = 0xC8; @@ -429,6 +450,16 @@ private: */ void EjectClient(Kernel::HLERequestContext& ctx); + /** + * NWM_UDS::EjectSpectators Disconnects all spectators and prevents them from rejoining. + * Inputs: + * 0 : Command header + * Outputs: + * 0 : Return header + * 1 : Result of function, 0 on success, otherwise error code + */ + void EjectSpectators(Kernel::HLERequestContext& ctx); + /** * NWM_UDS::DecryptBeaconData service function. * Decrypts the encrypted data tags contained in the 802.11 beacons. @@ -452,12 +483,31 @@ private: u32 sharedmem_size, const NodeInfo& node, u16 version, std::shared_ptr sharedmem); + void ShutdownHLE(); + Common::Expected PullPacketHLE(u32 bind_node_id, u32 max_out_buff_size, + u32 max_out_buff_size_aligned, + std::vector& output_buffer, + void* secure_data_out); + ConnectionStatus GetConnectionStatusHLE(); + ResultStatus DisconnectNetworkHLE(); + std::pair> BindHLE(u32 bind_node_id, + u32 recv_buffer_size, + u8 data_channel, + u16 network_node_id); + void UnbindHLE(u32 bind_node_id); + std::unique_ptr GetNodeInformationHLE(u16 network_node_id); + 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 BeginHostingNetwork(std::span network_info_buffer, std::vector passphrase); void ConnectToNetwork(Kernel::HLERequestContext& ctx, u16 command_id, std::span network_info_buffer, u8 connection_type, std::vector passphrase); + void ConnectToNetworkHLE(NetworkInfo net_info, u8 connection_type, std::vector passphrase); + void BeaconBroadcastCallback(std::uintptr_t user_data, s64 cycles_late); /** @@ -576,7 +626,7 @@ private: // Mutex to synchronize access to the connection status between the emulation thread and the // network thread. - std::mutex connection_status_mutex; + std::recursive_mutex connection_status_mutex; std::shared_ptr connection_event; @@ -590,6 +640,9 @@ private: template void serialize(Archive& ar, const unsigned int); friend class boost::serialization::access; + friend class Service::DLP::DLP_Base; + friend class Service::DLP::DLP_Clt_Base; + friend class Service::DLP::DLP_SRVR; }; } // namespace Service::NWM diff --git a/src/core/hle/service/nwm/uds_data.cpp b/src/core/hle/service/nwm/uds_data.cpp index 305aec843..1606d0286 100644 --- a/src/core/hle/service/nwm/uds_data.cpp +++ b/src/core/hle/service/nwm/uds_data.cpp @@ -290,7 +290,7 @@ std::vector GenerateEAPoLStartFrame(u16 association_id, ConnectionType conn_ const NodeInfo& node_info) { EAPoLStartPacket eapol_start{}; eapol_start.association_id = association_id; - eapol_start.conn_type = conn_type; + eapol_start.connection_type = conn_type; eapol_start.node.friend_code_seed = node_info.friend_code_seed; std::copy(node_info.username.begin(), node_info.username.end(), diff --git a/src/core/hle/service/nwm/uds_data.h b/src/core/hle/service/nwm/uds_data.h index faaf53448..50dbda15a 100644 --- a/src/core/hle/service/nwm/uds_data.h +++ b/src/core/hle/service/nwm/uds_data.h @@ -91,7 +91,7 @@ constexpr u16 EAPoLStartMagic = 0x201; struct EAPoLStartPacket { u16_be magic = EAPoLStartMagic; u16_be association_id; - enum_le conn_type; + enum_le connection_type; INSERT_PADDING_BYTES(3); EAPoLNodeInfo node; }; diff --git a/src/core/hw/aes/key.cpp b/src/core/hw/aes/key.cpp index 776eab670..2e46de838 100644 --- a/src/core/hw/aes/key.cpp +++ b/src/core/hw/aes/key.cpp @@ -1,4 +1,4 @@ -// Copyright 2017 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -133,6 +133,9 @@ AESIV nfc_iv; AESKey otp_key; AESIV otp_iv; +// gets xor'd with the mac address to produce the final iv +AESIV dlp_checksum_mod_iv; + KeySlot movable_key; KeySlot movable_cmac; @@ -249,6 +252,11 @@ void LoadPresetKeys() { continue; } + if (name == "dlpChecksumModIv") { + dlp_checksum_mod_iv = key; + continue; + } + const auto key_slot = ParseKeySlotName(name); if (!key_slot) { LOG_ERROR(HW_AES, "Invalid key name '{}'", name); @@ -371,4 +379,8 @@ const AESKey& GetMovableKey(bool cmac_key) { return cmac_key ? movable_cmac.normal.value() : movable_key.normal.value(); } +const AESIV& GetDlpChecksumModIv() { + return dlp_checksum_mod_iv; +} + } // namespace HW::AES diff --git a/src/core/hw/aes/key.h b/src/core/hw/aes/key.h index c9d0d4f09..2920e7b86 100644 --- a/src/core/hw/aes/key.h +++ b/src/core/hw/aes/key.h @@ -1,4 +1,4 @@ -// Copyright 2017 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -102,4 +102,6 @@ std::pair GetOTPKeyIV(); const AESKey& GetMovableKey(bool cmac_key); +const AESIV& GetDlpChecksumModIv(); + } // namespace HW::AES diff --git a/src/core/hw/default_keys.h b/src/core/hw/default_keys.h index 019d5cfd0..554b74ae1 100644 --- a/src/core/hw/default_keys.h +++ b/src/core/hw/default_keys.h @@ -1,8 +1,8 @@ -/* Generated by bin2c, do not edit manually */ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. -/* Contents of file keys.enc */ -const long int default_keys_enc_size = 7360; -const unsigned char default_keys_enc[7360] = { +unsigned char default_keys_enc[] = { 0x4E, 0x81, 0xE9, 0x54, 0xCC, 0xDE, 0xFD, 0x56, 0x7D, 0xD2, 0x72, 0xE6, 0xD9, 0xCD, 0x8E, 0x11, 0xE1, 0x7F, 0x74, 0xF4, 0xFC, 0x54, 0xA6, 0xA4, 0x27, 0xC2, 0xD7, 0x50, 0xEA, 0xE7, 0xBE, 0xC9, 0xA7, 0x5E, 0xE0, 0x2E, 0x4A, 0xBE, 0xF5, 0xD5, 0x0D, 0x22, 0x76, 0x2E, 0xB6, 0x80, 0xD8, 0x54, @@ -279,188 +279,193 @@ const unsigned char default_keys_enc[7360] = { 0x2A, 0xE5, 0x39, 0x2D, 0xA3, 0x4B, 0xD1, 0x82, 0xF5, 0x68, 0x1F, 0x42, 0xE4, 0x0B, 0xB0, 0x2E, 0x37, 0x3C, 0x2A, 0x12, 0x61, 0xEC, 0x54, 0x1D, 0xA2, 0xA3, 0x89, 0x54, 0x25, 0xAD, 0x17, 0xE0, 0x8A, 0xFB, 0xA8, 0xF4, 0x6D, 0xAF, 0xF0, 0x84, 0x12, 0xE1, 0x92, 0x72, 0x9B, 0x41, 0x99, 0xA6, - 0x3E, 0x12, 0x74, 0x28, 0x6F, 0x9C, 0xA3, 0x63, 0xC6, 0x88, 0x76, 0xC6, 0x22, 0x76, 0xEC, 0x48, - 0x2B, 0xB5, 0x41, 0x81, 0x45, 0xBB, 0xCB, 0x4D, 0x9D, 0x77, 0x95, 0x49, 0x5F, 0x43, 0x27, 0x40, - 0xD1, 0x4E, 0xEB, 0x2B, 0xD9, 0x0D, 0x7B, 0xD3, 0x36, 0xAE, 0x18, 0x9E, 0x45, 0x36, 0xA1, 0xA0, - 0xB2, 0x30, 0xF1, 0x82, 0xE5, 0x73, 0x5F, 0xC4, 0x75, 0x43, 0xE9, 0xD7, 0x16, 0xE1, 0x98, 0xB6, - 0x60, 0xEB, 0x43, 0x4D, 0x5C, 0xBE, 0x0C, 0xC1, 0x92, 0x8D, 0x9E, 0x25, 0x3A, 0x55, 0xF6, 0x66, - 0x7C, 0x7F, 0xB4, 0x3F, 0x08, 0xA3, 0x1F, 0xC4, 0xCE, 0x05, 0x0F, 0xBF, 0x99, 0x4D, 0x40, 0x5A, - 0x56, 0xC1, 0x50, 0x87, 0x07, 0xDF, 0xED, 0xA2, 0x43, 0x2A, 0xB1, 0x69, 0x36, 0x44, 0xD3, 0x42, - 0x48, 0x53, 0xF6, 0xD8, 0xA9, 0xD7, 0x61, 0xCB, 0x12, 0x8B, 0xCC, 0x5A, 0xE2, 0x47, 0xCD, 0x8C, - 0xCB, 0xBC, 0x2A, 0x56, 0xE0, 0x00, 0x0F, 0x99, 0x61, 0xE4, 0x4C, 0xAE, 0x83, 0x7D, 0xFB, 0xEF, - 0x61, 0x49, 0x40, 0xF9, 0x37, 0x27, 0x12, 0x34, 0xF4, 0x85, 0xAB, 0x27, 0xB6, 0x96, 0xBF, 0xFE, - 0x00, 0xD1, 0x5A, 0xF6, 0x55, 0x90, 0x64, 0x8C, 0x95, 0xCF, 0x15, 0x02, 0x31, 0x45, 0x4D, 0x70, - 0xC4, 0xF9, 0xFC, 0x57, 0x06, 0x93, 0x46, 0x03, 0xE3, 0x94, 0x3C, 0x94, 0x52, 0x54, 0xED, 0x02, - 0x44, 0x9D, 0x61, 0xB9, 0x74, 0x84, 0xA4, 0x06, 0x9F, 0x1D, 0x38, 0x26, 0xE2, 0x8E, 0x09, 0x11, - 0xA9, 0xAF, 0xA7, 0xFE, 0xE8, 0xFF, 0xFB, 0xF2, 0x07, 0xC7, 0xFF, 0x00, 0xC0, 0x9D, 0xDD, 0x91, - 0x38, 0x4B, 0x65, 0x0F, 0xE5, 0xB2, 0xD1, 0xF2, 0x20, 0x91, 0x19, 0x8C, 0x44, 0xB3, 0x71, 0x6A, - 0x68, 0x3D, 0xE4, 0x4F, 0x56, 0x16, 0xFD, 0x25, 0x61, 0x95, 0x5B, 0xA4, 0xE7, 0xED, 0x8B, 0x07, - 0x25, 0x69, 0xDA, 0xFB, 0xED, 0x6C, 0x60, 0x62, 0x5E, 0x9A, 0x3F, 0x8C, 0xC8, 0xE7, 0xF6, 0xC0, - 0x3F, 0xF1, 0x0E, 0x6A, 0xFE, 0x65, 0xC3, 0x04, 0xCC, 0xBD, 0x6E, 0x01, 0x5A, 0xE5, 0xF1, 0x00, - 0xA6, 0xDC, 0xE7, 0x19, 0x23, 0x6D, 0xB3, 0xB2, 0x72, 0x93, 0x8F, 0x83, 0xFC, 0x1F, 0x3E, 0x8F, - 0x1D, 0x20, 0x68, 0x05, 0x2F, 0x53, 0x7F, 0xE1, 0x71, 0x7F, 0xFC, 0x80, 0x71, 0x8D, 0x51, 0xFF, - 0xE1, 0x57, 0x77, 0x13, 0xDA, 0x99, 0x77, 0x27, 0xF7, 0xDB, 0xAD, 0x43, 0xB7, 0xC1, 0x13, 0x65, - 0x0E, 0xE6, 0x54, 0xCB, 0xC4, 0x3E, 0x3B, 0x49, 0x8E, 0x3F, 0xFE, 0xDC, 0x8D, 0x17, 0x10, 0x72, - 0x12, 0xC2, 0x65, 0xB3, 0x16, 0x05, 0xB2, 0xAC, 0xB5, 0x7D, 0xC8, 0x40, 0x72, 0xC7, 0xCF, 0xC5, - 0x7B, 0xA2, 0x54, 0xA3, 0xC6, 0x7C, 0x2C, 0x24, 0x13, 0x33, 0xEF, 0x8C, 0xC3, 0x5A, 0x07, 0xD1, - 0x18, 0xF8, 0x18, 0xEF, 0x06, 0xC6, 0x5A, 0x78, 0xC6, 0x63, 0x11, 0x09, 0xC2, 0xFC, 0x73, 0x75, - 0xF0, 0x92, 0x7A, 0x90, 0xD9, 0xE4, 0xDB, 0xCF, 0x4F, 0x0A, 0x8D, 0x04, 0x5E, 0xF2, 0x8A, 0x7D, - 0xBD, 0x9E, 0xFD, 0x59, 0x62, 0x0D, 0xE5, 0x53, 0x6B, 0xE0, 0xD5, 0x24, 0xB4, 0x53, 0xD4, 0xAD, - 0xBF, 0xC0, 0x26, 0x86, 0x95, 0x82, 0x80, 0x21, 0x86, 0x18, 0xED, 0xE4, 0xDE, 0x94, 0x9B, 0x5F, - 0x6C, 0x41, 0x90, 0x9E, 0x7B, 0x9C, 0x5F, 0x37, 0x11, 0xD1, 0xFF, 0x17, 0xA3, 0x90, 0xFE, 0x87, - 0xE8, 0x0C, 0x2D, 0x44, 0xD3, 0x7C, 0x2D, 0x63, 0xA4, 0xD0, 0x7D, 0xC3, 0x69, 0x6A, 0x44, 0x65, - 0xD3, 0xB3, 0xEB, 0x77, 0xAA, 0x4E, 0x88, 0xCE, 0xDB, 0x3F, 0x71, 0x9C, 0x67, 0xA6, 0x72, 0xA9, - 0xFD, 0x3E, 0x70, 0x94, 0xB0, 0xA8, 0x82, 0x41, 0xB6, 0x19, 0x87, 0xD8, 0x47, 0x3A, 0x02, 0xCA, - 0x25, 0x17, 0x11, 0x2F, 0xEB, 0xDA, 0x6C, 0x2A, 0x76, 0xA9, 0x19, 0x33, 0xB6, 0x80, 0xBA, 0x88, - 0xDD, 0xAE, 0xCF, 0x31, 0x7F, 0x8C, 0x9B, 0x28, 0x5D, 0x5A, 0xE8, 0xE1, 0x09, 0x3F, 0xF0, 0x25, - 0x88, 0xAF, 0xBC, 0xBB, 0x07, 0x1F, 0x16, 0xCA, 0x74, 0xB3, 0xF0, 0xEE, 0x24, 0x51, 0x80, 0xDD, - 0x21, 0xE0, 0x8A, 0xC7, 0xA4, 0x26, 0x42, 0xFA, 0x0B, 0x4A, 0x7F, 0x8D, 0x41, 0xED, 0x05, 0x1D, - 0x0F, 0xE6, 0xF2, 0x33, 0xF3, 0xA8, 0x27, 0x0E, 0x11, 0x15, 0xED, 0x59, 0x1A, 0x02, 0x8E, 0xCA, - 0x87, 0xCA, 0x09, 0x50, 0x59, 0xC8, 0x1F, 0xA6, 0xC9, 0x60, 0xB3, 0x4D, 0x60, 0x82, 0x12, 0x3F, - 0x83, 0x1B, 0x69, 0x6D, 0xCB, 0x43, 0x39, 0x20, 0x93, 0xDF, 0x53, 0xCD, 0xCA, 0x8F, 0x9F, 0x15, - 0x01, 0xE7, 0xDA, 0x60, 0xAD, 0x2F, 0xCC, 0xBE, 0x09, 0x4E, 0x0F, 0x35, 0x20, 0x6D, 0xCD, 0x32, - 0xB0, 0x51, 0x78, 0x17, 0xEE, 0x06, 0x72, 0x9D, 0x66, 0xB0, 0x0D, 0x09, 0x78, 0xA4, 0x9D, 0x9C, - 0x16, 0x4A, 0x3B, 0xBE, 0x89, 0x6B, 0x1B, 0xD8, 0xBD, 0xAF, 0x00, 0xAE, 0x13, 0x86, 0xE3, 0x38, - 0xB9, 0x15, 0x30, 0xFC, 0x5E, 0x74, 0x5F, 0xFE, 0xB1, 0xEC, 0xCF, 0xB6, 0xE2, 0xBF, 0x63, 0x28, - 0xA7, 0x3E, 0x64, 0xC6, 0xC1, 0x72, 0x86, 0x72, 0xFE, 0x03, 0xB1, 0x60, 0x03, 0x16, 0x6F, 0xDA, - 0xE7, 0x48, 0x33, 0x58, 0x66, 0x07, 0x6C, 0x96, 0x5A, 0x29, 0x7D, 0xBF, 0x1C, 0xA1, 0x1A, 0xC6, - 0x1C, 0x90, 0x6D, 0x5C, 0x94, 0xB4, 0x4A, 0x94, 0x5C, 0x4D, 0xAC, 0x13, 0x07, 0x5A, 0xF8, 0x68, - 0x37, 0x3C, 0x9C, 0x33, 0xF8, 0x7E, 0x85, 0x38, 0x57, 0x28, 0xA9, 0xE3, 0xEE, 0xD8, 0x15, 0x4F, - 0xB7, 0x02, 0x0D, 0x6B, 0xB7, 0xF0, 0x27, 0x1E, 0x23, 0x95, 0x46, 0x50, 0x75, 0x4A, 0x87, 0x0D, - 0x2E, 0xCA, 0xC0, 0x37, 0x6E, 0x20, 0x19, 0x1D, 0xD6, 0x1C, 0xFE, 0x66, 0x67, 0xBB, 0x61, 0x91, - 0xC8, 0x0D, 0x77, 0xB9, 0xE1, 0x03, 0x82, 0x73, 0x40, 0xB5, 0x61, 0x94, 0xC9, 0x71, 0x38, 0xFE, - 0x88, 0x25, 0x3B, 0xD6, 0x67, 0x78, 0x79, 0xBA, 0xE1, 0xBD, 0x12, 0x93, 0x57, 0x91, 0xB8, 0x2C, - 0xEE, 0x4E, 0x48, 0xEC, 0x43, 0x18, 0xFC, 0x33, 0xF3, 0x80, 0x7E, 0xB0, 0x1B, 0xAC, 0xA4, 0x6A, - 0x08, 0xF9, 0x7A, 0x72, 0x7F, 0x0D, 0xAE, 0x5D, 0x80, 0x29, 0xA0, 0x9C, 0x73, 0xF1, 0xD5, 0xB5, - 0x56, 0x5F, 0xF7, 0xDD, 0xDE, 0x51, 0xB2, 0x40, 0xDA, 0x13, 0x8B, 0x57, 0x34, 0xDA, 0x52, 0x6E, - 0xCF, 0x2D, 0x12, 0x60, 0x80, 0x48, 0x84, 0x23, 0xB0, 0x8A, 0x5D, 0x34, 0xA1, 0x29, 0x70, 0x7A, - 0xED, 0x01, 0x44, 0x3B, 0xFC, 0x73, 0x22, 0x37, 0x08, 0x95, 0xD8, 0x7D, 0x07, 0xEF, 0x6B, 0xCD, - 0x46, 0x73, 0x6A, 0xD3, 0xAE, 0x13, 0x7D, 0xAE, 0xAF, 0x30, 0xA6, 0xD6, 0x68, 0x10, 0x1E, 0x1C, - 0x2D, 0x64, 0xF3, 0xA9, 0xE6, 0xE1, 0x10, 0x46, 0xC3, 0x12, 0xFB, 0x29, 0x6A, 0x15, 0xA6, 0x06, - 0x5C, 0x9E, 0xD3, 0x32, 0xE4, 0xCD, 0x96, 0x29, 0xDD, 0xB3, 0x05, 0xEA, 0x27, 0xA5, 0x95, 0x66, - 0xB0, 0x79, 0x64, 0x70, 0x26, 0x7C, 0x46, 0x39, 0x18, 0x79, 0x8D, 0xDC, 0x83, 0x6F, 0x11, 0xF0, - 0x05, 0xC5, 0xDC, 0x54, 0xEB, 0xB7, 0xA1, 0x13, 0x4B, 0x13, 0x28, 0x49, 0x91, 0xCC, 0x2A, 0x26, - 0xB5, 0xF0, 0x25, 0x8E, 0xF4, 0xC0, 0xFF, 0x2F, 0x37, 0xD5, 0x57, 0x7E, 0x25, 0xBF, 0xBE, 0x91, - 0x5C, 0x83, 0x6D, 0xC5, 0x4A, 0xDE, 0x7C, 0xC8, 0x37, 0xEA, 0x0F, 0x51, 0xEE, 0x18, 0xDE, 0xC8, - 0xCC, 0xA6, 0xCE, 0x07, 0xB9, 0x12, 0x1A, 0xB9, 0x7F, 0x08, 0x44, 0x34, 0x15, 0x58, 0x32, 0x69, - 0x8C, 0xEA, 0x44, 0x8B, 0x05, 0x15, 0x7A, 0xD7, 0xBB, 0xD2, 0x0B, 0x57, 0x25, 0x11, 0x75, 0x3A, - 0x47, 0xD4, 0xA7, 0x37, 0x40, 0x0C, 0x0A, 0x25, 0x21, 0x85, 0x2E, 0x95, 0x5F, 0x15, 0x15, 0x8D, - 0x8E, 0xBB, 0x0B, 0x6D, 0xE8, 0x58, 0x3F, 0x0C, 0x1D, 0xAD, 0xE0, 0x61, 0x7F, 0xE5, 0x58, 0xD5, - 0x11, 0x95, 0x90, 0x2B, 0x1D, 0x28, 0x58, 0xE2, 0xB9, 0x9D, 0x52, 0x65, 0x98, 0xE4, 0xF7, 0x4C, - 0x3B, 0x63, 0x3F, 0x9A, 0xBC, 0x17, 0x38, 0xA5, 0xAC, 0x28, 0x37, 0x66, 0x38, 0xC3, 0x79, 0xC1, - 0x43, 0x2E, 0xAB, 0x26, 0x22, 0xA6, 0xCF, 0xB3, 0x9F, 0xBE, 0xAC, 0x7A, 0xF4, 0x31, 0xCA, 0x8D, - 0x03, 0xDD, 0xE6, 0xCC, 0xB3, 0x08, 0x57, 0x9C, 0x1E, 0xC2, 0xC6, 0x73, 0xB5, 0x54, 0x95, 0xD8, - 0xC6, 0xC2, 0xF1, 0x69, 0x4F, 0x1E, 0xE6, 0xB1, 0x77, 0x3E, 0x02, 0x86, 0x26, 0x06, 0x2B, 0x34, - 0xA1, 0x51, 0xC0, 0x3D, 0x8D, 0xC6, 0xD6, 0x4A, 0xD8, 0x29, 0x18, 0x4C, 0x0B, 0xEB, 0x74, 0x1C, - 0xDC, 0x13, 0x62, 0xB4, 0xEE, 0x96, 0xF4, 0x48, 0x09, 0x5D, 0x60, 0xD3, 0xAC, 0x85, 0xDF, 0x9A, - 0x10, 0xAB, 0x1A, 0x94, 0x31, 0x74, 0xF3, 0x1D, 0x76, 0x87, 0xB2, 0x3A, 0xD1, 0xF7, 0x93, 0x79, - 0x67, 0x37, 0x6A, 0x97, 0x1F, 0xB9, 0x9E, 0xE4, 0xA1, 0xD3, 0xEF, 0xB4, 0xE6, 0xB2, 0x76, 0x9B, - 0xAA, 0x68, 0x2E, 0xA3, 0x18, 0x23, 0xE4, 0xA9, 0xAF, 0xFC, 0x8D, 0xBE, 0x19, 0x18, 0xB6, 0xF9, - 0xCE, 0xD2, 0xE2, 0xB5, 0xEC, 0x06, 0x43, 0xDD, 0x5C, 0x19, 0xE9, 0xC0, 0x63, 0x08, 0x87, 0xAE, - 0xDE, 0x34, 0xAB, 0xDA, 0xE8, 0xA3, 0x47, 0x85, 0xD5, 0xB1, 0x7A, 0xC2, 0x0F, 0x6A, 0x82, 0x5A, - 0xA1, 0xA1, 0x1D, 0xCF, 0xC3, 0xB9, 0xA4, 0x87, 0x79, 0x01, 0x4A, 0x59, 0xB0, 0xBB, 0xB0, 0x01, - 0x1E, 0x3C, 0x7A, 0xB7, 0xAF, 0xAF, 0xF7, 0x5A, 0x53, 0x17, 0x4B, 0xEB, 0x83, 0x15, 0xB2, 0x9F, - 0x02, 0x3B, 0x19, 0xDA, 0x8C, 0x91, 0x00, 0xF3, 0x78, 0x52, 0xF6, 0xB5, 0x7A, 0x90, 0x1D, 0x77, - 0x33, 0x35, 0x85, 0x06, 0x7D, 0x5F, 0x0D, 0xF1, 0xC6, 0x2B, 0x9D, 0x6E, 0xEA, 0x9F, 0xC3, 0xC6, - 0x87, 0xCD, 0x65, 0x74, 0xEB, 0x42, 0x5B, 0x21, 0x5F, 0xE3, 0xAA, 0x66, 0x44, 0xE2, 0xBE, 0x91, - 0xC8, 0xF9, 0xDA, 0xEC, 0x04, 0x8C, 0x5B, 0x5B, 0x1F, 0x52, 0x09, 0x57, 0x26, 0x2E, 0x95, 0xFD, - 0xCC, 0xF1, 0xDD, 0xB1, 0xBB, 0x98, 0x33, 0xE6, 0x1A, 0xBE, 0x44, 0x1C, 0xC2, 0xD1, 0x33, 0xB7, - 0xE2, 0x26, 0xDE, 0xC5, 0x0E, 0x28, 0x6A, 0x30, 0xD3, 0x81, 0x42, 0xDA, 0x48, 0x0E, 0x6F, 0xB2, - 0x33, 0xF3, 0xB3, 0x0C, 0xE1, 0x29, 0xB0, 0x77, 0xA0, 0x6C, 0xCB, 0x17, 0xE1, 0x81, 0x66, 0x8F, - 0x77, 0xDD, 0x3E, 0x5D, 0x46, 0x84, 0xF2, 0x9B, 0x7F, 0xAE, 0x2A, 0x8A, 0xCF, 0xB6, 0xB5, 0x92, - 0xF0, 0x8B, 0x1C, 0xF4, 0x38, 0x7F, 0x14, 0x26, 0x00, 0x4B, 0x90, 0x55, 0x11, 0x35, 0xE5, 0x84, - 0x50, 0x5A, 0x79, 0x53, 0x5B, 0xB3, 0xB2, 0xD7, 0xAB, 0x6D, 0x87, 0xC5, 0x94, 0xF9, 0x6F, 0xEA, - 0x74, 0xA0, 0x6F, 0x72, 0x76, 0x66, 0x76, 0xBB, 0x74, 0xF4, 0x4A, 0x69, 0x37, 0x7C, 0xC9, 0x0D, - 0x3F, 0xDE, 0x47, 0xE9, 0x56, 0xC0, 0x97, 0x9A, 0x3A, 0xB0, 0x05, 0xDC, 0x99, 0xA4, 0x9C, 0x1C, - 0x49, 0x57, 0xBE, 0xB0, 0xE1, 0xB0, 0xDC, 0xAD, 0xC1, 0xCC, 0x31, 0xB1, 0x4B, 0xE2, 0x63, 0x7F, - 0x7A, 0xB4, 0x84, 0x55, 0x5E, 0xEF, 0x85, 0x4B, 0xD7, 0x5D, 0x60, 0x9B, 0x82, 0x47, 0x3C, 0x45, - 0x68, 0x5F, 0xCB, 0x59, 0x0E, 0xA3, 0x62, 0x61, 0xE6, 0x1B, 0x7D, 0x29, 0x36, 0x04, 0x57, 0x7E, - 0x73, 0xC6, 0x92, 0x52, 0x1C, 0x07, 0x47, 0xA1, 0x46, 0x9C, 0x55, 0x68, 0xFB, 0xC1, 0x11, 0x5A, - 0x85, 0x08, 0x09, 0xE7, 0xF9, 0x81, 0xC8, 0x0A, 0xEC, 0x5A, 0x46, 0x49, 0x5A, 0x84, 0xB0, 0xEF, - 0x90, 0x79, 0xD8, 0xC5, 0x26, 0xBE, 0x1E, 0xF5, 0x5E, 0xBE, 0x6F, 0x39, 0xD0, 0x96, 0xD1, 0x3B, - 0xAD, 0xD7, 0x2A, 0x91, 0xB5, 0x48, 0x58, 0x10, 0x79, 0x9B, 0x05, 0x98, 0x66, 0xAD, 0xF8, 0x38, - 0xC4, 0xF6, 0x56, 0xCC, 0xF3, 0x7D, 0x4B, 0xB6, 0x97, 0xD6, 0xE8, 0x8B, 0xC5, 0xFB, 0x83, 0x69, - 0x46, 0xE1, 0x4E, 0xF5, 0x67, 0xD5, 0x7B, 0x06, 0x8C, 0x5A, 0x82, 0x5C, 0x60, 0x33, 0xB1, 0xD3, - 0x50, 0x30, 0x52, 0x4E, 0xDA, 0x85, 0xBA, 0x98, 0x30, 0xB4, 0xAB, 0x22, 0x49, 0xC9, 0xD8, 0xB2, - 0xE0, 0x63, 0x1F, 0x12, 0x32, 0x1E, 0xD6, 0x05, 0x21, 0x86, 0x58, 0x53, 0x4F, 0xEA, 0x2A, 0x59, - 0x75, 0x35, 0x2D, 0x1A, 0x82, 0xEF, 0x8C, 0x71, 0x3B, 0xCD, 0x78, 0x32, 0xE8, 0xD2, 0x30, 0x12, - 0x79, 0x22, 0x4F, 0x4D, 0xAE, 0xFF, 0xA5, 0x48, 0x3C, 0xCA, 0x5F, 0x6A, 0x14, 0xB4, 0x96, 0xB7, - 0x6C, 0xD9, 0xC1, 0xD7, 0x24, 0xF7, 0xDE, 0x14, 0x70, 0x70, 0x14, 0xEE, 0x68, 0x4F, 0x39, 0x36, - 0xA9, 0xE0, 0x27, 0xEC, 0xFE, 0x03, 0x5D, 0x2D, 0xBD, 0x66, 0x8E, 0xA7, 0xB4, 0x1F, 0xA0, 0x94, - 0xBE, 0x51, 0xDE, 0x44, 0xEC, 0xE4, 0x6D, 0xFD, 0xAA, 0xAF, 0x9C, 0x3A, 0x41, 0x87, 0xF8, 0x4E, - 0x8A, 0xF4, 0x08, 0xC5, 0xB3, 0xD0, 0xB9, 0x1B, 0x23, 0x77, 0x7F, 0x39, 0x9D, 0xAE, 0x0C, 0xAA, - 0x63, 0xB3, 0x99, 0x84, 0xFB, 0xCE, 0x79, 0x7A, 0x34, 0x78, 0x36, 0xA2, 0x38, 0xF8, 0xF7, 0x2E, - 0x0C, 0x7D, 0xBD, 0xBB, 0xF8, 0x5B, 0x5F, 0x33, 0x92, 0x9E, 0x01, 0xEA, 0x85, 0x77, 0xA7, 0xB0, - 0xA5, 0x06, 0xEB, 0xF3, 0x75, 0x5A, 0x2E, 0xDD, 0xD6, 0x7A, 0x07, 0xE4, 0x24, 0x24, 0xC7, 0x76, - 0x52, 0xAD, 0x7B, 0x3C, 0x45, 0x29, 0xB8, 0x01, 0x32, 0xE5, 0x85, 0x0F, 0x2F, 0x50, 0x19, 0x54, - 0x20, 0x67, 0x58, 0x8B, 0x65, 0xFA, 0x4D, 0x4E, 0xA9, 0x70, 0xC4, 0x9A, 0x3C, 0xF3, 0xB0, 0x35, - 0x02, 0x13, 0x5B, 0xD6, 0xB2, 0x63, 0xBF, 0x2E, 0xB4, 0xB6, 0xC1, 0x7C, 0x8B, 0x75, 0xF6, 0x99, - 0x67, 0xBC, 0xC6, 0xA1, 0xB1, 0x58, 0xF1, 0x72, 0x3A, 0x92, 0x31, 0x5C, 0x2C, 0x2E, 0x1D, 0xF3, - 0x09, 0x9E, 0xD3, 0x18, 0xDB, 0x39, 0x11, 0x3D, 0xE1, 0x9D, 0x7B, 0xE3, 0x4D, 0x16, 0xCF, 0x4F, - 0x2B, 0xF9, 0x97, 0xEF, 0x6A, 0x3A, 0x30, 0xAE, 0x6B, 0x90, 0x85, 0x51, 0x14, 0x78, 0xD9, 0xDF, - 0xDC, 0x65, 0x3C, 0x83, 0xF5, 0xE9, 0xE6, 0xB5, 0x8B, 0x42, 0x8C, 0xBE, 0x05, 0x78, 0xE4, 0x9A, - 0xBA, 0x21, 0xD4, 0x30, 0x48, 0x89, 0x92, 0xE4, 0x7E, 0xF9, 0x43, 0x4D, 0x2C, 0xDF, 0xDE, 0x8E, - 0x63, 0xFB, 0x40, 0xDD, 0x0E, 0x2C, 0x34, 0x4F, 0x44, 0xAE, 0x29, 0xA2, 0x48, 0x58, 0x60, 0xC8, - 0xC7, 0x64, 0x1F, 0x69, 0x99, 0xD1, 0x01, 0x91, 0x81, 0x42, 0x54, 0x10, 0xBE, 0x82, 0x18, 0x39, - 0x78, 0xE4, 0x4E, 0xFA, 0xB6, 0xE6, 0x48, 0xB8, 0x36, 0x65, 0xDF, 0x00, 0xF0, 0x12, 0x60, 0xB3, - 0x74, 0x28, 0x1E, 0x68, 0xF1, 0x40, 0x9A, 0x29, 0xA1, 0xBB, 0x21, 0x9D, 0x96, 0x61, 0x8F, 0x85, - 0x6C, 0x88, 0x58, 0x91, 0x79, 0xF6, 0x88, 0x3A, 0x9A, 0x54, 0xC0, 0xE5, 0x13, 0x88, 0x30, 0x4A, - 0x65, 0xE1, 0x8D, 0x0D, 0x10, 0x61, 0xD8, 0xA5, 0x90, 0x02, 0xED, 0xA6, 0xE9, 0x49, 0xD9, 0xC7, - 0x86, 0x2C, 0xFF, 0xAC, 0xD6, 0x4E, 0xED, 0x5C, 0x4F, 0xA2, 0x8E, 0xF9, 0x18, 0x3B, 0xDE, 0x16, - 0x04, 0xD2, 0x75, 0xEC, 0x15, 0x9F, 0xF0, 0x01, 0xB5, 0xE7, 0x0C, 0x96, 0xBE, 0xC4, 0xBE, 0xEA, - 0xDB, 0xB7, 0x2B, 0xFC, 0x73, 0x6A, 0x1D, 0x0B, 0x74, 0xD8, 0x64, 0x57, 0xD0, 0xB9, 0x4F, 0x9A, - 0x72, 0x74, 0x07, 0xC5, 0x8D, 0xDB, 0x81, 0x4C, 0x13, 0x77, 0xCD, 0xDA, 0x01, 0x8E, 0xDF, 0xFF, - 0xCA, 0x11, 0x62, 0x37, 0xC9, 0xAC, 0xFD, 0x94, 0xF4, 0xCC, 0x42, 0xC7, 0x9B, 0xD1, 0xF9, 0x4D, - 0x85, 0x3B, 0xDC, 0xBF, 0xFC, 0x20, 0x4D, 0xE1, 0x52, 0xCD, 0x29, 0xF1, 0x7D, 0x2A, 0x54, 0xA1, - 0x2E, 0x18, 0x7B, 0xDD, 0x05, 0xE0, 0x36, 0x7D, 0x7C, 0x40, 0x11, 0x9A, 0xC8, 0xE1, 0x63, 0x39, - 0x7D, 0x72, 0x54, 0xB2, 0x1C, 0xE1, 0x40, 0x13, 0x6A, 0x1F, 0x76, 0xB1, 0xAD, 0x75, 0xE3, 0x24, - 0x7B, 0x3F, 0xA9, 0xCA, 0xFD, 0x28, 0x76, 0x6F, 0x65, 0x63, 0xA7, 0xCC, 0x71, 0x84, 0xE3, 0x04, - 0xC5, 0x05, 0x17, 0x5A, 0x1F, 0xD0, 0xEA, 0x69, 0xBB, 0x7A, 0xE1, 0xA1, 0xB0, 0xFB, 0xE0, 0xD2, - 0x70, 0x1F, 0x6B, 0x5C, 0x86, 0xE4, 0xDE, 0x8C, 0x5C, 0xC8, 0x36, 0xA9, 0xDD, 0x5D, 0x13, 0x82, - 0xDB, 0x6E, 0x93, 0x00, 0x77, 0x8C, 0xE1, 0xD3, 0x9A, 0x0C, 0x4D, 0xF4, 0x5A, 0x10, 0xDB, 0xBF, - 0x3D, 0xD0, 0x6C, 0x4E, 0xEC, 0x64, 0xA2, 0xF4, 0x5D, 0x29, 0x80, 0x4B, 0xE7, 0xA1, 0x14, 0xAE, - 0xB4, 0x78, 0x8B, 0x6E, 0xCB, 0xA1, 0xB2, 0x02, 0x35, 0xC7, 0x4E, 0x58, 0x7C, 0x98, 0x46, 0x05, - 0xCE, 0x56, 0x83, 0xB4, 0x5E, 0x82, 0x65, 0xF1, 0xC9, 0x9B, 0x29, 0xAC, 0x42, 0xEB, 0xE5, 0xF1, - 0x1D, 0x1A, 0x11, 0x0C, 0x63, 0xAD, 0xFD, 0xCF, 0x40, 0x68, 0xF1, 0xB3, 0xF4, 0x62, 0xB9, 0x9B, - 0x6C, 0x6C, 0x13, 0x94, 0xAF, 0x82, 0x80, 0xE0, 0xBA, 0x6C, 0xCB, 0x84, 0x0D, 0xA0, 0xE4, 0xAA, - 0x15, 0x8F, 0xAD, 0x29, 0xCE, 0x79, 0x7B, 0xF6, 0x70, 0x3F, 0xCC, 0x8B, 0x92, 0xA9, 0xC5, 0x17, - 0xBA, 0xE0, 0xF0, 0x9B, 0x96, 0x8E, 0x7F, 0xA0, 0x2F, 0x2C, 0x5F, 0x54, 0xF6, 0x5A, 0x9E, 0xBB, - 0xAC, 0x6C, 0xE3, 0xBF, 0xC4, 0x1C, 0xAB, 0x14, 0xEB, 0x12, 0xCE, 0xD8, 0xA5, 0x44, 0xCC, 0x4F, - 0x4B, 0x08, 0x4F, 0x2C, 0x00, 0x81, 0xD5, 0x17, 0x22, 0x07, 0x42, 0xCA, 0xFD, 0x49, 0xAD, 0x06, - 0x95, 0xC8, 0xD1, 0xC7, 0x3E, 0x39, 0x34, 0x1C, 0x41, 0x99, 0xC2, 0xAB, 0x8A, 0xED, 0x50, 0x12, - 0xE8, 0xC7, 0x75, 0x52, 0x19, 0x2B, 0xD4, 0xCC, 0xD3, 0xFA, 0x84, 0xE7, 0x0C, 0xCE, 0xE3, 0x93, - 0xCA, 0x60, 0xE5, 0xB7, 0x06, 0xDB, 0x84, 0xEE, 0x79, 0xA7, 0x54, 0x76, 0xE9, 0x46, 0x85, 0xEA, - 0x4F, 0xF3, 0xA1, 0xEF, 0x10, 0xC1, 0x4C, 0x12, 0xB0, 0xEE, 0x23, 0xDD, 0x81, 0x3A, 0x6E, 0x9F, - 0x01, 0x03, 0x04, 0x7C, 0x6D, 0x47, 0x84, 0xB7, 0xE7, 0x19, 0xE3, 0x4E, 0xCF, 0x23, 0x3A, 0xB2, - 0x23, 0x33, 0x00, 0xCB, 0x07, 0x78, 0x51, 0x8B, 0x0C, 0x30, 0x7A, 0x1F, 0x41, 0x14, 0x75, 0xFF, - 0x9E, 0x43, 0x81, 0xF4, 0x15, 0x89, 0x8B, 0xB7, 0x2B, 0xBC, 0x62, 0x48, 0x64, 0xD9, 0x26, 0xBE, - 0xEA, 0x22, 0xB6, 0x22, 0xB8, 0x6F, 0x2B, 0xB6, 0x9B, 0x8F, 0xC7, 0x63, 0x03, 0x83, 0xA7, 0x22, - 0xF8, 0x5C, 0x08, 0x87, 0x70, 0xA2, 0xCB, 0x6B, 0xD6, 0xC9, 0xF6, 0x59, 0x60, 0x8C, 0x10, 0xFA, - 0x3C, 0xAD, 0x15, 0x1F, 0x8F, 0x18, 0x01, 0x2F, 0xB9, 0x2C, 0x01, 0x59, 0x76, 0x18, 0xE6, 0x55, - 0x98, 0x23, 0x33, 0xA9, 0x05, 0xFC, 0x4C, 0xF3, 0x9A, 0xCB, 0xBA, 0x42, 0x60, 0x0C, 0x50, 0xEB, - 0x69, 0xF1, 0x22, 0x73, 0x03, 0x4B, 0x38, 0x74, 0xBF, 0xBB, 0x7B, 0x4C, 0x7F, 0x30, 0xF0, 0x21, - 0x8C, 0x73, 0x24, 0x69, 0x1F, 0x7F, 0xF4, 0x18, 0x0E, 0x4F, 0xB7, 0x99, 0x8D, 0x5A, 0xE9, 0xF7, - 0x79, 0x8B, 0x25, 0xCB, 0xC7, 0xEA, 0x8C, 0xA7, 0x36, 0x33, 0x72, 0x78, 0x2B, 0x9B, 0x4F, 0xA6, - 0x53, 0x10, 0xFA, 0xF7, 0x1C, 0x66, 0xBB, 0x7C, 0x72, 0x18, 0xBE, 0x91, 0x5C, 0x8C, 0xED, 0x75, - 0x3C, 0x35, 0xF4, 0x49, 0xAD, 0xB0, 0x49, 0x67, 0x05, 0x6B, 0x96, 0x46, 0xF9, 0x2D, 0xAF, 0x8E, - 0x90, 0x3A, 0x3C, 0x35, 0xF5, 0x66, 0x7E, 0xE8, 0x08, 0x04, 0x0D, 0xBF, 0x6E, 0x4E, 0x3F, 0xAD, - 0x0A, 0xB9, 0x06, 0xDF, 0x4B, 0xD1, 0x9E, 0x5D, 0x69, 0x13, 0x4B, 0xCD, 0xB6, 0xE7, 0x4A, 0x12, - 0xF2, 0x94, 0xE7, 0xBF, 0xE3, 0x48, 0x65, 0xF2, 0xD4, 0x2A, 0xCF, 0x17, 0xC2, 0xF8, 0x50, 0xC5, - 0xF9, 0x3B, 0x11, 0x04, 0x5D, 0x84, 0xE6, 0x9D, 0x97, 0x31, 0xDB, 0x72, 0xD0, 0x0F, 0x3E, 0x9D, - 0xE2, 0x5B, 0x81, 0xBF, 0xA3, 0xD1, 0xB3, 0x5D, 0xCF, 0x1D, 0x70, 0xDA, 0x96, 0xA3, 0x67, 0xA5, - 0xF0, 0x5E, 0xA8, 0xD0, 0x15, 0x47, 0x3D, 0xAE, 0xA4, 0x33, 0x80, 0xF4, 0x90, 0x1B, 0x27, 0xD1, - 0x4F, 0x5F, 0x2B, 0x1B, 0xA4, 0xEE, 0x5F, 0x3F, 0x0F, 0xC2, 0x70, 0x13, 0x42, 0x42, 0x98, 0xA1, - 0xC4, 0x0C, 0x2B, 0xB1, 0x4E, 0x69, 0xF2, 0xBC, 0xB5, 0x21, 0xD2, 0x11, 0xA4, 0xC0, 0x3D, 0xB5, - 0xBE, 0x4D, 0x0E, 0x84, 0x9B, 0xF0, 0xB0, 0x0F, 0xE2, 0x23, 0xCB, 0x66, 0x1D, 0x71, 0x4F, 0x20, - 0x2F, 0x3C, 0x9D, 0xC8, 0x3C, 0x11, 0xA7, 0x47, 0xA4, 0x3A, 0x46, 0xB0, 0x83, 0xA1, 0xD5, 0x34, - 0x71, 0xA9, 0x8B, 0xDB, 0xBA, 0x6F, 0x49, 0xDC, 0x69, 0xC9, 0xBF, 0xFE, 0x6C, 0x12, 0x58, 0x3B, - 0xDB, 0x42, 0x39, 0xF7, 0x63, 0xB2, 0xA4, 0xFD, 0x30, 0x48, 0x45, 0x4E, 0x02, 0x15, 0xBD, 0x4B, - 0xC3, 0x59, 0xF9, 0x0B, 0x87, 0x1F, 0xEB, 0x90, 0x0E, 0x69, 0x0C, 0xDE, 0x15, 0xD8, 0x7C, 0xA6, - 0xD2, 0xFC, 0xFA, 0x5F, 0xAE, 0x9B, 0x7D, 0x76, 0x0B, 0xE8, 0x53, 0xE4, 0x10, 0xC6, 0xF0, 0x3D, - 0x33, 0x7A, 0x9E, 0x4E, 0x57, 0x0F, 0x58, 0xB6, 0x13, 0x95, 0x89, 0x6D, 0x84, 0xC6, 0xB2, 0x22, - 0x37, 0xF9, 0x99, 0x27, 0xCC, 0x10, 0xA2, 0x14, 0x84, 0x0F, 0x9A, 0xE7, 0xB9, 0x52, 0xFC, 0xB3, - 0x3F, 0x97, 0x07, 0x67, 0xB5, 0xE5, 0x00, 0x32, 0x4D, 0x90, 0x4D, 0x6A, 0xFE, 0x17, 0xE1, 0xAC, - 0x58, 0x00, 0x69, 0xC8, 0x7A, 0x87, 0x60, 0x10, 0xB5, 0x9C, 0x64, 0xFC, 0xAE, 0xD8, 0x86, 0x88, - 0x5A, 0x76, 0xED, 0x72, 0x97, 0x19, 0x90, 0xD1, 0xE3, 0xAF, 0x6E, 0x07, 0x2F, 0x7A, 0xBB, 0xC6, - 0x0D, 0x63, 0x53, 0x23, 0x00, 0xA3, 0x8A, 0x88, 0x2B, 0x21, 0x9C, 0x3A, 0x9D, 0x86, 0x16, 0xE9, -}; + 0x4F, 0x1C, 0xE7, 0x11, 0xCB, 0x53, 0xD9, 0x1D, 0x46, 0x7F, 0xA5, 0x69, 0xFE, 0x2E, 0x02, 0x2C, + 0x39, 0xFC, 0xEC, 0x2E, 0xB5, 0x3D, 0x92, 0x03, 0x94, 0xDE, 0x72, 0x94, 0x0C, 0x48, 0xA1, 0x85, + 0xDC, 0x84, 0x7E, 0xE0, 0x0C, 0xDD, 0x9B, 0xC4, 0x13, 0x13, 0x01, 0x38, 0x11, 0x46, 0xF7, 0xEB, + 0x2D, 0x6F, 0x30, 0x50, 0x09, 0x99, 0x09, 0x28, 0x6A, 0x76, 0xD9, 0xAC, 0x1A, 0x4E, 0xA2, 0xEE, + 0xD9, 0x26, 0x05, 0xDB, 0x7E, 0x48, 0x9E, 0xC8, 0xEE, 0x6E, 0xE0, 0xE1, 0xED, 0x5C, 0x8A, 0x2A, + 0xC7, 0x4B, 0x78, 0xD8, 0x4B, 0x59, 0x9D, 0x53, 0x48, 0x8E, 0x7F, 0xE8, 0xC0, 0xA9, 0x4C, 0x65, + 0x48, 0x38, 0xDB, 0xE7, 0xBD, 0xBA, 0xC2, 0x45, 0xA3, 0x2E, 0x09, 0x2E, 0xCC, 0xBE, 0x26, 0x92, + 0x31, 0x0C, 0x66, 0xA4, 0xE8, 0xE7, 0x0B, 0x4A, 0x55, 0x37, 0xED, 0xFF, 0x95, 0xE0, 0xE2, 0xBC, + 0x63, 0x74, 0x89, 0x65, 0xCF, 0x9B, 0x5E, 0xCA, 0xF9, 0x4B, 0x52, 0x8A, 0xF9, 0x3F, 0xC0, 0x95, + 0xA9, 0xD6, 0xF5, 0xDD, 0xF9, 0x6E, 0xD8, 0x0C, 0x42, 0x76, 0xFD, 0xE5, 0x7A, 0xA6, 0xD0, 0x8E, + 0x5A, 0x2C, 0x56, 0x52, 0x45, 0x3A, 0x86, 0xAD, 0x94, 0xD2, 0x31, 0x62, 0x62, 0xFF, 0xFF, 0xF8, + 0x72, 0x29, 0x5D, 0x64, 0xC3, 0xEF, 0xC5, 0x98, 0xF6, 0x3D, 0xCC, 0x8A, 0xBF, 0xF8, 0xA4, 0x43, + 0x9F, 0x7C, 0x72, 0xAA, 0x2F, 0xA6, 0xDB, 0x69, 0xAD, 0x32, 0x70, 0xF4, 0xA4, 0x35, 0x84, 0x5D, + 0xFF, 0x3B, 0x4B, 0x83, 0x51, 0x44, 0xE0, 0x97, 0xF3, 0x3C, 0xFE, 0x42, 0x40, 0x0D, 0xBF, 0xAB, + 0x7D, 0x55, 0xC5, 0xCD, 0xA6, 0xDD, 0x07, 0xC7, 0xCD, 0xEC, 0x76, 0x14, 0x65, 0xD5, 0xC2, 0x62, + 0x69, 0xDC, 0x95, 0x26, 0x54, 0x29, 0x1F, 0x68, 0x46, 0x95, 0x40, 0x7D, 0xED, 0x45, 0x12, 0xD2, + 0x5B, 0x9E, 0xF5, 0x32, 0x4C, 0x84, 0xF7, 0xF4, 0x05, 0x93, 0x62, 0xC5, 0x43, 0xED, 0xF6, 0x8E, + 0x17, 0x55, 0x82, 0x26, 0x2C, 0xC8, 0x30, 0x90, 0x10, 0x50, 0x98, 0x14, 0x14, 0x2A, 0xF0, 0xC7, + 0x54, 0xA1, 0x29, 0xF3, 0xDA, 0x41, 0x42, 0x38, 0x91, 0xCB, 0x3D, 0x8F, 0x5C, 0xCA, 0xB7, 0xC6, + 0x56, 0x5C, 0x07, 0x79, 0xFF, 0xA5, 0x8B, 0x90, 0x49, 0x0B, 0xE2, 0x12, 0x9C, 0xAA, 0x7F, 0xF9, + 0x5F, 0x8E, 0x4C, 0xD2, 0xA2, 0xC8, 0x5E, 0x0E, 0xC4, 0xB9, 0x43, 0xE0, 0x57, 0x60, 0x1F, 0x68, + 0xED, 0x76, 0xA2, 0xD9, 0xE7, 0x78, 0x1C, 0x7A, 0xB5, 0x31, 0xED, 0x1D, 0x05, 0x2E, 0xA6, 0x15, + 0x14, 0x22, 0xEF, 0xA6, 0xB7, 0x98, 0xAF, 0x42, 0x47, 0x75, 0xAB, 0xEA, 0x5B, 0x91, 0x43, 0x5C, + 0x2E, 0xE7, 0x62, 0x1A, 0xF1, 0x6E, 0x9D, 0xB3, 0xAC, 0xA4, 0x4A, 0x5F, 0xFE, 0x14, 0x91, 0x02, + 0x3B, 0xEB, 0x3C, 0x5C, 0x9D, 0x08, 0x34, 0xAF, 0x9F, 0x5F, 0x8D, 0xDE, 0xD7, 0x13, 0x15, 0x99, + 0xAD, 0x7C, 0x21, 0x3E, 0x5C, 0x73, 0xED, 0xB1, 0x9F, 0x90, 0x37, 0xDF, 0x6F, 0xD2, 0xCB, 0xEE, + 0x8C, 0x3D, 0x13, 0x64, 0x6B, 0x57, 0xCA, 0xB0, 0xCB, 0x04, 0xAC, 0x05, 0x89, 0x29, 0xCB, 0x41, + 0x8C, 0x86, 0x98, 0x79, 0x89, 0x74, 0x7B, 0xAB, 0x59, 0xC5, 0x77, 0x31, 0xB3, 0x25, 0x7A, 0x72, + 0x44, 0xBF, 0x23, 0xB9, 0x1C, 0xC7, 0xD0, 0x75, 0xD8, 0x20, 0xC3, 0xD1, 0x62, 0x98, 0x6B, 0xE8, + 0x6E, 0xE5, 0x67, 0xEC, 0x08, 0xD8, 0x84, 0x33, 0x0B, 0xB4, 0x9E, 0xAE, 0xA4, 0xD9, 0x28, 0xA0, + 0xDF, 0xEF, 0xA6, 0xFA, 0x5F, 0x5B, 0x2A, 0x27, 0x9B, 0x8F, 0xF4, 0x88, 0x8E, 0x72, 0x16, 0xFF, + 0x8E, 0x00, 0xA9, 0x3D, 0x77, 0x8B, 0x11, 0xCD, 0xF9, 0xA2, 0xC2, 0xF0, 0xE0, 0xB6, 0xFD, 0x3F, + 0x16, 0x89, 0x50, 0xF9, 0x07, 0xCF, 0x13, 0xF8, 0x78, 0xEE, 0x8D, 0x76, 0x3F, 0xDC, 0xE0, 0x1D, + 0x0D, 0xAD, 0x67, 0xE4, 0x5B, 0x90, 0x49, 0x12, 0xDF, 0x70, 0xDF, 0xCA, 0xEC, 0xDA, 0x4A, 0xC8, + 0x5E, 0xA2, 0x6F, 0xAC, 0x0E, 0x11, 0x9A, 0x2C, 0x80, 0x94, 0x3B, 0xD3, 0xDB, 0xC0, 0x26, 0x29, + 0x91, 0x4F, 0x19, 0xB3, 0xE7, 0xCD, 0x76, 0xB5, 0xEE, 0x4E, 0x35, 0xCC, 0x7F, 0x62, 0x66, 0xAB, + 0xB9, 0x50, 0x96, 0x1A, 0xDB, 0x79, 0xB2, 0x1A, 0x4C, 0xE0, 0x5D, 0x32, 0xE0, 0x45, 0x71, 0x8C, + 0xB0, 0xFD, 0x74, 0xA8, 0x5D, 0x99, 0xA9, 0x56, 0x67, 0x3B, 0x29, 0x0C, 0x70, 0xD0, 0x5A, 0xB8, + 0xF7, 0x9C, 0xF7, 0x9F, 0x32, 0x91, 0xB8, 0x29, 0x0F, 0xCF, 0x4A, 0xA1, 0xD9, 0xFE, 0xFF, 0x90, + 0xF9, 0x5C, 0x1D, 0x4C, 0xB4, 0x33, 0xCE, 0xA5, 0xEF, 0x22, 0x97, 0xF4, 0x40, 0xCF, 0xB5, 0x84, + 0x93, 0xB7, 0x8F, 0xDB, 0xE3, 0x70, 0x17, 0xE1, 0x0E, 0x70, 0x0E, 0x6A, 0x5D, 0x41, 0xA5, 0xAD, + 0x78, 0xAF, 0xEC, 0x50, 0xC3, 0xF8, 0x80, 0xBB, 0x07, 0x3F, 0xAA, 0xBB, 0x02, 0xF3, 0x51, 0x64, + 0xF2, 0x3D, 0x36, 0x69, 0xAE, 0x54, 0x8B, 0xFA, 0x13, 0xE8, 0x12, 0x51, 0x5A, 0x2C, 0x89, 0x8A, + 0x43, 0x5F, 0x9B, 0xDE, 0x33, 0xD2, 0x2C, 0xCF, 0xAC, 0x92, 0x65, 0x37, 0x47, 0x96, 0x55, 0x43, + 0xB7, 0x64, 0x52, 0x99, 0xEC, 0x91, 0x77, 0x80, 0x05, 0x8C, 0x59, 0x53, 0x22, 0x79, 0x47, 0xD0, + 0x1B, 0x01, 0xD6, 0x97, 0xF5, 0x4A, 0x02, 0x90, 0xE6, 0xA5, 0xE9, 0x25, 0x8C, 0x94, 0x55, 0x60, + 0x09, 0x0C, 0x2D, 0xF0, 0xA5, 0xFF, 0x97, 0x07, 0x84, 0x24, 0x9A, 0x9A, 0x5D, 0xEC, 0xA3, 0x2C, + 0xF4, 0xB7, 0x91, 0xD2, 0x59, 0x54, 0x39, 0x51, 0xFA, 0x9A, 0x96, 0x1B, 0xD1, 0xC7, 0x30, 0xBF, + 0x0E, 0x00, 0xA6, 0xBC, 0x56, 0x27, 0xC8, 0x66, 0x8E, 0x07, 0xE1, 0x02, 0xB6, 0xEF, 0x68, 0xDF, + 0x52, 0x2C, 0x6B, 0xF1, 0xE7, 0xB6, 0xC4, 0x38, 0x0F, 0x31, 0xBC, 0x02, 0x2B, 0x66, 0x6D, 0x6E, + 0x15, 0xB4, 0x01, 0xC8, 0xD1, 0xEF, 0x3F, 0x73, 0x4B, 0xE3, 0x9A, 0x09, 0x96, 0x8C, 0x65, 0x72, + 0x99, 0x88, 0xC6, 0x1E, 0x67, 0x97, 0x4D, 0xE6, 0x7F, 0x76, 0x8B, 0x9D, 0x1A, 0xAC, 0xBD, 0xB4, + 0xCF, 0x05, 0x32, 0xA4, 0x28, 0x90, 0x34, 0xA4, 0x05, 0x40, 0xF6, 0x58, 0x45, 0xCC, 0x59, 0x50, + 0xAF, 0xFF, 0x27, 0x87, 0x02, 0x26, 0xF6, 0xC6, 0xCF, 0xFF, 0x0D, 0x2E, 0xCC, 0x8D, 0x50, 0x3E, + 0x68, 0xCD, 0x4D, 0xB1, 0xF5, 0xCF, 0x0D, 0xCA, 0x63, 0x10, 0x9F, 0x43, 0xD0, 0x86, 0xA4, 0xA4, + 0x36, 0xD4, 0xC2, 0x7D, 0x06, 0xA3, 0x8F, 0x1A, 0xCE, 0xFD, 0x46, 0x90, 0xFE, 0x95, 0xDD, 0xCE, + 0xBE, 0x6F, 0x6A, 0x9B, 0x40, 0xCC, 0x2D, 0xEF, 0xF2, 0xFE, 0x26, 0x98, 0x23, 0x5A, 0x0D, 0xAD, + 0xF3, 0x46, 0xB0, 0x91, 0x30, 0x6B, 0xCA, 0x2B, 0xB5, 0x1C, 0xE7, 0x61, 0x1C, 0x3F, 0x6F, 0x5B, + 0xB0, 0xEE, 0xF0, 0xF5, 0x42, 0x6A, 0x74, 0xB3, 0x5D, 0x13, 0x32, 0x30, 0x99, 0x51, 0x53, 0x0E, + 0xC3, 0x4E, 0xFA, 0x56, 0x0A, 0x02, 0x38, 0xA8, 0x06, 0x8F, 0xD8, 0x4B, 0x21, 0xF5, 0xFC, 0x0F, + 0x36, 0x09, 0x35, 0x50, 0x37, 0x77, 0xD2, 0x1A, 0x65, 0xED, 0x89, 0xEF, 0x89, 0x9F, 0xB9, 0x5B, + 0x2D, 0x6F, 0xF5, 0x2E, 0xF2, 0x4F, 0xEA, 0x93, 0xA5, 0x7E, 0xB9, 0x16, 0xB1, 0xA2, 0x68, 0x2C, + 0x93, 0xEA, 0x06, 0x28, 0x37, 0x7B, 0xDE, 0x9B, 0x1A, 0x75, 0x5D, 0x02, 0x8A, 0xB8, 0x9F, 0xE0, + 0x65, 0x6B, 0x15, 0x19, 0x89, 0x7F, 0xBD, 0x6D, 0xA3, 0xAB, 0x36, 0x50, 0xBA, 0x99, 0xAA, 0xE6, + 0x7E, 0x29, 0x4C, 0x01, 0x28, 0x4C, 0xBB, 0xD5, 0x15, 0x51, 0x50, 0xDE, 0x17, 0x2C, 0xFF, 0x90, + 0x9D, 0x2F, 0xFA, 0xED, 0x41, 0x23, 0xA2, 0x70, 0x42, 0xE4, 0x51, 0xE9, 0x1E, 0x32, 0x38, 0xA4, + 0x63, 0x05, 0xF5, 0x70, 0x16, 0x56, 0x95, 0x3C, 0xC8, 0x36, 0xDB, 0xCC, 0xA8, 0xF7, 0x70, 0x2F, + 0x5D, 0x15, 0x65, 0x01, 0x96, 0xC3, 0xC9, 0x67, 0x54, 0xEC, 0x40, 0xA4, 0xA1, 0x09, 0x46, 0x22, + 0x7D, 0x8F, 0x35, 0xC9, 0xB6, 0x15, 0xB5, 0x18, 0x4D, 0x3A, 0x43, 0xCF, 0x9F, 0x90, 0x9B, 0x56, + 0x5E, 0x48, 0x54, 0x2D, 0x82, 0x78, 0x0A, 0x0E, 0x29, 0x8B, 0x98, 0x03, 0xAC, 0x73, 0x97, 0x39, + 0xDB, 0xA0, 0x7D, 0x31, 0x12, 0xA8, 0xDC, 0x91, 0x56, 0xA4, 0xC0, 0x92, 0xB0, 0x94, 0xD0, 0xD9, + 0x5F, 0x6E, 0xF7, 0xCE, 0x55, 0xBF, 0xC7, 0x35, 0x47, 0x3B, 0x83, 0x90, 0xFE, 0x83, 0x29, 0xCC, + 0xD9, 0x28, 0xAC, 0x13, 0x86, 0x9D, 0x27, 0x71, 0xE6, 0xFE, 0xFC, 0xC9, 0xE6, 0xF5, 0x1E, 0xB5, + 0xE7, 0x30, 0xA5, 0x58, 0x03, 0xD0, 0xA3, 0x87, 0x60, 0x0A, 0x01, 0x3F, 0xE4, 0x2C, 0x7B, 0x4C, + 0x42, 0xBC, 0xF8, 0x8E, 0x9D, 0x01, 0x09, 0x0F, 0x85, 0x0B, 0x82, 0x0D, 0x7C, 0x76, 0xF2, 0x8D, + 0xB8, 0x8E, 0x27, 0xA9, 0xAD, 0x8A, 0x95, 0x38, 0x44, 0xC9, 0x1B, 0x96, 0x75, 0xD0, 0x6D, 0x51, + 0x93, 0x44, 0x25, 0x5A, 0x37, 0x84, 0x37, 0x16, 0x81, 0xF9, 0xF9, 0x11, 0x15, 0x1D, 0xB8, 0xFE, + 0x23, 0x59, 0xAC, 0xF5, 0xB2, 0x91, 0x30, 0xB3, 0x61, 0x86, 0x1B, 0xAD, 0x9F, 0xA7, 0xEE, 0x92, + 0x37, 0xF1, 0xE3, 0x88, 0x4C, 0x3D, 0x3A, 0x78, 0xC9, 0x16, 0x71, 0x4D, 0x99, 0x46, 0x68, 0x00, + 0xA4, 0x85, 0x0F, 0x3B, 0x0C, 0x2A, 0xDC, 0x93, 0x23, 0x1A, 0x38, 0x60, 0x28, 0x6A, 0x41, 0x59, + 0x57, 0x6E, 0xBA, 0x38, 0x72, 0x1F, 0x65, 0x3D, 0x22, 0xD0, 0x6C, 0xE9, 0x37, 0xE2, 0x51, 0xCA, + 0x2B, 0x43, 0x3D, 0x63, 0x3C, 0x58, 0x2F, 0x5D, 0x98, 0x24, 0x15, 0x79, 0x16, 0x76, 0xBF, 0x3F, + 0x20, 0xC3, 0xF2, 0x4C, 0x53, 0x72, 0xB8, 0x91, 0x6B, 0x01, 0xE1, 0x1D, 0xD2, 0x27, 0xB0, 0xA1, + 0xA1, 0x1D, 0x32, 0x08, 0x7D, 0xC4, 0x99, 0x86, 0x6C, 0x3A, 0x77, 0x0E, 0x3B, 0xCE, 0xC4, 0x6E, + 0x09, 0x44, 0xF7, 0x33, 0xBA, 0xA7, 0x4A, 0xF4, 0xAD, 0x26, 0xC6, 0x00, 0x42, 0x0B, 0xAE, 0x4A, + 0x21, 0x68, 0x93, 0x86, 0x66, 0xA2, 0xCF, 0xB5, 0x85, 0xE3, 0x72, 0xE7, 0x3B, 0xBA, 0xA6, 0x9F, + 0xE6, 0x2D, 0x01, 0xCB, 0x62, 0x7A, 0x5D, 0x3F, 0x97, 0xCA, 0x26, 0xEC, 0x1B, 0xB7, 0x28, 0x9D, + 0x49, 0xEE, 0xE9, 0xA1, 0x2E, 0x2F, 0xE5, 0x1E, 0x8F, 0xCB, 0x38, 0xD6, 0xA7, 0x3B, 0xAD, 0x63, + 0xF1, 0x7A, 0x6D, 0x99, 0x7F, 0x4F, 0x0F, 0xE8, 0x13, 0x21, 0xAA, 0x97, 0x2D, 0x8F, 0x54, 0x89, + 0x31, 0x44, 0xDA, 0x5F, 0x00, 0x2F, 0x1C, 0x25, 0x19, 0xA2, 0x54, 0xA6, 0xF7, 0x2A, 0x0A, 0xAA, + 0xD6, 0xF9, 0xD1, 0x3C, 0x1B, 0x23, 0xAC, 0x92, 0x4B, 0x18, 0xC3, 0x36, 0xB7, 0xDF, 0xFE, 0x10, + 0xEC, 0xF6, 0x96, 0xA4, 0x33, 0x6D, 0xD8, 0xB7, 0x9A, 0xE5, 0xF8, 0x93, 0xA1, 0x88, 0xC4, 0xAD, + 0x61, 0x15, 0xC7, 0xC0, 0x4D, 0xAD, 0x3B, 0x9E, 0xA3, 0x5E, 0x84, 0x4B, 0x26, 0x7F, 0xC6, 0x3A, + 0xDB, 0x90, 0x27, 0x26, 0xFA, 0x42, 0x9D, 0x31, 0xB3, 0x67, 0x94, 0x5A, 0x76, 0x6F, 0xFE, 0x27, + 0x52, 0x64, 0x3F, 0x60, 0xB8, 0xF5, 0xF0, 0x95, 0x0B, 0x9F, 0xA3, 0x0A, 0x96, 0xB8, 0xEE, 0x78, + 0x1F, 0x42, 0x2D, 0xF2, 0x2E, 0x90, 0xCE, 0x62, 0x1F, 0xA2, 0x65, 0xFD, 0x66, 0x21, 0x64, 0xC9, + 0xAD, 0x6A, 0xAC, 0xD9, 0x86, 0x1B, 0x16, 0x06, 0x73, 0x22, 0x56, 0x6C, 0x09, 0x1B, 0xCD, 0x8E, + 0xF8, 0xCE, 0xCA, 0xB2, 0xD5, 0x12, 0xF7, 0x4B, 0x74, 0x50, 0xD1, 0x78, 0xA4, 0x83, 0xE8, 0x38, + 0x43, 0xBE, 0xDE, 0x5A, 0x0B, 0xC3, 0x64, 0x53, 0x03, 0xC1, 0xEB, 0xA9, 0xEB, 0xCD, 0x92, 0x01, + 0xE1, 0x17, 0xB9, 0x7C, 0x2C, 0x10, 0x6D, 0xA1, 0x3F, 0x02, 0x19, 0xEA, 0x9E, 0x13, 0xAF, 0x65, + 0xF7, 0xBA, 0x9E, 0xFB, 0x29, 0xED, 0x95, 0x6F, 0xE5, 0xAA, 0x8B, 0x78, 0xFB, 0xE2, 0xB1, 0x9E, + 0x76, 0xC1, 0xF7, 0x02, 0x70, 0x58, 0x05, 0x6B, 0xB6, 0x8D, 0x29, 0x24, 0xD8, 0x1D, 0x7D, 0x64, + 0x7F, 0x70, 0x29, 0x8D, 0x3A, 0xF1, 0x4B, 0x2C, 0x52, 0xB3, 0x4A, 0x9A, 0x0B, 0xF0, 0x55, 0x8F, + 0xC1, 0xC8, 0x9B, 0x9B, 0x30, 0xB6, 0xCF, 0x08, 0xAA, 0x41, 0x60, 0xDE, 0xC1, 0x00, 0xA8, 0xDB, + 0x2D, 0xA7, 0xDE, 0x84, 0xAC, 0x92, 0xFC, 0x36, 0xE7, 0x17, 0x39, 0x09, 0x03, 0xFC, 0xB0, 0x0A, + 0x68, 0x62, 0xD0, 0xB1, 0xC0, 0xF9, 0x4A, 0x0D, 0xB5, 0x4C, 0xB8, 0xA5, 0xB6, 0xF1, 0xE4, 0x07, + 0x08, 0x2A, 0xDA, 0xDB, 0x5A, 0x1F, 0xD0, 0xA7, 0x01, 0xBB, 0x3C, 0x96, 0x6F, 0xCF, 0xCB, 0x0D, + 0xD7, 0xC3, 0x59, 0x30, 0xA3, 0xF1, 0x41, 0x4C, 0xC5, 0x8A, 0x88, 0xF0, 0xDC, 0xC6, 0x71, 0xEB, + 0x00, 0x79, 0xC2, 0x30, 0x27, 0xDC, 0xE7, 0xDE, 0x38, 0x87, 0xDD, 0x55, 0xB7, 0x9A, 0xF3, 0xB4, + 0x24, 0xBF, 0x26, 0x55, 0xBF, 0xCA, 0xE2, 0xCD, 0x01, 0x92, 0x04, 0x84, 0x41, 0xCE, 0x2A, 0xB2, + 0x44, 0xBE, 0x52, 0x5F, 0x07, 0xC2, 0x0F, 0x22, 0x64, 0x70, 0xC8, 0x5E, 0xFF, 0x9F, 0xCE, 0xCB, + 0x34, 0xC7, 0x4F, 0xD9, 0xA3, 0x7B, 0xBE, 0xAD, 0x8B, 0x10, 0x32, 0xAA, 0x39, 0xBE, 0xB1, 0x7C, + 0x16, 0x2E, 0x32, 0x61, 0x97, 0x3A, 0xE8, 0x41, 0x5F, 0x76, 0xE4, 0xAB, 0x55, 0x6B, 0x1D, 0x55, + 0x1A, 0x06, 0x9F, 0x17, 0x68, 0x23, 0x0D, 0x43, 0x27, 0xBC, 0xDC, 0x9F, 0x8D, 0x0F, 0xC4, 0x11, + 0xD3, 0x1F, 0xF3, 0x63, 0x0A, 0x19, 0x7F, 0x6F, 0xC6, 0xDE, 0x9F, 0xB6, 0x1F, 0x64, 0x21, 0x3A, + 0x76, 0xBD, 0x2F, 0x67, 0x1E, 0x2D, 0xFE, 0x24, 0x96, 0x01, 0x32, 0x7E, 0x07, 0xEA, 0xA8, 0x68, + 0x33, 0x00, 0xA0, 0x98, 0x1E, 0xBA, 0x69, 0xEF, 0xF8, 0x17, 0xF1, 0x9A, 0x09, 0x3E, 0x60, 0xEF, + 0x19, 0x95, 0xB2, 0x0F, 0xF0, 0xB9, 0xC4, 0x3B, 0x61, 0x03, 0x33, 0xE6, 0x3E, 0xDB, 0x65, 0x3E, + 0xAF, 0x09, 0x5C, 0xAB, 0xFB, 0x72, 0x6E, 0x8B, 0xAB, 0xA6, 0x6F, 0x8D, 0x6D, 0xDB, 0x13, 0xC2, + 0xF4, 0xF9, 0x25, 0x70, 0x56, 0x7F, 0xD3, 0xBE, 0x0A, 0xE3, 0x9C, 0x7A, 0x52, 0x9B, 0xC4, 0xDE, + 0xF0, 0x39, 0x70, 0xD5, 0x60, 0x65, 0x28, 0x1E, 0x99, 0x3A, 0x60, 0xA8, 0xD4, 0xED, 0xC5, 0xA7, + 0xC6, 0xF4, 0xE9, 0x1E, 0x9B, 0x6C, 0x79, 0xB0, 0x48, 0x34, 0x0E, 0x54, 0x92, 0x8D, 0x2B, 0x38, + 0x84, 0xDF, 0xCF, 0x86, 0x76, 0x6A, 0x74, 0x4B, 0xF2, 0xE8, 0x34, 0x99, 0x40, 0x31, 0x52, 0xC2, + 0xF5, 0xD8, 0x2B, 0x4E, 0x73, 0x08, 0xE1, 0x62, 0xF9, 0x84, 0xDF, 0x79, 0xC5, 0xB5, 0xB6, 0x9B, + 0xA9, 0x53, 0x16, 0xBE, 0xC6, 0x6E, 0xA6, 0x17, 0xB9, 0x55, 0xD4, 0xC7, 0xE2, 0x68, 0x86, 0x77, + 0x66, 0xBD, 0x5B, 0x98, 0xA8, 0x05, 0x84, 0x92, 0xCA, 0x8B, 0xDA, 0x30, 0x20, 0x5E, 0x93, 0x78, + 0xAD, 0xB0, 0x71, 0x4B, 0x43, 0x46, 0x3E, 0x9F, 0xE0, 0x26, 0x52, 0x6F, 0xA4, 0x28, 0xBC, 0xB0, + 0xBB, 0x6F, 0x44, 0x1F, 0xA6, 0x22, 0x9C, 0x51, 0xC3, 0xB2, 0x8C, 0x6A, 0xF5, 0x02, 0x19, 0x9F, + 0xD2, 0x1F, 0x6C, 0xC6, 0xDB, 0x21, 0xE9, 0x0F, 0xDF, 0xA7, 0xFB, 0x3A, 0xE0, 0xE9, 0x75, 0xD4, + 0x6B, 0x2F, 0x21, 0x96, 0xD2, 0x90, 0x83, 0xD4, 0x78, 0xE2, 0x34, 0x0B, 0x75, 0x9F, 0xBE, 0x93, + 0x97, 0xB6, 0xD7, 0xEF, 0xAD, 0xCF, 0xB8, 0xA6, 0xB8, 0x29, 0xBD, 0xBF, 0xC8, 0xA3, 0xE5, 0x2C, + 0x1C, 0x99, 0x40, 0x83, 0x51, 0xD5, 0x63, 0xCA, 0xC8, 0xCA, 0x67, 0x18, 0x61, 0x4D, 0x3F, 0xB7, + 0x95, 0xB6, 0x5B, 0x38, 0x0A, 0xA8, 0x60, 0x40, 0x7C, 0x06, 0xD4, 0xDE, 0xBD, 0xDE, 0x3E, 0x05, + 0x7D, 0xF5, 0xF8, 0x65, 0xE4, 0x5A, 0x42, 0x91, 0xD8, 0xFC, 0xCB, 0xA0, 0xC9, 0x91, 0x16, 0x9D, + 0x41, 0xB4, 0x16, 0x1C, 0xCE, 0x76, 0x75, 0xAC, 0x2F, 0x54, 0xF1, 0x0A, 0xEE, 0xD4, 0x88, 0xED, + 0x8F, 0xA0, 0x18, 0x53, 0x99, 0x46, 0x6E, 0x66, 0x48, 0xC8, 0x5F, 0xB9, 0xE0, 0xBF, 0xEF, 0x3E, + 0xC1, 0x64, 0xF2, 0xC4, 0xBD, 0x1B, 0x3B, 0xE5, 0x2A, 0x3E, 0x40, 0x2C, 0xE4, 0x8D, 0x32, 0xAB, + 0xC1, 0x0A, 0x28, 0xC0, 0xA5, 0xF6, 0xB5, 0xED, 0x5C, 0x68, 0x45, 0x05, 0xC0, 0x2F, 0x76, 0x95, + 0x5C, 0x16, 0x63, 0x51, 0xD0, 0x8E, 0x22, 0x2B, 0x74, 0x02, 0x10, 0xF6, 0x82, 0x00, 0x9A, 0x31, + 0x2C, 0xD0, 0xF2, 0x20, 0x44, 0x4A, 0x78, 0xE8, 0xEB, 0x04, 0x0F, 0x91, 0xDC, 0x21, 0xA5, 0xC1, + 0x9E, 0xCF, 0x78, 0x89, 0x19, 0xCE, 0xC8, 0x08, 0x91, 0x63, 0x25, 0x8C, 0x1D, 0x98, 0xB3, 0x27, + 0xB8, 0xFE, 0x55, 0x78, 0x35, 0x24, 0x96, 0x5C, 0x92, 0x9F, 0xC9, 0xD2, 0xAC, 0xCC, 0xCE, 0x65, + 0x5E, 0x36, 0xA8, 0xE2, 0x9B, 0x96, 0x69, 0x9B, 0x7F, 0xC3, 0xC4, 0x1B, 0x70, 0x76, 0xAA, 0xC0, + 0xFD, 0xBE, 0xAE, 0xDB, 0x85, 0x7F, 0x72, 0x06, 0xCE, 0x3E, 0x30, 0x5D, 0x3E, 0x24, 0x85, 0x14, + 0x8E, 0x32, 0x71, 0xEA, 0x80, 0x7E, 0x3B, 0x06, 0x65, 0xF1, 0xC8, 0xC8, 0x55, 0x2E, 0x2A, 0x9B, + 0x60, 0xF8, 0xC4, 0x1D, 0x75, 0x9F, 0xFC, 0xE4, 0x2B, 0x3F, 0x64, 0xE4, 0xFB, 0xE0, 0x24, 0x03, + 0xED, 0x8A, 0xD4, 0x7D, 0x31, 0x51, 0x1C, 0xCF, 0x61, 0xDD, 0x80, 0x05, 0x33, 0x59, 0xB0, 0xD1, + 0x83, 0x8F, 0xA8, 0xDF, 0xAC, 0x01, 0xB1, 0x2A, 0xE2, 0x5C, 0x6A, 0xA9, 0xB6, 0xCC, 0xB1, 0x03, + 0xF3, 0x7E, 0x14, 0x93, 0xB4, 0x85, 0x06, 0x33, 0x17, 0x15, 0x14, 0x75, 0xC8, 0x2B, 0xF3, 0xFF, + 0xD6, 0xAC, 0x6B, 0x32, 0xF2, 0xEC, 0x53, 0x8E, 0xEA, 0x04, 0xCB, 0xB0, 0x86, 0x1B, 0xD3, 0x75, + 0xF1, 0xF0, 0x3A, 0x56, 0xF6, 0x3F, 0x31, 0xD0, 0xC4, 0x79, 0x3C, 0x5F, 0x21, 0x73, 0xB5, 0xEA, + 0xED, 0x92, 0x0F, 0xDB, 0x32, 0x6E, 0xEC, 0x11, 0xC1, 0xF3, 0xF5, 0x36, 0x2D, 0x97, 0x5A, 0x50, + 0x13, 0x8F, 0xF7, 0x3A, 0x6E, 0x8A, 0x88, 0xB0, 0x0B, 0x11, 0x73, 0x0C, 0x7C, 0x5B, 0x02, 0xD3, + 0xDF, 0xBA, 0x4C, 0x3F, 0x6A, 0x69, 0x82, 0x68, 0x7A, 0xD1, 0xCD, 0xCA, 0xBA, 0xEB, 0xB1, 0xFD, + 0xCF, 0x45, 0x45, 0x94, 0x23, 0x58, 0xF4, 0xBF, 0x36, 0x5A, 0x6A, 0xBF, 0x0E, 0x7A, 0xF6, 0xE3, + 0x38, 0x42, 0x85, 0x55, 0x7F, 0x31, 0x49, 0x7A, 0x18, 0x21, 0x6C, 0xC9, 0xA5, 0x07, 0xAC, 0x1D, + 0x72, 0x47, 0x4E, 0x7F, 0xB7, 0x17, 0xBC, 0x7B, 0x26, 0xC5, 0x40, 0x2B, 0x95, 0xC6, 0xFF, 0xD1, + 0xCD, 0x46, 0xAB, 0xBB, 0xAB, 0xDC, 0x74, 0x60, 0x30, 0x75, 0x45, 0x49, 0xF7, 0x05, 0x52, 0xB8, + 0x32, 0xB2, 0x14, 0x8B, 0xE9, 0x10, 0x04, 0x28, 0xB4, 0x55, 0x50, 0xE4, 0x36, 0x9F, 0xF8, 0x21, + 0x24, 0xAB, 0x52, 0x19, 0x50, 0xA5, 0x7F, 0x68, 0x39, 0xDC, 0xC5, 0x8C, 0xAF, 0x10, 0xF6, 0x57, + 0x9B, 0x9B, 0x95, 0x33, 0x25, 0xC1, 0x68, 0xA5, 0xFA, 0xE7, 0xBA, 0x9D, 0xA0, 0x5E, 0x03, 0x92, + 0x14, 0x3E, 0xE9, 0xDC, 0x4F, 0x2F, 0xFD, 0x73, 0xE7, 0x49, 0x45, 0xEE, 0x80, 0x69, 0xB3, 0x1F, + 0x51, 0xE0, 0xB0, 0x1F, 0x89, 0xE0, 0xED, 0x1E, 0x89, 0x9C, 0xC3, 0xB7, 0x63, 0x05, 0x82, 0xD2, + 0xA7, 0xC7, 0xBC, 0xA5, 0x53, 0x9E, 0x17, 0x59, 0xFC, 0x8B, 0x48, 0x54, 0x32, 0x0E, 0x8E, 0x9F, + 0xC4, 0xBB, 0x43, 0x24, 0x78, 0x3A, 0xD2, 0xDF, 0xB7, 0xE0, 0xD7, 0x20, 0x9D, 0xBF, 0x49, 0xB8, + 0xD2, 0xB5, 0xA7, 0xD0, 0x1A, 0x32, 0x2B, 0x6C, 0xAD, 0xCA, 0x01, 0x16, 0xC7, 0xBD, 0x32, 0x7B, + 0x5A, 0x82, 0xB1, 0x2B, 0x42, 0x81, 0x4F, 0xB3, 0x91, 0x25, 0xB4, 0x91, 0x91, 0xD0, 0xB8, 0xB1, + 0x51, 0x24, 0x62, 0x92, 0x3D, 0x77, 0x3F, 0x25, 0x68, 0x9A, 0x34, 0x41, 0x33, 0xFF, 0xA0, 0xAF, + 0x1A, 0x14, 0x90, 0xE8, 0xC6, 0x36, 0x4F, 0xC5, 0xAF, 0xDC, 0x66, 0x2C, 0x1A, 0x73, 0x40, 0x5E, + 0xE8, 0x94, 0xD9, 0xC2, 0x27, 0x7F, 0xD7, 0x34, 0x06, 0xC4, 0x89, 0x00, 0xBF, 0x1A, 0x6A, 0x24, + 0x1A, 0x31, 0xEC, 0x91, 0xCD, 0xBB, 0x97, 0xBF, 0x50, 0x74, 0x24, 0xED, 0x0D, 0x72, 0xA5, 0xEA, + 0x43, 0x9E, 0x83, 0x33, 0x19, 0x8B, 0x97, 0x66, 0x9E, 0x43, 0xBA, 0xF2, 0x0A, 0x53, 0x37, 0xEF, + 0xE9, 0x60, 0x76, 0x98, 0x66, 0x0A, 0x21, 0x8D, 0xCA, 0x85, 0xB9, 0x9F, 0x91, 0xCC, 0x0D, 0xA3, + 0xCF, 0xB9, 0x65, 0xEA, 0x50, 0xD9, 0x8A, 0x0C, 0xAF, 0xA1, 0xB3, 0x3C, 0xBA, 0xA1, 0x6B, 0xBA, + 0x53, 0x37, 0xCA, 0x57, 0xF2, 0x55, 0x73, 0x12, 0x63, 0x61, 0x98, 0x01, 0x2C, 0x6E, 0x4A, 0x66, + 0xCC, 0x35, 0xA8, 0x86, 0x52, 0x45, 0xC0, 0xD4, 0x88, 0xDA, 0x00, 0xFF, 0x86, 0x84, 0x97, 0x1C, + 0x5B, 0x16, 0x96, 0x12, 0x77, 0x45, 0x81, 0x8F, 0xA2, 0xCC, 0x55, 0x12, 0x8D, 0x8C, 0xBD, 0xA8, + 0x61, 0xBA, 0x8D, 0x0C, 0xD4, 0x58, 0xC9, 0x03, 0xE4, 0x0C, 0xBE, 0x53, 0x4C, 0x9E, 0xA0, 0x58, + 0x6F, 0x16, 0xEB, 0x9F, 0x89, 0xB8, 0x94, 0x35, 0x58, 0xF8, 0x83, 0x94, 0x3D, 0x49, 0x44, 0x88, + 0x82, 0x18, 0xCB, 0xA9, 0x4B, 0x2D, 0x08, 0xAD, 0x8E, 0xCF, 0x08, 0x8F, 0xF3, 0x08, 0x3B, 0xFF, + 0x10, 0x80, 0x34, 0x31, 0xE7, 0xF9, 0xB9, 0x52, 0xD8, 0x78, 0xD0, 0x0F, 0x5E, 0xB7, 0xE2, 0xF9, + 0x37, 0xCB, 0xD4, 0x49, 0x36, 0x67, 0xF2, 0xE0, 0x78, 0xB9, 0x13, 0x89, 0xEC, 0x85, 0xFA, 0x6D, + 0x74, 0x5E, 0xC5, 0x59, 0xB6, 0xA9, 0xEE, 0x1C, 0x0E, 0xD4, 0xA3, 0x1E, 0x7A, 0x09, 0x0D, 0x4F, + 0xB8, 0x2D, 0xBE, 0x0C, 0xF7, 0x69, 0x04, 0x29, 0x44, 0x27, 0x94, 0x72, 0xC0, 0xAB, 0x86, 0x40, + 0x21, 0x5E, 0xC6, 0xBD, 0x24, 0x4A, 0x5E, 0x06, 0x46, 0x53, 0xA1, 0xD7, 0xD7, 0xBC, 0xD9, 0x97, + 0x13, 0xA9, 0x09, 0x15, 0x33, 0xBC, 0x9B, 0x13, 0x50, 0xCE, 0xA7, 0xDC, 0xFA, 0x69, 0x70, 0x22, + 0x14, 0x79, 0xD0, 0xA8, 0x3C, 0xB3, 0x46, 0xC3, 0xDA, 0x6C, 0x0C, 0xEC, 0x2A, 0xB2, 0x9B, 0x21, + 0xB2, 0xAD, 0x8C, 0x0C, 0x85, 0x9A, 0x8D, 0x7C, 0x10, 0xEA, 0x51, 0x1D, 0x2D, 0xDE, 0x7D, 0x8F}; + +const long int default_keys_enc_size = sizeof(default_keys_enc); diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 0b2058293..d988dbb83 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -34,6 +34,7 @@ namespace Loader { using namespace Common::Literals; static constexpr u64 UPDATE_TID_HIGH = 0x0004000e00000000; +static constexpr u64 DLP_CHILD_TID_HIGH = 0x0004000100000000; FileType AppLoader_NCCH::IdentifyType(FileUtil::IOFile* file) { u32 magic; @@ -314,12 +315,16 @@ ResultStatus AppLoader_NCCH::Load(std::shared_ptr& process) { LOG_INFO(Loader, "Program ID: {}", program_id); - u64 update_tid = (ncch_program_id & 0xFFFFFFFFULL) | UPDATE_TID_HIGH; - update_ncch.OpenFile( - Service::AM::GetTitleContentPath(Service::FS::MediaType::SDMC, update_tid)); - result = update_ncch.Load(); - if (result == ResultStatus::Success) { - overlay_ncch = &update_ncch; + bool is_dlp_child = (ncch_program_id & 0xFFFFFFFF00000000) == DLP_CHILD_TID_HIGH; + + if (!is_dlp_child) { + u64 update_tid = (ncch_program_id & 0xFFFFFFFFULL) | UPDATE_TID_HIGH; + update_ncch.OpenFile( + Service::AM::GetTitleContentPath(Service::FS::MediaType::SDMC, update_tid)); + result = update_ncch.Load(); + if (result == ResultStatus::Success) { + overlay_ncch = &update_ncch; + } } if (auto room_member = Network::GetRoomMember().lock()) {