Merge pull request #12184 from noahpistilli/kd-mail-send

IOS/KD: Implement Send Mail
This commit is contained in:
Admiral H. Curtiss 2023-12-02 17:15:56 +01:00 committed by GitHub
commit 85f4a460f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 787 additions and 13 deletions

View File

@ -33,7 +33,8 @@ public:
void FollowRedirects(long max); void FollowRedirects(long max);
s32 GetLastResponseCode(); s32 GetLastResponseCode();
Response Fetch(const std::string& url, Method method, const Headers& headers, const u8* payload, Response Fetch(const std::string& url, Method method, const Headers& headers, const u8* payload,
size_t size, AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only); size_t size, AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only,
std::span<Multiform> multiform = {});
static int CurlProgressCallback(Impl* impl, curl_off_t dltotal, curl_off_t dlnow, static int CurlProgressCallback(Impl* impl, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow); curl_off_t ultotal, curl_off_t ulnow);
@ -174,6 +175,13 @@ void HttpRequest::Impl::UseIPv4()
curl_easy_setopt(m_curl.get(), CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); curl_easy_setopt(m_curl.get(), CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
} }
HttpRequest::Response HttpRequest::PostMultiform(const std::string& url,
std::span<Multiform> multiform,
const Headers& headers, AllowedReturnCodes codes)
{
return m_impl->Fetch(url, Impl::Method::POST, headers, nullptr, 0, codes, multiform);
}
void HttpRequest::Impl::FollowRedirects(long max) void HttpRequest::Impl::FollowRedirects(long max)
{ {
curl_easy_setopt(m_curl.get(), CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(m_curl.get(), CURLOPT_FOLLOWLOCATION, 1);
@ -225,17 +233,33 @@ static size_t header_callback(char* buffer, size_t size, size_t nitems, void* us
HttpRequest::Response HttpRequest::Impl::Fetch(const std::string& url, Method method, HttpRequest::Response HttpRequest::Impl::Fetch(const std::string& url, Method method,
const Headers& headers, const u8* payload, const Headers& headers, const u8* payload,
size_t size, AllowedReturnCodes codes) size_t size, AllowedReturnCodes codes,
std::span<Multiform> multiform)
{ {
m_response_headers.clear(); m_response_headers.clear();
curl_easy_setopt(m_curl.get(), CURLOPT_POST, method == Method::POST); curl_easy_setopt(m_curl.get(), CURLOPT_POST, method == Method::POST);
curl_easy_setopt(m_curl.get(), CURLOPT_URL, url.c_str()); curl_easy_setopt(m_curl.get(), CURLOPT_URL, url.c_str());
if (method == Method::POST) if (method == Method::POST && multiform.empty())
{ {
curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDS, payload); curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDS, payload);
curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDSIZE, size); curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDSIZE, size);
} }
curl_mime* form = nullptr;
Common::ScopeGuard multiform_guard{[&form] { curl_mime_free(form); }};
if (!multiform.empty())
{
form = curl_mime_init(m_curl.get());
for (const auto& value : multiform)
{
curl_mimepart* part = curl_mime_addpart(form);
curl_mime_name(part, value.name.c_str());
curl_mime_data(part, value.data.c_str(), value.data.size());
}
curl_easy_setopt(m_curl.get(), CURLOPT_MIMEPOST, form);
}
curl_slist* list = nullptr; curl_slist* list = nullptr;
Common::ScopeGuard list_guard{[&list] { curl_slist_free_all(list); }}; Common::ScopeGuard list_guard{[&list] { curl_slist_free_all(list); }};
for (const auto& [name, value] : headers) for (const auto& [name, value] : headers)

View File

@ -8,6 +8,7 @@
#include <map> #include <map>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <span>
#include <string> #include <string>
#include <vector> #include <vector>
@ -35,6 +36,12 @@ public:
using Response = std::optional<std::vector<u8>>; using Response = std::optional<std::vector<u8>>;
using Headers = std::map<std::string, std::optional<std::string>>; using Headers = std::map<std::string, std::optional<std::string>>;
struct Multiform
{
std::string name;
std::string data;
};
void SetCookies(const std::string& cookies); void SetCookies(const std::string& cookies);
void UseIPv4(); void UseIPv4();
void FollowRedirects(long max = 1); void FollowRedirects(long max = 1);
@ -48,6 +55,10 @@ public:
Response Post(const std::string& url, const std::string& payload, const Headers& headers = {}, Response Post(const std::string& url, const std::string& payload, const Headers& headers = {},
AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only); AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only);
Response PostMultiform(const std::string& url, std::span<Multiform> multiform,
const Headers& headers = {},
AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only);
private: private:
class Impl; class Impl;
std::unique_ptr<Impl> m_impl; std::unique_ptr<Impl> m_impl;

View File

@ -383,6 +383,8 @@ add_library(core
IOS/Network/KD/VFF/VFFUtil.h IOS/Network/KD/VFF/VFFUtil.h
IOS/Network/KD/WC24File.h IOS/Network/KD/WC24File.h
IOS/Network/KD/Mail/MailCommon.h IOS/Network/KD/Mail/MailCommon.h
IOS/Network/KD/Mail/WC24FriendList.cpp
IOS/Network/KD/Mail/WC24FriendList.h
IOS/Network/KD/Mail/WC24Send.cpp IOS/Network/KD/Mail/WC24Send.cpp
IOS/Network/KD/Mail/WC24Send.h IOS/Network/KD/Mail/WC24Send.h
IOS/Network/MACUtils.cpp IOS/Network/MACUtils.cpp

View File

@ -11,6 +11,11 @@ namespace IOS::HLE::NWC24::Mail
{ {
constexpr u32 MAIL_LIST_MAGIC = 0x57635466; // WcTf constexpr u32 MAIL_LIST_MAGIC = 0x57635466; // WcTf
inline u32 CalculateFileOffset(u32 index)
{
return Common::swap32(128 + (index * 128));
}
#pragma pack(push, 1) #pragma pack(push, 1)
struct MailListHeader final struct MailListHeader final
{ {

View File

@ -0,0 +1,93 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/IOS/Network/KD/Mail/WC24FriendList.h"
#include "Core/IOS/FS/FileSystem.h"
#include "Core/IOS/Uids.h"
namespace IOS::HLE::NWC24::Mail
{
WC24FriendList::WC24FriendList(std::shared_ptr<FS::FileSystem> fs) : m_fs{std::move(fs)}
{
ReadFriendList();
}
void WC24FriendList::ReadFriendList()
{
const auto file = m_fs->OpenFile(PID_KD, PID_KD, FRIEND_LIST_PATH, FS::Mode::Read);
if (!file || !file->Read(&m_data, 1))
return;
const bool success = CheckFriendList();
if (!success)
ERROR_LOG_FMT(IOS_WC24, "There is an error in the Receive List for WC24 mail");
}
void WC24FriendList::WriteFriendList() const
{
constexpr FS::Modes public_modes{FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite};
m_fs->CreateFullPath(PID_KD, PID_KD, FRIEND_LIST_PATH, 0, public_modes);
const auto file = m_fs->CreateAndOpenFile(PID_KD, PID_KD, FRIEND_LIST_PATH, public_modes);
if (!file || !file->Write(&m_data, 1))
ERROR_LOG_FMT(IOS_WC24, "Failed to open or write WC24 Receive list file");
}
bool WC24FriendList::CheckFriendList() const
{
// 'WcFl' magic
if (Common::swap32(m_data.header.magic) != FRIEND_LIST_MAGIC)
{
ERROR_LOG_FMT(IOS_WC24, "Receive List magic mismatch ({} != {})",
Common::swap32(m_data.header.magic), FRIEND_LIST_MAGIC);
return false;
}
return true;
}
bool WC24FriendList::DoesFriendExist(u64 friend_id) const
{
return std::any_of(m_data.friend_codes.cbegin(), m_data.friend_codes.cend(),
[&friend_id](const u64 v) { return v == friend_id; });
}
std::vector<u64> WC24FriendList::GetUnconfirmedFriends() const
{
std::vector<u64> friends{};
for (u32 i = 0; i < MAX_ENTRIES; i++)
{
if (static_cast<FriendStatus>(Common::swap32(m_data.entries[i].status)) ==
FriendStatus::Unconfirmed &&
static_cast<FriendType>(Common::swap32(m_data.entries[i].friend_type)) == FriendType::Wii)
{
friends.push_back(Common::swap64(m_data.friend_codes.at(i)));
}
}
return friends;
}
u64 WC24FriendList::ConvertEmailToFriendCode(std::string_view email)
{
u32 upper = 0x80;
u32 lower{};
u32 idx{};
for (char chr : email)
{
if (idx == 7)
{
upper = upper | (email.size() & 0x1f);
break;
}
lower = (upper | chr) >> 0x18 | (lower | lower >> 0x1f) << 8;
upper = (upper | chr) * 0x100;
idx++;
}
return u64{lower} << 32 | upper;
}
} // namespace IOS::HLE::NWC24::Mail

View File

@ -0,0 +1,98 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <memory>
#include <string>
#include <vector>
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Common/Swap.h"
#include "Core/IOS/Network/KD/NWC24Config.h"
namespace IOS::HLE
{
namespace FS
{
class FileSystem;
}
namespace NWC24::Mail
{
constexpr const char FRIEND_LIST_PATH[] = "/" WII_WC24CONF_DIR "/nwc24fl.bin";
class WC24FriendList final
{
public:
explicit WC24FriendList(std::shared_ptr<FS::FileSystem> fs);
static u64 ConvertEmailToFriendCode(std::string_view email);
void ReadFriendList();
bool CheckFriendList() const;
void WriteFriendList() const;
bool DoesFriendExist(u64 friend_id) const;
std::vector<u64> GetUnconfirmedFriends() const;
private:
static constexpr u32 FRIEND_LIST_MAGIC = 0x5763466C; // WcFl
static constexpr u32 MAX_ENTRIES = 100;
#pragma pack(push, 1)
struct FriendListHeader final
{
u32 magic; // 'WcFl' 0x5763466C
u32 version;
u32 max_friend_entries;
u32 number_of_friends;
char padding[48];
};
static_assert(sizeof(FriendListHeader) == 64);
static_assert(std::is_trivially_copyable_v<FriendListHeader>);
enum class FriendType : u32
{
None,
Wii,
Email
};
enum class FriendStatus : u32
{
None,
Unconfirmed,
Confirmed,
Declined
};
struct FriendListEntry final
{
u32 friend_type;
u32 status;
char nickname[24];
u32 mii_id;
u32 system_id;
char reserved[24];
char email_or_code[96];
char padding[160];
};
static_assert(sizeof(FriendListEntry) == 320);
static_assert(std::is_trivially_copyable_v<FriendListEntry>);
struct FriendList final
{
FriendListHeader header;
std::array<u64, MAX_ENTRIES> friend_codes;
std::array<FriendListEntry, MAX_ENTRIES> entries;
};
static_assert(sizeof(FriendList) == 32864);
static_assert(std::is_trivially_copyable_v<FriendList>);
#pragma pack(pop)
FriendList m_data;
std::shared_ptr<FS::FileSystem> m_fs;
};
} // namespace NWC24::Mail
} // namespace IOS::HLE

View File

@ -3,8 +3,10 @@
#include "Core/IOS/Network/KD/Mail/WC24Send.h" #include "Core/IOS/Network/KD/Mail/WC24Send.h"
#include "Core/IOS/FS/FileSystem.h" #include "Core/IOS/FS/FileSystem.h"
#include "Core/IOS/Network/KD/VFF/VFFUtil.h"
#include "Core/IOS/Uids.h" #include "Core/IOS/Uids.h"
#include <fmt/chrono.h>
#include "Common/Assert.h" #include "Common/Assert.h"
namespace IOS::HLE::NWC24::Mail namespace IOS::HLE::NWC24::Mail
@ -44,7 +46,28 @@ bool WC24SendList::ReadSendList()
return false; return false;
} }
return CheckSendList(); // Make sure that next_entry_offset is not out of bounds.
if (m_data.header.next_entry_offset % 128 != 0 ||
m_data.header.next_entry_offset >
sizeof(MailListEntry) * (MAX_ENTRIES - 1) + sizeof(MailListEntry))
{
const std::optional<u32> next_entry_index = GetNextFreeEntryIndex();
if (!next_entry_index)
{
// If there are no free entries, we will have to overwrite an entry.
m_data.header.next_entry_offset = Common::swap32(128);
}
else
{
m_data.header.next_entry_offset = CalculateFileOffset(next_entry_index.value());
}
}
const s32 file_error = CheckSendList();
if (!file_error)
ERROR_LOG_FMT(IOS_WC24, "There is an error in the Send List for WC24 mail");
return true;
} }
bool WC24SendList::IsDisabled() const bool WC24SendList::IsDisabled() const
@ -82,6 +105,147 @@ bool WC24SendList::CheckSendList() const
return true; return true;
} }
u32 WC24SendList::GetNumberOfMail() const
{
ASSERT(!IsDisabled());
return Common::swap32(m_data.header.number_of_mail);
}
u32 WC24SendList::GetEntryId(u32 entry_index) const
{
ASSERT(!IsDisabled());
return Common::swap32(m_data.entries[entry_index].id);
}
u32 WC24SendList::GetMailSize(u32 index) const
{
ASSERT(!IsDisabled());
return Common::swap32(m_data.entries[index].msg_size);
}
ErrorCode WC24SendList::DeleteMessage(u32 index)
{
ASSERT(!IsDisabled());
ErrorCode error = NWC24::DeleteFileFromVFF(NWC24::Mail::SEND_BOX_PATH, GetMailPath(index), m_fs);
if (error != WC24_OK)
return error;
// Fix up the header then clear the entry.
m_data.header.number_of_mail = Common::swap32(Common::swap32(m_data.header.number_of_mail) - 1);
m_data.header.next_entry_id = Common::swap32(GetEntryId(index));
m_data.header.next_entry_offset = CalculateFileOffset(index);
m_data.header.total_size_of_messages =
Common::swap32(m_data.header.total_size_of_messages) - GetMailSize(index);
std::memset(&m_data.entries[index], 0, sizeof(MailListEntry));
return WC24_OK;
}
std::string WC24SendList::GetMailPath(u32 index) const
{
return fmt::format("mb/s{:07d}.msg", GetEntryId(index));
}
u32 WC24SendList::GetNextEntryId() const
{
ASSERT(!IsDisabled());
return Common::swap32(m_data.header.next_entry_id);
}
u32 WC24SendList::GetNextEntryIndex() const
{
ASSERT(!IsDisabled());
return (Common::swap32(m_data.header.next_entry_offset) - 128) / 128;
}
std::vector<u32> WC24SendList::GetMailToSend() const
{
ASSERT(!IsDisabled());
// The list is not guaranteed to have all entries consecutively.
// As such we must find the populated entries for the specified number of mails.
const u32 mail_count = std::min(GetNumberOfMail(), 16U);
u32 found{};
std::vector<u32> mails{};
for (u32 index = 0; index < MAX_ENTRIES; index++)
{
if (found == mail_count)
break;
if (GetEntryId(index) != 0)
{
mails.emplace_back(index);
found++;
}
}
return mails;
}
std::optional<u32> WC24SendList::GetNextFreeEntryIndex() const
{
for (u32 index = 0; index < MAX_ENTRIES; index++)
{
if (GetEntryId(index) == 0)
return index;
}
return std::nullopt;
}
ErrorCode WC24SendList::AddRegistrationMessages(const WC24FriendList& friend_list, u64 sender)
{
ASSERT(!IsDisabled());
// It is possible that the user composed a message before SendMail was called.
ReadSendList();
const std::vector<u64> unconfirmed_friends = friend_list.GetUnconfirmedFriends();
for (const u64 code : unconfirmed_friends)
{
const u32 entry_index = GetNextEntryIndex();
const u32 msg_id = GetNextEntryId();
m_data.entries[entry_index].id = Common::swap32(msg_id);
std::time_t t = std::time(nullptr);
const std::string formatted_message =
fmt::format(MAIL_REGISTRATION_STRING, sender, code, fmt::gmtime(t));
std::vector<u8> message{formatted_message.begin(), formatted_message.end()};
NWC24::ErrorCode reply =
NWC24::WriteToVFF(NWC24::Mail::SEND_BOX_PATH, GetMailPath(entry_index), m_fs, message);
if (reply != WC24_OK)
{
ERROR_LOG_FMT(IOS_WC24, "Error writing registration message to VFF");
return reply;
}
NOTICE_LOG_FMT(IOS_WC24, "Issued registration message for Wii Friend: {}", code);
// Update the header and some fields in the body
m_data.entries[entry_index].msg_size = Common::swap32(static_cast<u32>(message.size()));
m_data.header.number_of_mail = Common::swap32(GetNumberOfMail() + 1);
m_data.header.next_entry_id = Common::swap32(msg_id + 1);
m_data.header.total_size_of_messages =
Common::swap32(m_data.header.total_size_of_messages) + static_cast<u32>(message.size());
const std::optional<u32> next_entry_index = GetNextFreeEntryIndex();
if (!next_entry_index)
{
// If there are no free entries, we overwrite the first entry.
m_data.header.next_entry_offset = Common::swap32(128);
}
else
{
m_data.header.next_entry_offset = CalculateFileOffset(next_entry_index.value());
}
}
// Only flush on success.
WriteSendList();
return WC24_OK;
}
std::string_view WC24SendList::GetMailFlag() const std::string_view WC24SendList::GetMailFlag() const
{ {
ASSERT(!IsDisabled()); ASSERT(!IsDisabled());

View File

@ -3,13 +3,16 @@
#pragma once #pragma once
#include <optional>
#include <string_view> #include <string_view>
#include <vector>
#include "Common/CommonPaths.h" #include "Common/CommonPaths.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/Swap.h" #include "Common/Swap.h"
#include "Core/IOS/Network/KD/Mail/MailCommon.h" #include "Core/IOS/Network/KD/Mail/MailCommon.h"
#include "Core/IOS/Network/KD/Mail/WC24FriendList.h"
#include "Core/IOS/Network/KD/NWC24Config.h" #include "Core/IOS/Network/KD/NWC24Config.h"
namespace IOS::HLE namespace IOS::HLE
@ -34,11 +37,56 @@ public:
bool IsDisabled() const; bool IsDisabled() const;
std::string_view GetMailFlag() const; std::string_view GetMailFlag() const;
u32 GetNumberOfMail() const;
std::vector<u32> GetMailToSend() const;
u32 GetEntryId(u32 entry_index) const;
u32 GetMailSize(u32 index) const;
ErrorCode DeleteMessage(u32 index);
std::string GetMailPath(u32 index) const;
u32 GetNextEntryId() const;
u32 GetNextEntryIndex() const;
std::optional<u32> GetNextFreeEntryIndex() const;
ErrorCode AddRegistrationMessages(const WC24FriendList& friend_list, u64 sender);
private: private:
static constexpr u32 MAX_ENTRIES = 127; static constexpr u32 MAX_ENTRIES = 127;
static constexpr u32 SEND_LIST_SIZE = 16384; static constexpr u32 SEND_LIST_SIZE = 16384;
// Format for the message Wii Mail sends when trying to register a Wii Friend.
// Most fields can be static such as the X-Wii-AppId which is the Wii Menu,
// X-Wii-Cmd which is the registration command, and the attached file which is
// just 128 bytes of base64 encoded 0 bytes. That file is supposed to be friend profile data which
// is written to nwc24fl.bin, although it has been observed to always be 0.
static constexpr char MAIL_REGISTRATION_STRING[] =
"MAIL FROM: {0:016d}@wii.com\r\n"
"RCPT TO: {1:016d}wii.com\r\n"
"DATA\r\n"
"Date: {2:%a, %d %b %Y %X} GMT\r\n"
"From: {0:016d}@wii.com\r\n"
"To: {1:016d}@wii.com\r\n"
"Message-Id: <00002000B0DF6BB47FE0303E0DB0D@wii.com>\r\n"
"Subject: WC24 Cmd Message\r\n"
"X-Wii-AppId: 0-00000001-0001\r\n"
"X-Wii-Cmd: 80010001\r\n"
"MIME-Version: 1.0\r\n"
"Content-Type: multipart/mixed;\r\n "
"boundary=\"Boundary-NWC24-041B6CE500012\"\r\n"
"--Boundary-NWC24-041B6CE500012\r\n"
"Content-Type: text/plain; charset=us-ascii\r\n"
"Content-Transfer-Encoding: 7bit\r\n"
"WC24 Cmd Message\r\n"
"--Boundary-NWC24-041B6CE500012\r\n"
"Content-Type: application/octet-stream;\r\n "
"name=a0000018.dat\r\n"
"Content-Transfer-Encoding: base64\r\n"
"Content-Disposition: attachment;\r\n "
"filename=a0000018.dat\r\n\r\n "
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
"\r\n\r\n"
"--Boundary-NWC24-041B6CE500012--";
#pragma pack(push, 1) #pragma pack(push, 1)
struct SendList final struct SendList final
{ {

View File

@ -246,4 +246,16 @@ void NWC24Config::SetPassword(std::string_view password)
std::strncpy(m_data.paswd, password.data(), std::size(m_data.paswd)); std::strncpy(m_data.paswd, password.data(), std::size(m_data.paswd));
m_data.paswd[MAX_PASSWORD_LENGTH - 1] = '\0'; m_data.paswd[MAX_PASSWORD_LENGTH - 1] = '\0';
} }
std::string NWC24Config::GetSendURL() const
{
const size_t size = strnlen(m_data.http_urls[4], MAX_URL_LENGTH);
return {m_data.http_urls[4], size};
}
std::string_view NWC24Config::GetPassword() const
{
const size_t size = strnlen(m_data.paswd, MAX_PASSWORD_LENGTH);
return {m_data.paswd, size};
}
} // namespace IOS::HLE::NWC24 } // namespace IOS::HLE::NWC24

View File

@ -34,6 +34,8 @@ enum ErrorCode : s32
WC24_ERR_ID_REGISTERED = -36, WC24_ERR_ID_REGISTERED = -36,
WC24_ERR_DISABLED = -39, WC24_ERR_DISABLED = -39,
WC24_ERR_ID_NOT_REGISTERED = -44, WC24_ERR_ID_NOT_REGISTERED = -44,
WC24_MSG_DAMAGED = -71,
WC24_MSG_TOO_BIG = -72
}; };
enum class NWC24CreationStage : u32 enum class NWC24CreationStage : u32
@ -72,6 +74,8 @@ public:
std::string_view GetMlchkid() const; std::string_view GetMlchkid() const;
std::string GetCheckURL() const; std::string GetCheckURL() const;
std::string GetSendURL() const;
std::string_view GetPassword() const;
NWC24CreationStage CreationStage() const; NWC24CreationStage CreationStage() const;
void SetCreationStage(NWC24CreationStage creation_stage); void SetCreationStage(NWC24CreationStage creation_stage);

