Add initial WiiConnect24 support

This commit is contained in:
SketchMaster2001 2022-09-18 23:09:20 -04:00 committed by Admiral H. Curtiss
parent 487a11fd2c
commit e413d7f5ec
No known key found for this signature in database
GPG key ID: F051B4C4044F33FB
12 changed files with 863 additions and 1 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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 ||

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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" />