mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2026-03-27 22:06:39 -06:00
Update File Core and Add HLE DLP Client (#1741)
This commit is contained in:
parent
ac0ec5edea
commit
43cecd1692
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
251
src/core/hle/service/dlp/dlp_base.cpp
Normal file
251
src/core/hle/service/dlp/dlp_base.cpp
Normal 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
|
||||
387
src/core/hle/service/dlp/dlp_base.h
Normal file
387
src/core/hle/service/dlp/dlp_base.h
Normal 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
|
||||
@ -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
|
||||
};
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
856
src/core/hle/service/dlp/dlp_clt_base.cpp
Normal file
856
src/core/hle/service/dlp/dlp_clt_base.cpp
Normal 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
|
||||
144
src/core/hle/service/dlp/dlp_clt_base.h
Normal file
144
src/core/hle/service/dlp/dlp_clt_base.h
Normal 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
|
||||
31
src/core/hle/service/dlp/dlp_crypto.cpp
Normal file
31
src/core/hle/service/dlp/dlp_crypto.cpp
Normal 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
|
||||
@ -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
|
||||
};
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"},
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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()) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user