summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
authorSketchMaster2001 <batmanpistilli2006@gmail.com>2022-09-18 23:09:20 -0400
committerAdmiral H. Curtiss <pikachu025@gmail.com>2022-10-16 04:19:36 +0200
commite413d7f5ece44fd22b9deb7a192cdfcfdef8a0dd (patch)
treefe2f183fb1ecfe1be22d9f741b08837f3db38281 /Source
parent487a11fd2c43bcdd6c24c4da2009a750a32fdf59 (diff)
Add initial WiiConnect24 support
Diffstat (limited to 'Source')
-rw-r--r--Source/Core/Common/Crypto/AES.cpp17
-rw-r--r--Source/Core/Common/Crypto/AES.h3
-rw-r--r--Source/Core/Core/CMakeLists.txt6
-rw-r--r--Source/Core/Core/IOS/Network/KD/NWC24Config.h8
-rw-r--r--Source/Core/Core/IOS/Network/KD/NWC24DL.cpp143
-rw-r--r--Source/Core/Core/IOS/Network/KD/NWC24DL.h123
-rw-r--r--Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp141
-rw-r--r--Source/Core/Core/IOS/Network/KD/NetKDRequest.h36
-rw-r--r--Source/Core/Core/IOS/Network/KD/VFF/VFFUtil.cpp308
-rw-r--r--Source/Core/Core/IOS/Network/KD/VFF/VFFUtil.h43
-rw-r--r--Source/Core/Core/IOS/Network/KD/WC24File.h31
-rw-r--r--Source/Core/DolphinLib.props5
12 files changed, 863 insertions, 1 deletions
diff --git a/Source/Core/Common/Crypto/AES.cpp b/Source/Core/Common/Crypto/AES.cpp
index fe8f4ec728..82f7d1a9c2 100644
--- a/Source/Core/Common/Crypto/AES.cpp
+++ b/Source/Core/Common/Crypto/AES.cpp
@@ -408,4 +408,21 @@ std::unique_ptr<Context> CreateContextDecrypt(const u8* key)
return CreateContext<Mode::Decrypt>(key);
}
+// OFB encryption and decryption are the exact same. We don't encrypt though.
+void CryptOFB(const u8* key, const u8* iv, u8* iv_out, const u8* buf_in, u8* buf_out, size_t size)
+{
+ mbedtls_aes_context aes_ctx;
+ size_t iv_offset = 0;
+
+ std::array<u8, 16> iv_tmp{};
+ if (iv)
+ std::memcpy(&iv_tmp[0], iv, 16);
+
+ ASSERT(!mbedtls_aes_setkey_enc(&aes_ctx, key, 128));
+ mbedtls_aes_crypt_ofb(&aes_ctx, size, &iv_offset, &iv_tmp[0], buf_in, buf_out);
+
+ if (iv_out)
+ std::memcpy(iv_out, &iv_tmp[0], 16);
+}
+
} // namespace Common::AES
diff --git a/Source/Core/Common/Crypto/AES.h b/Source/Core/Common/Crypto/AES.h
index 338b71bc85..a2f9f5e393 100644
--- a/Source/Core/Common/Crypto/AES.h
+++ b/Source/Core/Common/Crypto/AES.h
@@ -46,4 +46,7 @@ public:
std::unique_ptr<Context> CreateContextEncrypt(const u8* key);
std::unique_ptr<Context> CreateContextDecrypt(const u8* key);
+// OFB decryption for WiiConnect24
+void CryptOFB(const u8* key, const u8* iv, u8* iv_out, const u8* buf_in, u8* buf_out, size_t size);
+
} // namespace Common::AES
diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt
index fe7b849782..03521804fd 100644
--- a/Source/Core/Core/CMakeLists.txt
+++ b/Source/Core/Core/CMakeLists.txt
@@ -360,6 +360,11 @@ add_library(core
IOS/Network/KD/NetKDTime.h
IOS/Network/KD/NWC24Config.cpp
IOS/Network/KD/NWC24Config.h
+ IOS/Network/KD/NWC24DL.cpp
+ IOS/Network/KD/NWC24DL.h
+ IOS/Network/KD/VFF/VFFUtil.cpp
+ IOS/Network/KD/VFF/VFFUtil.h
+ IOS/Network/KD/WC24File.h
IOS/Network/MACUtils.cpp
IOS/Network/MACUtils.h
IOS/Network/NCD/Manage.cpp
@@ -595,6 +600,7 @@ PUBLIC
videosoftware
PRIVATE
+ FatFs
fmt::fmt
${LZO}
ZLIB::ZLIB
diff --git a/Source/Core/Core/IOS/Network/KD/NWC24Config.h b/Source/Core/Core/IOS/Network/KD/NWC24Config.h
index 30a814cc19..c5e0c3a17e 100644
--- a/Source/Core/Core/IOS/Network/KD/NWC24Config.h
+++ b/Source/Core/Core/IOS/Network/KD/NWC24Config.h
@@ -19,6 +19,14 @@ enum ErrorCode : s32
{
WC24_OK = 0,
WC24_ERR_FATAL = -1,
+ WC24_ERR_NOT_FOUND = -13,
+ WC24_ERR_BROKEN = -14,
+ WC24_ERR_FILE_OPEN = -16,
+ WC24_ERR_FILE_CLOSE = -17,
+ WC24_ERR_FILE_READ = -18,
+ WC24_ERR_FILE_WRITE = -19,
+ WC24_ERR_NETWORK = -31,
+ WC24_ERR_SERVER = -32,
WC24_ERR_ID_NONEXISTANCE = -34,
WC24_ERR_ID_GENERATED = -35,
WC24_ERR_ID_REGISTERED = -36,
diff --git a/Source/Core/Core/IOS/Network/KD/NWC24DL.cpp b/Source/Core/Core/IOS/Network/KD/NWC24DL.cpp
new file mode 100644
index 0000000000..99073e9b72
--- /dev/null
+++ b/Source/Core/Core/IOS/Network/KD/NWC24DL.cpp
@@ -0,0 +1,143 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "Core/IOS/Network/KD/NWC24DL.h"
+
+#include "Common/BitUtils.h"
+#include "Common/CommonPaths.h"
+#include "Common/CommonTypes.h"
+#include "Common/Logging/Log.h"
+#include "Common/Swap.h"
+#include "Core/IOS/FS/FileSystem.h"
+#include "Core/IOS/Uids.h"
+
+namespace IOS::HLE::NWC24
+{
+constexpr const char DL_LIST_PATH[] = "/" WII_WC24CONF_DIR "/nwc24dl.bin";
+
+NWC24Dl::NWC24Dl(std::shared_ptr<FS::FileSystem> fs) : m_fs{std::move(fs)}
+{
+ ReadDlList();
+}
+
+void NWC24Dl::ReadDlList()
+{
+ const auto file = m_fs->OpenFile(PID_KD, PID_KD, DL_LIST_PATH, FS::Mode::Read);
+ if (!file || !file->Read(&m_data, 1))
+ return;
+
+ const s32 file_error = CheckNwc24DlList();
+ if (file_error)
+ ERROR_LOG_FMT(IOS_WC24, "There is an error in the DL list for WC24: {}", file_error);
+}
+
+s32 NWC24Dl::CheckNwc24DlList() const
+{
+ // 'WcDl' magic
+ if (Magic() != DL_LIST_MAGIC)
+ {
+ ERROR_LOG_FMT(IOS_WC24, "DL list magic mismatch");
+ return -1;
+ }
+
+ if (Version() != 1)
+ {
+ ERROR_LOG_FMT(IOS_WC24, "DL list version mismatch");
+ return -1;
+ }
+
+ return 0;
+}
+
+void NWC24Dl::WriteDlList() const
+{
+ constexpr FS::Modes public_modes{FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite};
+ m_fs->CreateFullPath(PID_KD, PID_KD, DL_LIST_PATH, 0, public_modes);
+ const auto file = m_fs->CreateAndOpenFile(PID_KD, PID_KD, DL_LIST_PATH, public_modes);
+
+ if (!file || !file->Write(&m_data, 1))
+ ERROR_LOG_FMT(IOS_WC24, "Failed to open or write WC24 DL list file");
+}
+
+bool NWC24Dl::DoesEntryExist(u16 entry_index)
+{
+ return m_data.entries[entry_index].low_title_id != 0;
+}
+
+std::string NWC24Dl::GetDownloadURL(u16 entry_index, std::optional<u8> subtask_id) const
+{
+ std::string url(m_data.entries[entry_index].dl_url);
+
+ // Determine if we need to append the subtask to the URL.
+ if (subtask_id &&
+ Common::ExtractBit(Common::swap32(m_data.entries[entry_index].subtask_bitmask), 1))
+ {
+ url.append(fmt::format(".{:02d}", *subtask_id));
+ }
+
+ return url;
+}
+
+std::string NWC24Dl::GetVFFContentName(u16 entry_index, std::optional<u8> subtask_id) const
+{
+ std::string content(m_data.entries[entry_index].filename);
+
+ // Determine if we need to append the subtask to the name.
+ if (subtask_id &&
+ Common::ExtractBit(Common::swap32(m_data.entries[entry_index].subtask_bitmask), 0))
+ {
+ content.append(fmt::format(".{:02d}", *subtask_id));
+ }
+
+ return content;
+}
+
+std::string NWC24Dl::GetVFFPath(u16 entry_index) const
+{
+ const u32 lower_title_id = Common::swap32(m_data.entries[entry_index].low_title_id);
+ const u32 high_title_id = Common::swap32(m_data.entries[entry_index].high_title_id);
+
+ return fmt::format("/title/{0:08x}/{1:08x}/data/wc24dl.vff", lower_title_id, high_title_id);
+}
+
+WC24PubkMod NWC24Dl::GetWC24PubkMod(u16 entry_index) const
+{
+ WC24PubkMod pubkMod;
+ const u32 lower_title_id = Common::swap32(m_data.entries[entry_index].low_title_id);
+ const u32 high_title_id = Common::swap32(m_data.entries[entry_index].high_title_id);
+
+ const std::string path =
+ fmt::format("/title/{0:08x}/{1:08x}/data/wc24pubk.mod", lower_title_id, high_title_id);
+
+ const auto file = m_fs->OpenFile(PID_KD, PID_KD, path, IOS::HLE::FS::Mode::Read);
+ file->Read(&pubkMod, 1);
+
+ return pubkMod;
+}
+
+bool NWC24Dl::IsEncrypted(u16 entry_index) const
+{
+ return !!Common::ExtractBit(Common::swap32(m_data.entries[entry_index].flags), 3);
+}
+
+u32 NWC24Dl::Magic() const
+{
+ return Common::swap32(m_data.header.magic);
+}
+
+void NWC24Dl::SetMagic(u32 magic)
+{
+ m_data.header.magic = Common::swap32(magic);
+}
+
+u32 NWC24Dl::Version() const
+{
+ return Common::swap32(m_data.header.version);
+}
+
+void NWC24Dl::SetVersion(u32 version)
+{
+ m_data.header.version = Common::swap32(version);
+}
+
+} // namespace IOS::HLE::NWC24
diff --git a/Source/Core/Core/IOS/Network/KD/NWC24DL.h b/Source/Core/Core/IOS/Network/KD/NWC24DL.h
new file mode 100644
index 0000000000..1704d15223
--- /dev/null
+++ b/Source/Core/Core/IOS/Network/KD/NWC24DL.h
@@ -0,0 +1,123 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <optional>
+#include <string>
+#include "Common/CommonTypes.h"
+#include "Core/IOS/Network/KD/WC24File.h"
+
+namespace IOS::HLE
+{
+namespace FS
+{
+class FileSystem;
+}
+namespace NWC24
+{
+class NWC24Dl final
+{
+public:
+ explicit NWC24Dl(std::shared_ptr<FS::FileSystem> fs);
+
+ void ReadDlList();
+ void WriteDlList() const;
+
+ s32 CheckNwc24DlList() const;
+
+ bool DoesEntryExist(u16 entry_index);
+ bool IsEncrypted(u16 entry_index) const;
+ std::string GetVFFContentName(u16 entry_index, std::optional<u8> subtask_id) const;
+ std::string GetDownloadURL(u16 entry_index, std::optional<u8> subtask_id) const;
+ std::string GetVFFPath(u16 entry_index) const;
+ WC24PubkMod GetWC24PubkMod(u16 entry_index) const;
+
+ u32 Magic() const;
+ void SetMagic(u32 magic);
+
+ u32 Version() const;
+ void SetVersion(u32 version);
+
+ static constexpr u32 MAX_ENTRIES = 120;
+
+private:
+ static constexpr u32 DL_LIST_MAGIC = 0x5763446C; // WcDl
+ static constexpr u32 MAX_SUBENTRIES = 32;
+
+ enum EntryType : u8
+ {
+ UNK = 1,
+ MAIL,
+ CHANNEL_CONTENT,
+ UNUSED = 0xff
+ };
+
+#pragma pack(push, 1)
+ // The following format is partially based off of https://wiibrew.org/wiki//dev/net/kd/request.
+ struct DLListHeader final
+ {
+ u32 magic; // 'WcDl' 0x5763446c
+ u32 version; // must be 1
+ u32 unk1;
+ u32 unk2;
+ u16 max_subentries; // Asserted to be less than max_entries
+ u16 reserved_mailnum;
+ u16 max_entries;
+ u8 reserved[106];
+ };
+
+ struct DLListRecord final
+ {
+ u32 low_title_id;
+ u32 next_dl_timestamp;
+ u32 last_modified_timestamp;
+ u8 flags;
+ u8 padding[3];
+ };
+
+ struct DLListEntry final
+ {
+ u16 index;
+ EntryType type;
+ u8 record_flags;
+ u32 flags;
+ u32 high_title_id;
+ u32 low_title_id;
+ u32 unknown1;
+ u16 group_id;
+ u16 padding1;
+ u16 remaining_downloads;
+ u16 error_count;
+ u16 dl_frequency;
+ u16 dl_frequency_when_err;
+ s32 error_code;
+ u8 subtask_id;
+ u8 subtask_type;
+ u8 subtask_flags;
+ u8 padding2;
+ u32 subtask_bitmask;
+ s32 unknown2;
+ u32 dl_timestamp; // Last DL time
+ u32 subtask_timestamps[32];
+ char dl_url[236];
+ char filename[64];
+ u8 unk6[29];
+ u8 should_use_rootca;
+ u16 unknown3;
+ };
+
+ struct DLList final
+ {
+ DLListHeader header;
+ DLListRecord records[MAX_ENTRIES];
+ DLListEntry entries[MAX_ENTRIES];
+ };
+#pragma pack(pop)
+
+ std::shared_ptr<FS::FileSystem> m_fs;
+ DLList m_data;
+};
+} // namespace NWC24
+} // namespace IOS::HLE
diff --git a/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp b/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp
index d5207141f5..083bd3f757 100644
--- a/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp
+++ b/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp
@@ -9,6 +9,7 @@
#include <string_view>
#include <utility>
+#include "Common/BitUtils.h"
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
@@ -19,6 +20,7 @@
#include "Core/CommonTitles.h"
#include "Core/HW/Memmap.h"
#include "Core/IOS/FS/FileSystem.h"
+#include "Core/IOS/Network/KD/VFF/VFFUtil.h"
#include "Core/IOS/Network/Socket.h"
#include "Core/IOS/Uids.h"
@@ -145,8 +147,15 @@ s32 NWC24MakeUserID(u64* nwc24_id, u32 hollywood_id, u16 id_ctr, HardwareModel h
} // Anonymous namespace
NetKDRequestDevice::NetKDRequestDevice(Kernel& ios, const std::string& device_name)
- : Device(ios, device_name), config{ios.GetFS()}
+ : Device(ios, device_name), config{ios.GetFS()}, m_dl_list{ios.GetFS()}
{
+ m_work_queue.Reset([this](AsyncTask task) {
+ const IPCReply reply = task.handler();
+ {
+ std::lock_guard lg(m_async_reply_lock);
+ m_async_replies.emplace(AsyncReply{task.request, reply.return_value});
+ }
+ });
}
NetKDRequestDevice::~NetKDRequestDevice()
@@ -154,6 +163,133 @@ NetKDRequestDevice::~NetKDRequestDevice()
WiiSockMan::GetInstance().Clean();
}
+void NetKDRequestDevice::Update()
+{
+ {
+ std::lock_guard lg(m_async_reply_lock);
+ while (!m_async_replies.empty())
+ {
+ const auto& reply = m_async_replies.front();
+ GetIOS()->EnqueueIPCReply(reply.request, reply.return_value);
+ m_async_replies.pop();
+ }
+ }
+}
+
+NWC24::ErrorCode NetKDRequestDevice::KDDownload(const u16 entry_index,
+ const std::optional<u8> subtask_id)
+{
+ std::vector<u8> file_data;
+
+ // Content metadata
+ const std::string content_name = m_dl_list.GetVFFContentName(entry_index, subtask_id);
+ const std::string url = m_dl_list.GetDownloadURL(entry_index, subtask_id);
+
+ INFO_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_DOWNLOAD_NOW_EX - NI - URL: {}", url);
+
+ INFO_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_DOWNLOAD_NOW_EX - NI - Name: {}", content_name);
+
+ const Common::HttpRequest::Response response = m_http.Get(url);
+
+ if (!response)
+ {
+ ERROR_LOG_FMT(IOS_WC24, "Failed to request data at {}", url);
+ return NWC24::WC24_ERR_SERVER;
+ }
+
+ // Check if the filesize is smaller than the header size.
+ if (response->size() < sizeof(NWC24::WC24File))
+ {
+ ERROR_LOG_FMT(IOS_WC24, "File at {} is too small to be a valid file.", url);
+ return NWC24::WC24_ERR_BROKEN;
+ }
+
+ // Now we read the file
+ NWC24::WC24File wc24File;
+ std::memcpy(&wc24File, response->data(), sizeof(NWC24::WC24File));
+
+ std::vector<u8> temp_buffer(response->begin() + 320, response->end());
+
+ if (m_dl_list.IsEncrypted(entry_index))
+ {
+ NWC24::WC24PubkMod pubkMod = m_dl_list.GetWC24PubkMod(entry_index);
+
+ file_data = std::vector<u8>(response->size() - 320);
+
+ Common::AES::CryptOFB(pubkMod.aes_key, wc24File.iv, wc24File.iv, temp_buffer.data(),
+ file_data.data(), temp_buffer.size());
+ }
+ else
+ {
+ file_data = std::move(temp_buffer);
+ }
+
+ NWC24::ErrorCode reply = IOS::HLE::NWC24::OpenVFF(m_dl_list.GetVFFPath(entry_index), content_name,
+ m_ios.GetFS(), file_data);
+
+ return reply;
+}
+
+IPCReply NetKDRequestDevice::HandleNWC24DownloadNowEx(const IOCtlRequest& request)
+{
+ const u32 flags = Memory::Read_U32(request.buffer_in);
+ // Nintendo converts the entry ID between a u32 and u16
+ // several times, presumably for alignment purposes.
+ // We'll skip past buffer_in+4 and keep the entry index as a u16.
+ const u16 entry_index = Memory::Read_U16(request.buffer_in + 6);
+ const u32 subtask_bitmask = Memory::Read_U32(request.buffer_in + 8);
+
+ INFO_LOG_FMT(IOS_WC24,
+ "NET_KD_REQ: IOCTL_NWC24_DOWNLOAD_NOW_EX - NI - flags: {}, index: {}, bitmask: {}",
+ flags, entry_index, subtask_bitmask);
+
+ if (entry_index >= NWC24::NWC24Dl::MAX_ENTRIES)
+ {
+ ERROR_LOG_FMT(IOS_WC24, "NET_KD_REQ: Entry index out of range.");
+ WriteReturnValue(NWC24::WC24_ERR_BROKEN, request.buffer_out);
+ return IPCReply(NWC24::WC24_ERR_BROKEN);
+ }
+
+ if (!m_dl_list.DoesEntryExist(entry_index))
+ {
+ ERROR_LOG_FMT(IOS_WC24, "NET_KD_REQ: Requested entry does not exist in download list!");
+ WriteReturnValue(NWC24::WC24_ERR_NOT_FOUND, request.buffer_out);
+ return IPCReply(NWC24::WC24_ERR_NOT_FOUND);
+ }
+
+ // While in theory reply will always get initialized by KDDownload, things happen.
+ // Returning NWC24::WC24_ERR_BROKEN or anything that isn't OK will prompt the channel to fix the
+ // entry's data.
+ NWC24::ErrorCode reply = NWC24::WC24_ERR_BROKEN;
+
+ // Determine if we have subtasks to handle
+ if (Common::ExtractBit(flags, 2))
+ {
+ for (u8 subtask_id = 0; subtask_id < 32; subtask_id++)
+ {
+ // Check if we are done
+ if (!Common::ExtractBit(subtask_bitmask, subtask_id))
+ {
+ break;
+ }
+
+ reply = KDDownload(entry_index, subtask_id);
+ if (reply != NWC24::WC24_OK)
+ {
+ // An error has occurred, break out and return error.
+ break;
+ }
+ }
+ }
+ else
+ {
+ reply = KDDownload(entry_index, std::nullopt);
+ }
+
+ WriteReturnValue(reply, request.buffer_out);
+ return IPCReply(reply);
+}
+
std::optional<IPCReply> NetKDRequestDevice::IOCtl(const IOCtlRequest& request)
{
enum : u32
@@ -288,6 +424,9 @@ std::optional<IPCReply> NetKDRequestDevice::IOCtl(const IOCtlRequest& request)
INFO_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_SAVE_MAIL_NOW - NI");
break;
+ case IOCTL_NWC24_DOWNLOAD_NOW_EX:
+ return LaunchAsyncTask(&NetKDRequestDevice::HandleNWC24DownloadNowEx, request);
+
case IOCTL_NWC24_REQUEST_SHUTDOWN:
{
if (request.buffer_in == 0 || request.buffer_in % 4 != 0 || request.buffer_in_size < 8 ||
diff --git a/Source/Core/Core/IOS/Network/KD/NetKDRequest.h b/Source/Core/Core/IOS/Network/KD/NetKDRequest.h
index 9172a490c3..aae52a11f5 100644
--- a/Source/Core/Core/IOS/Network/KD/NetKDRequest.h
+++ b/Source/Core/Core/IOS/Network/KD/NetKDRequest.h
@@ -3,13 +3,21 @@
#pragma once
+#include <queue>
#include <string>
+#include "Common/CommonPaths.h"
+#include "Common/CommonTypes.h"
+#include "Common/HttpRequest.h"
+#include "Common/WorkQueueThread.h"
#include "Core/IOS/Device.h"
#include "Core/IOS/Network/KD/NWC24Config.h"
+#include "Core/IOS/Network/KD/NWC24DL.h"
namespace IOS::HLE
{
+constexpr const char DL_CNT_PATH[] = "/" WII_WC24CONF_DIR "/dlcnt.bin";
+
// KD is the IOS module responsible for implementing WiiConnect24 functionality.
// It can perform HTTPS downloads, send and receive mail via SMTP, and execute a
// JavaScript-like language while the Wii is in standby mode.
@@ -17,11 +25,39 @@ class NetKDRequestDevice : public Device
{
public:
NetKDRequestDevice(Kernel& ios, const std::string& device_name);
+ IPCReply HandleNWC24DownloadNowEx(const IOCtlRequest& request);
+ NWC24::ErrorCode KDDownload(const u16 entry_index, const std::optional<u8> subtask_id);
~NetKDRequestDevice() override;
std::optional<IPCReply> IOCtl(const IOCtlRequest& request) override;
+ void Update() override;
private:
+ struct AsyncTask
+ {
+ IOS::HLE::Request request;
+ std::function<IPCReply()> handler;
+ };
+
+ struct AsyncReply
+ {
+ IOS::HLE::Request request;
+ s32 return_value;
+ };
+
+ template <typename Method, typename Request>
+ std::optional<IPCReply> LaunchAsyncTask(Method method, const Request& request)
+ {
+ m_work_queue.EmplaceItem(AsyncTask{request, std::bind(method, this, request)});
+ return std::nullopt;
+ }
+
NWC24::NWC24Config config;
+ NWC24::NWC24Dl m_dl_list;
+ Common::WorkQueueThread<AsyncTask> m_work_queue;
+ std::mutex m_async_reply_lock;
+ std::queue<AsyncReply> m_async_replies;
+ // TODO: Maybe move away from Common::HttpRequest?
+ Common::HttpRequest m_http{std::chrono::minutes{1}};
};
} // namespace IOS::HLE
diff --git a/Source/Core/Core/IOS/Network/KD/VFF/VFFUtil.cpp b/Source/Core/Core/IOS/Network/KD/VFF/VFFUtil.cpp
new file mode 100644
index 0000000000..ea0e828d29
--- /dev/null
+++ b/Source/Core/Core/IOS/Network/KD/VFF/VFFUtil.cpp
@@ -0,0 +1,308 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "Core/IOS/Network/KD/VFF/VFFUtil.h"
+
+#include <algorithm>
+#include <cmath>
+#include <vector>
+
+#include <fmt/format.h>
+
+// Does not compile if diskio.h is included first.
+// clang-format off
+#include "ff.h"
+#include "diskio.h"
+// clang-format on
+
+#include "Common/Align.h"
+#include "Common/FatFsUtil.h"
+#include "Common/Logging/Log.h"
+#include "Common/ScopeGuard.h"
+#include "Common/Swap.h"
+
+#include "Core/IOS/Uids.h"
+
+static DRESULT read_vff_header(IOS::HLE::FS::FileHandle* vff, FATFS* fs)
+{
+ struct IOS::HLE::NWC24::VFFHeader header;
+ if (!vff->Read(&header, 1))
+ {
+ ERROR_LOG_FMT(IOS_WC24, "Failed to read VFF header.");
+ return RES_ERROR;
+ }
+
+ u16 cluster_size = 0;
+ u16 cluster_count = 0;
+
+ switch (Common::swap16(header.endianness))
+ {
+ case IOS::HLE::NWC24::VF_BIG_ENDIAN:
+ cluster_size = Common::swap16(header.cluster_size) * 16;
+ cluster_count = Common::swap32(header.volume_size) / cluster_size;
+ break;
+ case IOS::HLE::NWC24::VF_LITTLE_ENDIAN:
+ // TODO: Actually implement.
+ // Another option is to just delete these VFFs and let the current channel create a Big Endian
+ // one.
+ return RES_ERROR;
+ }
+
+ if (cluster_count < 4085)
+ {
+ fs->fs_type = FS_FAT12;
+
+ u32 table_size = ((cluster_count + 1) / 2) * 3;
+ table_size = Common::AlignUp(table_size, cluster_size);
+
+ // Fsize is the full table size divided by 512 (Cluster size).
+ fs->fsize = table_size / 512;
+ }
+ else if (cluster_count < 65525)
+ {
+ fs->fs_type = FS_FAT16;
+
+ u32 table_size = cluster_count * 2;
+ table_size = Common::AlignUp(table_size, cluster_size);
+ fs->fsize = table_size / 512;
+ }
+ else
+ {
+ ERROR_LOG_FMT(IOS_WC24, "VFF not FAT12 or 16! Cluster size: {}", cluster_size);
+ return RES_ERROR;
+ }
+
+ fs->n_fats = 2;
+ fs->csize = 1;
+
+ // Root directory entry is 4096 bytes long, with each entry being 32 bytes. 4096 / 32 = 128
+ fs->n_rootdir = 128;
+
+ u32 sysect = 1 + (fs->fsize * 2) + fs->n_rootdir / (512 / 32);
+
+ // cluster_count is the total count whereas this is the actual amount of clusters we can use
+ u32 actual_cluster_count = cluster_count - sysect;
+
+ fs->n_fatent = actual_cluster_count + 2;
+ fs->volbase = 0;
+ fs->fatbase = 1;
+ fs->database = sysect;
+ // Root directory entry
+ fs->dirbase = fs->fatbase + fs->fsize * 2;
+
+ // Initialize cluster allocation information
+ fs->last_clst = fs->free_clst = 0xFFFFFFFF;
+ fs->fsi_flag = 0x80;
+
+ fs->id = 0;
+ fs->cdir = 0;
+
+ return RES_OK;
+}
+
+static FRESULT vff_mount(IOS::HLE::FS::FileHandle* vff, FATFS* fs)
+{
+ fs->fs_type = 0; // Clear the filesystem object
+ fs->pdrv = 0; // Volume hosting physical drive
+
+ DRESULT ret = read_vff_header(vff, fs);
+ if (ret != RES_OK)
+ return FR_DISK_ERR;
+
+ return FR_OK;
+}
+
+static DRESULT vff_read(IOS::HLE::FS::FileHandle* vff, BYTE pdrv, BYTE* buff, LBA_t sector,
+ UINT count)
+{
+ // We cannot read or write data to the 0th sector in a VFF.
+ if (sector == 0)
+ {
+ ERROR_LOG_FMT(IOS_WC24, "Attempted to read the 0th sector in the VFF: Invalid VFF?");
+ return RES_ERROR;
+ }
+
+ const u64 offset = static_cast<u64>(sector) * IOS::HLE::NWC24::SECTOR_SIZE - 480;
+ if (!vff->Seek(offset, IOS::HLE::FS::SeekMode::Set))
+ {
+ ERROR_LOG_FMT(IOS_WC24, "VFF seek failed (offset={})", offset);
+ return RES_ERROR;
+ }
+
+ const size_t size = static_cast<size_t>(count) * IOS::HLE::NWC24::SECTOR_SIZE;
+ if (!vff->Read(buff, size))
+ {
+ ERROR_LOG_FMT(IOS_WC24, "VFF read failed (offset={}, size={})", offset, size);
+ return RES_ERROR;
+ }
+
+ return RES_OK;
+}
+
+static DRESULT vff_write(IOS::HLE::FS::FileHandle* vff, BYTE pdrv, const BYTE* buff, LBA_t sector,
+ UINT count)
+{
+ if (sector == 0)
+ {
+ ERROR_LOG_FMT(IOS_WC24, "Attempted to write to the 0th sector in the VFF: Invalid VFF?");
+ return RES_ERROR;
+ }
+
+ const u64 offset = static_cast<u64>(sector) * IOS::HLE::NWC24::SECTOR_SIZE - 480;
+ if (!vff->Seek(offset, IOS::HLE::FS::SeekMode::Set))
+ {
+ ERROR_LOG_FMT(IOS_WC24, "VFF seek failed (offset={})", offset);
+ return RES_ERROR;
+ }
+
+ const size_t size = static_cast<size_t>(count) * IOS::HLE::NWC24::SECTOR_SIZE;
+ const auto res = vff->Write(buff, size);
+ if (!res)
+ {
+ ERROR_LOG_FMT(IOS_WC24, "VFF write failed (offset={}, size={})", offset, size);
+ return RES_ERROR;
+ }
+
+ return RES_OK;
+}
+
+static DRESULT vff_ioctl(IOS::HLE::FS::FileHandle* vff, BYTE pdrv, BYTE cmd, void* buff)
+{
+ switch (cmd)
+ {
+ case CTRL_SYNC:
+ return RES_OK;
+ case GET_SECTOR_COUNT:
+ *reinterpret_cast<LBA_t*>(buff) = vff->GetStatus()->size / IOS::HLE::NWC24::SECTOR_SIZE;
+ return RES_OK;
+ default:
+ WARN_LOG_FMT(IOS_WC24, "Unexpected FAT ioctl {}", cmd);
+ return RES_OK;
+ }
+}
+
+namespace IOS::HLE::NWC24
+{
+static ErrorCode WriteFile(const std::string& filename, const std::vector<u8>& tmp_buffer)
+{
+ FIL dst;
+ const auto open_error_code = f_open(&dst, filename.c_str(), FA_CREATE_ALWAYS | FA_WRITE);
+ if (open_error_code != FR_OK)
+ {
+ ERROR_LOG_FMT(IOS_WC24, "Failed to open file {} in VFF", filename);
+ return WC24_ERR_FILE_OPEN;
+ }
+
+ size_t size = tmp_buffer.size();
+ size_t offset = 0;
+ while (size > 0)
+ {
+ constexpr size_t MAX_CHUNK_SIZE = 32768;
+ u32 chunk_size = static_cast<u32>(std::min(size, MAX_CHUNK_SIZE));
+
+ u32 written_size;
+ const auto write_error_code =
+ f_write(&dst, tmp_buffer.data() + offset, chunk_size, &written_size);
+ if (write_error_code != FR_OK)
+ {
+ ERROR_LOG_FMT(IOS_WC24, "Failed to write file {} to VFF {}", filename, write_error_code);
+ return WC24_ERR_FILE_WRITE;
+ }
+
+ if (written_size != chunk_size)
+ {
+ ERROR_LOG_FMT(IOS_WC24, "Failed to write bytes of file {} to VFF ({} != {})", filename,
+ written_size, chunk_size);
+ return WC24_ERR_FILE_WRITE;
+ }
+
+ size -= chunk_size;
+ offset += chunk_size;
+ }
+
+ const auto close_error_code = f_close(&dst);
+ if (close_error_code != FR_OK)
+ {
+ ERROR_LOG_FMT(IOS_WC24, "Failed to close file {} in VFF", filename);
+ return WC24_ERR_FILE_CLOSE;
+ }
+
+ return WC24_OK;
+}
+
+namespace
+{
+class VffFatFsCallbacks : public Common::FatFsCallbacks
+{
+public:
+ int DiskRead(u8 pdrv, u8* buff, u32 sector, unsigned int count) override
+ {
+ return vff_read(m_vff, pdrv, buff, sector, count);
+ }
+
+ int DiskWrite(u8 pdrv, const u8* buff, u32 sector, unsigned int count) override
+ {
+ return vff_write(m_vff, pdrv, buff, sector, count);
+ }
+
+ int DiskIOCtl(u8 pdrv, u8 cmd, void* buff) override { return vff_ioctl(m_vff, pdrv, cmd, buff); }
+
+ IOS::HLE::FS::FileHandle* m_vff;
+};
+} // namespace
+
+ErrorCode OpenVFF(const std::string& path, const std::string& filename,
+ const std::shared_ptr<FS::FileSystem>& fs, const std::vector<u8>& data)
+{
+ VffFatFsCallbacks callbacks;
+ ErrorCode return_value;
+ Common::RunInFatFsContext(callbacks, [&]() {
+ auto temp = fs->OpenFile(PID_KD, PID_KD, path, FS::Mode::ReadWrite);
+ if (!temp)
+ {
+ ERROR_LOG_FMT(IOS_WC24, "Failed to open VFF at: {}", path);
+ return_value = WC24_ERR_NOT_FOUND;
+ return;
+ }
+
+ callbacks.m_vff = &*temp;
+
+ Common::ScopeGuard vff_delete_guard{[&] { fs->Delete(PID_KD, PID_KD, path); }};
+
+ FATFS fatfs;
+ const FRESULT fatfs_mount_error_code = f_mount(&fatfs, "", 0);
+ if (fatfs_mount_error_code != FR_OK)
+ {
+ // The VFF is most likely broken.
+ ERROR_LOG_FMT(IOS_WC24, "Failed to mount VFF at: {}", path);
+ return_value = WC24_ERR_BROKEN;
+ return;
+ }
+
+ const FRESULT vff_mount_error_code = vff_mount(callbacks.m_vff, &fatfs);
+ if (vff_mount_error_code != FR_OK)
+ {
+ // The VFF is most likely broken.
+ ERROR_LOG_FMT(IOS_WC24, "Failed to mount VFF at: {}", path);
+ return_value = WC24_ERR_BROKEN;
+ return;
+ }
+
+ Common::ScopeGuard unmount_guard{[] { f_unmount(""); }};
+
+ const auto write_error_code = WriteFile(filename, data);
+ if (write_error_code != WC24_OK)
+ {
+ return_value = write_error_code;
+ return;
+ }
+
+ vff_delete_guard.Dismiss();
+
+ return_value = WC24_OK;
+ return;
+ });
+
+ return return_value;
+}
+} // namespace IOS::HLE::NWC24
diff --git a/Source/Core/Core/IOS/Network/KD/VFF/VFFUtil.h b/Source/Core/Core/IOS/Network/KD/VFF/VFFUtil.h
new file mode 100644
index 0000000000..ca3dc4c5a8
--- /dev/null
+++ b/Source/Core/Core/IOS/Network/KD/VFF/VFFUtil.h
@@ -0,0 +1,43 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "Common/CommonTypes.h"
+#include "Core/IOS/FS/FileSystem.h"
+#include "Core/IOS/Network/KD/NWC24Config.h"
+
+namespace IOS::HLE
+{
+namespace FS
+{
+class FileSystem;
+}
+namespace NWC24
+{
+constexpr u16 SECTOR_SIZE = 512;
+constexpr u16 VF_LITTLE_ENDIAN = 0xFFFE;
+constexpr u16 VF_BIG_ENDIAN = 0xFEFF;
+ErrorCode OpenVFF(const std::string& path, const std::string& filename,
+ const std::shared_ptr<FS::FileSystem>& fs, const std::vector<u8>& data);
+
+#pragma pack(push, 1)
+struct VFFHeader final
+{
+ u8 magic[4];
+ u16 endianness;
+ u16 unknown_marker;
+ u32 volume_size;
+ u16 cluster_size;
+ u16 empty;
+ u16 unknown;
+ u8 padding[14];
+};
+static_assert(sizeof(VFFHeader) == 32);
+#pragma pack(pop)
+} // namespace NWC24
+} // namespace IOS::HLE
diff --git a/Source/Core/Core/IOS/Network/KD/WC24File.h b/Source/Core/Core/IOS/Network/KD/WC24File.h
new file mode 100644
index 0000000000..8ab38c5d6c
--- /dev/null
+++ b/Source/Core/Core/IOS/Network/KD/WC24File.h
@@ -0,0 +1,31 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "Common/CommonTypes.h"
+
+namespace IOS::HLE::NWC24
+{
+#pragma pack(push, 1)
+struct WC24File final
+{
+ char magic[4];
+ u32 version;
+ u32 filler;
+ u8 crypt_type;
+ u8 padding[3];
+ u8 reserved[32];
+ u8 iv[16];
+ u8 rsa_signature[256];
+};
+
+struct WC24PubkMod final
+{
+ u8 rsa_public[256];
+ u8 rsa_reserved[256];
+ u8 aes_key[16];
+ u8 aes_reserved[16];
+};
+#pragma pack(pop)
+} // namespace IOS::HLE::NWC24
diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props
index e0c8baf5ab..bdc3c0b911 100644
--- a/Source/Core/DolphinLib.props
+++ b/Source/Core/DolphinLib.props
@@ -352,6 +352,9 @@
<ClInclude Include="Core\IOS\Network\KD\NetKDRequest.h" />
<ClInclude Include="Core\IOS\Network\KD\NetKDTime.h" />
<ClInclude Include="Core\IOS\Network\KD\NWC24Config.h" />
+ <ClInclude Include="Core\IOS\Network\KD\NWC24DL.h" />
+ <ClInclude Include="Core\IOS\Network\KD\VFF\VFFUtil.h" />
+ <ClInclude Include="Core\IOS\Network\KD\WC24File.h" />
<ClInclude Include="Core\IOS\Network\MACUtils.h" />
<ClInclude Include="Core\IOS\Network\NCD\Manage.h" />
<ClInclude Include="Core\IOS\Network\NCD\WiiNetConfig.h" />
@@ -976,6 +979,8 @@
<ClCompile Include="Core\IOS\Network\KD\NetKDRequest.cpp" />
<ClCompile Include="Core\IOS\Network\KD\NetKDTime.cpp" />
<ClCompile Include="Core\IOS\Network\KD\NWC24Config.cpp" />
+ <ClCompile Include="Core\IOS\Network\KD\NWC24DL.cpp" />
+ <ClCompile Include="Core\IOS\Network\KD\VFF\VFFUtil.cpp" />
<ClCompile Include="Core\IOS\Network\MACUtils.cpp" />
<ClCompile Include="Core\IOS\Network\NCD\Manage.cpp" />
<ClCompile Include="Core\IOS\Network\NCD\WiiNetConfig.cpp" />