Update File Core and Add HLE DLP Client (#1741)

This commit is contained in:
lannoene 2026-02-22 08:07:24 -08:00 committed by GitHub
parent ac0ec5edea
commit 43cecd1692
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 2617 additions and 497 deletions

View File

@ -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

View File

@ -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<RomFSReader>& 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<FileUtil::IOFile> NCCHContainer::Reopen(
const std::unique_ptr<FileUtil::IOFile>& 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;

View File

@ -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<DecryptionState>()) {
@ -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;

View File

@ -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

View File

@ -39,6 +39,8 @@ SERVICE_CONSTRUCT_IMPL(Service::APT::Module)
namespace Service::APT {
constexpr u32 max_wireless_reboot_info_size = 0x10;
template <class Archive>
void Module::serialize(Archive& ar, const unsigned int file_version) {
DEBUG_SERIALIZATION_POINT;
@ -64,7 +66,7 @@ std::shared_ptr<Module> Module::NSInterface::GetModule() const {
void Module::NSInterface::SetWirelessRebootInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const auto size = rp.Pop<u32>();
const auto size = std::min(rp.Pop<u32>(), max_wireless_reboot_info_size);
const auto buffer = rp.PopStaticBuffer();
apt->wireless_reboot_info = std::move(buffer);

View File

@ -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.

View File

@ -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.

View File

@ -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 <cryptopp/aes.h>
#include <cryptopp/modes.h>
#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<CFG::Module> DLP_Base::GetCFG() {
return Service::CFG::GetModule(system);
}
std::shared_ptr<NWM::NWM_UDS> DLP_Base::GetUDS() {
return system.ServiceManager().GetService<Service::NWM::NWM_UDS>("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<u32>(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<Kernel::SharedMemory> shared_mem,
std::shared_ptr<Kernel::Event> 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<u8> 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<u8>(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<u32>(uds->GetConnectionStatusHLE().status));
return false;
}
return true;
}
int DLP_Base::RecvFrom(u16 node_id, std::vector<u8>& buffer) {
constexpr u32 max_pullpacket_size = 0x3c00;
std::vector<u8> 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<u32>(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<u8>& 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<u8*>(_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<u32*>(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<u8*>(&aes_value)[3] & 0b0111) + 2;
u8 num_shift_extra_hash = (reinterpret_cast<u8*>(&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<u8, 0x10> 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<DLPPacketHeader*>(pk);
if (ph->size != sz) {
LOG_ERROR(Service_DLP, "Packet size in header does not match size received");
return false;
}
if (checksum) {
std::vector<u8> pk_copy;
pk_copy.resize(sz);
memcpy(pk_copy.data(), pk, sz);
auto ph_cpy = reinterpret_cast<DLPPacketHeader*>(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

View File

@ -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 <semaphore>
// DLP save states are not supported
namespace Service::DLP {
using DLP_Username = std::array<u16_le, 10>;
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<u8, 16> age_ratings;
std::array<u16, 64> short_description; // UTF-16
std::array<u16, 128> long_description; // UTF-16
std::array<u16, 0x900> icon; // 48x48, RGB565
u32 size;
u8 unk2;
u8 unk3;
u16 padding;
std::vector<u8> ToBuffer() {
std::vector<u8> 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<u16, 2> pad;
DLP_Username username;
u32 unk1;
u32 network_node_id;
};
static_assert(sizeof(DLPNodeInfo) == 0x28);
struct DLPEventDescription {
std::array<u8, 0x18> 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<u8, 4> dl_pk_head_broadcast_header = {dl_pk_type_broadcast, 0x02};
constexpr inline std::array<u8, 4> dl_pk_head_auth_header = {dl_pk_type_auth, 0x02};
constexpr inline std::array<u8, 4> dl_pk_head_start_dist_header = {dl_pk_type_start_dist, 0x02};
constexpr inline std::array<u8, 4> dl_pk_head_distribute_header = {dl_pk_type_distribute, 0x02};
constexpr inline std::array<u8, 4> dl_pk_head_finish_dist_header = {dl_pk_type_finish_dist, 0x02};
constexpr inline std::array<u8, 4> dl_pk_head_start_game_header = {dl_pk_type_start_game, 0x02};
struct DLPPacketHeader {
union {
std::array<u8, 4> magic;
struct {
u8 type;
u8 mag0x02;
u16 unk; // usually 0x00 0x00
};
};
u16_be size; // size of the whole packet, including the header
std::array<u8, 2> unk1; // always 0x02 0x00
u32 checksum; // always calculate
u8 packet_index; // starts at 0
std::array<u8, 3> 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<u8, 3> 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<u16_be, 64> title_short;
std::array<u16_be, 128> title_long;
std::array<u16_be, 0x9c> icon_part;
u64 unk;
};
#pragma pack(pop)
static_assert(sizeof(DLPBroadcastPacket1) == 768);
struct DLPBroadcastPacket2 {
DLPPacketHeader head;
std::array<u16_be, 0x2cc> icon_part;
};
static_assert(sizeof(DLPBroadcastPacket2) == 1448);
struct DLPBroadcastPacket3 {
DLPPacketHeader head;
std::array<u16_be, 0x2cc> icon_part;
};
static_assert(sizeof(DLPBroadcastPacket3) == 1448);
struct DLPBroadcastPacket4 {
DLPPacketHeader head;
std::array<u16_be, 0x2cc> icon_part;
};
static_assert(sizeof(DLPBroadcastPacket4) == 1448);
struct DLPBroadcastPacket5 {
DLPPacketHeader head;
std::array<u8, 0x8> unk1;
std::array<u8, 0x8> unk2;
std::array<u8, 0x598> 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<u8, 2> padding;
std::array<u8, 2> 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<u8, 0x18> 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<u8, 9> 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<Kernel::SessionRequestHandler> GetServiceFrameworkSharedPtr() = 0;
virtual bool IsHost() = 0;
Core::System& system;
std::shared_ptr<Kernel::SharedMemory> dlp_sharedmem;
std::shared_ptr<Kernel::SharedMemory> uds_sharedmem;
std::shared_ptr<Kernel::Event> dlp_status_event; // out
std::shared_ptr<Kernel::Event> 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<u8> dlp_password_buf{};
std::array<u8, 9> wireless_reboot_passphrase;
const u32 dlp_content_block_length = 182;
std::shared_ptr<CFG::Module> GetCFG();
std::shared_ptr<NWM::NWM_UDS> GetUDS();
void GetEventDescription(Kernel::HLERequestContext& ctx);
void InitializeDlpBase(u32 shared_mem_size, std::shared_ptr<Kernel::SharedMemory> shared_mem,
std::shared_ptr<Kernel::Event> event, DLP_Username username);
void FinalizeDlpBase();
bool ConnectToNetworkAsync(NWM::NetworkInfo net_info, NWM::ConnectionType conn_type,
std::vector<u8> passphrase);
int RecvFrom(u16 node_id, std::vector<u8>& buffer);
bool SendTo(u16 node_id, u8 data_channel, std::vector<u8>& 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 <typename T>
static T* GetPacketBody(std::vector<u8>& 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<T*>(b.data());
}
static DLPPacketHeader* GetPacketHead(std::vector<u8>& b) {
if (b.size() < sizeof(DLPPacketHeader)) {
LOG_CRITICAL(Service_DLP, "Packet is too small to fit a DLP header");
return nullptr;
}
return reinterpret_cast<DLPPacketHeader*>(b.data());
}
static u32 GeneratePKChecksum(u32 aes_value, void* input_buffer, u32 packet_size);
template <typename T>
T* PGen_SetPK(std::array<u8, 4> magic, u8 packet_index, std::array<u8, 3> 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<T>(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<u8> send_packet_ctx;
};
} // namespace Service::DLP

View File

@ -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<Kernel::SessionRequestHandler> 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>();
u32 max_beacons = rp.Pop<u32>();
u32 constant_mem_size = rp.Pop<u32>();
auto [shared_mem, event] = rp.PopObjects<Kernel::SharedMemory, Kernel::Event>();
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<Network::MacAddress>();
[[maybe_unused]] u32 tid_low = rp.PopRaw<u32>();
[[maybe_unused]] u32 tid_high = rp.PopRaw<u32>();
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<Network::MacAddress>();
[[maybe_unused]] u32 tid_low = rp.PopRaw<u32>();
[[maybe_unused]] u32 tid_high = rp.PopRaw<u32>();
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<Network::MacAddress>();
[[maybe_unused]] u32 tid_low = rp.PopRaw<u32>();
[[maybe_unused]] u32 tid_high = rp.PopRaw<u32>();
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
};

View File

@ -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<DLP_CLNT> {
class DLP_CLNT final : public ServiceFramework<DLP_CLNT>, public DLP_Clt_Base {
public:
DLP_CLNT();
~DLP_CLNT() = default;
virtual ~DLP_CLNT() = default;
virtual std::shared_ptr<Kernel::SessionRequestHandler> 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

View File

@ -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<Kernel::SharedMemory> shared_mem,
std::shared_ptr<Kernel::Event> 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<u32>(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<u16>();
scan_title_id_filter = rp.Pop<u64>();
scan_mac_address_filter = rp.PopRaw<Network::MacAddress>();
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<u32>();
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<Network::MacAddress>();
[[maybe_unused]] u32 tid_low = rp.Pop<u32>();
[[maybe_unused]] u32 tid_high = rp.Pop<u32>();
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<u8> 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<u8>();
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<u8> 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<Network::MacAddress>();
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<Network::MacAddress>();
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<DLP_Clt_Base> p) : p_obj(p) {}
void WakeUp(std::shared_ptr<Kernel::Thread> 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<DLP_Clt_Base> p_obj;
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<Kernel::HLERequestContext::WakeupCallback>(*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<Network::MacAddress>();
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>();
u32 dlp_child_high = rp.Pop<u32>();
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<u64>(dlp_child_high) << 32 | dlp_child_low;
// ConnectToNetworkAsync won't work here beacuse this is
// synchronous
auto shared_this = std::dynamic_pointer_cast<DLP_Clt_Base>(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<u8>(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<ThreadCallback>(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<u16>();
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<u8> connected_nodes_buffer;
connected_nodes_buffer.resize(node_array_len * sizeof(u16));
memcpy(connected_nodes_buffer.data(), conn_status.nodes,
std::min<u32>(connected_nodes_buffer.size(), conn_status.total_nodes) * sizeof(u16));
rb.Push(ResultSuccess);
rb.Push<u32>(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<u16>();
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<int>(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<int, bool> got_broadcast_packet;
std::unordered_map<int, std::vector<u8>> broadcast_packet_idx_buf;
DLP_Username server_username; // workaround before I decrypt the beacon data
std::vector<u8> 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<DLPPacketHeader*>(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<DLPBroadcastPacket1*>(broadcast_packet_idx_buf[0].data());
auto broad_pk2 = reinterpret_cast<DLPBroadcastPacket2*>(broadcast_packet_idx_buf[1].data());
auto broad_pk3 = reinterpret_cast<DLPBroadcastPacket3*>(broadcast_packet_idx_buf[2].data());
auto broad_pk4 = reinterpret_cast<DLPBroadcastPacket4*>(broadcast_packet_idx_buf[3].data());
[[maybe_unused]] auto broad_pk5 =
reinterpret_cast<DLPBroadcastPacket5*>(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<NWM::TagHeader*>(place)->length + sizeof(NWM::TagHeader)) {
auto th = reinterpret_cast<NWM::TagHeader*>(place);
if (th->tag_id == static_cast<u8>(NWM::TagId::VendorSpecific) &&
th->length <= sizeof(NWM::NetworkInfoTag) - sizeof(NWM::TagHeader)) {
// cast to network info and check if correct
auto ni_tag = reinterpret_cast<NWM::NetworkInfoTag*>(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<u8>(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<ReceivedFragment> received_fragments;
while (sleep_poll(dlp_poll_rate_ms), is_connected) {
std::vector<u8> 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<DLPClt_AuthAck>(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<DLPClt_StartDistributionAck_NoContentNeeded>(
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<DLPClt_StartDistributionAck_ContentNeeded>(
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<DLPSrvr_ContentDistributionFragment>(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<u8> cf(r_pbody->content_fragment,
static_cast<u16>(r_pbody->frag_size));
ReceivedFragment frag{
.index = static_cast<u32>(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<DLPSrvr_FinishContentUpload>(recv_buf);
auto s_body = PGen_SetPK<DLPClt_FinishContentUploadAck>(
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<DLPClt_BeginGameAck>(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<DLPSrvr_BeginGameFinal>(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<ReceivedFragment>& frags) {
auto cia_file = std::make_unique<AM::CIAFile>(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

View File

@ -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<DLPNodeInfo, 16> node_info;
u32 unk4;
u32 unk5;
std::vector<u8> ToBuffer() {
std::vector<u8> 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<std::pair<DLPTitleInfo, DLPServerInfo>> scanned_title_info;
std::map<Network::MacAddress, bool>
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<u8> 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<Kernel::SharedMemory> shared_mem,
std::shared_ptr<Kernel::Event> 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<ReceivedFragment>& 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

View File

@ -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 <cryptopp/aes.h>
#include <cryptopp/modes.h>
#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<u8*>(_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<CryptoPP::AES>::Encryption aes;
aes.SetKeyWithIV(key.data(), CryptoPP::AES::BLOCKSIZE, iv_ctr);
aes.ProcessData(out, out, size);
}
} // namespace Service::DLP

View File

@ -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<Kernel::SessionRequestHandler> 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>();
u32 max_beacons = rp.Pop<u32>();
constexpr u32 constant_mem_size = 0;
auto [shared_mem, event] = rp.PopObjects<Kernel::SharedMemory, Kernel::Event>();
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>();
u32 max_beacons = rp.Pop<u32>();
constexpr u32 constant_mem_size = 0;
auto username = rp.PopRaw<std::array<u16_le, 10>>();
rp.Skip(1, false); // possible null terminator or unk flags
auto [shared_mem, event] = rp.PopObjects<Kernel::SharedMemory, Kernel::Event>();
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
};

View File

@ -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<DLP_FKCL> {
class DLP_FKCL final : public ServiceFramework<DLP_FKCL>, public DLP_Clt_Base {
public:
DLP_FKCL();
~DLP_FKCL() = default;
virtual std::shared_ptr<Kernel::SessionRequestHandler> 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

View File

@ -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<Kernel::SessionRequestHandler> DLP_SRVR::GetServiceFrameworkSharedPtr() {
return shared_from_this();
}
void DLP_SRVR::IsChild(Kernel::HLERequestContext& ctx) {
auto fs = system.ServiceManager().GetService<Service::FS::FS_USER>("fs:USER");
IPC::RequestParser rp(ctx);
rp.Skip(1, false);
u32 process_id = rp.Pop<u32>();
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"},

View File

@ -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<DLP_SRVR> {
class DLP_SRVR final : public ServiceFramework<DLP_SRVR>, public DLP_Base {
public:
DLP_SRVR();
~DLP_SRVR() = default;
virtual std::shared_ptr<Kernel::SessionRequestHandler> GetServiceFrameworkSharedPtr();
virtual bool IsHost() {
return true;
}
private:
void IsChild(Kernel::HLERequestContext& ctx);

View File

@ -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<u32>();
const u32 unk = rp.Pop<u32>();
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);

View File

@ -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<u32>(eapol_start.conn_type));
static_cast<u32>(eapol_start.connection_type));
}
// Send the EAPoL-Logoff packet.
@ -595,9 +595,7 @@ boost::optional<Network::MacAddress> 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<u32>();
// scan input struct
u32 unk1 = rp.Pop<u32>();
u32 unk2 = rp.Pop<u32>();
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>();
u32 id = rp.Pop<u32>();
// 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<Kernel::Event> input_event = rp.PopObject<Kernel::Event>();
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<u32>(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<u32>(sizeof(BeaconEntryHeader) + beacon.data.size());
entry.total_size = static_cast<u32>(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<u32>(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<std::shared_ptr<Kernel::Event>> NWM_UDS::Initialize(
u32 sharedmem_size, const NodeInfo& node, u16 version,
std::shared_ptr<Kernel::SharedMemory> 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<NodeInfo> 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<NodeInfo>(*itr);
}
void NWM_UDS::GetNodeInformation(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
u16 network_node_id = rp.Pop<u16>();
@ -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<NodeInfo>(*itr);
rb.PushRaw<NodeInfo>(*node);
}
LOG_DEBUG(Service_NWM, "called");
}
void NWM_UDS::Bind(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
u32 bind_node_id = rp.Pop<u32>();
u32 recv_buffer_size = rp.Pop<u32>();
u8 data_channel = rp.Pop<u8>();
u16 network_node_id = rp.Pop<u16>();
LOG_DEBUG(Service_NWM, "called");
std::pair<ResultStatus, std::shared_ptr<Kernel::Event>> 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>();
u32 recv_buffer_size = rp.Pop<u32>();
u8 data_channel = rp.Pop<u8>();
u16 network_node_id = rp.Pop<u16>();
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<u16>();
u8 flag = rp.Pop<u8>();
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<u8>();
std::vector<u8> 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<u8> 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<u8> 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>();
u32 max_out_buff_size_aligned = rp.Pop<u32>();
u32 max_out_buff_size = rp.Pop<u32>();
std::vector<u8> 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<u32>(*ret); // return is data size if gt/eq to 0
rb.Push<u16>(secure_data.src_node_id);
rb.PushStaticBuffer(std::move(output_buffer), 0);
}
Common::Expected<int, ResultStatus> NWM_UDS::PullPacketHLE(u32 bind_node_id, u32 max_out_buff_size,
u32 max_out_buff_size_aligned,
std::vector<u8>& 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<u32>(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<u8> output_buffer(buff_size);
IPC::RequestBuilder rb = rp.MakeBuilder(3, 2);
rb.Push(ResultSuccess);
rb.Push<u32>(0);
rb.Push<u16>(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<SecureDataHeader*>(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<u8> 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<u32>(data_size);
rb.Push<u16>(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<Kernel::Thread> 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<u8> passphrase) {
network_info = net_info;
u32 out_buffer_size = rp.Pop<u32>();
conn_type = static_cast<ConnectionType>(connection_type);
// scan input struct
u32 unk1 = rp.Pop<u32>();
u32 unk2 = rp.Pop<u32>();
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>();
u32 id = rp.Pop<u32>();
// 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<Kernel::Event> input_event = rp.PopObject<Kernel::Event>();
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<ThreadCallback>(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<u32>(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<u32>(sizeof(BeaconEntryHeader) + beacon.data.size());
entry.total_size = static_cast<u32>(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<u32>(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<const u8> network_info_buffer, u8 connection_type,
std::vector<u8> passphrase) {
network_info = {};
std::memcpy(&network_info, network_info_buffer.data(), network_info_buffer.size());
conn_type = static_cast<ConnectionType>(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<ThreadCallback>(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<Service::CFG::CFG_U>("cfg:u");
if (cfg.get()) {
if (auto cfg = system.ServiceManager().GetService<Service::CFG::CFG_U>("cfg:u")) {
auto cfg_module = cfg->GetModule();
mac = Service::CFG::MacToArray(cfg_module->GetMacAddress());
}

View File

@ -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<u8, 6>;
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<Kernel::SharedMemory> sharedmem);
void ShutdownHLE();
Common::Expected<int, ResultStatus> PullPacketHLE(u32 bind_node_id, u32 max_out_buff_size,
u32 max_out_buff_size_aligned,
std::vector<u8>& output_buffer,
void* secure_data_out);
ConnectionStatus GetConnectionStatusHLE();
ResultStatus DisconnectNetworkHLE();
std::pair<ResultStatus, std::shared_ptr<Kernel::Event>> 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<NodeInfo> GetNodeInformationHLE(u16 network_node_id);
ResultStatus SendToHLE(u32 dest_node_id, u8 data_channel, u32 data_size, u8 flags,
std::vector<u8> input_buffer);
Result UpdateNetworkAttributeHLE(u16 bitmask, u8 flag);
Result BeginHostingNetwork(std::span<const u8> network_info_buffer, std::vector<u8> passphrase);
void ConnectToNetwork(Kernel::HLERequestContext& ctx, u16 command_id,
std::span<const u8> network_info_buffer, u8 connection_type,
std::vector<u8> passphrase);
void ConnectToNetworkHLE(NetworkInfo net_info, u8 connection_type, std::vector<u8> 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<Kernel::Event> connection_event;
@ -590,6 +640,9 @@ private:
template <class Archive>
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

View File

@ -290,7 +290,7 @@ std::vector<u8> 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(),

View File

@ -91,7 +91,7 @@ constexpr u16 EAPoLStartMagic = 0x201;
struct EAPoLStartPacket {
u16_be magic = EAPoLStartMagic;
u16_be association_id;
enum_le<ConnectionType> conn_type;
enum_le<ConnectionType> connection_type;
INSERT_PADDING_BYTES(3);
EAPoLNodeInfo node;
};

View File

@ -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

View File

@ -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<AESKey, AESIV> GetOTPKeyIV();
const AESKey& GetMovableKey(bool cmac_key);
const AESIV& GetDlpChecksumModIv();
} // namespace HW::AES

View File

@ -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);

View File

@ -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<Kernel::Process>& 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()) {