From 60f0ad501a7eb05b1d8ecf163a66e74aef6bea0f Mon Sep 17 00:00:00 2001 From: Sketch <75850871+SketchMaster2001@users.noreply.github.com> Date: Sun, 10 Dec 2023 20:39:16 -0500 Subject: [PATCH] IOS/KD: Implement receiving mail --- Source/Core/Core/CMakeLists.txt | 5 + .../Core/IOS/Network/KD/Mail/MailCommon.h | 16 +- .../Core/IOS/Network/KD/Mail/MailParser.cpp | 473 ++++++++++++++++++ .../Core/IOS/Network/KD/Mail/MailParser.h | 103 ++++ .../IOS/Network/KD/Mail/WC24FriendList.cpp | 30 +- .../Core/IOS/Network/KD/Mail/WC24FriendList.h | 2 +- .../Core/IOS/Network/KD/Mail/WC24Receive.cpp | 283 +++++++++++ .../Core/IOS/Network/KD/Mail/WC24Receive.h | 83 +++ .../Core/IOS/Network/KD/Mail/WC24Send.cpp | 4 +- .../Core/Core/IOS/Network/KD/NWC24Config.cpp | 10 + Source/Core/Core/IOS/Network/KD/NWC24Config.h | 4 + .../Core/Core/IOS/Network/KD/NetKDRequest.cpp | 216 +++++++- .../Core/Core/IOS/Network/KD/NetKDRequest.h | 6 + Source/Core/DolphinLib.props | 4 + 14 files changed, 1232 insertions(+), 7 deletions(-) create mode 100644 Source/Core/Core/IOS/Network/KD/Mail/MailParser.cpp create mode 100644 Source/Core/Core/IOS/Network/KD/Mail/MailParser.h create mode 100644 Source/Core/Core/IOS/Network/KD/Mail/WC24Receive.cpp create mode 100644 Source/Core/Core/IOS/Network/KD/Mail/WC24Receive.h diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 6b0022d63c..3abcbd0458 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -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 ) diff --git a/Source/Core/Core/IOS/Network/KD/Mail/MailCommon.h b/Source/Core/Core/IOS/Network/KD/Mail/MailCommon.h index b92e1394f0..93babfe169 100644 --- a/Source/Core/Core/IOS/Network/KD/Mail/MailCommon.h +++ b/Source/Core/Core/IOS/Network/KD/Mail/MailCommon.h @@ -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; diff --git a/Source/Core/Core/IOS/Network/KD/Mail/MailParser.cpp b/Source/Core/Core/IOS/Network/KD/Mail/MailParser.cpp new file mode 100644 index 0000000000..3a1bc09a8c --- /dev/null +++ b/Source/Core/Core/IOS/Network/KD/Mail/MailParser.cpp @@ -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 +#include + +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(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 MailParser::GetMessageData(u32 index) const +{ + std::vector 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 raw_fields = SplitString(m_message_data[index], '\n'); + for (u32 i = 0; i < raw_fields.size(); i++) + { + std::vector 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 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(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(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(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(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(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 diff --git a/Source/Core/Core/IOS/Network/KD/Mail/MailParser.h b/Source/Core/Core/IOS/Network/KD/Mail/MailParser.h new file mode 100644 index 0000000000..b0f7c9f649 --- /dev/null +++ b/Source/Core/Core/IOS/Network/KD/Mail/MailParser.h @@ -0,0 +1,103 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#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 +#include +#include +#include + +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 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 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; + using Headers = std::vector
; + + MultipartParser m_parser{}; + WC24ReceiveList* m_receive_list; + std::vector m_message_data; + std::vector 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 diff --git a/Source/Core/Core/IOS/Network/KD/Mail/WC24FriendList.cpp b/Source/Core/Core/IOS/Network/KD/Mail/WC24FriendList.cpp index 6c6b7852f8..7495ba6288 100644 --- a/Source/Core/Core/IOS/Network/KD/Mail/WC24FriendList.cpp +++ b/Source/Core/Core/IOS/Network/KD/Mail/WC24FriendList.cpp @@ -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(FriendStatus::Confirmed)); + } + } + + return false; +} + std::vector WC24FriendList::GetUnconfirmedFriends() const { std::vector 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(status)); + return; + } + } +} } // namespace IOS::HLE::NWC24::Mail diff --git a/Source/Core/Core/IOS/Network/KD/Mail/WC24FriendList.h b/Source/Core/Core/IOS/Network/KD/Mail/WC24FriendList.h index ee3fe047fc..d533040bc9 100644 --- a/Source/Core/Core/IOS/Network/KD/Mail/WC24FriendList.h +++ b/Source/Core/Core/IOS/Network/KD/Mail/WC24FriendList.h @@ -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 GetUnconfirmedFriends() const; private: diff --git a/Source/Core/Core/IOS/Network/KD/Mail/WC24Receive.cpp b/Source/Core/Core/IOS/Network/KD/Mail/WC24Receive.cpp new file mode 100644 index 0000000000..d015aaae57 --- /dev/null +++ b/Source/Core/Core/IOS/Network/KD/Mail/WC24Receive.cpp @@ -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) : 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 diff --git a/Source/Core/Core/IOS/Network/KD/Mail/WC24Receive.h b/Source/Core/Core/IOS/Network/KD/Mail/WC24Receive.h new file mode 100644 index 0000000000..756b3bcb20 --- /dev/null +++ b/Source/Core/Core/IOS/Network/KD/Mail/WC24Receive.h @@ -0,0 +1,83 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#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); + 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 entries; + }; +#pragma pack(pop) + + ReceiveList m_data; + std::shared_ptr m_fs; + bool m_is_disabled = false; +}; +} // namespace NWC24::Mail +} // namespace IOS::HLE diff --git a/Source/Core/Core/IOS/Network/KD/Mail/WC24Send.cpp b/Source/Core/Core/IOS/Network/KD/Mail/WC24Send.cpp index f92a24c8d4..5cd8d7adba 100644 --- a/Source/Core/Core/IOS/Network/KD/Mail/WC24Send.cpp +++ b/Source/Core/Core/IOS/Network/KD/Mail/WC24Send.cpp @@ -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) : m_fs{std::move(fs)} { diff --git a/Source/Core/Core/IOS/Network/KD/NWC24Config.cpp b/Source/Core/Core/IOS/Network/KD/NWC24Config.cpp index b191642543..16ddb537cc 100644 --- a/Source/Core/Core/IOS/Network/KD/NWC24Config.cpp +++ b/Source/Core/Core/IOS/Network/KD/NWC24Config.cpp @@ -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 diff --git a/Source/Core/Core/IOS/Network/KD/NWC24Config.h b/Source/Core/Core/IOS/Network/KD/NWC24Config.h index 9db688ee02..cea17f2c66 100644 --- a/Source/Core/Core/IOS/Network/KD/NWC24Config.h +++ b/Source/Core/Core/IOS/Network/KD/NWC24Config.h @@ -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); diff --git a/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp b/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp index 14553199d6..b8da501e14 100644 --- a/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp +++ b/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp @@ -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& 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 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 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 subtask_id) { diff --git a/Source/Core/Core/IOS/Network/KD/NetKDRequest.h b/Source/Core/Core/IOS/Network/KD/NetKDRequest.h index 848454fa71..31f9fe3a76 100644 --- a/Source/Core/Core/IOS/Network/KD/NetKDRequest.h +++ b/Source/Core/Core/IOS/Network/KD/NetKDRequest.h @@ -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 m_work_queue; Common::WorkQueueThread> m_scheduler_work_queue; diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 5f1c59ac4d..38b36931e4 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -378,7 +378,9 @@ + + @@ -1045,7 +1047,9 @@ + +