Add DLP:SRVR + misc bug fixes (#1828)

* Add DLP:SRVR + add friend code seed hack for LM1 + add multiple filters in IPC debugger + fix cia_container smdh offset not being applied, possible IF statement underflowing + default initialize boss variables + fix IPC header asserts in AM functions + add extra debug info to IPC param assert

* Make server & client more resistant to high ping conditions

* Remove DLP from list of online recommended modules

* Fix license headers + fix clang formatting + fix server create network assert

* Fix recorder.cpp license header
This commit is contained in:
lannoene 2026-03-08 07:48:09 -07:00 committed by GitHub
parent 70c9e18eea
commit abc1980418
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 1388 additions and 162 deletions

View File

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -160,16 +160,23 @@ QString IPCRecorderWidget::GetFunctionName(const IPCDebugger::RequestRecord& rec
void IPCRecorderWidget::ApplyFilter(int index) {
auto* item = ui->main->invisibleRootItem()->child(index);
const QString filter = ui->filter->text();
if (filter.isEmpty()) {
const QString filter_full = ui->filter->text();
if (filter_full.isEmpty()) {
item->setHidden(false);
return;
}
for (int i = 0; i < item->columnCount(); ++i) {
if (item->text(i).contains(filter)) {
item->setHidden(false);
return;
auto filters = filter_full.split(u' ');
for (auto& filter : filters) {
if (filter.isEmpty()) {
continue;
}
for (int i = 0; i < item->columnCount(); ++i) {
if (item->text(i).contains(filter)) {
item->setHidden(false);
return;
}
}
}

View File

@ -187,6 +187,16 @@ HackManager hack_manager = {
0x00040000001AA300, // 3 (JPN)
},
}},
{HackType::SPOOF_FRIEND_CODE_SEED,
HackEntry{
.mode = HackAllowMode::FORCE,
.affected_title_ids =
{
// Luigi's Mansion 3ds
0x00040000001D1800, // JPN
0x00040000001D1900, // USA
0x00040000001D1A00, // EUR
},
}},
}};
}

View File

@ -15,6 +15,7 @@ enum class HackType : int {
ONLINE_LLE_REQUIRED,
REGION_FROM_SECURE,
REQUIRES_SHADER_FIXUP,
SPOOF_FRIEND_CODE_SEED,
};
class UserHackData {};

View File

@ -199,13 +199,14 @@ Loader::ResultStatus CIAContainer::LoadMetadata(std::span<const u8> meta_data, s
}
Loader::ResultStatus CIAContainer::LoadSMDH(std::span<const u8> smdh_data, std::size_t offset) {
if (smdh_data.size() - offset < sizeof(Loader::SMDH)) {
if (smdh_data.size() < sizeof(Loader::SMDH) ||
offset > smdh_data.size() - sizeof(Loader::SMDH)) {
return Loader::ResultStatus::Error;
}
cia_smdh = std::make_unique<Loader::SMDH>();
std::memcpy(cia_smdh.get(), smdh_data.data(), sizeof(Loader::SMDH));
std::memcpy(cia_smdh.get(), smdh_data.data() + offset, sizeof(Loader::SMDH));
return Loader::ResultStatus::Success;
}

View File

@ -1,4 +1,4 @@
// Copyright 2016 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -32,8 +32,9 @@ public:
}
void ValidateHeader() {
DEBUG_ASSERT_MSG(index == TotalSize(), "Operations do not match the header (cmd {:#x})",
header.raw);
DEBUG_ASSERT_MSG(index == TotalSize(),
"Operations do not match the header (cmd {:#x}, session: {})", header.raw,
context->Session()->GetName());
}
void Skip(unsigned size_in_words, bool set_to_null) {

View File

@ -2278,7 +2278,7 @@ void Module::Interface::GetProductCode(Kernel::HLERequestContext& ctx) {
ProductCode product_code;
IPC::RequestBuilder rb = rp.MakeBuilder(6, 0);
IPC::RequestBuilder rb = rp.MakeBuilder(5, 0);
FileSys::NCCHContainer ncch(path);
ncch.Load();
std::memcpy(&product_code.code, &ncch.ncch_header.product_code, 0x10);
@ -2866,7 +2866,7 @@ void Module::Interface::GetNumImportTitleContextsImpl(IPC::RequestParser& rp,
bool include_installing,
bool include_finalizing) {
IPC::RequestBuilder rb = rp.MakeBuilder(3, 0);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
u32 count = 0;
@ -2905,7 +2905,7 @@ void Module::Interface::GetImportTitleContextListImpl(IPC::RequestParser& rp,
}
}
IPC::RequestBuilder rb = rp.MakeBuilder(3, 0);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push(written);
}

View File

@ -1,4 +1,4 @@
// Copyright 2015 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -980,10 +980,10 @@ private:
Core::System& system;
std::shared_ptr<Kernel::Event> task_finish_event;
u8 new_arrival_flag;
u8 ns_data_new_flag;
u8 ns_data_new_flag_privileged;
u8 output_flag;
u8 new_arrival_flag{};
u8 ns_data_new_flag{};
u8 ns_data_new_flag_privileged{};
u8 output_flag{};
template <class Archive>
void serialize(Archive& ar, const unsigned int);

View File

@ -139,7 +139,7 @@ bool DLP_Base::ConnectToNetworkAsync(NWM::NetworkInfo net_info, NWM::ConnectionT
return true;
}
int DLP_Base::RecvFrom(u16 node_id, std::vector<u8>& buffer) {
int DLP_Base::RecvFrom(u16 node_id, std::vector<u8>& buffer, u16* out_node) {
constexpr u32 max_pullpacket_size = 0x3c00;
std::vector<u8> buffer_out;
@ -158,6 +158,9 @@ int DLP_Base::RecvFrom(u16 node_id, std::vector<u8>& buffer) {
}
buffer = buffer_out;
if (out_node) {
*out_node = secure_data.src_node_id;
}
return *ret; // size
}
@ -244,8 +247,40 @@ bool DLP_Base::ValidatePacket(u32 aes, void* pk, size_t sz, bool checksum) {
}
u32 DLP_Base::GetNumFragmentsFromTitleSize(u32 tsize) {
return Common::AlignUp(tsize - broad_title_size_diff, content_fragment_size) /
content_fragment_size;
return Common::AlignUp(tsize, content_fragment_size) / content_fragment_size;
}
Loader::SMDH::TitleLanguage DLP_Base::SystemLanguageToSMDHLanguage(CFG::SystemLanguage sys_lang) {
using namespace Loader;
using namespace CFG;
switch (sys_lang) {
case LANGUAGE_JP:
return SMDH::TitleLanguage::Japanese;
case LANGUAGE_EN:
return SMDH::TitleLanguage::English;
case LANGUAGE_FR:
return SMDH::TitleLanguage::French;
case LANGUAGE_DE:
return SMDH::TitleLanguage::German;
case LANGUAGE_IT:
return SMDH::TitleLanguage::Italian;
case LANGUAGE_ES:
return SMDH::TitleLanguage::Spanish;
case LANGUAGE_ZH:
return SMDH::TitleLanguage::SimplifiedChinese;
case LANGUAGE_KO:
return SMDH::TitleLanguage::Korean;
case LANGUAGE_NL:
return SMDH::TitleLanguage::Dutch;
case LANGUAGE_PT:
return SMDH::TitleLanguage::Portuguese;
case LANGUAGE_RU:
return SMDH::TitleLanguage::Russian;
case LANGUAGE_TW:
return SMDH::TitleLanguage::TraditionalChinese;
default:;
}
UNREACHABLE_MSG("Unknown system language: {}", static_cast<u32>(sys_lang));
}
} // namespace Service::DLP

View File

@ -8,6 +8,7 @@
#include "core/hle/service/cfg/cfg.h"
#include "core/hle/service/nwm/nwm_uds.h"
#include "core/hle/service/service.h"
#include "core/loader/smdh.h"
#include <semaphore>
@ -17,7 +18,7 @@ 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;
constexpr inline u32 content_fragment_size = 1440; // const frag packet size
struct DLPTitleInfo {
u32 unique_id; // games look at this to make sure it's their title info
@ -25,9 +26,9 @@ struct DLPTitleInfo {
Network::MacAddress mac_addr;
u16 version; // XX: probably?
std::array<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
std::array<u16, 64> title_short; // UTF-16
std::array<u16, 128> title_long; // UTF-16
std::array<u16, 0x900> icon; // 48x48, RGB565
u32 size;
u8 unk2;
u8 unk3;
@ -58,6 +59,18 @@ struct DLPEventDescription {
static_assert(sizeof(DLPEventDescription) == 0x18);
enum class DLP_Clt_State : u16 {
NotInitialized = 0, // TODO: check on hardware. it probably just errors
Initialized = 1,
Scanning = 2,
Joined = 5,
Downloading = 6,
WaitingForServerReady = 7,
Complete = 9,
WaitingForAccept =
64, // when a client joins, but is not accepted yet (used on servers w/ manual accept)
};
// START BIG ENDIAN
constexpr inline u8 dl_pk_type_broadcast = 0x01;
@ -79,15 +92,22 @@ struct DLPPacketHeader {
std::array<u8, 4> magic;
struct {
u8 type;
u8 mag0x02;
u16 unk; // usually 0x00 0x00
u8 mag0x02; // usually 0x02, but appears to depend on the internal state of a random
// buffer
u16 padding;
};
};
u16_be size; // size of the whole packet, including the header
u16_le must_be_2; // always set to 2
u32 checksum; // always calculate
u8 packet_index; // starts at 0
union {
std::array<u8, 3> resp_id; // client: copies this from host packet when responding to it
struct { // for server use
u8 pk_seq_num;
std::array<u8, 2> resp_id_high;
};
};
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);
@ -114,18 +134,19 @@ struct DLPPacketBool {
static_assert(sizeof(DLPPacketBool) == sizeof(u32_be));
constexpr u32 broad_title_size_diff = 111360;
#pragma pack(push, 2)
struct DLPBroadcastPacket1 {
DLPPacketHeader head;
u64_be child_title_id; // title id of the child being broadcasted
u64 unk1;
u64 unk2;
std::array<u8, 2> unk1;
std::array<u8, 2> unk6; // need 0x1 0x1
u32_be content_block_size; // full byte size of content block
u8 max_nodes;
std::array<u8, 7> unk2;
u64 unk3;
u64 unk4; // all 0s
u32_be size; // size minus broad_title_size_diff
u32 unk5;
u64 unk4; // all 0s
u32_be transfer_size;
u32_be required_size;
std::array<u16_be, 64> title_short;
std::array<u16_be, 128> title_long;
std::array<u16_be, 0x9c> icon_part;
@ -193,7 +214,8 @@ static_assert(sizeof(DLPSrvr_StartDistribution) == 0x14);
struct DLPClt_StartDistributionAck_NoContentNeeded {
DLPPacketHeader head;
DLPPacketBool initialized; // 0x1
u32 unk2; // 0x0
u16_be unk2; // 0x0. if 0x20, move to ContentNeeded
u16_be unk3; // 0x0
};
static_assert(sizeof(DLPClt_StartDistributionAck_NoContentNeeded) == 0x18);
@ -212,14 +234,17 @@ static_assert(sizeof(DLPClt_StartDistributionAck_ContentNeeded) == 0x38);
// perform distribution of content
// packet_index is 1
#pragma pack(push, 1)
struct DLPSrvr_ContentDistributionFragment {
DLPPacketHeader head;
u32_be content_magic; // extra magic value
u32_be unk1; // 0x1 BE
u8 unk2;
std::array<u8, 3> padding;
u32_be content_block; // content_block index starting at 0
u16_be frag_index; // BE % dlp_content_block_length
u16_be frag_size; // BE
u8 content_fragment[];
};
#pragma pack(pop)
static_assert(sizeof(DLPSrvr_ContentDistributionFragment) == 28);
@ -237,7 +262,7 @@ static_assert(sizeof(DLPSrvr_FinishContentUpload) == 0x18);
struct DLPClt_FinishContentUploadAck {
DLPPacketHeader head;
DLPPacketBool initialized; // 0x1
u8 unk2; // 0x1
u8 finished_cur_block; // 0x1 if finished downloading current block
u8 needs_content; // 0x1 if downloading conetnt
u32_be seq_ack; // BE client increments this every ack
u16 unk4; // 0x0
@ -279,6 +304,9 @@ static_assert(sizeof(DLPSrvr_BeginGameFinal) == 0x20);
// END BIG ENDIAN
class DLP_Base {
public:
static Loader::SMDH::TitleLanguage SystemLanguageToSMDHLanguage(CFG::SystemLanguage);
protected:
DLP_Base(Core::System& s);
virtual ~DLP_Base() = default;
@ -298,17 +326,27 @@ protected:
const u32 uds_sharedmem_size = 0x4000;
const u32 uds_version = 0x400;
const u32 recv_buffer_size = 0x3c00;
const u32 dlp_channel = 0x10;
const u8 num_broadcast_packets = 5;
u32 dlp_sharedmem_size{};
constexpr static inline u32 dlp_poll_rate_normal = 100;
constexpr static inline u16 dlp_wlan_comm_id = 0x2810;
constexpr static inline u16 dlp_net_info_channel = 0x1;
constexpr static inline u16 dlp_bind_node_id = 0x1;
constexpr static inline u32 dlp_recv_buffer_size = 0x3c00;
constexpr static inline u8 dlp_broadcast_data_channel = 0x1;
constexpr static inline u8 dlp_client_data_channel = 0x2;
constexpr static inline u8 dlp_host_network_node_id = 0x1;
const u8 first_client_node_id = dlp_host_network_node_id + 1;
DLP_Username username;
// stubbed as HLE NWM_UDS does not check this. Should be: 0km@tsa$uhmy1a0sa + nul
std::vector<u8> dlp_password_buf{};
std::array<u8, 9> wireless_reboot_passphrase;
const u32 dlp_content_block_length = 182;
constexpr static inline u32 dlp_content_block_length = 182;
std::shared_ptr<CFG::Module> GetCFG();
std::shared_ptr<NWM::NWM_UDS> GetUDS();
@ -321,7 +359,8 @@ protected:
bool ConnectToNetworkAsync(NWM::NetworkInfo net_info, NWM::ConnectionType conn_type,
std::vector<u8> passphrase);
int RecvFrom(u16 node_id, std::vector<u8>& buffer);
// out_node: if node_id is broadcast, check which node we received from
int RecvFrom(u16 node_id, std::vector<u8>& buffer, u16* out_node = nullptr);
bool SendTo(u16 node_id, u8 data_channel, std::vector<u8>& buffer, u8 flags = 0);
static std::u16string DLPUsernameAsString16(DLP_Username uname);
@ -349,7 +388,7 @@ protected:
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,
LOG_DEBUG(Service_DLP,
"Tried to send 2 packets concurrently, causing blocking on this thread");
sm_packet_sender_session.acquire();
}
@ -357,7 +396,7 @@ protected:
auto ph = GetPacketHead(send_packet_ctx);
ph->magic = magic;
ph->size = sizeof(T);
ph->unk1 = {0x02, 0x00};
ph->must_be_2 = 2;
ph->resp_id = resp_id;
ph->packet_index = packet_index;
return GetPacketBody<T>(send_packet_ctx);
@ -372,6 +411,15 @@ protected:
send_packet_ctx.clear();
sm_packet_sender_session.release();
}
// adds x bytes to the packet and refreshes the
// packet body pointer
template <typename T>
void PGen_AddPacketData(T*& p, size_t num_bytes) {
send_packet_ctx.resize(send_packet_ctx.size() + num_bytes);
auto head = GetPacketHead(send_packet_ctx);
head->size = send_packet_ctx.size();
p = GetPacketBody<T>(send_packet_ctx);
}
// input the host mac address
u32 GenDLPChecksumKey(Network::MacAddress mac_addr);
static void DLPEncryptCTR(void* out, size_t size, const u8* iv_ctr);

View File

@ -394,8 +394,15 @@ void DLP_Clt_Base::GetConnectingNodes(Kernel::HLERequestContext& ctx) {
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));
auto connected_nodes = reinterpret_cast<u16*>(connected_nodes_buffer.data());
for (int i = 0, out_idx = 0; i < NWM::UDSMaxNodes; i++) {
auto node_id = conn_status.nodes[i];
if (!node_id) {
continue;
}
connected_nodes[out_idx++] = node_id;
}
rb.Push(ResultSuccess);
rb.Push<u32>(conn_status.total_nodes);
@ -476,8 +483,8 @@ void DLP_Clt_Base::BeaconScanCallback(std::uintptr_t user_data, s64 cycles_late)
// set our next scan interval
system.CoreTiming().ScheduleEvent(
msToCycles(std::max<int>(0, beacon_scan_interval_ms -
beacon_parse_timer_total.GetTimeElapsed().count())) -
msToCycles(std::max<int>(100, beacon_scan_interval_ms -
beacon_parse_timer_total.GetTimeElapsed().count())) -
cycles_late,
beacon_scan_event, 0);
}
@ -578,14 +585,14 @@ void DLP_Clt_Base::CacheBeaconTitleInfo(Network::WifiPacket& beacon) {
// copy over title string data
std::copy(broad_pk1->title_short.begin(), broad_pk1->title_short.end(),
c_title_info.short_description.begin());
c_title_info.title_short.begin());
std::copy(broad_pk1->title_long.begin(), broad_pk1->title_long.end(),
c_title_info.long_description.begin());
c_title_info.title_long.begin());
// unique id should be the title id without the tid high shifted 1 byte right
c_title_info.unique_id = (broad_pk1->child_title_id & 0xFFFFFFFF) >> 8;
c_title_info.size = broad_pk1->size + broad_title_size_diff;
c_title_info.size = broad_pk1->transfer_size;
// copy over the icon data
auto icon_copy_loc = c_title_info.icon.begin();
@ -661,9 +668,6 @@ void DLP_Clt_Base::ClientConnectionManager() {
std::this_thread::sleep_for(std::chrono::milliseconds(poll_rate));
};
constexpr u32 dlp_poll_rate_normal = 100;
constexpr u32 dlp_poll_rate_distribute = 0;
u32 dlp_poll_rate_ms = dlp_poll_rate_normal;
bool got_corrupted_packets = false;
@ -672,7 +676,7 @@ void DLP_Clt_Base::ClientConnectionManager() {
while (sleep_poll(dlp_poll_rate_ms), is_connected) {
std::vector<u8> recv_buf;
if (int sz = RecvFrom(dlp_host_network_node_id, recv_buf)) {
if (int sz = RecvFrom(dlp_bind_node_id, recv_buf)) {
auto p_head = GetPacketHead(recv_buf);
// validate packet header
if (!ValidatePacket(aes, p_head, sz, should_verify_checksum)) {
@ -684,6 +688,7 @@ void DLP_Clt_Base::ClientConnectionManager() {
// now we can parse the packet
std::scoped_lock lock{clt_state_mutex, title_info_mutex};
if (p_head->type == dl_pk_type_auth) {
LOG_DEBUG(Service_DLP, "Recv auth");
auto s_body =
PGen_SetPK<DLPClt_AuthAck>(dl_pk_head_auth_header, 0, p_head->resp_id);
s_body->initialized = true;
@ -692,6 +697,7 @@ void DLP_Clt_Base::ClientConnectionManager() {
s_body->resp_id = {0x01, 0x02};
PGen_SendPK(aes, dlp_host_network_node_id, dlp_client_data_channel);
} else if (p_head->type == dl_pk_type_start_dist) {
LOG_DEBUG(Service_DLP, "Recv start dist");
// poll rate on non-downloading clients still needs to
// be quick enough to eat broadcast content frag packets
dlp_poll_rate_ms = dlp_poll_rate_distribute;
@ -752,6 +758,13 @@ void DLP_Clt_Base::ClientConnectionManager() {
is_downloading_content = false;
LOG_INFO(Service_DLP, "Finished downloading content. Installing...");
if (received_fragments.size() != dlp_units_total) {
LOG_WARNING(Service_DLP,
"There was a mismatch in fragment indexes, because we did "
"not receive all fragments {} / {}",
received_fragments.size(), dlp_units_total);
}
if (!InstallEncryptedCIAFromFragments(received_fragments)) {
LOG_ERROR(Service_DLP, "Could not install DLP encrypted content");
} else {
@ -759,27 +772,31 @@ void DLP_Clt_Base::ClientConnectionManager() {
}
clt_state = DLP_Clt_State::WaitingForServerReady;
} else if (FinishedCurrentContentBlock()) {
current_content_block++;
}
}
} else if (p_head->type == dl_pk_type_finish_dist) {
if (p_head->packet_index == 1) {
auto r_pbody = GetPacketBody<DLPSrvr_FinishContentUpload>(recv_buf);
[[maybe_unused]] 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->finished_cur_block = FinishedCurrentContentBlock();
s_body->needs_content = is_downloading_content;
s_body->seq_ack = r_pbody->seq_num + 1;
s_body->seq_ack = current_content_block;
s_body->unk4 = 0x0;
PGen_SendPK(aes, dlp_host_network_node_id, dlp_client_data_channel);
LOG_DEBUG(Service_DLP, "Recv finish dist, fc: {}, {} / {}",
FinishedCurrentContentBlock(), dlp_units_downloaded, dlp_units_total);
} else {
LOG_ERROR(Service_DLP, "Received finish dist packet, but packet index was {}",
p_head->packet_index);
}
} else if (p_head->type == dl_pk_type_start_game) {
LOG_DEBUG(Service_DLP, "Recv start game");
if (p_head->packet_index == 0) {
dlp_poll_rate_ms = dlp_poll_rate_normal;
auto s_body = PGen_SetPK<DLPClt_BeginGameAck>(dl_pk_head_start_game_header, 0,
@ -801,7 +818,7 @@ void DLP_Clt_Base::ClientConnectionManager() {
}
}
uds->UnbindHLE(dlp_host_network_node_id);
uds->UnbindHLE(dlp_bind_node_id);
uds->DisconnectNetworkHLE();
}
@ -823,6 +840,7 @@ bool DLP_Clt_Base::InstallEncryptedCIAFromFragments(std::set<ReceivedFragment>&
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;
@ -853,4 +871,9 @@ bool DLP_Clt_Base::IsIdling() {
return !is_scanning && !is_connected;
}
bool DLP_Clt_Base::FinishedCurrentContentBlock() {
return dlp_units_downloaded % dlp_content_block_length == 0 ||
dlp_units_downloaded == dlp_units_total;
}
} // namespace Service::DLP

View File

@ -10,16 +10,6 @@
namespace Service::DLP {
enum class DLP_Clt_State : u32 {
NotInitialized = 0, // TODO: check on hardware. it probably just errors
Initialized = 1,
Scanning = 2,
Joined = 5,
Downloading = 6,
WaitingForServerReady = 7,
Complete = 9,
};
// number of bars
enum class DLPSignalStrength : u8 {
VeryWeak = 0,
@ -79,18 +69,13 @@ protected:
///< beacons until at least one tinfo buf element is cleared
bool is_scanning = false;
constexpr static inline int beacon_scan_interval_ms = 1000;
constexpr static inline u32 dlp_poll_rate_distribute = 0;
std::vector<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;
@ -125,6 +110,7 @@ protected:
bool InstallEncryptedCIAFromFragments(std::set<ReceivedFragment>& frags);
void DisconnectFromServer();
bool IsIdling();
bool FinishedCurrentContentBlock();
void GetMyStatus(Kernel::HLERequestContext& ctx);
void GetChannels(Kernel::HLERequestContext& ctx);

View File

@ -36,7 +36,7 @@ void DLP_FKCL::InitializeWithName(Kernel::HLERequestContext& 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>>();
auto username = rp.PopRaw<DLP_Username>();
rp.Skip(1, false); // possible null terminator or unk flags
auto [shared_mem, event] = rp.PopObjects<Kernel::SharedMemory, Kernel::Event>();

View File

@ -5,10 +5,17 @@
#include "common/archives.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/file_sys/archive_ncch.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/result.h"
#include "core/hle/romfs.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/dlp/dlp_srvr.h"
#include "core/hle/service/fs/archive.h"
#include "core/hle/service/fs/fs_user.h"
SERIALIZE_EXPORT_IMPL(Service::DLP::DLP_SRVR)
@ -19,8 +26,148 @@ std::shared_ptr<Kernel::SessionRequestHandler> DLP_SRVR::GetServiceFrameworkShar
return shared_from_this();
}
std::shared_ptr<FS::FS_USER> DLP_SRVR::GetFS() {
return system.ServiceManager().GetService<FS::FS_USER>("fs:USER");
}
u8 DLP_SRVR::GetSrvrState() {
std::scoped_lock lock(srvr_state_mutex);
return static_cast<u32>(srvr_state);
}
void DLP_SRVR::InitializeSrvrCommon(u32 shared_mem_size, u8 max_clnts, u32 process_id,
DLP_Username uname,
std::shared_ptr<Kernel::SharedMemory> shared_mem,
std::shared_ptr<Kernel::Event> event) {
InitializeDlpBase(shared_mem_size, shared_mem, event, uname);
// this gets our mac address, and we will be the host
host_mac_address = GetUDS()->GetMacAddress();
max_clients = max_clnts;
client_states.resize(max_clients);
// set up client array
for (u8 i = first_client_node_id; auto& cl : client_states) {
cl.network_node_id = i;
cl.state = ClientState::NotJoined;
cl.pk_seq_num = 0;
i++;
}
if (!CacheContentFileInMemory(process_id)) {
LOG_WARNING(
Service_DLP,
"Unable to load cia file. You will not be able to perform content distribution!");
}
std::scoped_lock lock(srvr_state_mutex);
srvr_state = DLP_Srvr_State::Initialized;
}
bool DLP_SRVR::CacheContentFileInMemory(u32 process_id) {
auto fs = GetFS();
if (!fs) {
LOG_ERROR(Service_DLP, "Could not get pointer to fs");
return false;
}
auto title_info = fs->GetProgramLaunchInfo(process_id);
// get special content index. could have made a new
// HLE func in FS, but this is so small that it
// doesn't matter
ResultVal<u16> index;
if (title_info->media_type == FS::MediaType::GameCard) {
index = fs->GetSpecialContentIndexFromGameCard(title_info->program_id,
FS::SpecialContentType::DLPChild);
} else {
index = fs->GetSpecialContentIndexFromTMD(title_info->media_type, title_info->program_id,
FS::SpecialContentType::DLPChild);
}
if (!index) {
LOG_ERROR(Service_DLP, "Could not get special content index from program id 0x{:x}",
title_info->program_id);
return false;
}
// read as ncch to find the content
FileSys::NCCHArchive container(title_info->program_id, title_info->media_type);
std::array<char, 8> exefs_filepath{};
FileSys::Path file_path =
FileSys::MakeNCCHFilePath(FileSys::NCCHFileOpenType::NCCHData, *index,
FileSys::NCCHFilePathType::RomFS, exefs_filepath);
FileSys::Mode open_mode = {};
open_mode.read_flag.Assign(1);
const u32 open_attributes_none = 0;
auto file_result = container.OpenFile(file_path, open_mode, open_attributes_none);
if (file_result.Failed()) {
LOG_ERROR(Service_DLP, "Could not open DLP child archive");
return false;
}
auto romfs = std::move(file_result).Unwrap();
std::vector<u8> romfs_buffer(romfs->GetSize());
romfs->Read(0, romfs_buffer.size(), romfs_buffer.data());
romfs->Close();
u64 dlp_child_tid = (title_info->program_id & 0xFFFFFFFF) | DLP_CHILD_TID_HIGH;
auto filename = fmt::format("{:016x}.cia", dlp_child_tid);
LOG_INFO(Service_DLP, "Loading romfs file: {}", filename.c_str());
const RomFS::RomFSFile child_file =
RomFS::GetFile(romfs_buffer.data(), {Common::UTF8ToUTF16(filename)});
if (!child_file.Length()) {
LOG_ERROR(Service_DLP, "DLP child is missing from archive");
return false;
}
distribution_content.resize(child_file.Length());
memcpy(distribution_content.data(), child_file.Data(), child_file.Length());
FileSys::CIAContainer cia_container;
if (cia_container.Load(distribution_content) != Loader::ResultStatus::Success) {
LOG_ERROR(Service_DLP, "Could not load DLP child header");
return false;
}
const auto& smdh = cia_container.GetSMDH();
if (!smdh) {
LOG_ERROR(Service_DLP, "Failed to load DLP child SMDH");
return false;
}
// load up title_broadcast_info
memset(&title_broadcast_info, 0, sizeof(title_broadcast_info));
title_broadcast_info.title_id = dlp_child_tid;
title_broadcast_info.title_size = child_file.Length();
title_broadcast_info.transfer_size = cia_container.GetMetadataOffset();
title_broadcast_info.required_size = child_file.Length(); // TODO: verify on HW
memcpy(title_broadcast_info.icon.data(), smdh->large_icon.data(), smdh->large_icon.size());
auto request_lang = SystemLanguageToSMDHLanguage(GetCFG()->GetSystemLanguage());
LOG_DEBUG(Service_DLP, "Requesting lang: {}", static_cast<u32>(request_lang));
auto t_long = smdh->GetLongTitle(request_lang);
auto t_short = smdh->GetShortTitle(request_lang);
memcpy(title_broadcast_info.title_short.data(), t_short.data(), t_short.size());
memcpy(title_broadcast_info.title_long.data(), t_long.data(), t_long.size());
LOG_INFO(Service_DLP, "Successfully cached DLP child content");
return true;
}
void DLP_SRVR::IsChild(Kernel::HLERequestContext& ctx) {
auto fs = system.ServiceManager().GetService<Service::FS::FS_USER>("fs:USER");
auto fs = GetFS();
IPC::RequestParser rp(ctx);
u32 process_id = rp.Pop<u32>();
@ -35,8 +182,9 @@ void DLP_SRVR::IsChild(Kernel::HLERequestContext& ctx) {
// check if tid corresponds to dlp filter
u32 tid[2];
memcpy(tid, &title_info->program_id, sizeof(tid));
LOG_INFO(Service_DLP, "Checked on tid high: {:x} (low {:x})", tid[1], tid[0]);
child = (tid[1] & 0xFFFFC000) == 0x40000 && (tid[1] & 0xFFFF) == 0x1;
LOG_INFO(Service_DLP, "Checked on tid high: {:x} (low {:x}). Is child: {}", tid[1], tid[0],
child);
} else { // child not found
child = false;
LOG_ERROR(Service_DLP,
@ -49,29 +197,796 @@ void DLP_SRVR::IsChild(Kernel::HLERequestContext& ctx) {
rb.Push(child);
}
void DLP_SRVR::GetDupNoticeNeed(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
// this is not a 3ds. we don't have
// to update anything.
bool need = false;
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push(need);
}
void DLP_SRVR::GetServerState(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
rb.Push(GetSrvrState());
}
void DLP_SRVR::Initialize(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
auto shared_mem_size = rp.Pop<u32>();
auto max_clients = rp.Pop<u8>();
auto process_id = rp.Pop<u32>();
rp.Skip(3, false); // unk
auto [sharedmem, net_event] = rp.PopObjects<Kernel::SharedMemory, Kernel::Event>();
InitializeSrvrCommon(shared_mem_size, max_clients, process_id,
String16AsDLPUsername(GetCFG()->GetUsername()), sharedmem, net_event);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
void DLP_SRVR::InitializeWithName(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
auto shared_mem_size = rp.Pop<u32>();
auto max_clients = rp.Pop<u8>();
auto process_id = rp.Pop<u32>();
rp.Skip(3, false); // unk
auto username = rp.PopRaw<DLP_Username>();
rp.Skip(1, false);
auto [sharedmem, net_event] = rp.PopObjects<Kernel::SharedMemory, Kernel::Event>();
InitializeSrvrCommon(shared_mem_size, max_clients, process_id, username, sharedmem, net_event);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
void DLP_SRVR::Finalize(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
std::scoped_lock lock(broadcast_mutex);
is_broadcasting = false;
EndConnectionManager();
distribution_content.clear();
FinalizeDlpBase();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
void DLP_SRVR::StartHosting(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_DLP, "called");
IPC::RequestParser rp(ctx);
std::scoped_lock lock{srvr_state_mutex, broadcast_mutex};
manual_accept = rp.Pop<bool>();
u8 channel = rp.Pop<u8>();
if (channel != 0 && channel != 1 && channel != 6 && channel != 11) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(Result(ErrorDescription::OutOfRange, ErrorModule::DLP,
ErrorSummary::InvalidArgument, ErrorLevel::Permanent));
return;
}
NWM::NetworkInfo net_info;
net_info.max_nodes = max_clients + 1;
net_info.channel = channel; // if 0x0, gets set to default channel: 11
net_info.wlan_comm_id = dlp_wlan_comm_id;
net_info.application_data_size = 0;
GetUDS()->BeginHostingNetwork({reinterpret_cast<u8*>(&net_info), sizeof(NWM::NetworkInfo)},
dlp_password_buf);
is_broadcasting = true;
constexpr u32 title_broadcast_delay_ms = 1;
system.CoreTiming().ScheduleEvent(title_broadcast_delay_ms, title_broadcast_event, 0);
srvr_state = DLP_Srvr_State::Accepting;
is_hosting = true;
dlp_srvr_poll_rate_ms = dlp_poll_rate_normal;
server_connection_worker = std::thread([this] { ServerConnectionManager(); });
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
void DLP_SRVR::EndHosting(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_DLP, "called");
IPC::RequestParser rp(ctx);
std::scoped_lock lock{srvr_state_mutex, broadcast_mutex};
is_broadcasting = false;
srvr_state = DLP_Srvr_State::Initialized;
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
void DLP_SRVR::GetConnectingClients(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
u32 len = rp.Pop<u32>();
auto mapped_buf = rp.PopMappedBuffer();
std::vector<u16> buf_connecting;
std::scoped_lock lock(client_states_mutex);
for (auto& cl : client_states) {
if (cl.state == ClientState::NotJoined) {
continue;
}
buf_connecting.push_back(cl.network_node_id);
}
// ignore the host node
mapped_buf.Write(buf_connecting.data(), 0,
std::min((u32)buf_connecting.size(), len) * sizeof(u16));
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
rb.Push(ResultSuccess);
rb.Push<u32>(buf_connecting.size());
rb.PushMappedBuffer(mapped_buf);
}
void DLP_SRVR::GetClientInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
auto node_id = rp.Pop<u16>();
auto node_info = GetUDS()->GetNodeInformationHLE(node_id);
if (!node_info) {
LOG_ERROR(Service_DLP, "Could not get node info for network node id 0x{:x}", node_id);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(Result(ErrorDescription::NoData, ErrorModule::DLP, ErrorSummary::NotFound,
ErrorLevel::Status));
return;
}
IPC::RequestBuilder rb = rp.MakeBuilder(11, 0);
if (node_id == dlp_host_network_node_id) {
// even though it's not supposed to use
// this info, we will still give it the
// info
rb.Push(Result(ErrorDescription::NoData, ErrorModule::DLP, ErrorSummary::NotFound,
ErrorLevel::Status));
} else {
rb.Push(ResultSuccess);
}
rb.PushRaw(UDSToDLPNodeInfo(*node_info));
}
void DLP_SRVR::GetClientState(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
u8 node_id = rp.Pop<u16>();
// TODO: check if nodata is returned if the
// node requested isn't joined yet
std::scoped_lock lock(client_states_mutex);
bool state_should_error = false;
auto cl = GetClState(node_id, state_should_error);
if (!cl) {
LOG_WARNING(Service_DLP, "Node id out of range: 0x{:x}", node_id);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(Result(ErrorDescription::NoData, ErrorModule::DLP, ErrorSummary::NotFound,
ErrorLevel::Status));
return;
}
IPC::RequestBuilder rb = rp.MakeBuilder(6, 0);
rb.Push(ResultSuccess);
rb.Push(cl->GetMyState());
rb.Push<u32>(cl->dlp_units_total);
rb.Push<u32>(cl->GetDlpUnitsDownloaded());
// unk, see dlp_client_base state
rb.Push<u32>(0x0);
rb.Push<u32>(0x0);
}
void DLP_SRVR::StartDistribution(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_DLP, "called");
// when StartDistribution is called, the real
// DLP_SRVR doesn't stop its broadcast thread
// we do because we're better. we're stronger.
std::scoped_lock lock{srvr_state_mutex, broadcast_mutex};
is_broadcasting = false;
is_distributing = true;
is_waiting_for_passphrase = false;
dlp_srvr_poll_rate_ms = dlp_poll_rate_distribute;
srvr_state = DLP_Srvr_State::WaitingForDistribution;
auto aes = GenDLPChecksumKey(host_mac_address);
// now send distribute info requests
for (auto& cl : client_states) {
if (cl.state == ClientState::NotJoined)
continue;
if (cl.state != ClientState::Accepted) {
LOG_ERROR(Service_DLP, "Client was not ready start distribution");
}
cl.state = ClientState::NeedsDistributeAck;
auto s_body = PGen_SetPK<DLPSrvr_StartDistribution>(dl_pk_head_start_dist_header, 1,
cl.GetPkRespId());
s_body->initialized = true;
PGen_SendPK(aes, cl.network_node_id, dlp_client_data_channel);
}
IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
void DLP_SRVR::BeginGame(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_DLP, "called");
IPC::RequestParser rp(ctx);
auto passphrase = rp.PopRaw<std::array<u8, 9>>();
std::scoped_lock lock{client_states_mutex};
auto aes = GenDLPChecksumKey(host_mac_address);
for (auto& cl : client_states) {
if (cl.state == ClientState::NotJoined) {
continue;
}
if (cl.state == ClientState::SentPassphrase) {
LOG_WARNING(Service_DLP, "Already sent passphrase");
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(-1); // TODO: find real error
return;
}
if (cl.state != ClientState::DistributeDone) {
LOG_ERROR(Service_DLP, "Client is not ready to begin the game (state: {})",
static_cast<u8>(cl.state));
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(-1); // TODO: find real error
return;
}
auto s_body =
PGen_SetPK<DLPSrvr_BeginGameFinal>(dl_pk_head_start_game_header, 1, cl.GetPkRespId());
s_body->unk1 = 0x1;
s_body->wireless_reboot_passphrase = passphrase;
s_body->unk2 = 0x9; // could be our state
PGen_SendPK(aes, cl.network_node_id, dlp_client_data_channel);
cl.state = ClientState::SentPassphrase;
}
is_waiting_for_passphrase = true;
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
void DLP_SRVR::AcceptClient(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_DLP, "called");
IPC::RequestParser rp(ctx);
auto node_id = rp.Pop<u16>();
std::scoped_lock lock(client_states_mutex);
auto cl = GetClState(node_id);
if (!cl) {
LOG_ERROR(Service_DLP, "Could not find client node id: {}", node_id);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push<u32>(-1); // TODO: find real error code
return;
}
SendAuthPacket(*cl);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
void DLP_SRVR::DisconnectClient(Kernel::HLERequestContext& ctx) {
LOG_INFO(Service_DLP, "called");
IPC::RequestParser rp(ctx);
auto node_id = rp.Pop<u16>();
std::scoped_lock lock(client_states_mutex);
auto cl = GetClState(node_id);
if (!cl) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push<u32>(-1); // TODO: find real error code
return;
}
GetUDS()->EjectClientHLE(node_id);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
void DLP_SRVR::TitleBroadcastCallback(std::uintptr_t user_data, s64 cycles_late) {
std::scoped_lock lock(broadcast_mutex);
if (!is_broadcasting) {
return;
}
auto aes = GenDLPChecksumKey(host_mac_address);
std::array<u8, 3> broadcast_resp_id = {0x05};
auto p1 = PGen_SetPK<DLPBroadcastPacket1>(dl_pk_head_broadcast_header, 0, broadcast_resp_id);
p1->child_title_id = title_broadcast_info.title_id;
p1->transfer_size = title_broadcast_info.transfer_size;
p1->required_size = title_broadcast_info.required_size;
p1->content_block_size = content_fragment_size * dlp_content_block_length;
p1->max_nodes = max_clients + 1;
std::copy(title_broadcast_info.title_short.begin(), title_broadcast_info.title_short.end(),
p1->title_short.begin());
std::copy(title_broadcast_info.title_long.begin(), title_broadcast_info.title_long.end(),
p1->title_long.begin());
// copy in the icon data
size_t cur_icon_start = 0, cur_icon_end = 0;
auto copy_pk_icon = [&](std::span<u16_be>&& icon_part) {
cur_icon_start = cur_icon_end;
cur_icon_end += icon_part.size();
std::copy(title_broadcast_info.icon.begin() + cur_icon_start,
title_broadcast_info.icon.begin() + cur_icon_end, icon_part.begin());
};
copy_pk_icon({p1->icon_part.begin(), p1->icon_part.end()});
// i'm not sure what this is. is this regional?
p1->unk1 = {0x0, 0x10};
p1->unk6 = {0x1, 0x1};
PGen_SendPK(aes, broadcast_node_id, dlp_broadcast_data_channel);
auto p2 = PGen_SetPK<DLPBroadcastPacket2>(dl_pk_head_broadcast_header, 1, broadcast_resp_id);
copy_pk_icon({p2->icon_part.begin(), p2->icon_part.end()});
PGen_SendPK(aes, broadcast_node_id, dlp_broadcast_data_channel);
auto p3 = PGen_SetPK<DLPBroadcastPacket3>(dl_pk_head_broadcast_header, 2, broadcast_resp_id);
copy_pk_icon({p3->icon_part.begin(), p3->icon_part.end()});
PGen_SendPK(aes, broadcast_node_id, dlp_broadcast_data_channel);
auto p4 = PGen_SetPK<DLPBroadcastPacket4>(dl_pk_head_broadcast_header, 3, broadcast_resp_id);
copy_pk_icon({p4->icon_part.begin(), p4->icon_part.end()});
PGen_SendPK(aes, broadcast_node_id, dlp_broadcast_data_channel);
[[maybe_unused]] auto p5 =
PGen_SetPK<DLPBroadcastPacket5>(dl_pk_head_broadcast_header, 4, broadcast_resp_id);
PGen_SendPK(aes, broadcast_node_id, dlp_broadcast_data_channel);
system.CoreTiming().ScheduleEvent(msToCycles(title_broadcast_interval_ms) - cycles_late,
title_broadcast_event, 0);
}
void DLP_SRVR::ServerConnectionManager() {
auto uds = GetUDS();
auto aes = GenDLPChecksumKey(host_mac_address);
auto [ret, data_available_event] = uds->BindHLE(dlp_bind_node_id, dlp_recv_buffer_size,
dlp_client_data_channel, broadcast_node_id);
if (ret != NWM::ResultStatus::ResultSuccess) {
LOG_ERROR(Service_DLP, "Could not bind on node id 0x{:x}", dlp_bind_node_id);
return;
}
auto sleep_poll = [](size_t poll_rate) -> void {
std::this_thread::sleep_for(std::chrono::milliseconds(poll_rate));
};
LOG_INFO(Service_DLP, "Started");
while (sleep_poll(dlp_srvr_poll_rate_ms), is_hosting) {
// these mutexes are preventing the poll_rate from being
// 0 ms (since they are unfair mutexes)
std::scoped_lock lock{client_states_mutex, srvr_state_mutex};
auto conn_status = uds->GetConnectionStatusHLE();
auto any_needs_distribute = [this]() -> bool {
for (auto& cl : client_states) {
if (cl.state == ClientState::NotJoined) {
continue;
}
if (cl.state == ClientState::NeedsContent ||
cl.state == ClientState::DoesNotNeedContent ||
cl.state == ClientState::NeedsDistributeAck) {
return true;
}
if (cl.state != ClientState::DistributeDone) {
LOG_WARNING(Service_DLP,
"Invalid client state, should be in content distribution phase");
}
}
return false;
};
auto all_got_passphrase = [this]() -> bool {
for (auto& cl : client_states) {
if (cl.state == ClientState::NotJoined) {
continue;
}
if (cl.state != ClientState::SentPassphrase) {
return false;
}
}
return true;
};
// send outgoing messages
for (auto& cl : client_states) {
// client disconnected
if (cl.state != ClientState::NotJoined && !conn_status.nodes[cl.network_node_id - 1]) {
cl.state = ClientState::NotJoined;
LOG_INFO(Service_DLP, "Client disconnected");
}
switch (cl.state) {
case ClientState::NotJoined: {
// check if client should be joined
if (!conn_status.nodes[cl.network_node_id - 1]) {
break;
}
cl.state = ClientState::NeedsAuth;
cl.pk_seq_num = 0;
LOG_INFO(Service_DLP, "Client connected");
}
// fall through
case ClientState::NeedsAuth: {
if (manual_accept) {
break;
}
}
// fall through
case ClientState::NeedsAuthAck: {
// coded it this way so that it
// matches the hw behavior of constantly
// sending accept packets
SendAuthPacket(cl);
break;
}
case ClientState::Accepted:
break; // can idle
case ClientState::NeedsDistributeAck:
break;
case ClientState::NeedsContent: {
if (!cl.can_download_next_block) {
if (cl.sent_next_block_req) {
continue;
}
// send content block ack request (finish dist)
auto s_body = PGen_SetPK<DLPSrvr_FinishContentUpload>(
dl_pk_head_finish_dist_header, 1, cl.GetPkRespId());
s_body->initialized = true;
s_body->seq_num = cl.next_req_ack;
PGen_SendPK(aes, cl.network_node_id, dlp_client_data_channel);
cl.sent_next_block_req = true;
continue;
}
// TODO: change to broadcast content instead of
// sending it to individual clients. I'm doing it
// like this for simplicity's sake, but a real 3ds
// needs to broadcast the packets for efficiency.
for (size_t i = 0; i < dlp_content_block_length; i++) {
if (cl.GetTotalFragIndex(i) == cl.dlp_units_total) {
LOG_DEBUG(Service_DLP, "Cut block short");
break;
}
SendNextCIAFragment(cl, i);
}
cl.can_download_next_block = false;
cl.sent_next_block_req = false;
cl.current_content_block++;
break;
}
case ClientState::DoesNotNeedContent: {
if (cl.can_download_next_block) {
LOG_ERROR(Service_DLP, "Got unexpected content block request when client did "
"not request content previously");
continue;
}
// since we have a global poll rate,
// we need to rate limit packet sending
// to individual clients
if (cl.ShouldRateLimit(dlp_poll_rate_normal)) {
continue;
}
// send finish dist req
auto s_body = PGen_SetPK<DLPSrvr_FinishContentUpload>(dl_pk_head_finish_dist_header,
1, cl.GetPkRespId());
s_body->initialized = true;
s_body->seq_num = 0;
PGen_SendPK(aes, cl.network_node_id, dlp_client_data_channel);
break;
}
case ClientState::DistributeDone:
case ClientState::SentPassphrase: {
if (cl.ShouldRateLimit(dlp_poll_rate_normal)) {
continue;
}
auto s_body = PGen_SetPK<DLPSrvr_BeginGame>(dl_pk_head_start_game_header, 0,
cl.GetPkRespId());
s_body->unk1 = 0x1;
s_body->unk2 = 0x9;
PGen_SendPK(aes, cl.network_node_id, dlp_client_data_channel);
break;
}
default:
LOG_ERROR(Service_DLP, "Unknown client state: {}", static_cast<u8>(cl.state));
}
}
// receive incoming response messages
std::vector<u8> recv_buf;
u16 recv_node = 0;
while (int sz = RecvFrom(dlp_bind_node_id, recv_buf, &recv_node)) {
auto p_head = GetPacketHead(recv_buf);
auto cl = GetClState(recv_node);
if (!cl) {
LOG_ERROR(Service_DLP, "Could not get client state from received packet's node id");
continue;
}
// validate packet header
if (!ValidatePacket(aes, p_head, sz, should_verify_checksum)) {
LOG_ERROR(Service_DLP, "Could not validate DLP packet header");
continue;
}
if (p_head->type == dl_pk_type_auth) {
LOG_DEBUG(Service_DLP, "Recv auth");
if (cl->state != ClientState::NeedsAuthAck)
continue;
auto r_pbody = GetPacketBody<DLPClt_AuthAck>(recv_buf);
cl->state = ClientState::Accepted;
cl->resp_id = r_pbody->resp_id;
cl->IncrSeqNum();
} else if (p_head->type == dl_pk_type_start_dist) {
LOG_DEBUG(Service_DLP, "Recv start dist");
if (cl->state != ClientState::NeedsDistributeAck)
continue;
// packet needs to be at least the size of
// no_content_needed
auto r_pbody_nc =
GetPacketBody<DLPClt_StartDistributionAck_NoContentNeeded>(recv_buf);
if (r_pbody_nc->unk2 == 0x20) { // check if we should upgrade to content_needed
[[maybe_unused]] auto r_pbody_c =
GetPacketBody<DLPClt_StartDistributionAck_ContentNeeded>(recv_buf);
cl->state = ClientState::NeedsContent;
srvr_state = DLP_Srvr_State::Distributing;
cl->current_content_block = 0;
cl->sent_next_block_req = false;
cl->can_download_next_block = true;
cl->dlp_units_total =
GetNumFragmentsFromTitleSize(title_broadcast_info.title_size);
cl->next_req_ack = 0;
} else { // keep no_content_needed
if (!r_pbody_nc->initialized) {
LOG_WARNING(Service_DLP, "Corrupted packet info");
}
cl->sent_next_block_req = false;
cl->can_download_next_block = false;
// the reason we have this enum value is because
// we still need it to confirm that it finished
// "downloading" content
cl->state = ClientState::DoesNotNeedContent;
}
cl->IncrSeqNum();
} else if (p_head->type == dl_pk_type_distribute) {
LOG_ERROR(Service_DLP, "Unexpected content distribution fragment");
} else if (p_head->type == dl_pk_type_finish_dist) {
LOG_DEBUG(Service_DLP, "Recv finish dist");
if (cl->state != ClientState::NeedsContent &&
cl->state != ClientState::DoesNotNeedContent) {
continue;
}
if (cl->can_download_next_block) {
// got finish dist when we didn't need it
LOG_WARNING(
Service_DLP,
"Got finish dist when client was already queued to download next block");
continue;
}
auto r_pbody = GetPacketBody<DLPClt_FinishContentUploadAck>(recv_buf);
if (!r_pbody->needs_content) {
LOG_INFO(Service_DLP, "Client has finished downloading content");
cl->state = ClientState::DistributeDone;
cl->sent_next_block_req = false;
} else {
if (cl->current_content_block != r_pbody->seq_ack) {
if (r_pbody->finished_cur_block) {
LOG_WARNING(Service_DLP,
"Received out of order block request. Ignoring. ({} != {})",
static_cast<u32>(r_pbody->seq_ack),
cl->current_content_block);
}
cl->sent_next_block_req = false;
cl->IncrSeqNum(); // client expects us to increment here
continue;
}
}
cl->can_download_next_block = r_pbody->needs_content;
cl->next_req_ack++;
cl->IncrSeqNum();
} else if (p_head->type == dl_pk_type_start_game) {
LOG_DEBUG(Service_DLP, "Recv start game");
if (cl->state != ClientState::DistributeDone &&
cl->state != ClientState::SentPassphrase) {
continue;
}
auto r_pbody = GetPacketBody<DLPClt_BeginGameAck>(recv_buf);
if (r_pbody->unk2 != 0x9) {
LOG_WARNING(Service_DLP, "Client BeginGameAck unk2 is not 0x9 yet");
continue;
}
cl->IncrSeqNum();
} else {
LOG_ERROR(Service_DLP, "Unknown packet type: 0x{:x}", p_head->type);
}
}
if (is_distributing && !any_needs_distribute()) {
is_distributing = false;
srvr_state = DLP_Srvr_State::NeedToSendPassphrase; // causes begingame to run
is_waiting_for_passphrase = true;
dlp_srvr_poll_rate_ms = dlp_poll_rate_normal;
} else if (is_waiting_for_passphrase && all_got_passphrase()) {
is_waiting_for_passphrase = false;
srvr_state = DLP_Srvr_State::Complete;
// we have to stop the server client manager thread
// right now to avoid a race condition where we're waiting
// for one client to leave, but another client joins our server again
// thinking it's a raw UDS server, so it shows up as a connecting client.
// and if we just ignored it, it wouldn't try to reconnect to our real server,
// meaning that not doing this would case an unrecoverable error.
// a real 3DS gets around this by using different RF channels and
// the built-in DLP server password, which will prevent the client from
// joining our server.
is_hosting = false;
// force set all clients to disconnected state
for (auto& cl : client_states) {
cl.state = ClientState::NotJoined;
}
}
}
uds->UnbindHLE(dlp_bind_node_id);
uds->DestroyNetworkHLE();
LOG_INFO(Service_DLP, "Ended");
}
void DLP_SRVR::SendAuthPacket(ClientState& cl) {
std::scoped_lock lock(client_states_mutex);
auto aes = GenDLPChecksumKey(host_mac_address);
auto s_body = PGen_SetPK<DLPSrvr_Auth>(dl_pk_head_auth_header, 1, default_resp_id);
s_body->unk1 = 0x0;
PGen_SendPK(aes, cl.network_node_id, dlp_client_data_channel);
if (cl.state == ClientState::NeedsAuth) {
cl.state = ClientState::NeedsAuthAck;
}
}
bool DLP_SRVR::SendNextCIAFragment(ClientState& cl, u8 block_frag_index) {
std::scoped_lock lock(client_states_mutex);
size_t cur_frag_index = cl.GetTotalFragIndex(block_frag_index);
size_t frag_offset = content_fragment_size * cur_frag_index;
if (frag_offset > distribution_content.size()) {
LOG_ERROR(Service_DLP, "Frag offset is larger than the CIA size");
return false;
}
u16 frag_size =
std::min<size_t>(content_fragment_size, distribution_content.size() - frag_offset);
if (frag_offset + frag_size > distribution_content.size()) {
LOG_ERROR(Service_DLP, "Frag size is too large to properly fit the CIA content");
return false;
}
auto aes = GenDLPChecksumKey(host_mac_address);
std::span<u8> send_frag{distribution_content.begin() + frag_offset, frag_size};
// uses default resp id because it's supposed to be broadcast
auto s_body = PGen_SetPK<DLPSrvr_ContentDistributionFragment>(dl_pk_head_distribute_header, 1,
default_resp_id);
PGen_AddPacketData(s_body, frag_size);
// only checks if the low byte is 0x1
s_body->unk2 = 0x1;
s_body->padding = {0x8A, 0x4A, 0xE3};
s_body->content_block = cl.current_content_block;
s_body->frag_index = block_frag_index;
s_body->frag_size = frag_size;
memcpy(s_body->content_fragment, send_frag.data(), frag_size);
PGen_SendPK(aes, cl.network_node_id, dlp_client_data_channel);
return true;
}
void DLP_SRVR::EndConnectionManager() {
is_hosting = false;
if (server_connection_worker.joinable()) {
server_connection_worker.join();
}
}
// make sure to lock the client states mutex before
// calling this
DLP_SRVR::ClientState* DLP_SRVR::GetClState(u8 node_id, bool should_error) {
u8 cl_state_index = node_id - first_client_node_id;
if (cl_state_index >= client_states.size() && should_error) {
LOG_CRITICAL(Service_DLP, "Out of range node id {}", node_id);
return nullptr;
}
return &client_states[cl_state_index];
}
// can cause race conditions, only use this
// in places where the core loop isn't running
void DLP_SRVR::EnsureEndBroadcaster() {
std::scoped_lock lock(broadcast_mutex);
is_broadcasting = false;
system.CoreTiming().UnscheduleEvent(title_broadcast_event, 0);
}
DLP_SRVR::DLP_SRVR() : ServiceFramework("dlp:SRVR", 1), DLP_Base(Core::System::GetInstance()) {
static const FunctionInfo functions[] = {
// clang-format off
{0x0001, nullptr, "Initialize"},
{0x0002, nullptr, "Finalize"},
{0x0003, nullptr, "GetServerState"},
{0x0004, nullptr, "GetEventDescription"},
{0x0005, nullptr, "StartAccepting"},
{0x0006, nullptr, "EndAccepting"},
{0x0007, nullptr, "StartDistribution"},
{0x0008, nullptr, "SendWirelessRebootPassphrase"},
{0x0009, nullptr, "AcceptClient"},
{0x000A, nullptr, "DisconnectClient"},
{0x000B, nullptr, "GetConnectingClients"},
{0x000C, nullptr, "GetClientInfo"},
{0x000D, nullptr, "GetClientState"},
{0x0001, &DLP_SRVR::Initialize, "Initialize"},
{0x0002, &DLP_SRVR::Finalize, "Finalize"},
{0x0003, &DLP_SRVR::GetServerState, "GetServerState"},
{0x0004, &DLP_SRVR::GetEventDescription, "GetEventDescription"},
{0x0005, &DLP_SRVR::StartHosting, "StartHosting"},
{0x0006, &DLP_SRVR::EndHosting, "EndHosting"},
{0x0007, &DLP_SRVR::StartDistribution, "StartDistribution"},
{0x0008, &DLP_SRVR::BeginGame, "BeginGame"},
{0x0009, &DLP_SRVR::AcceptClient, "AcceptClient"},
{0x000A, &DLP_SRVR::DisconnectClient, "DisconnectClient"},
{0x000B, &DLP_SRVR::GetConnectingClients, "GetConnectingClients"},
{0x000C, &DLP_SRVR::GetClientInfo, "GetClientInfo"},
{0x000D, &DLP_SRVR::GetClientState, "GetClientState"},
{0x000E, &DLP_SRVR::IsChild, "IsChild"},
{0x000F, nullptr, "InitializeWithName"},
{0x0010, nullptr, "GetDupNoticeNeed"},
{0x000F, &DLP_SRVR::InitializeWithName, "InitializeWithName"},
{0x0010, &DLP_SRVR::GetDupNoticeNeed, "GetDupNoticeNeed"},
// clang-format on
};
RegisterHandlers(functions);
title_broadcast_event = system.CoreTiming().RegisterEvent(
"dlp:SRVR::TitleBroadcastCallback", [this](std::uintptr_t user_data, s64 cycles_late) {
TitleBroadcastCallback(user_data, cycles_late);
});
}
DLP_SRVR::~DLP_SRVR() {
EnsureEndBroadcaster();
EndConnectionManager();
}
} // namespace Service::DLP

View File

@ -4,15 +4,30 @@
#pragma once
#include "common/timer.h"
#include "core/hle/service/service.h"
#include "dlp_base.h"
namespace Service::FS {
class FS_USER;
}; // namespace Service::FS
namespace Service::DLP {
enum class DLP_Srvr_State : u8 {
NotInitialized = 0,
Initialized = 1,
Accepting = 2,
WaitingForDistribution = 6, // server is processing clients' needs
Distributing = 7,
NeedToSendPassphrase = 8, // finished distribution
Complete = 9,
};
class DLP_SRVR final : public ServiceFramework<DLP_SRVR>, public DLP_Base {
public:
DLP_SRVR();
~DLP_SRVR() = default;
~DLP_SRVR();
virtual std::shared_ptr<Kernel::SessionRequestHandler> GetServiceFrameworkSharedPtr();
virtual bool IsHost() {
@ -20,9 +35,161 @@ public:
}
private:
std::shared_ptr<FS::FS_USER> GetFS();
std::recursive_mutex srvr_state_mutex;
DLP_Srvr_State srvr_state = DLP_Srvr_State::NotInitialized;
u8 max_clients;
std::vector<u8> distribution_content;
bool manual_accept;
constexpr static inline int title_broadcast_interval_ms = 500;
constexpr static inline u32 dlp_poll_rate_distribute = 1;
const u16 broadcast_node_id = 0xFFFF;
bool is_broadcasting;
std::mutex broadcast_mutex;
const std::array<u8, 3> default_resp_id{};
Network::MacAddress host_mac_address;
std::atomic_int dlp_srvr_poll_rate_ms;
struct TitleBroadcastInfo {
u64 title_id;
u64 title_size;
u64 transfer_size;
u64 required_size;
std::array<u16, 0x900> icon;
std::array<u16, 0x40> title_short;
std::array<u16, 0x80> title_long;
} title_broadcast_info;
std::atomic_bool is_distributing;
std::atomic_bool is_waiting_for_passphrase;
struct ClientState {
ClientState() {
rate_timer.Start();
}
bool needs_content_download;
bool can_download_next_block;
bool sent_next_block_req;
bool is_accepted;
u8 pk_seq_num;
void IncrSeqNum() {
pk_seq_num++;
}
Common::Timer rate_timer;
bool ShouldRateLimit(int ms) {
if (rate_timer.GetTimeDifference().count() < ms) {
return true;
}
rate_timer.Update();
return false;
}
u32 dlp_units_total;
u32 GetDlpUnitsDownloaded() {
return current_content_block * dlp_content_block_length;
}
u32 current_content_block;
u32 GetTotalFragIndex(u8 block_frag_index) {
return current_content_block * dlp_content_block_length + block_frag_index;
}
u8 network_node_id;
std::array<u8, 2> resp_id;
u16 next_req_ack;
std::array<u8, 3> GetPkRespId() {
return {pk_seq_num, resp_id[0], resp_id[1]};
}
u32 GetMyState() {
DLP_Clt_State state_lle;
switch (state) {
case NotJoined:
LOG_WARNING(Service_DLP, "Trying to get LLE state of client that is not joined");
state_lle = DLP_Clt_State::Initialized;
break;
case NeedsAuth:
case NeedsAuthAck:
state_lle = DLP_Clt_State::WaitingForAccept;
break;
case Accepted:
state_lle = DLP_Clt_State::Joined;
break;
case NeedsDistributeAck:
case NeedsContent:
case DoesNotNeedContent:
state_lle = DLP_Clt_State::Downloading;
break;
case DistributeDone:
state_lle = DLP_Clt_State::WaitingForServerReady;
break;
case SentPassphrase:
state_lle = DLP_Clt_State::Complete;
break;
default:
LOG_ERROR(Service_DLP, "Unknown client state");
break;
}
bool is_joined = state != NotJoined && state != NeedsAuth && state != NeedsAuthAck;
return static_cast<u32>(state_lle) << 24 | is_joined << 16 | network_node_id;
}
// could be used in getclientstate
enum : u8 {
NotJoined = 0,
NeedsAuth,
NeedsAuthAck, // protects us from clients authing themselves
Accepted,
NeedsDistributeAck,
NeedsContent,
DoesNotNeedContent,
DistributeDone,
SentPassphrase,
} state;
};
std::vector<ClientState> client_states;
std::recursive_mutex client_states_mutex;
ClientState* GetClState(u8 node_id, bool should_error = true);
Core::TimingEventType* title_broadcast_event;
std::atomic_bool is_hosting;
std::thread server_connection_worker;
void ServerConnectionManager();
void EndConnectionManager();
void SendAuthPacket(ClientState& cl);
bool SendNextCIAFragment(ClientState& cl, u8 block_frag_index);
u8 GetSrvrState();
void InitializeSrvrCommon(u32 shared_mem_size, u8 max_clnts, u32 process_id, DLP_Username uname,
std::shared_ptr<Kernel::SharedMemory> shared_mem,
std::shared_ptr<Kernel::Event> event);
bool CacheContentFileInMemory(u32 process_id);
void TitleBroadcastCallback(std::uintptr_t user_data, s64 cycles_late);
void EnsureEndBroadcaster();
void IsChild(Kernel::HLERequestContext& ctx);
void GetDupNoticeNeed(Kernel::HLERequestContext& ctx);
void GetServerState(Kernel::HLERequestContext& ctx);
void Initialize(Kernel::HLERequestContext& ctx);
void InitializeWithName(Kernel::HLERequestContext& ctx);
void Finalize(Kernel::HLERequestContext& ctx);
void StartHosting(Kernel::HLERequestContext& ctx);
void EndHosting(Kernel::HLERequestContext& ctx);
void GetConnectingClients(Kernel::HLERequestContext& ctx);
void GetClientInfo(Kernel::HLERequestContext& ctx);
void GetClientState(Kernel::HLERequestContext& ctx);
void StartDistribution(Kernel::HLERequestContext& ctx);
void BeginGame(Kernel::HLERequestContext& ctx);
void AcceptClient(Kernel::HLERequestContext& ctx);
void DisconnectClient(Kernel::HLERequestContext& ctx);
SERVICE_SERIALIZATION_SIMPLE
friend class ClientState;
};
} // namespace Service::DLP

View File

@ -82,6 +82,10 @@ public:
secure_value_backend = backend;
}
static ResultVal<u16> GetSpecialContentIndexFromGameCard(u64 title_id, SpecialContentType type);
static ResultVal<u16> GetSpecialContentIndexFromTMD(MediaType media_type, u64 title_id,
SpecialContentType type);
private:
void Initialize(Kernel::HLERequestContext& ctx);
@ -740,10 +744,6 @@ private:
*/
void GetSaveDataSecureValue(Kernel::HLERequestContext& ctx);
static ResultVal<u16> GetSpecialContentIndexFromGameCard(u64 title_id, SpecialContentType type);
static ResultVal<u16> GetSpecialContentIndexFromTMD(MediaType media_type, u64 title_id,
SpecialContentType type);
std::unordered_map<u32, ProgramInfo> program_info_map;
std::string current_gamecard_path;

View File

@ -9,6 +9,7 @@
#include <cryptopp/osrng.h>
#include "common/archives.h"
#include "common/common_types.h"
#include "common/hacks/hack_manager.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
@ -355,7 +356,7 @@ void NWM_UDS::HandleSecureDataPacket(const Network::WifiPacket& packet) {
if (connection_status.status != NetworkStatus::ConnectedAsHost &&
connection_status.status != NetworkStatus::ConnectedAsClient &&
connection_status.status != NetworkStatus::ConnectedAsSpectator) {
LOG_DEBUG(Service_NWM, "Ignored SecureDataPacket because connection status is {}",
LOG_TRACE(Service_NWM, "Ignored SecureDataPacket because connection status is {}",
static_cast<u32>(connection_status.status));
return;
}
@ -716,6 +717,19 @@ ResultVal<std::shared_ptr<Kernel::Event>> NWM_UDS::Initialize(
return connection_status_event;
}
// allows people who haven't set up their
// 3ds to play local play on games which
// require a unique friend code seed
void NWM_UDS::CheckSpoofFriendCodeSeed(Kernel::HLERequestContext& ctx, NodeInfo& node) {
u64 caller_tid = ctx.ClientThread()->owner_process.lock()->codeset->program_id;
if (Common::Hacks::hack_manager.GetHackAllowMode(
Common::Hacks::HackType::SPOOF_FRIEND_CODE_SEED, caller_tid,
Common::Hacks::HackAllowMode::DISALLOW) == Common::Hacks::HackAllowMode::FORCE) {
auto mac_address = GetMacAddress();
memcpy(&node.friend_code_seed, mac_address.data(), mac_address.size());
}
}
void NWM_UDS::InitializeWithVersion(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
u32 sharedmem_size = rp.Pop<u32>();
@ -723,6 +737,8 @@ void NWM_UDS::InitializeWithVersion(Kernel::HLERequestContext& ctx) {
u16 version = rp.Pop<u16>();
auto sharedmem = rp.PopObject<Kernel::SharedMemory>();
CheckSpoofFriendCodeSeed(ctx, node);
auto result = Initialize(sharedmem_size, node, version, std::move(sharedmem));
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
@ -739,6 +755,8 @@ void NWM_UDS::InitializeDeprecated(Kernel::HLERequestContext& ctx) {
auto node = rp.PopRaw<NodeInfo>();
auto sharedmem = rp.PopObject<Kernel::SharedMemory>();
CheckSpoofFriendCodeSeed(ctx, node);
// The deprecated version uses fixed 0x100 as the version
auto result = Initialize(sharedmem_size, node, 0x100, std::move(sharedmem));
@ -960,13 +978,8 @@ Result NWM_UDS::BeginHostingNetwork(std::span<const u8> network_info_buffer,
// Notify the application that the first node was set.
connection_status.changed_nodes |= 1;
if (auto room_member = Network::GetRoomMember().lock()) {
if (room_member->IsConnected()) {
network_info.host_mac_address = room_member->GetMacAddress();
} else {
network_info.host_mac_address = {{0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
}
}
network_info.host_mac_address = GetMacAddress();
node_info[0] = current_node;
// If the game has a preferred channel, use that instead.
@ -1021,33 +1034,21 @@ void NWM_UDS::BeginHostingNetworkDeprecated(Kernel::HLERequestContext& ctx) {
rb.Push(result);
}
void NWM_UDS::EjectClient(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u16 network_node_id = rp.Pop<u16>();
LOG_WARNING(Service_NWM, "(stubbed) called");
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
Result NWM_UDS::EjectClientHLE(u16 network_node_id) {
// The host can not be kicked.
if (network_node_id == 1) {
rb.Push(Result(ErrorDescription::NotAuthorized, ErrorModule::UDS,
ErrorSummary::WrongArgument, ErrorLevel::Usage));
return;
return Result(ErrorDescription::NotAuthorized, ErrorModule::UDS,
ErrorSummary::WrongArgument, ErrorLevel::Usage);
}
std::scoped_lock lock(connection_status_mutex);
if (connection_status.status != NetworkStatus::ConnectedAsHost) {
// Only the host can kick people.
rb.Push(Result(ErrorDescription::NotAuthorized, ErrorModule::UDS,
ErrorSummary::InvalidState, ErrorLevel::Usage));
LOG_WARNING(Service_NWM, "called with status {}", connection_status.status);
return;
return Result(ErrorDescription::NotAuthorized, ErrorModule::UDS, ErrorSummary::InvalidState,
ErrorLevel::Usage);
}
// This function always returns success if the status is valid.
rb.Push(ResultSuccess);
using Network::WifiPacket;
Network::MacAddress dest_address = Network::BroadcastMac;
@ -1056,7 +1057,7 @@ void NWM_UDS::EjectClient(Kernel::HLERequestContext& ctx) {
if (!address) {
// There is no error if the network node id was not found.
return;
return ResultSuccess;
}
dest_address = *address;
}
@ -1070,6 +1071,21 @@ void NWM_UDS::EjectClient(Kernel::HLERequestContext& ctx) {
SendPacket(deauth);
SendPacket(deauth);
}
// This function always returns success if the status is valid.
return ResultSuccess;
}
void NWM_UDS::EjectClient(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u16 network_node_id = rp.Pop<u16>();
LOG_WARNING(Service_NWM, "(stubbed) called");
auto res = EjectClientHLE(network_node_id);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(res);
}
Result NWM_UDS::UpdateNetworkAttributeHLE(u16 node_bitmask, u8 flag) {
@ -1092,21 +1108,17 @@ void NWM_UDS::UpdateNetworkAttribute(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
void NWM_UDS::DestroyNetwork(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
Result NWM_UDS::DestroyNetworkHLE() {
// Unschedule the beacon broadcast event.
system.CoreTiming().UnscheduleEvent(beacon_broadcast_event, 0);
// Only a host can destroy
std::scoped_lock lock(connection_status_mutex);
if (connection_status.status != NetworkStatus::ConnectedAsHost) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(Result(ErrCodes::WrongStatus, ErrorModule::UDS, ErrorSummary::InvalidState,
ErrorLevel::Status));
LOG_WARNING(Service_NWM, "called with status {}",
static_cast<u32>(connection_status.status));
return;
return Result(ErrCodes::WrongStatus, ErrorModule::UDS, ErrorSummary::InvalidState,
ErrorLevel::Status);
}
// TODO(B3N30): Send 3 Deauth packets
@ -1118,14 +1130,22 @@ void NWM_UDS::DestroyNetwork(Kernel::HLERequestContext& ctx) {
node_map.clear();
connection_status_event->Signal();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
for (auto& bind_node : channel_data) {
bind_node.second.event->Signal();
}
channel_data.clear();
rb.Push(ResultSuccess);
return ResultSuccess;
}
void NWM_UDS::DestroyNetwork(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
auto res = DestroyNetworkHLE();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(res);
LOG_DEBUG(Service_NWM, "called");
}
@ -1266,7 +1286,7 @@ void NWM_UDS::PullPacket(Kernel::HLERequestContext& ctx) {
IPC::RequestBuilder rb = rp.MakeBuilder(3, 2);
rb.Push(ResultSuccess);
rb.Push<u32>(*ret); // return is data size if gt/eq to 0
rb.Push<u32>(*ret);
rb.Push<u16>(secure_data.src_node_id);
rb.PushStaticBuffer(std::move(output_buffer), 0);
}
@ -1621,6 +1641,25 @@ void NWM_UDS::BeaconBroadcastCallback(std::uintptr_t user_data, s64 cycles_late)
beacon_broadcast_event, 0);
}
Network::MacAddress NWM_UDS::GetMacAddress() {
MacAddress mac;
if (auto room_member = Network::GetRoomMember().lock();
room_member && room_member->IsConnected()) {
mac = room_member->GetMacAddress();
if (mac != CFG::GetConsoleMacAddress(system)) {
LOG_WARNING(Service_NWM, "Room member mac address is different from the console mac "
"address. Using room member mac address.");
}
} else {
// if we are not connected to the room, we can
// use the system mac address. In hopefully all cases
// this will match the room member mac addr anyways
mac = CFG::GetConsoleMacAddress(system);
}
return mac;
}
NWM_UDS::NWM_UDS(Core::System& system) : ServiceFramework("nwm::UDS"), system(system) {
static const FunctionInfo functions[] = {
// clang-format off
@ -1665,20 +1704,7 @@ NWM_UDS::NWM_UDS(Core::System& system) : ServiceFramework("nwm::UDS"), system(sy
BeaconBroadcastCallback(user_data, cycles_late);
});
MacAddress mac;
if (auto cfg = system.ServiceManager().GetService<Service::CFG::CFG_U>("cfg:u")) {
auto cfg_module = cfg->GetModule();
mac = Service::CFG::MacToArray(cfg_module->GetMacAddress());
}
if (auto room_member = Network::GetRoomMember().lock()) {
if (room_member->IsConnected()) {
mac = room_member->GetMacAddress();
}
}
system.Kernel().GetSharedPageHandler().SetMacAddress(mac);
system.Kernel().GetSharedPageHandler().SetMacAddress(GetMacAddress());
if (auto room_member = Network::GetRoomMember().lock()) {
wifi_packet_received = room_member->BindOnWifiPacketReceived(

View File

@ -117,7 +117,7 @@ struct ConnectionStatus {
static_assert(sizeof(ConnectionStatus) == 0x30, "ConnectionStatus has incorrect size.");
struct NetworkInfo {
std::array<u8, 6> host_mac_address;
MacAddress host_mac_address;
u8 channel;
u8 unk1;
u8 initialized;
@ -479,6 +479,8 @@ private:
*/
void DecryptBeaconData(Kernel::HLERequestContext& ctx);
void CheckSpoofFriendCodeSeed(Kernel::HLERequestContext& ctx, NodeInfo& node);
ResultVal<std::shared_ptr<Kernel::Event>> Initialize(
u32 sharedmem_size, const NodeInfo& node, u16 version,
std::shared_ptr<Kernel::SharedMemory> sharedmem);
@ -499,6 +501,8 @@ private:
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 DestroyNetworkHLE();
Result EjectClientHLE(u16 node_id);
Result BeginHostingNetwork(std::span<const u8> network_info_buffer, std::vector<u8> passphrase);
@ -508,6 +512,8 @@ private:
void ConnectToNetworkHLE(NetworkInfo net_info, u8 connection_type, std::vector<u8> passphrase);
Network::MacAddress GetMacAddress();
void BeaconBroadcastCallback(std::uintptr_t user_data, s64 cycles_late);
/**

View File

@ -78,7 +78,7 @@ const std::array<ServiceModuleInfo, 41> service_module_map{
false},
{"CECD", 0x00040130'00002602, CECD::InstallInterfaces, true},
{"CFG", 0x00040130'00001702, CFG::InstallInterfaces, false},
{"DLP", 0x00040130'00002802, DLP::InstallInterfaces, true},
{"DLP", 0x00040130'00002802, DLP::InstallInterfaces, false},
{"DSP", 0x00040130'00001A02, DSP::InstallInterfaces, false},
{"FRD", 0x00040130'00003202, FRD::InstallInterfaces, true},
{"GSP", 0x00040130'00001C02, GSP::InstallInterfaces, false},