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 CreateContextDecrypt(const u8* key) return CreateContext(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 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 CreateContextEncrypt(const u8* key); std::unique_ptr 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 20ffacfa62..d8a562761d 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -363,6 +363,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 @@ -598,6 +603,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) : 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 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 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 +#include +#include +#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); + + 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 subtask_id) const; + std::string GetDownloadURL(u16 entry_index, std::optional 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 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 #include +#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 subtask_id) +{ + std::vector 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 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(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 NetKDRequestDevice::IOCtl(const IOCtlRequest& request) { enum : u32 @@ -288,6 +424,9 @@ std::optional 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 #include +#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 subtask_id); ~NetKDRequestDevice() override; std::optional IOCtl(const IOCtlRequest& request) override; + void Update() override; private: + struct AsyncTask + { + IOS::HLE::Request request; + std::function handler; + }; + + struct AsyncReply + { + IOS::HLE::Request request; + s32 return_value; + }; + + template + std::optional 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 m_work_queue; + std::mutex m_async_reply_lock; + std::queue 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 +#include +#include + +#include + +// 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(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(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(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(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(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& 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(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, const std::vector& 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 +#include +#include + +#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, const std::vector& 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 e2d2527163..ec5342b71f 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -354,6 +354,9 @@ + + + @@ -980,6 +983,8 @@ + +