IOS/KD: Implement receiving mail
This commit is contained in:
parent
e46d7500a1
commit
60f0ad501a
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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)}
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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" />
|
||||
|
|
Loading…
Reference in New Issue