IOS/KD: Implement receiving mail

This commit is contained in:
Sketch 2023-12-10 20:39:16 -05:00 committed by Admiral H. Curtiss
parent e46d7500a1
commit 60f0ad501a
No known key found for this signature in database
GPG Key ID: F051B4C4044F33FB
14 changed files with 1232 additions and 7 deletions

View File

@ -393,8 +393,12 @@ add_library(core
IOS/Network/KD/VFF/VFFUtil.h
IOS/Network/KD/WC24File.h
IOS/Network/KD/Mail/MailCommon.h
IOS/Network/KD/Mail/MailParser.cpp
IOS/Network/KD/Mail/MailParser.h
IOS/Network/KD/Mail/WC24FriendList.cpp
IOS/Network/KD/Mail/WC24FriendList.h
IOS/Network/KD/Mail/WC24Receive.cpp
IOS/Network/KD/Mail/WC24Receive.h
IOS/Network/KD/Mail/WC24Send.cpp
IOS/Network/KD/Mail/WC24Send.h
IOS/Network/MACUtils.cpp
@ -649,6 +653,7 @@ PRIVATE
fmt::fmt
LZO::LZO
LZ4::LZ4
MultipartParser
ZLIB::ZLIB
)

View File

@ -10,6 +10,9 @@
namespace IOS::HLE::NWC24::Mail
{
// Friend code used by Nintendo to send announcements.
static constexpr u64 NINTENDO_FRIEND_CODE = 9999999900000000ULL;
constexpr u32 MAIL_LIST_MAGIC = 0x57635466; // WcTf
inline u32 CalculateFileOffset(u32 index)
@ -17,6 +20,17 @@ inline u32 CalculateFileOffset(u32 index)
return Common::swap32(128 + (index * 128));
}
constexpr u32 PackData(u32 one, u32 two)
{
return (one & 0xFFFFF) | two << 20;
}
enum class FlagOP
{
Or,
And
};
#pragma pack(push, 1)
struct MailListHeader final
{
@ -55,7 +69,7 @@ struct MailListEntry final
u64 from_friend_code;
u32 minutes_since_1900;
u32 padding;
u8 always_1;
u8 number_of_recipients;
u8 number_of_multipart_entries;
u16 app_group;
u32 packed_from;

View File

@ -0,0 +1,473 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/IOS/Network/KD/Mail/MailParser.h"
#include "Common/Align.h"
#include "Common/BitUtils.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Core/IOS/Network/KD/Mail/MailCommon.h"
#include <chrono>
#include <regex>
namespace IOS::HLE::NWC24::Mail
{
MailParser::MailParser(const std::string& boundary, const u32 num_of_mail,
WC24ReceiveList* receive_list)
: m_receive_list(receive_list), m_message_data(num_of_mail + 1), m_headers(num_of_mail + 1)
{
m_parser.setBoundary(boundary);
m_parser.onPartBegin = EmptyCallback;
m_parser.onHeaderField = [&](const char* buf, size_t start, size_t end, void* user_data) {
const Header header_key = std::make_pair(std::string(buf + start, end - start), std::string());
m_headers[current_index].push_back(header_key);
};
m_parser.onHeaderValue = [&](const char* buf, size_t start, size_t end, void* user_data) {
m_headers[current_index][current_header].second = std::string(buf + start, end - start);
};
m_parser.onHeaderEnd = [&](const char* buf, size_t start, size_t end, void* user_data) {
current_header++;
};
m_parser.onPartData = [&](const char* buf, size_t start, size_t end, void* user_data) {
m_message_data[current_index].append(std::string(buf + start, end - start));
};
m_parser.onPartEnd = [&](const char* buf, size_t start, size_t end, void* user_data) {
current_index++;
current_header = 0;
};
m_parser.onEnd = EmptyCallback;
}
ErrorCode MailParser::Parse(std::string_view buf)
{
m_parser.feed(reinterpret_cast<const char*>(buf.data()), buf.size());
if (m_parser.hasError())
{
ERROR_LOG_FMT(IOS_WC24, "Mail parser failed with error: {}", m_parser.getErrorMessage());
return WC24_ERR_FATAL;
}
return WC24_OK;
}
std::vector<u8> MailParser::GetMessageData(u32 index) const
{
std::vector<u8> data{m_message_data[index].begin(), m_message_data[index].end()};
data.resize(Common::AlignUp(data.size(), 32));
return data;
}
std::string MailParser::GetHeaderValue(u32 index, std::string_view key,
IsMultipart is_multipart) const
{
// Multipart fields are parsed in a way that allow for the headers to be stored in a pair where
// we don't need to do any string parsing. The raw message on the other hand doesn't do that
// because we require the entire message to get offsets of many fields.
if (is_multipart == IsMultipart{true})
{
for (const auto& [name, value] : m_headers[index])
{
if (name == key)
{
return std::string{value};
}
}
return {};
}
else
{
std::string val{};
const std::vector<std::string> raw_fields = SplitString(m_message_data[index], '\n');
for (u32 i = 0; i < raw_fields.size(); i++)
{
std::vector<std::string> key_value = SplitString(raw_fields[i], ':');
if (Common::CaseInsensitiveEquals(key_value[0], key))
{
// There should always be a key value pair if the above is true.
if (key_value.size() < 2)
return {};
// The To header acts differently as there can be multiple recipients
// Each recipient is on a new line, seperated by a comma.
// Once we have reached a recipient with no comma, we are done.
if (key == "To" && key_value[1].find(',') != std::string::npos)
{
// Append the first recipient.
// There is a space at the beginning we need to get rid of
val += key_value[1].substr(1) + '\n';
for (u32 j = i + 1; j < raw_fields.size() - i + 1; j++)
{
val += raw_fields[j] + '\n';
if (raw_fields[j].find(',') == std::string::npos)
{
// Remove CRLF newlines and break
val.erase(val.size() - 2);
break;
}
}
break;
}
// Remove the header key and join the rest of the strings
key_value.erase(key_value.begin());
val = StripWhitespace(JoinStrings(key_value, ":"));
break;
}
}
return val;
}
}
u32 MailParser::GetHeaderLength(u32 index) const
{
return m_message_data[index].find("\r\n\r") + 4;
}
std::string MailParser::GetMessage(u32 index, IsMultipart is_multipart) const
{
if (is_multipart == IsMultipart{true})
return m_message_data[index];
return m_message_data[index].substr(GetHeaderLength(index), m_message_data[index].size());
}
std::string MailParser::GetFullMessage(u32 index) const
{
return m_message_data[index];
}
ErrorCode MailParser::ParseContentTransferEncoding(u32 index, u32 receive_index,
IsMultipart is_multipart) const
{
std::string message = GetMessage(index, is_multipart);
u32 message_length = message.size();
const std::string str_transfer_enc =
GetHeaderValue(index, "Content-Transfer-Encoding", is_multipart);
if (str_transfer_enc.empty())
{
// If it is empty the Wii will assume either 7-bit or 8-bit.
m_receive_list->SetMessageLength(receive_index, message_length);
m_receive_list->SetEncodedMessageLength(receive_index, message_length);
// We will return not found to tell us that the field does not exist.
// This ensures we do not set the content transfer encoding field in multipart.
return WC24_ERR_NOT_FOUND;
}
if (str_transfer_enc == "7bit" || str_transfer_enc == "8bit")
{
m_receive_list->SetEncodedMessageLength(receive_index, message_length);
m_receive_list->SetMessageLength(receive_index, message_length);
}
else if (str_transfer_enc == "base64")
{
m_receive_list->SetEncodedMessageLength(receive_index, message_length);
// Remove the newlines and base64 padding.
const u32 padding = std::count(message.begin(), message.end(), '=');
message.erase(std::remove(message.begin(), message.end(), '\r'), message.end());
message.erase(std::remove(message.begin(), message.end(), '\n'), message.end());
message_length = message.size() - padding;
m_receive_list->SetMessageLength(receive_index, (message_length * 3) / 4);
}
else
{
// TODO (Sketch): Implement quoted-printable. I do not know what app(s) uses this encoding, but
// it is supported by KD.
return WC24_ERR_NOT_SUPPORTED;
}
if (is_multipart != IsMultipart{true})
{
// We don't have the required data to do this for multipart.
const u32 offset = m_message_data[index].find("Content-Transfer-Encoding") + 27;
const u32 size = str_transfer_enc.size();
m_receive_list->SetPackedContentTransferEncoding(receive_index, offset, size);
}
return WC24_OK;
}
ErrorCode MailParser::ParseContentType(u32 index, u32 receive_index, IsMultipart is_multipart)
{
const std::string str_type = GetHeaderValue(index, "Content-Type", is_multipart);
if (str_type.empty())
return WC24_ERR_FORMAT;
const std::vector<std::string> values = SplitString(str_type, ';');
std::string str_content_type = values[0];
Common::ToLower(&str_content_type);
const auto content_type_iter = m_content_types.find(str_content_type);
if (content_type_iter == m_content_types.end())
return WC24_ERR_NOT_SUPPORTED;
m_content_type_str = content_type_iter->first;
m_content_type = content_type_iter->second;
if (m_content_type == ContentType::Plain)
{
if (values.size() < 2)
return WC24_ERR_NOT_SUPPORTED;
if (is_multipart != IsMultipart{true})
m_receive_list->UpdateFlag(receive_index, 0xfffeffff, FlagOP::And);
m_charset = values[1].substr(9);
if (is_multipart == IsMultipart{true})
return WC24_OK;
const u32 offset = GetFullMessage(index).find("Content-Type:") + str_content_type.size() + 24;
m_receive_list->SetPackedCharset(receive_index, offset, m_charset.size());
}
else if (m_content_type == ContentType::Alt || m_content_type == ContentType::Mixed ||
m_content_type == ContentType::Related)
{
if (is_multipart != IsMultipart{true})
m_receive_list->UpdateFlag(receive_index, 0x10000, FlagOP::Or);
std::string boundary_val{};
if (values.size() < 2)
{
// With emails, this typically evaluates to false. On the occasion that we receive a multipart
// message from a Wii, the boundary is stored two lines below the content type. A much safer
// way to get it is to search for it.
const u32 boundary_pos = GetFullMessage(index).find("boundary=\"");
boundary_val = m_message_data[index].substr(boundary_pos + 10);
boundary_val = boundary_val.substr(0, boundary_val.find('"'));
}
else
{
boundary_val = values[1];
boundary_val.erase(0, 11);
boundary_val.erase(boundary_val.end() - 1, boundary_val.end());
}
const std::string boundary = fmt::format("--{}", boundary_val);
MailParser parser{boundary_val, 2, m_receive_list};
// The multipart parser is very particular, we need to find `--boundary` and pass only that.
std::string multipart_message = m_message_data[index];
const size_t multipart_offset = multipart_message.find(boundary);
if (multipart_offset == std::string::npos)
return WC24_ERR_FORMAT;
parser.Parse(multipart_message.substr(multipart_offset));
// The receive list has space for 3 multipart fields. If there are more in the message, they
// will be ignored.
for (int i = 0; i < 3; i++)
{
ErrorCode reply = parser.ParseMultipartField(this, index, i, receive_index);
if (reply != WC24_OK)
{
return reply;
}
}
}
else
{
if (is_multipart != IsMultipart{true})
m_receive_list->UpdateFlag(receive_index, 0xfffeffff, FlagOP::And);
}
return WC24_OK;
}
ErrorCode MailParser::ParseMultipartField(const MailParser* parent, u32 parent_index,
u32 multipart_index, u32 receive_index)
{
if (m_headers[multipart_index].empty())
return WC24_OK;
// The first multipart field is written to the base fields. The rest are put in their special
// fields.
if (multipart_index == 0)
{
const u32 offset = parent->GetFullMessage(parent_index).find(m_parser.boundary);
ErrorCode err = ParseContentTransferEncoding(multipart_index, receive_index, IsMultipart{true});
if (err != WC24_OK && err != WC24_ERR_NOT_FOUND)
return err;
// Set the transfer encoding if needed.
if (err == WC24_OK)
{
const u32 content_encoding_offset =
parent->GetFullMessage(parent_index).find("Content-Transfer-Encoding", offset) + 27;
const u32 size =
GetHeaderValue(multipart_index, "Content-Transfer-Encoding", IsMultipart{true}).size();
m_receive_list->SetPackedContentTransferEncoding(receive_index, content_encoding_offset,
size);
}
err = ParseContentType(multipart_index, receive_index, IsMultipart{true});
if (err != WC24_OK)
return err;
// Set the content type
const u32 content_type_size = m_charset.size();
const u32 content_type_offset =
parent->GetFullMessage(parent_index).find("Content-Type", offset) +
m_content_type_str.size() + 24;
m_receive_list->SetPackedCharset(receive_index, content_type_offset, content_type_size);
// Finally the message offset.
m_receive_list->SetMessageOffset(
receive_index,
parent->GetFullMessage(parent_index).find(GetMessage(multipart_index, IsMultipart{true})));
return WC24_OK;
}
const u32 offset =
parent->GetFullMessage(parent_index).find(GetMessage(multipart_index, IsMultipart{true}));
const ErrorCode err = ParseContentType(multipart_index, receive_index, IsMultipart{true});
if (err != WC24_OK)
return err;
m_receive_list->SetMultipartContentType(receive_index, multipart_index - 1,
static_cast<u32>(m_content_type));
std::string message = GetMessage(multipart_index, IsMultipart{true});
m_receive_list->SetMultipartField(receive_index, multipart_index - 1, offset, message.size());
// Remove the newlines and base64 padding.
const u32 padding = std::count(message.begin(), message.end(), '=');
message.erase(std::remove(message.begin(), message.end(), '\r'), message.end());
message.erase(std::remove(message.begin(), message.end(), '\n'), message.end());
const u32 message_length = message.size() - padding;
m_receive_list->SetMultipartSize(receive_index, multipart_index - 1, (message_length * 3) / 4);
return WC24_OK;
}
void MailParser::ParseDate(u32 index, u32 receive_index) const
{
// The date that it wants is minutes since January 1st 1900.
const std::string date = GetHeaderValue(index, "Date");
if (date.empty())
return;
std::tm time{};
std::istringstream ss(date);
ss >> std::get_time(&time, "%d %b %Y %T %z");
const u32 seconds_since_1900 = mktime(&time) + MINUTES_FROM_EPOCH_TO_1900;
m_receive_list->SetTime(receive_index, static_cast<u32>(std::floor(seconds_since_1900 / 60)));
}
ErrorCode MailParser::ParseFrom(u32 index, u32 receive_index, WC24FriendList& friend_list) const
{
u64 friend_code{};
u64 value{};
const std::string str_friend = GetHeaderValue(index, "From");
if (str_friend.empty())
return WC24_ERR_FORMAT;
// Determine if this is a Wii sender or email.
if (std::regex_search(str_friend, m_wii_number_regex))
{
friend_code = std::stoull(str_friend.substr(1, 16), nullptr, 10);
value = friend_code;
}
else
{
// For emails, the friend code stored in the nwc24fl.bin differs from the value we need to set.
friend_code = WC24FriendList::ConvertEmailToFriendCode(str_friend);
value = u64{GetFullMessage(index).find("From") + 2} << 32 | static_cast<u64>(str_friend.size());
}
if (!friend_list.IsFriend(Common::swap64(friend_code)) && friend_code != NINTENDO_FRIEND_CODE)
{
WARN_LOG_FMT(IOS_WC24, "Received message from someone who is not a friend, discarding.");
return WC24_ERR_NOT_FOUND;
}
m_receive_list->SetFromFriendCode(receive_index, value);
const u32 from_address_size = str_friend.size();
const u32 offset = m_message_data[index].find("From:") + 6;
m_receive_list->SetPackedFrom(receive_index, offset, from_address_size);
return WC24_OK;
}
void MailParser::ParseSubject(u32 index, u32 receive_index) const
{
const std::string subject = GetHeaderValue(index, "Subject");
if (subject.empty())
return;
const u32 offset = m_message_data[index].find("Subject") + 9;
m_receive_list->SetPackedSubject(receive_index, offset, subject.size());
}
ErrorCode MailParser::ParseTo(u32 index, u32 receive_index) const
{
const std::string to = GetHeaderValue(index, "To");
if (to.empty())
return WC24_ERR_FORMAT;
const u32 offset = m_message_data[index].find("To") + 4;
m_receive_list->SetPackedTo(receive_index, offset, to.size());
m_receive_list->SetNumberOfRecipients(receive_index,
static_cast<u8>(std::count(to.begin(), to.end(), ',')) + 1);
return WC24_OK;
}
ErrorCode MailParser::ParseWiiAppId(u32 index, u32 receive_index) const
{
const std::string full_id = GetHeaderValue(index, "X-Wii-AppId");
if (full_id.empty())
{
// Fallback to default values
m_receive_list->SetWiiAppGroupId(receive_index, 0);
m_receive_list->SetWiiAppId(receive_index, 0);
return WC24_OK;
}
// Sanity checks to make sure everything is valid.
const char s = full_id.at(0);
if (full_id.size() != 15 || s - 48 > 4 || full_id.at(1) != '-' || full_id.at(10) != '-')
return WC24_ERR_FORMAT;
if ((s & 1) != 0)
m_receive_list->UpdateFlag(receive_index, 4, FlagOP::Or);
if (Common::ExtractBit(s, 1) != 0)
m_receive_list->UpdateFlag(receive_index, 8, FlagOP::Or);
// Determine the app ID.
u32 app_id = std::stoul(full_id.substr(2, 10), nullptr, 16);
m_receive_list->SetWiiAppId(receive_index, app_id);
// Now the group id.
u32 group_id = std::stoul(full_id.substr(11, 14), nullptr, 16);
m_receive_list->SetWiiAppGroupId(receive_index, static_cast<u16>(group_id));
return WC24_OK;
}
ErrorCode MailParser::ParseWiiCmd(u32 index, u32 receive_index) const
{
std::string str_cmd = GetHeaderValue(index, "X-Wii-Cmd");
if (str_cmd.empty())
return WC24_OK;
if (str_cmd.size() != 8)
return WC24_ERR_FORMAT;
u32 cmd = std::stoul(str_cmd, nullptr, 16);
m_receive_list->SetWiiCmd(receive_index, cmd);
return WC24_OK;
}
} // namespace IOS::HLE::NWC24::Mail

View File

@ -0,0 +1,103 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <MultipartParser.h>
#include "Common/CommonTypes.h"
#include "Core/IOS/Network/KD/Mail/WC24FriendList.h"
#include "Core/IOS/Network/KD/Mail/WC24Receive.h"
#include "Core/IOS/Network/KD/NWC24Config.h"
#include <map>
#include <regex>
#include <string>
#include <vector>
namespace IOS::HLE
{
namespace FS
{
class FileSystem;
}
namespace NWC24::Mail
{
class MailParser final
{
public:
MailParser(const std::string& boundary, u32 num_of_mail, WC24ReceiveList* receive_list);
// Used with the mail flag
enum class ContentType : u32
{
Plain = 0x10000,
HTML = 0x10001,
Binary = 0x30000,
MessageBoard = 0x30001,
WiiMini = 0x30002,
Jpeg = 0x20000,
WiiPicture = 0x20001,
Mixed = 0xF0000,
Alt = 0xF0001,
Related = 0xF0002
};
enum class IsMultipart : bool;
ErrorCode Parse(std::string_view buf);
ErrorCode ParseContentTransferEncoding(u32 index, u32 receive_index,
IsMultipart is_multipart = IsMultipart{false}) const;
ErrorCode ParseContentType(u32 index, u32 receive_index,
IsMultipart is_multipart = IsMultipart{false});
void ParseDate(u32 index, u32 receive_index) const;
ErrorCode ParseFrom(u32 index, u32 receive_index, WC24FriendList& friend_list) const;
void ParseSubject(u32 index, u32 receive_index) const;
ErrorCode ParseTo(u32 index, u32 receive_index) const;
ErrorCode ParseWiiAppId(u32 index, u32 receive_index) const;
ErrorCode ParseWiiCmd(u32 index, u32 receive_index) const;
std::vector<u8> GetMessageData(u32 index) const;
std::string GetMessage(u32 index, IsMultipart is_multipart = IsMultipart{false}) const;
std::string GetFullMessage(u32 index) const;
std::string GetHeaderValue(u32 index, std::string_view key,
IsMultipart is_multipart = IsMultipart{false}) const;
u32 GetHeaderLength(u32 index) const;
private:
static void EmptyCallback(const char* buffer, size_t start, size_t end, void* user_data){};
ErrorCode ParseMultipartField(const MailParser* parent, u32 parent_index, u32 multipart_index,
u32 receive_index);
// Minutes from January 1st 1900 to Unix epoch.
static constexpr u32 MINUTES_FROM_EPOCH_TO_1900 = 2208988800;
const std::map<std::string, ContentType> m_content_types = {
{"application/octet-stream", ContentType::Binary},
{"application/x-wii-minidata", ContentType::WiiMini},
{"application/x-wii-msgboard", ContentType::MessageBoard},
{"image/jpeg", ContentType::Jpeg},
{"image/x-wii-picture", ContentType::WiiPicture},
{"multipart/alternative", ContentType::Alt},
{"multipart/mixed", ContentType::Mixed},
{"multipart/related", ContentType::Related},
{"text/html", ContentType::HTML},
{"text/plain", ContentType::Plain},
};
using Header = std::pair<std::string, std::string>;
using Headers = std::vector<Header>;
MultipartParser m_parser{};
WC24ReceiveList* m_receive_list;
std::vector<std::string> m_message_data;
std::vector<Headers> m_headers;
std::regex m_wii_number_regex{"w\\d{16}"};
u32 current_index{};
u32 current_header{};
std::string m_charset{};
std::string m_content_type_str{};
ContentType m_content_type{};
};
} // namespace NWC24::Mail
} // namespace IOS::HLE

View File

@ -13,6 +13,7 @@
#include "Common/Swap.h"
#include "Core/IOS/FS/FileSystem.h"
#include "Core/IOS/Network/KD/Mail/MailCommon.h"
#include "Core/IOS/Uids.h"
namespace IOS::HLE::NWC24::Mail
@ -56,12 +57,28 @@ bool WC24FriendList::CheckFriendList() const
return true;
}
bool WC24FriendList::DoesFriendExist(u64 friend_id) const
bool WC24FriendList::IsFriend(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; });
}
bool WC24FriendList::IsFriendEstablished(u64 code) const
{
if (code == Common::swap64(NINTENDO_FRIEND_CODE))
return true;
for (u32 i = 0; i < MAX_ENTRIES; i++)
{
if (Common::swap64(m_data.friend_codes[i]) == code)
{
return m_data.entries[i].status == Common::swap32(static_cast<u32>(FriendStatus::Confirmed));
}
}
return false;
}
std::vector<u64> WC24FriendList::GetUnconfirmedFriends() const
{
std::vector<u64> friends{};
@ -100,4 +117,15 @@ u64 WC24FriendList::ConvertEmailToFriendCode(std::string_view email)
return u64{lower} << 32 | upper;
}
void WC24FriendList::SetFriendStatus(u64 code, FriendStatus status)
{
for (u32 i = 0; i < MAX_ENTRIES; i++)
{
if (Common::swap64(m_data.friend_codes[i]) == code)
{
m_data.entries[i].status = Common::swap32(static_cast<u32>(status));
return;
}
}
}
} // namespace IOS::HLE::NWC24::Mail