View File

@ -155,7 +155,7 @@ s32 NWC24MakeUserID(u64* nwc24_id, u32 hollywood_id, u16 id_ctr, HardwareModel h
NetKDRequestDevice::NetKDRequestDevice(EmulationKernel& ios, const std::string& device_name) NetKDRequestDevice::NetKDRequestDevice(EmulationKernel& ios, const std::string& device_name)
: EmulationDevice(ios, device_name), m_config{ios.GetFS()}, m_dl_list{ios.GetFS()}, : EmulationDevice(ios, device_name), m_config{ios.GetFS()}, m_dl_list{ios.GetFS()},
m_send_list{ios.GetFS()} m_send_list{ios.GetFS()}, m_friend_list{ios.GetFS()}
{ {
// Enable all NWC24 permissions // Enable all NWC24 permissions
m_scheduler_buffer[1] = Common::swap32(-1); m_scheduler_buffer[1] = Common::swap32(-1);
@ -275,6 +275,12 @@ void NetKDRequestDevice::SchedulerWorker(const SchedulerEvent event)
{ {
LogError(ErrorType::CheckMail, code); LogError(ErrorType::CheckMail, code);
} }
code = KDSendMail();
if (code != NWC24::WC24_OK)
{
LogError(ErrorType::SendMail, code);
}
} }
} }
@ -315,6 +321,15 @@ void NetKDRequestDevice::LogError(ErrorType error_type, s32 error_code)
case ErrorType::CheckMail: case ErrorType::CheckMail:
new_code = -(102200 - error_code); new_code = -(102200 - error_code);
break; break;
case ErrorType::SendMail:
new_code = -(105000 - error_code);
break;
case ErrorType::ReceiveMail:
new_code = -(100300 - error_code);
break;
case ErrorType::CGI:
new_code = -(error_code + 110000);
break;
} }
std::lock_guard lg(m_scheduler_buffer_lock); std::lock_guard lg(m_scheduler_buffer_lock);
@ -498,6 +513,133 @@ NWC24::ErrorCode NetKDRequestDevice::DetermineSubtask(u16 entry_index,
return NWC24::WC24_ERR_INVALID_VALUE; return NWC24::WC24_ERR_INVALID_VALUE;
} }
NWC24::ErrorCode NetKDRequestDevice::KDSendMail()
{
bool success = false;
Common::ScopeGuard exit_guard([&] {
std::lock_guard lg(m_scheduler_buffer_lock);
if (success)
{
// m_scheduler_buffer[11] contains the amount of times we have sent for mail this IOS
// session.
m_scheduler_buffer[14] = Common::swap32(Common::swap32(m_scheduler_buffer[14]) + 1);
}
m_scheduler_buffer[4] = static_cast<u32>(CurrentFunction::None);
m_send_list.WriteSendList();
});
{
std::lock_guard lg(m_scheduler_buffer_lock);
m_scheduler_buffer[4] = Common::swap32(static_cast<u32>(CurrentFunction::Send));
}
m_send_list.ReadSendList();
const std::string auth =
fmt::format("mlid=w{}\r\npasswd={}", m_config.Id(), m_config.GetPassword());
std::vector<Common::HttpRequest::Multiform> multiform = {{"mlid", auth}};
std::vector<u32> mails = m_send_list.GetMailToSend();
for (const u32 file_index : mails)
{
const u32 entry_id = m_send_list.GetEntryId(file_index);
const u32 mail_size = m_send_list.GetMailSize(file_index);
if (mail_size > MAX_MAIL_SIZE)
{
WARN_LOG_FMT(IOS_WC24,
"NET_KD_REQ: IOCTL_NWC24_SEND_MAIL_NOW: Mail at index {} was too large to send.",
entry_id);
LogError(ErrorType::SendMail, NWC24::WC24_MSG_TOO_BIG);
NWC24::ErrorCode res = m_send_list.DeleteMessage(file_index);
if (res != NWC24::WC24_OK)
{
LogError(ErrorType::SendMail, res);
}
mails.erase(std::remove(mails.begin(), mails.end(), file_index), mails.end());
continue;
}
std::vector<u8> mail_data(mail_size);
NWC24::ErrorCode res = NWC24::ReadFromVFF(
NWC24::Mail::SEND_BOX_PATH, m_send_list.GetMailPath(file_index), m_ios.GetFS(), mail_data);
if (res != NWC24::WC24_OK)
{
ERROR_LOG_FMT(IOS_WC24, "Reading mail at index {} failed with error code {}.", entry_id,
static_cast<s32>(res));
LogError(ErrorType::SendMail, NWC24::WC24_MSG_DAMAGED);
res = m_send_list.DeleteMessage(file_index);
if (res != NWC24::WC24_OK)
{
LogError(ErrorType::SendMail, res);
}
mails.erase(std::remove(mails.begin(), mails.end(), file_index), mails.end());
continue;
}
const std::string mail_str = {mail_data.begin(), mail_data.end()};
multiform.push_back({fmt::format("m{}", entry_id), mail_str});
}
const Common::HttpRequest::Response response =
m_http.PostMultiform(m_config.GetSendURL(), multiform);
if (!response)
{
ERROR_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_SEND_MAIL_NOW: Failed to request data at {}.",
m_config.GetSendURL());
return NWC24::WC24_ERR_SERVER;
}
// Now check if any mail failed to save to the server.
const std::string response_str = {response->begin(), response->end()};
const std::string code = GetValueFromCGIResponse(response_str, "cd");
if (code != "100")
{
ERROR_LOG_FMT(
IOS_WC24,
"NET_KD_REQ: IOCTL_NWC24_CHECK_MAIL_NOW: Mail server returned non-success code: {}", code);
return NWC24::WC24_ERR_SERVER;
}
// Reverse in order to delete from bottom to top of the send list.
// We do this to ensure that new entries can be written as close to the beginning of the file as
// possible.
for (auto it = mails.rbegin(); it != mails.rend(); ++it)
{
const u32 entry_id = m_send_list.GetEntryId(*it);
Common::ScopeGuard delete_guard([&] {
NWC24::ErrorCode res = m_send_list.DeleteMessage(*it);
if (res != NWC24::WC24_OK)
{
LogError(ErrorType::SendMail, res);
}
});
const std::string value = GetValueFromCGIResponse(response_str, fmt::format("cd{}", entry_id));
s32 cgi_code{};
const bool did_parse = TryParse(value, &cgi_code);
if (!did_parse)
{
ERROR_LOG_FMT(IOS_WC24, "Mail server returned invalid CGI response code.");
LogError(ErrorType::CGI, NWC24::WC24_ERR_SERVER);
break;
}
if (cgi_code != 100)
{
ERROR_LOG_FMT(IOS_WC24, "Mail server failed to save mail at index {}", entry_id);
LogError(ErrorType::CGI, cgi_code);
}
}
success = true;
return NWC24::WC24_OK;
}
NWC24::ErrorCode NetKDRequestDevice::KDDownload(const u16 entry_index, NWC24::ErrorCode NetKDRequestDevice::KDDownload(const u16 entry_index,
const std::optional<u8> subtask_id) const std::optional<u8> subtask_id)
{ {
@ -622,8 +764,8 @@ NWC24::ErrorCode NetKDRequestDevice::KDDownload(const u16 entry_index,
} }
} }
NWC24::ErrorCode reply = IOS::HLE::NWC24::OpenVFF(m_dl_list.GetVFFPath(entry_index), content_name, NWC24::ErrorCode reply = IOS::HLE::NWC24::WriteToVFF(m_dl_list.GetVFFPath(entry_index),
m_ios.GetFS(), file_data); content_name, m_ios.GetFS(), file_data);
if (reply != NWC24::WC24_OK) if (reply != NWC24::WC24_OK)
{ {
@ -657,6 +799,13 @@ IPCReply NetKDRequestDevice::HandleNWC24CheckMailNow(const IOCtlRequest& request
return IPCReply(IPC_SUCCESS); return IPCReply(IPC_SUCCESS);
} }
IPCReply NetKDRequestDevice::HandleNWC24SendMailNow(const IOCtlRequest& request)
{
const NWC24::ErrorCode reply = KDSendMail();
WriteReturnValue(reply, request.buffer_out);
return IPCReply(IPC_SUCCESS);
}
IPCReply NetKDRequestDevice::HandleNWC24DownloadNowEx(const IOCtlRequest& request) IPCReply NetKDRequestDevice::HandleNWC24DownloadNowEx(const IOCtlRequest& request)
{ {
if (m_dl_list.IsDisabled() || !m_dl_list.ReadDlList()) if (m_dl_list.IsDisabled() || !m_dl_list.ReadDlList())
@ -981,6 +1130,9 @@ std::optional<IPCReply> NetKDRequestDevice::IOCtl(const IOCtlRequest& request)
case IOCTL_NWC24_DOWNLOAD_NOW_EX: case IOCTL_NWC24_DOWNLOAD_NOW_EX:
return LaunchAsyncTask(&NetKDRequestDevice::HandleNWC24DownloadNowEx, request); return LaunchAsyncTask(&NetKDRequestDevice::HandleNWC24DownloadNowEx, request);
case IOCTL_NWC24_SEND_MAIL_NOW:
return LaunchAsyncTask(&NetKDRequestDevice::HandleNWC24SendMailNow, request);
case IOCTL_NWC24_REQUEST_SHUTDOWN: case IOCTL_NWC24_REQUEST_SHUTDOWN:
{ {
if (request.buffer_in == 0 || request.buffer_in % 4 != 0 || request.buffer_in_size < 8 || if (request.buffer_in == 0 || request.buffer_in % 4 != 0 || request.buffer_in_size < 8 ||

View File

@ -13,6 +13,7 @@
#include "Common/HttpRequest.h" #include "Common/HttpRequest.h"
#include "Common/WorkQueueThread.h" #include "Common/WorkQueueThread.h"
#include "Core/IOS/Device.h" #include "Core/IOS/Device.h"
#include "Core/IOS/Network/KD/Mail/WC24FriendList.h"
#include "Core/IOS/Network/KD/Mail/WC24Send.h" #include "Core/IOS/Network/KD/Mail/WC24Send.h"
#include "Core/IOS/Network/KD/NWC24Config.h" #include "Core/IOS/Network/KD/NWC24Config.h"
#include "Core/IOS/Network/KD/NWC24DL.h" #include "Core/IOS/Network/KD/NWC24DL.h"
@ -72,6 +73,9 @@ private:
Client, Client,
Server, Server,
CheckMail, CheckMail,
SendMail,
ReceiveMail,
CGI,
}; };
enum class SchedulerEvent enum class SchedulerEvent
@ -80,14 +84,18 @@ private:
Download, Download,
}; };
IPCReply HandleNWC24SendMailNow(const IOCtlRequest& request);
NWC24::ErrorCode KDCheckMail(u32* mail_flag, u32* interval); NWC24::ErrorCode KDCheckMail(u32* mail_flag, u32* interval);
IPCReply HandleRequestRegisterUserId(const IOCtlRequest& request); IPCReply HandleRequestRegisterUserId(const IOCtlRequest& request);
NWC24::ErrorCode KDSendMail();
void LogError(ErrorType error_type, s32 error_code); void LogError(ErrorType error_type, s32 error_code);
void SchedulerTimer(); void SchedulerTimer();
void SchedulerWorker(SchedulerEvent event); void SchedulerWorker(SchedulerEvent event);
NWC24::ErrorCode DetermineDownloadTask(u16* entry_index, std::optional<u8>* subtask_id) const; NWC24::ErrorCode DetermineDownloadTask(u16* entry_index, std::optional<u8>* subtask_id) const;
NWC24::ErrorCode DetermineSubtask(u16 entry_index, std::optional<u8>* subtask_id) const; NWC24::ErrorCode DetermineSubtask(u16 entry_index, std::optional<u8>* subtask_id) const;
static constexpr u32 MAX_MAIL_SIZE = 208952;
static std::string GetValueFromCGIResponse(const std::string& response, const std::string& key); static std::string GetValueFromCGIResponse(const std::string& response, const std::string& key);
static constexpr std::array<u8, 20> MAIL_CHECK_KEY = {0xce, 0x4c, 0xf2, 0x9a, 0x3d, 0x6b, 0xe1, static constexpr std::array<u8, 20> MAIL_CHECK_KEY = {0xce, 0x4c, 0xf2, 0x9a, 0x3d, 0x6b, 0xe1,
0xc2, 0x61, 0x91, 0x72, 0xb5, 0xcb, 0x29, 0xc2, 0x61, 0x91, 0x72, 0xb5, 0xcb, 0x29,
@ -98,6 +106,7 @@ private:
NWC24::NWC24Config m_config; NWC24::NWC24Config m_config;
NWC24::NWC24Dl m_dl_list; NWC24::NWC24Dl m_dl_list;
NWC24::Mail::WC24SendList m_send_list; NWC24::Mail::WC24SendList m_send_list;
NWC24::Mail::WC24FriendList m_friend_list;
Common::WorkQueueThread<AsyncTask> m_work_queue; Common::WorkQueueThread<AsyncTask> m_work_queue;
Common::WorkQueueThread<std::function<void()>> m_scheduler_work_queue; Common::WorkQueueThread<std::function<void()>> m_scheduler_work_queue;
std::mutex m_async_reply_lock; std::mutex m_async_reply_lock;

View File

@ -237,6 +237,48 @@ static ErrorCode WriteFile(const std::string& filename, const std::vector<u8>& t
return WC24_OK; return WC24_OK;
} }
static ErrorCode ReadFile(const std::string& filename, std::vector<u8>& out)
{
FIL src{};
const auto open_error_code = f_open(&src, filename.c_str(), FA_READ);
if (open_error_code != FR_OK)
{
ERROR_LOG_FMT(IOS_WC24, "Failed to open file {} in VFF", filename);
return WC24_ERR_FILE_OPEN;
}
Common::ScopeGuard vff_close_guard{[&] { f_close(&src); }};
u32 size = static_cast<u32>(out.size());
u32 read_size{};
const auto read_error_code = f_read(&src, out.data(), size, &read_size);
if (read_error_code != FR_OK)
{
ERROR_LOG_FMT(IOS_WC24, "Failed to read file {} in VFF: {}", filename,
static_cast<u32>(read_error_code));
return WC24_ERR_FILE_READ;
}
if (read_size != size)
{
ERROR_LOG_FMT(IOS_WC24, "Failed to read bytes of file {} to VFF ({} != {})", filename,
read_size, size);
return WC24_ERR_FILE_READ;
}
// As prior operations did not fail, dismiss the guard and handle a potential error with f_close.
vff_close_guard.Dismiss();
const auto close_error_code = f_close(&src);
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 namespace
{ {
class VffFatFsCallbacks : public Common::FatFsCallbacks class VffFatFsCallbacks : public Common::FatFsCallbacks
@ -258,7 +300,7 @@ public:
}; };
} // namespace } // namespace
ErrorCode OpenVFF(const std::string& path, const std::string& filename, ErrorCode WriteToVFF(const std::string& path, const std::string& filename,
const std::shared_ptr<FS::FileSystem>& fs, const std::vector<u8>& data) const std::shared_ptr<FS::FileSystem>& fs, const std::vector<u8>& data)
{ {
VffFatFsCallbacks callbacks; VffFatFsCallbacks callbacks;
@ -286,6 +328,8 @@ ErrorCode OpenVFF(const std::string& path, const std::string& filename,
return; return;
} }
Common::ScopeGuard unmount_guard{[] { f_unmount(""); }};
const FRESULT vff_mount_error_code = vff_mount(callbacks.m_vff, &fatfs); const FRESULT vff_mount_error_code = vff_mount(callbacks.m_vff, &fatfs);
if (vff_mount_error_code != FR_OK) if (vff_mount_error_code != FR_OK)
{ {
@ -295,8 +339,6 @@ ErrorCode OpenVFF(const std::string& path, const std::string& filename,
return; return;
} }
Common::ScopeGuard unmount_guard{[] { f_unmount(""); }};
const auto write_error_code = WriteFile(filename, data); const auto write_error_code = WriteFile(filename, data);
if (write_error_code != WC24_OK) if (write_error_code != WC24_OK)
{ {
@ -312,4 +354,108 @@ ErrorCode OpenVFF(const std::string& path, const std::string& filename,
return return_value; return return_value;
} }
ErrorCode ReadFromVFF(const std::string& path, const std::string& filename,
const std::shared_ptr<FS::FileSystem>& fs, std::vector<u8>& out)
{
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;
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;
}
Common::ScopeGuard unmount_guard{[] { f_unmount(""); }};
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;
}
const ErrorCode read_error_code = ReadFile(filename, out);
if (read_error_code != WC24_OK)
{
return_value = read_error_code;
return;
}
return_value = WC24_OK;
return;
});
return return_value;
}
ErrorCode DeleteFileFromVFF(const std::string& path, const std::string& filename,
const std::shared_ptr<FS::FileSystem>& fs)
{
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;
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;
}
Common::ScopeGuard unmount_guard{[] { f_unmount(""); }};
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;
}
const FRESULT unlink_code = f_unlink(filename.c_str());
if (unlink_code != FR_OK)
{
ERROR_LOG_FMT(IOS_WC24, "Failed to delete file {} in VFF at: {} Code: {}", filename, path,
static_cast<u32>(unlink_code));
return_value = WC24_ERR_BROKEN;
return;
}
return_value = WC24_OK;
return;
});
return return_value;
}
} // namespace IOS::HLE::NWC24 } // namespace IOS::HLE::NWC24

