summaryrefslogtreecommitdiffstats
path: root/src/core/hle/service
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/core/hle/service/apt/apt.cpp154
-rw-r--r--src/core/hle/service/apt/bcfnt/bcfnt.cpp6
-rw-r--r--src/core/hle/service/boss/boss_p.cpp3
-rw-r--r--src/core/hle/service/frd/frd.cpp43
-rw-r--r--src/core/hle/service/frd/frd.h13
-rw-r--r--src/core/hle/service/frd/frd_u.cpp2
-rw-r--r--src/core/hle/service/nwm/nwm_uds.cpp77
-rw-r--r--src/core/hle/service/nwm/uds_data.cpp278
-rw-r--r--src/core/hle/service/nwm/uds_data.h78
9 files changed, 630 insertions, 24 deletions
diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp
index 25e7b777d..df4b5cc3f 100644
--- a/src/core/hle/service/apt/apt.cpp
+++ b/src/core/hle/service/apt/apt.cpp
@@ -6,11 +6,13 @@
#include "common/file_util.h"
#include "common/logging/log.h"
#include "core/core.h"
+#include "core/file_sys/file_backend.h"
#include "core/hle/applets/applet.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/mutex.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/shared_memory.h"
+#include "core/hle/romfs.h"
#include "core/hle/service/apt/apt.h"
#include "core/hle/service/apt/apt_a.h"
#include "core/hle/service/apt/apt_s.h"
@@ -27,6 +29,7 @@ namespace APT {
/// Handle to shared memory region designated to for shared system font
static Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem;
+static bool shared_font_loaded = false;
static bool shared_font_relocated = false;
static Kernel::SharedPtr<Kernel::Mutex> lock;
@@ -71,7 +74,7 @@ void Initialize(Service::Interface* self) {
void GetSharedFont(Service::Interface* self) {
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x44, 0, 0); // 0x00440000
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
- if (!shared_font_mem) {
+ if (!shared_font_loaded) {
LOG_ERROR(Service_APT, "shared font file missing - go dump it from your 3ds");
rb.Push<u32>(-1); // TODO: Find the right error code
rb.Skip(1 + 2, true);
@@ -644,36 +647,146 @@ void CheckNew3DS(Service::Interface* self) {
LOG_WARNING(Service_APT, "(STUBBED) called");
}
-void Init() {
- AddService(new APT_A_Interface);
- AddService(new APT_S_Interface);
- AddService(new APT_U_Interface);
-
- HLE::Applets::Init();
-
- // Load the shared system font (if available).
+static u32 DecompressLZ11(const u8* in, u8* out) {
+ u32_le decompressed_size;
+ memcpy(&decompressed_size, in, sizeof(u32));
+ in += 4;
+
+ u8 type = decompressed_size & 0xFF;
+ ASSERT(type == 0x11);
+ decompressed_size >>= 8;
+
+ u32 current_out_size = 0;
+ u8 flags = 0, mask = 1;
+ while (current_out_size < decompressed_size) {
+ if (mask == 1) {
+ flags = *(in++);
+ mask = 0x80;
+ } else {
+ mask >>= 1;
+ }
+
+ if (flags & mask) {
+ u8 byte1 = *(in++);
+ u32 length = byte1 >> 4;
+ u32 offset;
+ if (length == 0) {
+ u8 byte2 = *(in++);
+ u8 byte3 = *(in++);
+ length = (((byte1 & 0x0F) << 4) | (byte2 >> 4)) + 0x11;
+ offset = (((byte2 & 0x0F) << 8) | byte3) + 0x1;
+ } else if (length == 1) {
+ u8 byte2 = *(in++);
+ u8 byte3 = *(in++);
+ u8 byte4 = *(in++);
+ length = (((byte1 & 0x0F) << 12) | (byte2 << 4) | (byte3 >> 4)) + 0x111;
+ offset = (((byte3 & 0x0F) << 8) | byte4) + 0x1;
+ } else {
+ u8 byte2 = *(in++);
+ length = (byte1 >> 4) + 0x1;
+ offset = (((byte1 & 0x0F) << 8) | byte2) + 0x1;
+ }
+
+ for (u32 i = 0; i < length; i++) {
+ *out = *(out - offset);
+ ++out;
+ }
+
+ current_out_size += length;
+ } else {
+ *(out++) = *(in++);
+ current_out_size++;
+ }
+ }
+ return decompressed_size;
+}
+
+static bool LoadSharedFont() {
+ // TODO (wwylele): load different font archive for region CHN/KOR/TWN
+ const u64_le shared_font_archive_id_low = 0x0004009b00014002;
+ const u64_le shared_font_archive_id_high = 0x00000001ffffff00;
+ std::vector<u8> shared_font_archive_id(16);
+ std::memcpy(&shared_font_archive_id[0], &shared_font_archive_id_low, sizeof(u64));
+ std::memcpy(&shared_font_archive_id[8], &shared_font_archive_id_high, sizeof(u64));
+ FileSys::Path archive_path(shared_font_archive_id);
+ auto archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::NCCH, archive_path);
+ if (archive_result.Failed())
+ return false;
+
+ std::vector<u8> romfs_path(20, 0); // 20-byte all zero path for opening RomFS
+ FileSys::Path file_path(romfs_path);
+ FileSys::Mode open_mode = {};
+ open_mode.read_flag.Assign(1);
+ auto file_result = Service::FS::OpenFileFromArchive(*archive_result, file_path, open_mode);
+ if (file_result.Failed())
+ return false;
+
+ auto romfs = std::move(file_result).Unwrap();
+ std::vector<u8> romfs_buffer(romfs->backend->GetSize());
+ romfs->backend->Read(0, romfs_buffer.size(), romfs_buffer.data());
+ romfs->backend->Close();
+
+ const u8* font_file = RomFS::GetFilePointer(romfs_buffer.data(), {u"cbf_std.bcfnt.lz"});
+ if (font_file == nullptr)
+ return false;
+
+ struct {
+ u32_le status;
+ u32_le region;
+ u32_le decompressed_size;
+ INSERT_PADDING_WORDS(0x1D);
+ } shared_font_header{};
+ static_assert(sizeof(shared_font_header) == 0x80, "shared_font_header has incorrect size");
+
+ shared_font_header.status = 2; // successfully loaded
+ shared_font_header.region = 1; // region JPN/EUR/USA
+ shared_font_header.decompressed_size =
+ DecompressLZ11(font_file, shared_font_mem->GetPointer(0x80));
+ std::memcpy(shared_font_mem->GetPointer(), &shared_font_header, sizeof(shared_font_header));
+ *shared_font_mem->GetPointer(0x83) = 'U'; // Change the magic from "CFNT" to "CFNU"
+
+ return true;
+}
+
+static bool LoadLegacySharedFont() {
+ // This is the legacy method to load shared font.
// The expected format is a decrypted, uncompressed BCFNT file with the 0x80 byte header
// generated by the APT:U service. The best way to get is by dumping it from RAM. We've provided
// a homebrew app to do this: https://github.com/citra-emu/3dsutils. Put the resulting file
// "shared_font.bin" in the Citra "sysdata" directory.
-
std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + SHARED_FONT;
FileUtil::CreateFullPath(filepath); // Create path if not already created
FileUtil::IOFile file(filepath, "rb");
-
if (file.IsOpen()) {
- // Create shared font memory object
- using Kernel::MemoryPermission;
- shared_font_mem =
- Kernel::SharedMemory::Create(nullptr, 0x332000, // 3272 KB
- MemoryPermission::ReadWrite, MemoryPermission::Read, 0,
- Kernel::MemoryRegion::SYSTEM, "APT:SharedFont");
- // Read shared font data
file.ReadBytes(shared_font_mem->GetPointer(), file.GetSize());
+ return true;
+ }
+
+ return false;
+}
+
+void Init() {
+ AddService(new APT_A_Interface);
+ AddService(new APT_S_Interface);
+ AddService(new APT_U_Interface);
+
+ HLE::Applets::Init();
+
+ using Kernel::MemoryPermission;
+ shared_font_mem =
+ Kernel::SharedMemory::Create(nullptr, 0x332000, // 3272 KB
+ MemoryPermission::ReadWrite, MemoryPermission::Read, 0,
+ Kernel::MemoryRegion::SYSTEM, "APT:SharedFont");
+
+ if (LoadSharedFont()) {
+ shared_font_loaded = true;
+ } else if (LoadLegacySharedFont()) {
+ LOG_WARNING(Service_APT, "Loaded shared font by legacy method");
+ shared_font_loaded = true;
} else {
- LOG_WARNING(Service_APT, "Unable to load shared font: %s", filepath.c_str());
- shared_font_mem = nullptr;
+ LOG_WARNING(Service_APT, "Unable to load shared font");
+ shared_font_loaded = false;
}
lock = Kernel::Mutex::Create(false, "APT_U:Lock");
@@ -693,6 +806,7 @@ void Init() {
void Shutdown() {
shared_font_mem = nullptr;
+ shared_font_loaded = false;
shared_font_relocated = false;
lock = nullptr;
notification_event = nullptr;
diff --git a/src/core/hle/service/apt/bcfnt/bcfnt.cpp b/src/core/hle/service/apt/bcfnt/bcfnt.cpp
index 57eb39d75..6d2474702 100644
--- a/src/core/hle/service/apt/bcfnt/bcfnt.cpp
+++ b/src/core/hle/service/apt/bcfnt/bcfnt.cpp
@@ -78,7 +78,8 @@ void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAd
memcpy(&cmap, data, sizeof(cmap));
// Relocate the offsets in the CMAP section
- cmap.next_cmap_offset += offset;
+ if (cmap.next_cmap_offset != 0)
+ cmap.next_cmap_offset += offset;
memcpy(data, &cmap, sizeof(cmap));
} else if (memcmp(section_header.magic, "CWDH", 4) == 0) {
@@ -86,7 +87,8 @@ void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAd
memcpy(&cwdh, data, sizeof(cwdh));
// Relocate the offsets in the CWDH section
- cwdh.next_cwdh_offset += offset;
+ if (cwdh.next_cwdh_offset != 0)
+ cwdh.next_cwdh_offset += offset;
memcpy(data, &cwdh, sizeof(cwdh));
} else if (memcmp(section_header.magic, "TGLP", 4) == 0) {
diff --git a/src/core/hle/service/boss/boss_p.cpp b/src/core/hle/service/boss/boss_p.cpp
index ee941e228..3990d0d6e 100644
--- a/src/core/hle/service/boss/boss_p.cpp
+++ b/src/core/hle/service/boss/boss_p.cpp
@@ -66,7 +66,10 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00360084, SetTaskQuery, "SetTaskQuery"},
{0x00370084, GetTaskQuery, "GetTaskQuery"},
// boss:p
+ {0x04010082, nullptr, "InitializeSessionPrivileged"},
{0x04040080, nullptr, "GetAppNewFlag"},
+ {0x040D0182, nullptr, "GetNsDataIdListPrivileged"},
+ {0x040E0182, nullptr, "GetNsDataIdListPrivileged1"},
{0x04130082, nullptr, "SendPropertyPrivileged"},
{0x041500C0, nullptr, "DeleteNsDataPrivileged"},
{0x04160142, nullptr, "GetNsDataHeaderInfoPrivileged"},
diff --git a/src/core/hle/service/frd/frd.cpp b/src/core/hle/service/frd/frd.cpp
index 76ecda8b7..7ad7798da 100644
--- a/src/core/hle/service/frd/frd.cpp
+++ b/src/core/hle/service/frd/frd.cpp
@@ -6,6 +6,7 @@
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/hle/ipc.h"
+#include "core/hle/ipc_helpers.h"
#include "core/hle/result.h"
#include "core/hle/service/frd/frd.h"
#include "core/hle/service/frd/frd_a.h"
@@ -105,6 +106,48 @@ void GetMyScreenName(Service::Interface* self) {
LOG_WARNING(Service_FRD, "(STUBBED) called");
}
+void UnscrambleLocalFriendCode(Service::Interface* self) {
+ const size_t scrambled_friend_code_size = 12;
+ const size_t friend_code_size = 8;
+
+ IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1C, 1, 2);
+ const u32 friend_code_count = rp.Pop<u32>();
+ size_t in_buffer_size;
+ const VAddr scrambled_friend_codes = rp.PopStaticBuffer(&in_buffer_size, false);
+ ASSERT_MSG(in_buffer_size == (friend_code_count * scrambled_friend_code_size),
+ "Wrong input buffer size");
+
+ size_t out_buffer_size;
+ VAddr unscrambled_friend_codes = rp.PeekStaticBuffer(0, &out_buffer_size);
+ ASSERT_MSG(out_buffer_size == (friend_code_count * friend_code_size),
+ "Wrong output buffer size");
+
+ for (u32 current = 0; current < friend_code_count; ++current) {
+ // TODO(B3N30): Unscramble the codes and compare them against the friend list
+ // Only write 0 if the code isn't in friend list, otherwise write the
+ // unscrambled one
+ //
+ // Code for unscrambling (should be compared to HW):
+ // std::array<u16, 6> scambled_friend_code;
+ // Memory::ReadBlock(scrambled_friend_codes+(current*scrambled_friend_code_size),
+ // scambled_friend_code.data(), scrambled_friend_code_size); std::array<u16, 4>
+ // unscrambled_friend_code; unscrambled_friend_code[0] = scambled_friend_code[0] ^
+ // scambled_friend_code[5]; unscrambled_friend_code[1] = scambled_friend_code[1] ^
+ // scambled_friend_code[5]; unscrambled_friend_code[2] = scambled_friend_code[2] ^
+ // scambled_friend_code[5]; unscrambled_friend_code[3] = scambled_friend_code[3] ^
+ // scambled_friend_code[5];
+
+ u64 result = 0ull;
+ Memory::WriteBlock(unscrambled_friend_codes + (current * sizeof(result)), &result,
+ sizeof(result));
+ }
+
+ LOG_WARNING(Service_FRD, "(STUBBED) called");
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
+ rb.Push(RESULT_SUCCESS);
+ rb.PushStaticBuffer(unscrambled_friend_codes, out_buffer_size, 0);
+}
+
void SetClientSdkVersion(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
diff --git a/src/core/hle/service/frd/frd.h b/src/core/hle/service/frd/frd.h
index e61940ea0..66a87c8cd 100644
--- a/src/core/hle/service/frd/frd.h
+++ b/src/core/hle/service/frd/frd.h
@@ -96,6 +96,19 @@ void GetMyFriendKey(Service::Interface* self);
void GetMyScreenName(Service::Interface* self);
/**
+ * FRD::UnscrambleLocalFriendCode service function
+ * Inputs:
+ * 1 : Friend code count
+ * 2 : ((count * 12) << 14) | 0x402
+ * 3 : Pointer to encoded friend codes. Each is 12 bytes large
+ * 64 : ((count * 8) << 14) | 2
+ * 65 : Pointer to write decoded local friend codes to. Each is 8 bytes large.
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+void UnscrambleLocalFriendCode(Service::Interface* self);
+
+/**
* FRD::SetClientSdkVersion service function
* Inputs:
* 1 : Used SDK Version
diff --git a/src/core/hle/service/frd/frd_u.cpp b/src/core/hle/service/frd/frd_u.cpp
index 496f29ca9..6970ff768 100644
--- a/src/core/hle/service/frd/frd_u.cpp
+++ b/src/core/hle/service/frd/frd_u.cpp
@@ -36,7 +36,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00190042, nullptr, "GetFriendFavoriteGame"},
{0x001A00C4, nullptr, "GetFriendInfo"},
{0x001B0080, nullptr, "IsIncludedInFriendList"},
- {0x001C0042, nullptr, "UnscrambleLocalFriendCode"},
+ {0x001C0042, UnscrambleLocalFriendCode, "UnscrambleLocalFriendCode"},
{0x001D0002, nullptr, "UpdateGameModeDescription"},
{0x001E02C2, nullptr, "UpdateGameMode"},
{0x001F0042, nullptr, "SendInvitation"},
diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp
index a7149c9e8..6dbdff044 100644
--- a/src/core/hle/service/nwm/nwm_uds.cpp
+++ b/src/core/hle/service/nwm/nwm_uds.cpp
@@ -15,6 +15,7 @@
#include "core/hle/result.h"
#include "core/hle/service/nwm/nwm_uds.h"
#include "core/hle/service/nwm/uds_beacon.h"
+#include "core/hle/service/nwm/uds_data.h"
#include "core/memory.h"
namespace Service {
@@ -373,6 +374,80 @@ static void DestroyNetwork(Interface* self) {
}
/**
+ * NWM_UDS::SendTo service function.
+ * Sends a data frame to the UDS network we're connected to.
+ * Inputs:
+ * 0 : Command header.
+ * 1 : Unknown.
+ * 2 : u16 Destination network node id.
+ * 3 : u8 Data channel.
+ * 4 : Buffer size >> 2
+ * 5 : Data size
+ * 6 : Flags
+ * 7 : Input buffer descriptor
+ * 8 : Input buffer address
+ * Outputs:
+ * 0 : Return header
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void SendTo(Interface* self) {
+ IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x17, 6, 2);
+
+ rp.Skip(1, false);
+ u16 dest_node_id = rp.Pop<u16>();
+ u8 data_channel = rp.Pop<u8>();
+ rp.Skip(1, false);
+ u32 data_size = rp.Pop<u32>();
+ u32 flags = rp.Pop<u32>();
+
+ size_t desc_size;
+ const VAddr input_address = rp.PopStaticBuffer(&desc_size, false);
+ ASSERT(desc_size == data_size);
+
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+
+ if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsClient) &&
+ connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) {
+ rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::UDS,
+ ErrorSummary::InvalidState, ErrorLevel::Status));
+ return;
+ }
+
+ if (dest_node_id == connection_status.network_node_id) {
+ rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::UDS,
+ ErrorSummary::WrongArgument, ErrorLevel::Status));
+ return;
+ }
+
+ // TODO(Subv): Do something with the flags.
+
+ constexpr size_t MaxSize = 0x5C6;
+ if (data_size > MaxSize) {
+ rb.Push(ResultCode(ErrorDescription::TooLarge, ErrorModule::UDS,
+ ErrorSummary::WrongArgument, ErrorLevel::Usage));
+ return;
+ }
+
+ std::vector<u8> data(data_size);
+ Memory::ReadBlock(input_address, data.data(), data.size());
+
+ // TODO(Subv): Increment the sequence number after each sent packet.
+ u16 sequence_number = 0;
+ std::vector<u8> data_payload = GenerateDataPayload(
+ data, data_channel, dest_node_id, connection_status.network_node_id, sequence_number);
+
+ // TODO(Subv): Retrieve the MAC address of the dest_node_id and our own to encrypt
+ // and encapsulate the payload.
+
+ // TODO(Subv): Send the frame.
+
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_WARNING(Service_NWM, "(STUB) called dest_node_id=%u size=%u flags=%u channel=%u",
+ static_cast<u32>(dest_node_id), data_size, flags, static_cast<u32>(data_channel));
+}
+
+/**
* NWM_UDS::GetChannel service function.
* Returns the WiFi channel in which the network we're connected to is transmitting.
* Inputs:
@@ -600,7 +675,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00130040, nullptr, "Unbind"},
{0x001400C0, nullptr, "PullPacket"},
{0x00150080, nullptr, "SetMaxSendDelay"},
- {0x00170182, nullptr, "SendTo"},
+ {0x00170182, SendTo, "SendTo"},
{0x001A0000, GetChannel, "GetChannel"},
{0x001B0302, InitializeWithVersion, "InitializeWithVersion"},
{0x001D0044, BeginHostingNetwork, "BeginHostingNetwork"},
diff --git a/src/core/hle/service/nwm/uds_data.cpp b/src/core/hle/service/nwm/uds_data.cpp
new file mode 100644
index 000000000..8c6742dba
--- /dev/null
+++ b/src/core/hle/service/nwm/uds_data.cpp
@@ -0,0 +1,278 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include <cryptopp/aes.h>
+#include <cryptopp/ccm.h>
+#include <cryptopp/filters.h>
+#include <cryptopp/md5.h>
+#include <cryptopp/modes.h>
+#include "core/hle/service/nwm/nwm_uds.h"
+#include "core/hle/service/nwm/uds_data.h"
+#include "core/hw/aes/key.h"
+
+namespace Service {
+namespace NWM {
+
+using MacAddress = std::array<u8, 6>;
+
+/*
+ * Generates a SNAP-enabled 802.2 LLC header for the specified protocol.
+ * @returns a buffer with the bytes of the generated header.
+ */
+static std::vector<u8> GenerateLLCHeader(EtherType protocol) {
+ LLCHeader header{};
+ header.protocol = static_cast<u16>(protocol);
+
+ std::vector<u8> buffer(sizeof(header));
+ memcpy(buffer.data(), &header, sizeof(header));
+
+ return buffer;
+}
+
+/*
+ * Generates a Nintendo UDS SecureData header with the specified parameters.
+ * @returns a buffer with the bytes of the generated header.
+ */
+static std::vector<u8> GenerateSecureDataHeader(u16 data_size, u8 channel, u16 dest_node_id,
+ u16 src_node_id, u16 sequence_number) {
+ SecureDataHeader header{};
+ header.protocol_size = data_size + sizeof(SecureDataHeader);
+ // Note: This size includes everything except the first 4 bytes of the structure,
+ // reinforcing the hypotheses that the first 4 bytes are actually the header of
+ // another container protocol.
+ header.securedata_size = data_size + sizeof(SecureDataHeader) - 4;
+ // Frames sent by the emulated application are never UDS management frames
+ header.is_management = 0;
+ header.data_channel = channel;
+ header.sequence_number = sequence_number;
+ header.dest_node_id = dest_node_id;
+ header.src_node_id = src_node_id;
+
+ std::vector<u8> buffer(sizeof(header));
+ memcpy(buffer.data(), &header, sizeof(header));
+
+ return buffer;
+}
+
+/*
+ * Calculates the CTR used for the AES-CTR process that calculates
+ * the CCMP crypto key for data frames.
+ * @returns The CTR used for data frames crypto key generation.
+ */
+static std::array<u8, CryptoPP::MD5::DIGESTSIZE> GetDataCryptoCTR(const NetworkInfo& network_info) {
+ DataFrameCryptoCTR data{};
+
+ data.host_mac = network_info.host_mac_address;
+ data.wlan_comm_id = network_info.wlan_comm_id;
+ data.id = network_info.id;
+ data.network_id = network_info.network_id;
+
+ std::array<u8, CryptoPP::MD5::DIGESTSIZE> hash;
+ CryptoPP::MD5().CalculateDigest(hash.data(), reinterpret_cast<u8*>(&data), sizeof(data));
+
+ return hash;
+}
+
+/*
+ * Generates the key used for encrypting the 802.11 data frames generated by UDS.
+ * @returns The key used for data frames crypto.
+ */
+static std::array<u8, CryptoPP::AES::BLOCKSIZE> GenerateDataCCMPKey(
+ const std::vector<u8>& passphrase, const NetworkInfo& network_info) {
+ // Calculate the MD5 hash of the input passphrase.
+ std::array<u8, CryptoPP::MD5::DIGESTSIZE> passphrase_hash;
+ CryptoPP::MD5().CalculateDigest(passphrase_hash.data(), passphrase.data(), passphrase.size());
+
+ std::array<u8, CryptoPP::AES::BLOCKSIZE> ccmp_key;
+
+ // The CCMP key is the result of encrypting the MD5 hash of the passphrase with AES-CTR using
+ // keyslot 0x2D.
+ using CryptoPP::AES;
+ std::array<u8, CryptoPP::MD5::DIGESTSIZE> counter = GetDataCryptoCTR(network_info);
+ std::array<u8, AES::BLOCKSIZE> key = HW::AES::GetNormalKey(HW::AES::KeySlotID::UDSDataKey);
+ CryptoPP::CTR_Mode<AES>::Encryption aes;
+ aes.SetKeyWithIV(key.data(), AES::BLOCKSIZE, counter.data());
+ aes.ProcessData(ccmp_key.data(), passphrase_hash.data(), passphrase_hash.size());
+
+ return ccmp_key;
+}
+
+/*
+ * Generates the Additional Authenticated Data (AAD) for an UDS 802.11 encrypted data frame.
+ * @returns a buffer with the bytes of the AAD.
+ */
+static std::vector<u8> GenerateCCMPAAD(const MacAddress& sender, const MacAddress& receiver,
+ const MacAddress& bssid, u16 frame_control) {
+ // Reference: IEEE 802.11-2007
+
+ // 8.3.3.3.2 Construct AAD (22-30 bytes)
+ // The AAD is constructed from the MPDU header. The AAD does not include the header Duration
+ // field, because the Duration field value can change due to normal IEEE 802.11 operation (e.g.,
+ // a rate change during retransmission). For similar reasons, several subfields in the Frame
+ // Control field are masked to 0.
+ struct {
+ u16_be FC; // MPDU Frame Control field
+ MacAddress A1;
+ MacAddress A2;
+ MacAddress A3;
+ u16_be SC; // MPDU Sequence Control field
+ } aad_struct{};
+
+ constexpr u16 AADFrameControlMask = 0x8FC7;
+ aad_struct.FC = frame_control & AADFrameControlMask;
+ aad_struct.SC = 0;
+
+ bool to_ds = (frame_control & (1 << 0)) != 0;
+ bool from_ds = (frame_control & (1 << 1)) != 0;
+ // In the 802.11 standard, ToDS = 1 and FromDS = 1 is a valid configuration,
+ // however, the 3DS doesn't seem to transmit frames with such combination.
+ ASSERT_MSG(to_ds != from_ds, "Invalid combination");
+
+ // The meaning of the address fields depends on the ToDS and FromDS fields.
+ if (from_ds) {
+ aad_struct.A1 = receiver;
+ aad_struct.A2 = bssid;
+ aad_struct.A3 = sender;
+ }
+
+ if (to_ds) {
+ aad_struct.A1 = bssid;
+ aad_struct.A2 = sender;
+ aad_struct.A3 = receiver;
+ }
+
+ std::vector<u8> aad(sizeof(aad_struct));
+ std::memcpy(aad.data(), &aad_struct, sizeof(aad_struct));
+
+ return aad;
+}
+
+/*
+ * Decrypts the payload of an encrypted 802.11 data frame using the specified key.
+ * @returns The decrypted payload.
+ */
+static std::vector<u8> DecryptDataFrame(const std::vector<u8>& encrypted_payload,
+ const std::array<u8, CryptoPP::AES::BLOCKSIZE>& ccmp_key,
+ const MacAddress& sender, const MacAddress& receiver,
+ const MacAddress& bssid, u16 sequence_number,
+ u16 frame_control) {
+
+ // Reference: IEEE 802.11-2007
+
+ std::vector<u8> aad = GenerateCCMPAAD(sender, receiver, bssid, frame_control);
+
+ std::vector<u8> packet_number{0,
+ 0,
+ 0,
+ 0,
+ static_cast<u8>((sequence_number >> 8) & 0xFF),
+ static_cast<u8>(sequence_number & 0xFF)};
+
+ // 8.3.3.3.3 Construct CCM nonce (13 bytes)
+ std::vector<u8> nonce;
+ nonce.push_back(0); // priority
+ nonce.insert(nonce.end(), sender.begin(), sender.end()); // Address 2
+ nonce.insert(nonce.end(), packet_number.begin(), packet_number.end()); // PN
+
+ try {
+ CryptoPP::CCM<CryptoPP::AES, 8>::Decryption d;
+ d.SetKeyWithIV(ccmp_key.data(), ccmp_key.size(), nonce.data(), nonce.size());
+ d.SpecifyDataLengths(aad.size(), encrypted_payload.size() - 8, 0);
+
+ CryptoPP::AuthenticatedDecryptionFilter df(
+ d, nullptr, CryptoPP::AuthenticatedDecryptionFilter::MAC_AT_END |
+ CryptoPP::AuthenticatedDecryptionFilter::THROW_EXCEPTION);
+ // put aad
+ df.ChannelPut(CryptoPP::AAD_CHANNEL, aad.data(), aad.size());
+
+ // put cipher with mac
+ df.ChannelPut(CryptoPP::DEFAULT_CHANNEL, encrypted_payload.data(),
+ encrypted_payload.size() - 8);
+ df.ChannelPut(CryptoPP::DEFAULT_CHANNEL,
+ encrypted_payload.data() + encrypted_payload.size() - 8, 8);
+
+ df.ChannelMessageEnd(CryptoPP::AAD_CHANNEL);
+ df.ChannelMessageEnd(CryptoPP::DEFAULT_CHANNEL);
+ df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL);
+
+ int size = df.MaxRetrievable();
+
+ std::vector<u8> pdata(size);
+ df.Get(pdata.data(), size);
+ return pdata;
+ } catch (CryptoPP::Exception&) {
+ LOG_ERROR(Service_NWM, "failed to decrypt");
+ }
+
+ return {};
+}
+
+/*
+ * Encrypts the payload of an 802.11 data frame using the specified key.
+ * @returns The encrypted payload.
+ */
+static std::vector<u8> EncryptDataFrame(const std::vector<u8>& payload,
+ const std::array<u8, CryptoPP::AES::BLOCKSIZE>& ccmp_key,
+ const MacAddress& sender, const MacAddress& receiver,
+ const MacAddress& bssid, u16 sequence_number,
+ u16 frame_control) {
+ // Reference: IEEE 802.11-2007
+
+ std::vector<u8> aad = GenerateCCMPAAD(sender, receiver, bssid, frame_control);
+
+ std::vector<u8> packet_number{0,
+ 0,
+ 0,
+ 0,
+ static_cast<u8>((sequence_number >> 8) & 0xFF),
+ static_cast<u8>(sequence_number & 0xFF)};
+
+ // 8.3.3.3.3 Construct CCM nonce (13 bytes)
+ std::vector<u8> nonce;
+ nonce.push_back(0); // priority
+ nonce.insert(nonce.end(), sender.begin(), sender.end()); // Address 2
+ nonce.insert(nonce.end(), packet_number.begin(), packet_number.end()); // PN
+
+ try {
+ CryptoPP::CCM<CryptoPP::AES, 8>::Encryption d;
+ d.SetKeyWithIV(ccmp_key.data(), ccmp_key.size(), nonce.data(), nonce.size());
+ d.SpecifyDataLengths(aad.size(), payload.size(), 0);
+
+ CryptoPP::AuthenticatedEncryptionFilter df(d);
+ // put aad
+ df.ChannelPut(CryptoPP::AAD_CHANNEL, aad.data(), aad.size());
+ df.ChannelMessageEnd(CryptoPP::AAD_CHANNEL);
+
+ // put plaintext
+ df.ChannelPut(CryptoPP::DEFAULT_CHANNEL, payload.data(), payload.size());
+ df.ChannelMessageEnd(CryptoPP::DEFAULT_CHANNEL);
+
+ df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL);
+
+ int size = df.MaxRetrievable();
+
+ std::vector<u8> cipher(size);
+ df.Get(cipher.data(), size);
+ return cipher;
+ } catch (CryptoPP::Exception&) {
+ LOG_ERROR(Service_NWM, "failed to encrypt");
+ }
+
+ return {};
+}
+
+std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 dest_node,
+ u16 src_node, u16 sequence_number) {
+ std::vector<u8> buffer = GenerateLLCHeader(EtherType::SecureData);
+ std::vector<u8> securedata_header =
+ GenerateSecureDataHeader(data.size(), channel, dest_node, src_node, sequence_number);
+
+ buffer.insert(buffer.end(), securedata_header.begin(), securedata_header.end());
+ buffer.insert(buffer.end(), data.begin(), data.end());
+ return buffer;
+}
+
+} // namespace NWM
+} // namespace Service
diff --git a/src/core/hle/service/nwm/uds_data.h b/src/core/hle/service/nwm/uds_data.h
new file mode 100644
index 000000000..a23520a41
--- /dev/null
+++ b/src/core/hle/service/nwm/uds_data.h
@@ -0,0 +1,78 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <vector>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace NWM {
+
+enum class SAP : u8 { SNAPExtensionUsed = 0xAA };
+
+enum class PDUControl : u8 { UnnumberedInformation = 3 };
+
+enum class EtherType : u16 { SecureData = 0x876D, EAPoL = 0x888E };
+
+/*
+ * 802.2 header, UDS packets always use SNAP for these headers,
+ * which means the dsap and ssap are always SNAPExtensionUsed (0xAA)
+ * and the OUI is always 0.
+ */
+struct LLCHeader {
+ u8 dsap = static_cast<u8>(SAP::SNAPExtensionUsed);
+ u8 ssap = static_cast<u8>(SAP::SNAPExtensionUsed);
+ u8 control = static_cast<u8>(PDUControl::UnnumberedInformation);
+ std::array<u8, 3> OUI = {};
+ u16_be protocol;
+};
+
+static_assert(sizeof(LLCHeader) == 8, "LLCHeader has the wrong size");
+
+/*
+ * Nintendo SecureData header, every UDS packet contains one,
+ * it is used to store metadata about the transmission such as
+ * the source and destination network node ids.
+ */
+struct SecureDataHeader {
+ // TODO(Subv): It is likely that the first 4 bytes of this header are
+ // actually part of another container protocol.
+ u16_be protocol_size;
+ INSERT_PADDING_BYTES(2);
+ u16_be securedata_size;
+ u8 is_management;
+ u8 data_channel;
+ u16_be sequence_number;
+ u16_be dest_node_id;
+ u16_be src_node_id;
+};
+
+static_assert(sizeof(SecureDataHeader) == 14, "SecureDataHeader has the wrong size");
+
+/*
+ * The raw bytes of this structure are the CTR used in the encryption (AES-CTR)
+ * process used to generate the CCMP key for data frame encryption.
+ */
+struct DataFrameCryptoCTR {
+ u32_le wlan_comm_id;
+ u32_le network_id;
+ std::array<u8, 6> host_mac;
+ u16_le id;
+};
+
+static_assert(sizeof(DataFrameCryptoCTR) == 16, "DataFrameCryptoCTR has the wrong size");
+
+/**
+ * Generates an unencrypted 802.11 data payload.
+ * @returns The generated frame payload.
+ */
+std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 dest_node,
+ u16 src_node, u16 sequence_number);
+
+} // namespace NWM
+} // namespace Service