View File

@ -33,7 +33,7 @@ public:
bool CheckFriendList() const;
void WriteFriendList() const;
bool DoesFriendExist(u64 friend_id) const;
bool IsFriend(u64 friend_id) const;
std::vector<u64> GetUnconfirmedFriends() const;
private:

View File

@ -0,0 +1,283 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/IOS/Network/KD/Mail/WC24Receive.h"
#include "Common/Assert.h"
#include "Common/Swap.h"
#include "Core/IOS/FS/FileSystem.h"
#include "Core/IOS/Uids.h"
namespace IOS::HLE::NWC24::Mail
{
constexpr char RECEIVE_LIST_PATH[] = "/" WII_WC24CONF_DIR "/mbox"
"/wc24recv.ctl";
WC24ReceiveList::WC24ReceiveList(std::shared_ptr<FS::FileSystem> fs) : m_fs{std::move(fs)}
{
if (!ReadReceiveList())
{
ERROR_LOG_FMT(IOS_WC24, "There is an error in the Receive List for WC24 mail. Mail will be "
"unavailable for this IOS session.");
m_is_disabled = true;
// If the Send list is corrupted, delete it.
const FS::ResultCode result = m_fs->Delete(PID_KD, PID_KD, RECEIVE_LIST_PATH);
if (result != FS::ResultCode::Success && result != FS::ResultCode::NotFound)
{
ERROR_LOG_FMT(IOS_WC24, "Failed to delete the Receive list.");
}
}
}
bool WC24ReceiveList::ReadReceiveList()
{
const auto file = m_fs->OpenFile(PID_KD, PID_KD, RECEIVE_LIST_PATH, FS::Mode::Read);
if (!file || !file->Read(&m_data, 1))
{
ERROR_LOG_FMT(IOS_WC24, "Failed to read the Receive list");
return false;
}
if (!CheckReceiveList())
{
ERROR_LOG_FMT(IOS_WC24, "There is an error in the Receive List for WC24 mail");
return false;
}
return true;
}
void WC24ReceiveList::WriteReceiveList() const
{
ASSERT(!IsDisabled());
constexpr FS::Modes public_modes{FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite};
m_fs->CreateFullPath(PID_KD, PID_KD, RECEIVE_LIST_PATH, 0, public_modes);
const auto file = m_fs->CreateAndOpenFile(PID_KD, PID_KD, RECEIVE_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 WC24ReceiveList::CheckReceiveList() const
{
// 'WcTF' magic
if (Common::swap32(m_data.header.magic) != MAIL_LIST_MAGIC)
{
ERROR_LOG_FMT(IOS_WC24, "Receive List magic mismatch ({} != {})",
Common::swap32(m_data.header.magic), MAIL_LIST_MAGIC);
return false;
}
if (Common::swap32(m_data.header.version) != 4)
{
ERROR_LOG_FMT(IOS_WC24, "Receive List version mismatch");
return false;
}
return true;
}
bool WC24ReceiveList::IsDisabled() const
{
return m_is_disabled;
}
u32 WC24ReceiveList::GetNextEntryId() const
{
ASSERT(!IsDisabled());
return Common::swap32(m_data.header.next_entry_id);
}
u32 WC24ReceiveList::GetNextEntryIndex() const
{
ASSERT(!IsDisabled());
return (Common::swap32(m_data.header.next_entry_offset) - 128) / 128;
}
void WC24ReceiveList::InitFlag(u32 index)
{
ASSERT(!IsDisabled());
m_data.entries[index].flag = 0x200200;
}
void WC24ReceiveList::FinalizeEntry(u32 index)
{
ASSERT(!IsDisabled());
u32 next_entry_index = UINT32_MAX;
for (u32 i = 0; i < MAX_ENTRIES; i++)
{
if (m_data.entries[i].id == 0)
{
next_entry_index = i;
break;
}
}
if (next_entry_index == UINT32_MAX)
{
// If the file is full, it will clear the first entry.
ClearEntry(0);
next_entry_index = 0;
}
m_data.entries[index].flag = Common::swap32(m_data.entries[index].flag | 0x220);
m_data.entries[index].always_0x80000000 = Common::swap32(0x80000000);
m_data.header.next_entry_offset = CalculateFileOffset(next_entry_index);
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(Common::swap32(m_data.header.next_entry_id) + 1);
}
void WC24ReceiveList::ClearEntry(u32 index)
{
ASSERT(!IsDisabled());
std::memset(&m_data.entries[index], 0, sizeof(MailListEntry));
}
u32 WC24ReceiveList::GetAppID(u32 index) const
{
ASSERT(!IsDisabled());
return Common::swap32(m_data.entries[index].app_id);
}
u16 WC24ReceiveList::GetAppGroup(u32 index) const
{
ASSERT(!IsDisabled());
return Common::swap16(m_data.entries[index].app_group);
}
void WC24ReceiveList::UpdateFlag(u32 index, u32 value, FlagOP op)
{
ASSERT(!IsDisabled());
if (op == FlagOP::Or)
m_data.entries[index].flag |= value;
else
m_data.entries[index].flag &= value;
}
void WC24ReceiveList::SetMessageId(u32 index, u32 id)
{
ASSERT(!IsDisabled());
m_data.entries[index].id = Common::swap32(id);
}
void WC24ReceiveList::SetMessageSize(u32 index, u32 size)
{
ASSERT(!IsDisabled());
m_data.entries[index].msg_size = Common::swap32(size);
}
void WC24ReceiveList::SetHeaderLength(u32 index, u32 size)
{
ASSERT(!IsDisabled());
m_data.entries[index].header_length = Common::swap32(size);
}
void WC24ReceiveList::SetMessageOffset(u32 index, u32 offset)
{
ASSERT(!IsDisabled());
m_data.entries[index].message_offset = Common::swap32(offset);
}
void WC24ReceiveList::SetEncodedMessageLength(u32 index, u32 size)
{
ASSERT(!IsDisabled());
m_data.entries[index].encoded_length = Common::swap32(size);
}
void WC24ReceiveList::SetMessageLength(u32 index, u32 size)
{
ASSERT(!IsDisabled());
m_data.entries[index].message_length = Common::swap32(size);
}
void WC24ReceiveList::SetPackedContentTransferEncoding(u32 index, u32 offset, u32 size)
{
ASSERT(!IsDisabled());
m_data.entries[index].packed_transfer_encoding = Common::swap32(PackData(offset, size));
}
void WC24ReceiveList::SetPackedCharset(u32 index, u32 offset, u32 size)
{
ASSERT(!IsDisabled());
m_data.entries[index].packed_charset = Common::swap32(PackData(offset, size));
}
void WC24ReceiveList::SetTime(u32 index, u32 time)
{
ASSERT(!IsDisabled());
m_data.entries[index].minutes_since_1900 = Common::swap32(time);
}
void WC24ReceiveList::SetFromFriendCode(u32 index, u64 friend_code)
{
ASSERT(!IsDisabled());
m_data.entries[index].from_friend_code = Common::swap64(friend_code);
}
void WC24ReceiveList::SetPackedFrom(u32 index, u32 offset, u32 size)
{
ASSERT(!IsDisabled());
m_data.entries[index].packed_from = Common::swap32(PackData(offset, size));
}
void WC24ReceiveList::SetPackedSubject(u32 index, u32 offset, u32 size)
{
ASSERT(!IsDisabled());
m_data.entries[index].packed_subject = Common::swap32(PackData(offset, size));
}
void WC24ReceiveList::SetPackedTo(u32 index, u32 offset, u32 size)
{
ASSERT(!IsDisabled());
m_data.entries[index].packed_to = Common::swap32(PackData(offset, size));
}
void WC24ReceiveList::SetWiiAppId(u32 index, u32 id)
{
ASSERT(!IsDisabled());
m_data.entries[index].app_id = Common::swap32(id);
}
void WC24ReceiveList::SetWiiAppGroupId(u32 index, u16 id)
{
ASSERT(!IsDisabled());
m_data.entries[index].app_group = Common::swap16(id);
}
void WC24ReceiveList::SetWiiCmd(u32 index, u32 cmd)
{
ASSERT(!IsDisabled());
m_data.entries[index].wii_cmd = Common::swap32(cmd);
}
void WC24ReceiveList::SetNumberOfRecipients(u32 index, u8 num)
{
ASSERT(!IsDisabled());
m_data.entries[index].number_of_recipients = num;
}
void WC24ReceiveList::SetMultipartField(u32 index, u32 multipart_index, u32 offset, u32 size)
{
ASSERT(!IsDisabled());
m_data.entries[index].multipart_entries[multipart_index] =
MultipartEntry{Common::swap32(offset), Common::swap32(size)};
m_data.entries[index].number_of_multipart_entries++;
}
void WC24ReceiveList::SetMultipartContentType(u32 index, u32 multipart_index, u32 type)
{
ASSERT(!IsDisabled());
m_data.entries[index].multipart_content_types[multipart_index] = Common::swap32(type);
}
void WC24ReceiveList::SetMultipartSize(u32 index, u32 multipart_index, u32 size)
{
ASSERT(!IsDisabled());
m_data.entries[index].multipart_sizes[multipart_index] = Common::swap32(size);
}
std::string WC24ReceiveList::GetMailPath(const u32 index)
{
return fmt::format("mb/r{:07d}.msg", index);
}
} // namespace IOS::HLE::NWC24::Mail

View File

@ -0,0 +1,83 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <string>
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Core/IOS/Network/KD/Mail/MailCommon.h"
#include "Core/IOS/Network/KD/NWC24Config.h"
namespace IOS::HLE
{
namespace FS
{
class FileSystem;
}
namespace NWC24::Mail
{
constexpr const char RECEIVE_BOX_PATH[] = "/" WII_WC24CONF_DIR "/mbox"
"/wc24recv.mbx";
class WC24ReceiveList final
{
public:
static std::string GetMailPath(u32 index);
explicit WC24ReceiveList(std::shared_ptr<FS::FileSystem> fs);
bool ReadReceiveList();
bool CheckReceiveList() const;
void WriteReceiveList() const;
bool IsDisabled() const;
u32 GetNextEntryId() const;
u32 GetNextEntryIndex() const;
u32 GetAppID(u32 index) const;
u16 GetAppGroup(u32 index) const;
void FinalizeEntry(u32 index);
void ClearEntry(u32 index);
void InitFlag(u32 index);
void UpdateFlag(u32 index, u32 value, FlagOP op);
void SetMessageId(u32 index, u32 id);
void SetMessageSize(u32 index, u32 size);
void SetHeaderLength(u32 index, u32 size);
void SetMessageOffset(u32 index, u32 offset);
void SetEncodedMessageLength(u32 index, u32 size);
void SetMessageLength(u32 index, u32 size);
void SetPackedContentTransferEncoding(u32 index, u32 offset, u32 size);
void SetPackedCharset(u32 index, u32 offset, u32 size);
void SetMultipartField(u32 index, u32 multipart_index, u32 offset, u32 size);
void SetMultipartContentType(u32 index, u32 multipart_index, u32 type);
void SetMultipartSize(u32 index, u32 multipart_index, u32 size);
void SetTime(u32 index, u32 time);
void SetFromFriendCode(u32 index, u64 friend_code);
void SetPackedFrom(u32 index, u32 offset, u32 size);
void SetPackedSubject(u32 index, u32 offset, u32 size);
void SetPackedTo(u32 index, u32 offset, u32 size);
void SetWiiAppId(u32 index, u32 id);
void SetWiiAppGroupId(u32 index, u16 id);
void SetWiiCmd(u32 index, u32 cmd);
void SetNumberOfRecipients(u32 index, u8 num);
static constexpr u32 MAX_ENTRIES = 255;
private:
#pragma pack(push, 1)
struct ReceiveList final
{
MailListHeader header;
std::array<MailListEntry, MAX_ENTRIES> entries;
};
#pragma pack(pop)
ReceiveList m_data;
std::shared_ptr<FS::FileSystem> m_fs;
bool m_is_disabled = false;
};
} // namespace NWC24::Mail
} // namespace IOS::HLE

View File

@ -24,8 +24,8 @@
namespace IOS::HLE::NWC24::Mail
{
constexpr const char SEND_LIST_PATH[] = "/" WII_WC24CONF_DIR "/mbox"
"/wc24send.ctl";
constexpr char SEND_LIST_PATH[] = "/" WII_WC24CONF_DIR "/mbox"
"/wc24send.ctl";
WC24SendList::WC24SendList(std::shared_ptr<FS::FileSystem> fs) : m_fs{std::move(fs)}
{

View File

@ -258,4 +258,14 @@ std::string_view NWC24Config::GetPassword() const
const size_t size = strnlen(m_data.paswd, MAX_PASSWORD_LENGTH);
return {m_data.paswd, size};
}
std::string NWC24Config::GetDeleteURL() const
{
return {m_data.http_urls[3]};
}
std::string NWC24Config::GetReceiveURL() const
{
return {m_data.http_urls[2]};
}
} // namespace IOS::HLE::NWC24

View File

@ -20,6 +20,7 @@ enum ErrorCode : s32
WC24_OK = 0,
WC24_ERR_FATAL = -1,
WC24_ERR_INVALID_VALUE = -3,
WC24_ERR_NOT_SUPPORTED = -4,
WC24_ERR_NULL = -5,
WC24_ERR_NOT_FOUND = -13,
WC24_ERR_BROKEN = -14,
@ -27,6 +28,7 @@ enum ErrorCode : s32
WC24_ERR_FILE_CLOSE = -17,
WC24_ERR_FILE_READ = -18,
WC24_ERR_FILE_WRITE = -19,
WC24_ERR_FORMAT = -24,
WC24_ERR_NETWORK = -31,
WC24_ERR_SERVER = -32,
WC24_ERR_ID_NOT_GENERATED = -34,
@ -76,6 +78,8 @@ public:
std::string GetCheckURL() const;
std::string GetSendURL() const;
std::string_view GetPassword() const;
std::string GetDeleteURL() const;
std::string GetReceiveURL() const;
NWC24CreationStage CreationStage() const;
void SetCreationStage(NWC24CreationStage creation_stage);

View File

@ -24,6 +24,7 @@
#include "Core/CommonTitles.h"
#include "Core/HW/Memmap.h"
#include "Core/IOS/FS/FileSystem.h"
#include "Core/IOS/Network/KD/Mail/MailParser.h"
#include "Core/IOS/Network/KD/NetKDTime.h"
#include "Core/IOS/Network/KD/VFF/VFFUtil.h"
#include "Core/IOS/Network/Socket.h"
@ -156,7 +157,7 @@ s32 NWC24MakeUserID(u64* nwc24_id, u32 hollywood_id, u16 id_ctr, HardwareModel h
NetKDRequestDevice::NetKDRequestDevice(EmulationKernel& ios, const std::string& device_name,
const std::shared_ptr<NetKDTimeDevice>& time_device)
: EmulationDevice(ios, device_name), m_config{ios.GetFS()}, m_dl_list{ios.GetFS()},
m_send_list{ios.GetFS()}, m_friend_list{ios.GetFS()}, m_time_device{time_device}
m_send_list{ios.GetFS()}, m_receive_list{ios.GetFS()}, m_friend_list{ios.GetFS()}, m_time_device{time_device}
{
// Enable all NWC24 permissions
m_scheduler_buffer[1] = Common::swap32(-1);
@ -169,7 +170,8 @@ NetKDRequestDevice::NetKDRequestDevice(EmulationKernel& ios, const std::string&
}
});
m_handle_mail = !ios.GetIOSC().IsUsingDefaultId() && !m_send_list.IsDisabled();
m_handle_mail = !ios.GetIOSC().IsUsingDefaultId() && !m_send_list.IsDisabled() &&
!m_receive_list.IsDisabled();
m_scheduler_work_queue.Reset("WiiConnect24 Scheduler Worker",
[](std::function<void()> task) { task(); });
@ -278,6 +280,16 @@ void NetKDRequestDevice::SchedulerWorker(const SchedulerEvent event)
{
LogError(ErrorType::CheckMail, code);
}
else if (mail_flag == 1)
{
code = KDReceiveMail();
if (code != NWC24::WC24_OK)
LogError(ErrorType::ReceiveMail, code);
code = KDSaveMail();
if (code != NWC24::WC24_OK)
LogError(ErrorType::ReceiveMail, code);
}
code = KDSendMail();
if (code != NWC24::WC24_OK)
@ -639,6 +651,206 @@ NWC24::ErrorCode NetKDRequestDevice::KDSendMail()
return NWC24::WC24_OK;
}
NWC24::ErrorCode NetKDRequestDevice::KDReceiveMail()
{
m_scheduler_buffer[4] = Common::swap32(3);
std::string form_data = fmt::format("mlid=w{}&passwd={}&maxsize={}", m_config.Id(),
m_config.GetPassword(), MAX_MAIL_RECEIVE_SIZE);
const Common::HttpRequest::Response response = m_http.Post(m_config.GetReceiveURL(), form_data);
if (!response)
{
ERROR_LOG_FMT(IOS_WC24,
"NET_KD_REQ: IOCTL_NWC24_RECEIVE_MAIL_NOW: Failed to request data at {}.",
m_config.GetCheckURL());
return NWC24::WC24_ERR_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_RECEIVE_MAIL_NOW: Mail server returned non-success code: {}",
code);
return NWC24::WC24_ERR_SERVER;
}
const std::string str_mail_num = GetValueFromCGIResponse(response_str, "mailnum");
s32 mail_num{};
const bool did_parse = TryParse(str_mail_num, &mail_num);
if (!did_parse)
{
ERROR_LOG_FMT(
IOS_WC24,
"NET_KD_REQ: IOCTL_NWC24_RECEIVE_MAIL_NOW: Mail server returned invalid number of mails.");
return NWC24::WC24_ERR_SERVER;
}
// Receive only saves the mail to FS. The SaveMailNow IOCTL is called to save to mailbox.
constexpr FS::Modes public_modes{FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite};
const auto file = m_ios.GetFS()->CreateAndOpenFile(PID_KD, PID_KD, TEMP_MAIL_PATH, public_modes);
if (!file || !file->Write(response->data(), response->size()))
{
ERROR_LOG_FMT(IOS_WC24,
"NET_KD_REQ: IOCTL_NWC24_RECEIVE_MAIL_NOW: Failed to write temporary mail file.");
return NWC24::WC24_ERR_FILE_WRITE;
}
// Now delete from the server.
form_data =
fmt::format("mlid=w{}&passwd={}&delnum={}", m_config.Id(), m_config.GetPassword(), mail_num);
m_http.Post(m_config.GetDeleteURL(), form_data);
m_scheduler_buffer[7] = Common::swap32(Common::swap32(m_scheduler_buffer[7]) + mail_num);
m_scheduler_buffer[12] = Common::swap32(Common::swap32(m_scheduler_buffer[12]) + 1);
m_scheduler_buffer[4] = 0;
return NWC24::WC24_OK;
}
NWC24::ErrorCode NetKDRequestDevice::KDSaveMail()
{
m_scheduler_buffer[4] = Common::swap32(6);
Common::ScopeGuard mail_del_guard([&] { m_ios.GetFS()->Delete(PID_KD, PID_KD, TEMP_MAIL_PATH); });
const auto file = m_ios.GetFS()->OpenFile(PID_KD, PID_KD, TEMP_MAIL_PATH, FS::Mode::Read);
if (!file)
{
ERROR_LOG_FMT(IOS_WC24,
"NET_KD_REQ: IOCTL_NWC24_SAVE_MAIL_NOW: Failed to open temporary mail file.");
return NWC24::WC24_ERR_FILE_OPEN;
}
std::string mail_str(file->GetStatus()->size, '\0');
if (!file->Read(mail_str.data(), file->GetStatus()->size))
{
ERROR_LOG_FMT(IOS_WC24,
"NET_KD_REQ: IOCTL_NWC24_SAVE_MAIL_NOW: Failed to read temporary mail file.");
return NWC24::WC24_ERR_FILE_READ;
}
const std::string boundary = mail_str.substr(0, mail_str.find('\r')).erase(0, 2);
u32 mail_num{};
const std::string str_mail_num = GetValueFromCGIResponse(mail_str, "mailnum");
if (!TryParse(str_mail_num, &mail_num))
{
ERROR_LOG_FMT(
IOS_WC24,
"NET_KD_REQ: IOCTL_NWC24_SAVE_MAIL_NOW: Mail server returned invalid number of mails.");
return NWC24::WC24_ERR_SERVER;
}
NWC24::Mail::MailParser parser{boundary, mail_num, &m_receive_list};
NWC24::ErrorCode reply = parser.Parse(mail_str);
if (reply != NWC24::WC24_OK)
return reply;
for (u32 i = 1; i <= mail_num; i++)
{
const u32 entry_index = m_receive_list.GetNextEntryIndex();
Common::ScopeGuard mail_parse_guard(
[&, entry_index] { m_receive_list.ClearEntry(entry_index); });
const u32 msg_id = m_receive_list.GetNextEntryId();
const std::vector<u8> data = parser.GetMessageData(i);
const u32 header_len = parser.GetHeaderLength(i);
m_receive_list.InitFlag(entry_index);
m_receive_list.SetMessageId(entry_index, msg_id);
m_receive_list.SetMessageSize(entry_index, data.size());
m_receive_list.SetHeaderLength(entry_index, header_len);
m_receive_list.SetMessageOffset(entry_index, header_len);
reply = parser.ParseFrom(i, entry_index, m_friend_list);
if (reply != NWC24::WC24_OK)
{
ERROR_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_SAVE_MAIL_NOW: Failed to parse From field.");
continue;
}
reply = parser.ParseContentTransferEncoding(i, entry_index);
if (reply != NWC24::WC24_OK && reply != NWC24::WC24_ERR_NOT_FOUND)
{
ERROR_LOG_FMT(
IOS_WC24,
"NET_KD_REQ: IOCTL_NWC24_SAVE_MAIL_NOW: Failed to parse Content Transfer Encoding.");
continue;
}
reply = parser.ParseContentType(i, entry_index);
if (reply != NWC24::WC24_OK)
{
ERROR_LOG_FMT(IOS_WC24,
"NET_KD_REQ: IOCTL_NWC24_SAVE_MAIL_NOW: Failed to parse Content Type.");
continue;
}
parser.ParseDate(i, entry_index);
// This can be empty.
parser.ParseSubject(i, entry_index);
reply = parser.ParseTo(i, entry_index);
if (reply != NWC24::WC24_OK)
{
ERROR_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_SAVE_MAIL_NOW: Failed to parse To field.");
continue;
}
reply = parser.ParseWiiAppId(i, entry_index);
if (reply != NWC24::WC24_OK)
{
ERROR_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_SAVE_MAIL_NOW: Failed to parse Wii App ID.");
continue;
}
if (m_receive_list.GetAppID(entry_index) == 0 || m_receive_list.GetAppGroup(entry_index) == 0)
{
if (m_receive_list.GetAppID(entry_index) == 0)
{
m_receive_list.SetWiiCmd(entry_index, 0x44001);
}
else
{
m_receive_list.SetWiiCmd(entry_index, 0x80000);
}
m_receive_list.UpdateFlag(entry_index, 2, NWC24::Mail::FlagOP::Or);
}
else
{
m_receive_list.UpdateFlag(entry_index, 1, NWC24::Mail::FlagOP::Or);
}
reply = parser.ParseWiiCmd(i, entry_index);
if (reply != NWC24::WC24_OK)
{
ERROR_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_SAVE_MAIL_NOW: Failed to parse command.");
continue;
}
reply =
NWC24::WriteToVFF(NWC24::Mail::RECEIVE_BOX_PATH,
NWC24::Mail::WC24ReceiveList::GetMailPath(msg_id), m_ios.GetFS(), data);
if (reply != NWC24::WC24_OK)
{
ERROR_LOG_FMT(IOS_WC24,
"NET_KD_REQ: IOCTL_NWC24_SAVE_MAIL_NOW: Failed to write message to VFF.");
continue;
}
m_receive_list.FinalizeEntry(entry_index);
mail_parse_guard.Dismiss();
}
m_receive_list.WriteReceiveList();
m_scheduler_buffer[13] = Common::swap32(Common::swap32(m_scheduler_buffer[13]) + 1);
m_scheduler_buffer[4] = 0;
return NWC24::WC24_OK;
}
NWC24::ErrorCode NetKDRequestDevice::KDDownload(const u16 entry_index,
const std::optional<u8> subtask_id)
{

View File

@ -15,6 +15,7 @@
#include "Common/WorkQueueThread.h"
#include "Core/IOS/Device.h"
#include "Core/IOS/Network/KD/Mail/WC24FriendList.h"
#include "Core/IOS/Network/KD/Mail/WC24Receive.h"
#include "Core/IOS/Network/KD/Mail/WC24Send.h"
#include "Core/IOS/Network/KD/NWC24Config.h"
#include "Core/IOS/Network/KD/NWC24DL.h"
@ -91,6 +92,8 @@ private:
NWC24::ErrorCode KDCheckMail(u32* mail_flag, u32* interval);
IPCReply HandleRequestRegisterUserId(const IOCtlRequest& request);
NWC24::ErrorCode KDSendMail();
NWC24::ErrorCode KDReceiveMail();
NWC24::ErrorCode KDSaveMail();
void LogError(ErrorType error_type, s32 error_code);
void SchedulerTimer();
@ -105,10 +108,13 @@ private:
0x8c, 0x89, 0x72, 0xd4, 0x50, 0xad};
static constexpr u32 DEFAULT_SCHEDULER_SPAN_MINUTES = 11;
static constexpr u32 MAX_MAIL_RECEIVE_SIZE = 1578040;
static constexpr char TEMP_MAIL_PATH[] = "/" WII_WC24CONF_DIR "/mbox/recvtmp.msg";
NWC24::NWC24Config m_config;
NWC24::NWC24Dl m_dl_list;
NWC24::Mail::WC24SendList m_send_list;
NWC24::Mail::WC24ReceiveList m_receive_list;
NWC24::Mail::WC24FriendList m_friend_list;
Common::WorkQueueThread<AsyncTask> m_work_queue;
Common::WorkQueueThread<std::function<void()>> m_scheduler_work_queue;

View File

@ -378,7 +378,9 @@
<ClInclude Include="Core\IOS\Network\KD\VFF\VFFUtil.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\MailParser.h" />
<ClInclude Include="Core\IOS\Network\KD\Mail\WC24FriendList.h" />
<ClInclude Include="Core\IOS\Network\KD\Mail\WC24Receive.h" />
<ClInclude Include="Core\IOS\Network\KD\Mail\WC24Send.h" />
<ClInclude Include="Core\IOS\Network\MACUtils.h" />
<ClInclude Include="Core\IOS\Network\NCD\Manage.h" />
@ -1045,7 +1047,9 @@
<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\KD\Mail\MailParser.cpp" />
<ClCompile Include="Core\IOS\Network\KD\Mail\WC24FriendList.cpp" />
<ClCompile Include="Core\IOS\Network\KD\Mail\WC24Receive.cpp" />
<ClCompile Include="Core\IOS\Network\KD\Mail\WC24Send.cpp" />
<ClCompile Include="Core\IOS\Network\MACUtils.cpp" />
<ClCompile Include="Core\IOS\Network\NCD\Manage.cpp" />