View File

@ -22,8 +22,12 @@ namespace NWC24
constexpr u16 SECTOR_SIZE = 512; constexpr u16 SECTOR_SIZE = 512;
constexpr u16 VF_LITTLE_ENDIAN = 0xFFFE; constexpr u16 VF_LITTLE_ENDIAN = 0xFFFE;
constexpr u16 VF_BIG_ENDIAN = 0xFEFF; constexpr u16 VF_BIG_ENDIAN = 0xFEFF;
ErrorCode OpenVFF(const std::string& path, const std::string& filename, ErrorCode WriteToVFF(const std::string& path, const std::string& filename,
const std::shared_ptr<FS::FileSystem>& fs, const std::vector<u8>& data); const std::shared_ptr<FS::FileSystem>& fs, const std::vector<u8>& data);
ErrorCode ReadFromVFF(const std::string& path, const std::string& filename,
const std::shared_ptr<FS::FileSystem>& fs, std::vector<u8>& out);
ErrorCode DeleteFileFromVFF(const std::string& path, const std::string& filename,
const std::shared_ptr<FS::FileSystem>& fs);
#pragma pack(push, 1) #pragma pack(push, 1)
struct VFFHeader final struct VFFHeader final

View File

@ -366,6 +366,7 @@
<ClInclude Include="Core\IOS\Network\KD\VFF\VFFUtil.h" /> <ClInclude Include="Core\IOS\Network\KD\VFF\VFFUtil.h" />
<ClInclude Include="Core\IOS\Network\KD\WC24File.h" /> <ClInclude Include="Core\IOS\Network\KD\WC24File.h" />
<ClInclude Include="Core\IOS\Network\KD\Mail\MailCommon.h" /> <ClInclude Include="Core\IOS\Network\KD\Mail\MailCommon.h" />
<ClInclude Include="Core\IOS\Network\KD\Mail\WC24FriendList.h" />
<ClInclude Include="Core\IOS\Network\KD\Mail\WC24Send.h" /> <ClInclude Include="Core\IOS\Network\KD\Mail\WC24Send.h" />
<ClInclude Include="Core\IOS\Network\MACUtils.h" /> <ClInclude Include="Core\IOS\Network\MACUtils.h" />
<ClInclude Include="Core\IOS\Network\NCD\Manage.h" /> <ClInclude Include="Core\IOS\Network\NCD\Manage.h" />
@ -1017,6 +1018,7 @@
<ClCompile Include="Core\IOS\Network\KD\NWC24Config.cpp" /> <ClCompile Include="Core\IOS\Network\KD\NWC24Config.cpp" />
<ClCompile Include="Core\IOS\Network\KD\NWC24DL.cpp" /> <ClCompile Include="Core\IOS\Network\KD\NWC24DL.cpp" />
<ClCompile Include="Core\IOS\Network\KD\VFF\VFFUtil.cpp" /> <ClCompile Include="Core\IOS\Network\KD\VFF\VFFUtil.cpp" />
<ClCompile Include="Core\IOS\Network\KD\Mail\WC24FriendList.cpp" />
<ClCompile Include="Core\IOS\Network\KD\Mail\WC24Send.cpp" /> <ClCompile Include="Core\IOS\Network\KD\Mail\WC24Send.cpp" />
<ClCompile Include="Core\IOS\Network\MACUtils.cpp" /> <ClCompile Include="Core\IOS\Network\MACUtils.cpp" />
<ClCompile Include="Core\IOS\Network\NCD\Manage.cpp" /> <ClCompile Include="Core\IOS\Network\NCD\Manage.cpp" />