mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-11-14 21:37:52 -07:00
Merge pull request #11072 from SketchMaster2001/wiiconnect24
Add initial WiiConnect24 support
This commit is contained in:
commit
c0476fdac3
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
143
Source/Core/Core/IOS/Network/KD/NWC24DL.cpp
Normal file
143
Source/Core/Core/IOS/Network/KD/NWC24DL.cpp
Normal 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
|
123
Source/Core/Core/IOS/Network/KD/NWC24DL.h
Normal file
123
Source/Core/Core/IOS/Network/KD/NWC24DL.h
Normal 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
|
@ -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 ||
|
||||
|
@ -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
|
||||
|
308
Source/Core/Core/IOS/Network/KD/VFF/VFFUtil.cpp
Normal file
308
Source/Core/Core/IOS/Network/KD/VFF/VFFUtil.cpp
Normal 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
|
43
Source/Core/Core/IOS/Network/KD/VFF/VFFUtil.h
Normal file
43
Source/Core/Core/IOS/Network/KD/VFF/VFFUtil.h
Normal 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
|
31
Source/Core/Core/IOS/Network/KD/WC24File.h
Normal file
31
Source/Core/Core/IOS/Network/KD/WC24File.h
Normal 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
|
@ -354,6 +354,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" />
|
||||
@ -980,6 +983,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" />
|
||||
|
Loading…
Reference in New Issue
Block a user