Merge pull request #12184 from noahpistilli/kd-mail-send
IOS/KD: Implement Send Mail
This commit is contained in:
commit
85f4a460f9
|
@ -33,7 +33,8 @@ public:
|
||||||
void FollowRedirects(long max);
|
void FollowRedirects(long max);
|
||||||
s32 GetLastResponseCode();
|
s32 GetLastResponseCode();
|
||||||
Response Fetch(const std::string& url, Method method, const Headers& headers, const u8* payload,
|
Response Fetch(const std::string& url, Method method, const Headers& headers, const u8* payload,
|
||||||
size_t size, AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only);
|
size_t size, AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only,
|
||||||
|
std::span<Multiform> multiform = {});
|
||||||
|
|
||||||
static int CurlProgressCallback(Impl* impl, curl_off_t dltotal, curl_off_t dlnow,
|
static int CurlProgressCallback(Impl* impl, curl_off_t dltotal, curl_off_t dlnow,
|
||||||
curl_off_t ultotal, curl_off_t ulnow);
|
curl_off_t ultotal, curl_off_t ulnow);
|
||||||
|
@ -174,6 +175,13 @@ void HttpRequest::Impl::UseIPv4()
|
||||||
curl_easy_setopt(m_curl.get(), CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
curl_easy_setopt(m_curl.get(), CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HttpRequest::Response HttpRequest::PostMultiform(const std::string& url,
|
||||||
|
std::span<Multiform> multiform,
|
||||||
|
const Headers& headers, AllowedReturnCodes codes)
|
||||||
|
{
|
||||||
|
return m_impl->Fetch(url, Impl::Method::POST, headers, nullptr, 0, codes, multiform);
|
||||||
|
}
|
||||||
|
|
||||||
void HttpRequest::Impl::FollowRedirects(long max)
|
void HttpRequest::Impl::FollowRedirects(long max)
|
||||||
{
|
{
|
||||||
curl_easy_setopt(m_curl.get(), CURLOPT_FOLLOWLOCATION, 1);
|
curl_easy_setopt(m_curl.get(), CURLOPT_FOLLOWLOCATION, 1);
|
||||||
|
@ -225,17 +233,33 @@ static size_t header_callback(char* buffer, size_t size, size_t nitems, void* us
|
||||||
|
|
||||||
HttpRequest::Response HttpRequest::Impl::Fetch(const std::string& url, Method method,
|
HttpRequest::Response HttpRequest::Impl::Fetch(const std::string& url, Method method,
|
||||||
const Headers& headers, const u8* payload,
|
const Headers& headers, const u8* payload,
|
||||||
size_t size, AllowedReturnCodes codes)
|
size_t size, AllowedReturnCodes codes,
|
||||||
|
std::span<Multiform> multiform)
|
||||||
{
|
{
|
||||||
m_response_headers.clear();
|
m_response_headers.clear();
|
||||||
curl_easy_setopt(m_curl.get(), CURLOPT_POST, method == Method::POST);
|
curl_easy_setopt(m_curl.get(), CURLOPT_POST, method == Method::POST);
|
||||||
curl_easy_setopt(m_curl.get(), CURLOPT_URL, url.c_str());
|
curl_easy_setopt(m_curl.get(), CURLOPT_URL, url.c_str());
|
||||||
if (method == Method::POST)
|
if (method == Method::POST && multiform.empty())
|
||||||
{
|
{
|
||||||
curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDS, payload);
|
curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDS, payload);
|
||||||
curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDSIZE, size);
|
curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDSIZE, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
curl_mime* form = nullptr;
|
||||||
|
Common::ScopeGuard multiform_guard{[&form] { curl_mime_free(form); }};
|
||||||
|
if (!multiform.empty())
|
||||||
|
{
|
||||||
|
form = curl_mime_init(m_curl.get());
|
||||||
|
for (const auto& value : multiform)
|
||||||
|
{
|
||||||
|
curl_mimepart* part = curl_mime_addpart(form);
|
||||||
|
curl_mime_name(part, value.name.c_str());
|
||||||
|
curl_mime_data(part, value.data.c_str(), value.data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_easy_setopt(m_curl.get(), CURLOPT_MIMEPOST, form);
|
||||||
|
}
|
||||||
|
|
||||||
curl_slist* list = nullptr;
|
curl_slist* list = nullptr;
|
||||||
Common::ScopeGuard list_guard{[&list] { curl_slist_free_all(list); }};
|
Common::ScopeGuard list_guard{[&list] { curl_slist_free_all(list); }};
|
||||||
for (const auto& [name, value] : headers)
|
for (const auto& [name, value] : headers)
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <span>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -35,6 +36,12 @@ public:
|
||||||
using Response = std::optional<std::vector<u8>>;
|
using Response = std::optional<std::vector<u8>>;
|
||||||
using Headers = std::map<std::string, std::optional<std::string>>;
|
using Headers = std::map<std::string, std::optional<std::string>>;
|
||||||
|
|
||||||
|
struct Multiform
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::string data;
|
||||||
|
};
|
||||||
|
|
||||||
void SetCookies(const std::string& cookies);
|
void SetCookies(const std::string& cookies);
|
||||||
void UseIPv4();
|
void UseIPv4();
|
||||||
void FollowRedirects(long max = 1);
|
void FollowRedirects(long max = 1);
|
||||||
|
@ -48,6 +55,10 @@ public:
|
||||||
Response Post(const std::string& url, const std::string& payload, const Headers& headers = {},
|
Response Post(const std::string& url, const std::string& payload, const Headers& headers = {},
|
||||||
AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only);
|
AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only);
|
||||||
|
|
||||||
|
Response PostMultiform(const std::string& url, std::span<Multiform> multiform,
|
||||||
|
const Headers& headers = {},
|
||||||
|
AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class Impl;
|
class Impl;
|
||||||
std::unique_ptr<Impl> m_impl;
|
std::unique_ptr<Impl> m_impl;
|
||||||
|
|
|
@ -383,6 +383,8 @@ add_library(core
|
||||||
IOS/Network/KD/VFF/VFFUtil.h
|
IOS/Network/KD/VFF/VFFUtil.h
|
||||||
IOS/Network/KD/WC24File.h
|
IOS/Network/KD/WC24File.h
|
||||||
IOS/Network/KD/Mail/MailCommon.h
|
IOS/Network/KD/Mail/MailCommon.h
|
||||||
|
IOS/Network/KD/Mail/WC24FriendList.cpp
|
||||||
|
IOS/Network/KD/Mail/WC24FriendList.h
|
||||||
IOS/Network/KD/Mail/WC24Send.cpp
|
IOS/Network/KD/Mail/WC24Send.cpp
|
||||||
IOS/Network/KD/Mail/WC24Send.h
|
IOS/Network/KD/Mail/WC24Send.h
|
||||||
IOS/Network/MACUtils.cpp
|
IOS/Network/MACUtils.cpp
|
||||||
|
|
|
@ -11,6 +11,11 @@ namespace IOS::HLE::NWC24::Mail
|
||||||
{
|
{
|
||||||
constexpr u32 MAIL_LIST_MAGIC = 0x57635466; // WcTf
|
constexpr u32 MAIL_LIST_MAGIC = 0x57635466; // WcTf
|
||||||
|
|
||||||
|
inline u32 CalculateFileOffset(u32 index)
|
||||||
|
{
|
||||||
|
return Common::swap32(128 + (index * 128));
|
||||||
|
}
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
struct MailListHeader final
|
struct MailListHeader final
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
// Copyright 2023 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "Core/IOS/Network/KD/Mail/WC24FriendList.h"
|
||||||
|
#include "Core/IOS/FS/FileSystem.h"
|
||||||
|
#include "Core/IOS/Uids.h"
|
||||||
|
|
||||||
|
namespace IOS::HLE::NWC24::Mail
|
||||||
|
{
|
||||||
|
WC24FriendList::WC24FriendList(std::shared_ptr<FS::FileSystem> fs) : m_fs{std::move(fs)}
|
||||||
|
{
|
||||||
|
ReadFriendList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WC24FriendList::ReadFriendList()
|
||||||
|
{
|
||||||
|
const auto file = m_fs->OpenFile(PID_KD, PID_KD, FRIEND_LIST_PATH, FS::Mode::Read);
|
||||||
|
if (!file || !file->Read(&m_data, 1))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const bool success = CheckFriendList();
|
||||||
|
if (!success)
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "There is an error in the Receive List for WC24 mail");
|
||||||
|
}
|
||||||
|
|
||||||
|
void WC24FriendList::WriteFriendList() const
|
||||||
|
{
|
||||||
|
constexpr FS::Modes public_modes{FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite};
|
||||||
|
m_fs->CreateFullPath(PID_KD, PID_KD, FRIEND_LIST_PATH, 0, public_modes);
|
||||||
|
const auto file = m_fs->CreateAndOpenFile(PID_KD, PID_KD, FRIEND_LIST_PATH, public_modes);
|
||||||
|
|
||||||
|
if (!file || !file->Write(&m_data, 1))
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "Failed to open or write WC24 Receive list file");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WC24FriendList::CheckFriendList() const
|
||||||
|
{
|
||||||
|
// 'WcFl' magic
|
||||||
|
if (Common::swap32(m_data.header.magic) != FRIEND_LIST_MAGIC)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "Receive List magic mismatch ({} != {})",
|
||||||
|
Common::swap32(m_data.header.magic), FRIEND_LIST_MAGIC);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WC24FriendList::DoesFriendExist(u64 friend_id) const
|
||||||
|
{
|
||||||
|
return std::any_of(m_data.friend_codes.cbegin(), m_data.friend_codes.cend(),
|
||||||
|
[&friend_id](const u64 v) { return v == friend_id; });
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u64> WC24FriendList::GetUnconfirmedFriends() const
|
||||||
|
{
|
||||||
|
std::vector<u64> friends{};
|
||||||
|
for (u32 i = 0; i < MAX_ENTRIES; i++)
|
||||||
|
{
|
||||||
|
if (static_cast<FriendStatus>(Common::swap32(m_data.entries[i].status)) ==
|
||||||
|
FriendStatus::Unconfirmed &&
|
||||||
|
static_cast<FriendType>(Common::swap32(m_data.entries[i].friend_type)) == FriendType::Wii)
|
||||||
|
{
|
||||||
|
friends.push_back(Common::swap64(m_data.friend_codes.at(i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return friends;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 WC24FriendList::ConvertEmailToFriendCode(std::string_view email)
|
||||||
|
{
|
||||||
|
u32 upper = 0x80;
|
||||||
|
u32 lower{};
|
||||||
|
|
||||||
|
u32 idx{};
|
||||||
|
for (char chr : email)
|
||||||
|
{
|
||||||
|
if (idx == 7)
|
||||||
|
{
|
||||||
|
upper = upper | (email.size() & 0x1f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
lower = (upper | chr) >> 0x18 | (lower | lower >> 0x1f) << 8;
|
||||||
|
upper = (upper | chr) * 0x100;
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return u64{lower} << 32 | upper;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace IOS::HLE::NWC24::Mail
|
|
@ -0,0 +1,98 @@
|
||||||
|
// Copyright 2023 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Common/CommonPaths.h"
|
||||||
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "Common/Logging/Log.h"
|
||||||
|
#include "Common/Swap.h"
|
||||||
|
#include "Core/IOS/Network/KD/NWC24Config.h"
|
||||||
|
|
||||||
|
namespace IOS::HLE
|
||||||
|
{
|
||||||
|
namespace FS
|
||||||
|
{
|
||||||
|
class FileSystem;
|
||||||
|
}
|
||||||
|
namespace NWC24::Mail
|
||||||
|
{
|
||||||
|
constexpr const char FRIEND_LIST_PATH[] = "/" WII_WC24CONF_DIR "/nwc24fl.bin";
|
||||||
|
class WC24FriendList final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit WC24FriendList(std::shared_ptr<FS::FileSystem> fs);
|
||||||
|
static u64 ConvertEmailToFriendCode(std::string_view email);
|
||||||
|
|
||||||
|
void ReadFriendList();
|
||||||
|
bool CheckFriendList() const;
|
||||||
|
void WriteFriendList() const;
|
||||||
|
|
||||||
|
bool DoesFriendExist(u64 friend_id) const;
|
||||||
|
std::vector<u64> GetUnconfirmedFriends() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr u32 FRIEND_LIST_MAGIC = 0x5763466C; // WcFl
|
||||||
|
static constexpr u32 MAX_ENTRIES = 100;
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct FriendListHeader final
|
||||||
|
{
|
||||||
|
u32 magic; // 'WcFl' 0x5763466C
|
||||||
|
u32 version;
|
||||||
|
u32 max_friend_entries;
|
||||||
|
u32 number_of_friends;
|
||||||
|
char padding[48];
|
||||||
|
};
|
||||||
|
static_assert(sizeof(FriendListHeader) == 64);
|
||||||
|
static_assert(std::is_trivially_copyable_v<FriendListHeader>);
|
||||||
|
|
||||||
|
enum class FriendType : u32
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Wii,
|
||||||
|
Email
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class FriendStatus : u32
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Unconfirmed,
|
||||||
|
Confirmed,
|
||||||
|
Declined
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FriendListEntry final
|
||||||
|
{
|
||||||
|
u32 friend_type;
|
||||||
|
u32 status;
|
||||||
|
char nickname[24];
|
||||||
|
u32 mii_id;
|
||||||
|
u32 system_id;
|
||||||
|
char reserved[24];
|
||||||
|
char email_or_code[96];
|
||||||
|
char padding[160];
|
||||||
|
};
|
||||||
|
static_assert(sizeof(FriendListEntry) == 320);
|
||||||
|
static_assert(std::is_trivially_copyable_v<FriendListEntry>);
|
||||||
|
|
||||||
|
struct FriendList final
|
||||||
|
{
|
||||||
|
FriendListHeader header;
|
||||||
|
std::array<u64, MAX_ENTRIES> friend_codes;
|
||||||
|
std::array<FriendListEntry, MAX_ENTRIES> entries;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(FriendList) == 32864);
|
||||||
|
static_assert(std::is_trivially_copyable_v<FriendList>);
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
FriendList m_data;
|
||||||
|
std::shared_ptr<FS::FileSystem> m_fs;
|
||||||
|
};
|
||||||
|
} // namespace NWC24::Mail
|
||||||
|
} // namespace IOS::HLE
|
|
@ -3,8 +3,10 @@
|
||||||
|
|
||||||
#include "Core/IOS/Network/KD/Mail/WC24Send.h"
|
#include "Core/IOS/Network/KD/Mail/WC24Send.h"
|
||||||
#include "Core/IOS/FS/FileSystem.h"
|
#include "Core/IOS/FS/FileSystem.h"
|
||||||
|
#include "Core/IOS/Network/KD/VFF/VFFUtil.h"
|
||||||
#include "Core/IOS/Uids.h"
|
#include "Core/IOS/Uids.h"
|
||||||
|
|
||||||
|
#include <fmt/chrono.h>
|
||||||
#include "Common/Assert.h"
|
#include "Common/Assert.h"
|
||||||
|
|
||||||
namespace IOS::HLE::NWC24::Mail
|
namespace IOS::HLE::NWC24::Mail
|
||||||
|
@ -44,7 +46,28 @@ bool WC24SendList::ReadSendList()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CheckSendList();
|
// Make sure that next_entry_offset is not out of bounds.
|
||||||
|
if (m_data.header.next_entry_offset % 128 != 0 ||
|
||||||
|
m_data.header.next_entry_offset >
|
||||||
|
sizeof(MailListEntry) * (MAX_ENTRIES - 1) + sizeof(MailListEntry))
|
||||||
|
{
|
||||||
|
const std::optional<u32> next_entry_index = GetNextFreeEntryIndex();
|
||||||
|
if (!next_entry_index)
|
||||||
|
{
|
||||||
|
// If there are no free entries, we will have to overwrite an entry.
|
||||||
|
m_data.header.next_entry_offset = Common::swap32(128);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_data.header.next_entry_offset = CalculateFileOffset(next_entry_index.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const s32 file_error = CheckSendList();
|
||||||
|
if (!file_error)
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "There is an error in the Send List for WC24 mail");
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WC24SendList::IsDisabled() const
|
bool WC24SendList::IsDisabled() const
|
||||||
|
@ -82,6 +105,147 @@ bool WC24SendList::CheckSendList() const
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u32 WC24SendList::GetNumberOfMail() const
|
||||||
|
{
|
||||||
|
ASSERT(!IsDisabled());
|
||||||
|
return Common::swap32(m_data.header.number_of_mail);
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 WC24SendList::GetEntryId(u32 entry_index) const
|
||||||
|
{
|
||||||
|
ASSERT(!IsDisabled());
|
||||||
|
return Common::swap32(m_data.entries[entry_index].id);
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 WC24SendList::GetMailSize(u32 index) const
|
||||||
|
{
|
||||||
|
ASSERT(!IsDisabled());
|
||||||
|
return Common::swap32(m_data.entries[index].msg_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode WC24SendList::DeleteMessage(u32 index)
|
||||||
|
{
|
||||||
|
ASSERT(!IsDisabled());
|
||||||
|
ErrorCode error = NWC24::DeleteFileFromVFF(NWC24::Mail::SEND_BOX_PATH, GetMailPath(index), m_fs);
|
||||||
|
if (error != WC24_OK)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
// Fix up the header then clear the entry.
|
||||||
|
m_data.header.number_of_mail = Common::swap32(Common::swap32(m_data.header.number_of_mail) - 1);
|
||||||
|
m_data.header.next_entry_id = Common::swap32(GetEntryId(index));
|
||||||
|
m_data.header.next_entry_offset = CalculateFileOffset(index);
|
||||||
|
m_data.header.total_size_of_messages =
|
||||||
|
Common::swap32(m_data.header.total_size_of_messages) - GetMailSize(index);
|
||||||
|
|
||||||
|
std::memset(&m_data.entries[index], 0, sizeof(MailListEntry));
|
||||||
|
return WC24_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string WC24SendList::GetMailPath(u32 index) const
|
||||||
|
{
|
||||||
|
return fmt::format("mb/s{:07d}.msg", GetEntryId(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 WC24SendList::GetNextEntryId() const
|
||||||
|
{
|
||||||
|
ASSERT(!IsDisabled());
|
||||||
|
return Common::swap32(m_data.header.next_entry_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 WC24SendList::GetNextEntryIndex() const
|
||||||
|
{
|
||||||
|
ASSERT(!IsDisabled());
|
||||||
|
return (Common::swap32(m_data.header.next_entry_offset) - 128) / 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u32> WC24SendList::GetMailToSend() const
|
||||||
|
{
|
||||||
|
ASSERT(!IsDisabled());
|
||||||
|
// The list is not guaranteed to have all entries consecutively.
|
||||||
|
// As such we must find the populated entries for the specified number of mails.
|
||||||
|
const u32 mail_count = std::min(GetNumberOfMail(), 16U);
|
||||||
|
u32 found{};
|
||||||
|
|
||||||
|
std::vector<u32> mails{};
|
||||||
|
for (u32 index = 0; index < MAX_ENTRIES; index++)
|
||||||
|
{
|
||||||
|
if (found == mail_count)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (GetEntryId(index) != 0)
|
||||||
|
{
|
||||||
|
mails.emplace_back(index);
|
||||||
|
found++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mails;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<u32> WC24SendList::GetNextFreeEntryIndex() const
|
||||||
|
{
|
||||||
|
for (u32 index = 0; index < MAX_ENTRIES; index++)
|
||||||
|
{
|
||||||
|
if (GetEntryId(index) == 0)
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode WC24SendList::AddRegistrationMessages(const WC24FriendList& friend_list, u64 sender)
|
||||||
|
{
|
||||||
|
ASSERT(!IsDisabled());
|
||||||
|
// It is possible that the user composed a message before SendMail was called.
|
||||||
|
ReadSendList();
|
||||||
|
|
||||||
|
const std::vector<u64> unconfirmed_friends = friend_list.GetUnconfirmedFriends();
|
||||||
|
for (const u64 code : unconfirmed_friends)
|
||||||
|
{
|
||||||
|
const u32 entry_index = GetNextEntryIndex();
|
||||||
|
const u32 msg_id = GetNextEntryId();
|
||||||
|
m_data.entries[entry_index].id = Common::swap32(msg_id);
|
||||||
|
|
||||||
|
std::time_t t = std::time(nullptr);
|
||||||
|
|
||||||
|
const std::string formatted_message =
|
||||||
|
fmt::format(MAIL_REGISTRATION_STRING, sender, code, fmt::gmtime(t));
|
||||||
|
std::vector<u8> message{formatted_message.begin(), formatted_message.end()};
|
||||||
|
NWC24::ErrorCode reply =
|
||||||
|
NWC24::WriteToVFF(NWC24::Mail::SEND_BOX_PATH, GetMailPath(entry_index), m_fs, message);
|
||||||
|
|
||||||
|
if (reply != WC24_OK)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "Error writing registration message to VFF");
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
NOTICE_LOG_FMT(IOS_WC24, "Issued registration message for Wii Friend: {}", code);
|
||||||
|
|
||||||
|
// Update the header and some fields in the body
|
||||||
|
m_data.entries[entry_index].msg_size = Common::swap32(static_cast<u32>(message.size()));
|
||||||
|
m_data.header.number_of_mail = Common::swap32(GetNumberOfMail() + 1);
|
||||||
|
m_data.header.next_entry_id = Common::swap32(msg_id + 1);
|
||||||
|
m_data.header.total_size_of_messages =
|
||||||
|
Common::swap32(m_data.header.total_size_of_messages) + static_cast<u32>(message.size());
|
||||||
|
|
||||||
|
const std::optional<u32> next_entry_index = GetNextFreeEntryIndex();
|
||||||
|
if (!next_entry_index)
|
||||||
|
{
|
||||||
|
// If there are no free entries, we overwrite the first entry.
|
||||||
|
m_data.header.next_entry_offset = Common::swap32(128);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_data.header.next_entry_offset = CalculateFileOffset(next_entry_index.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only flush on success.
|
||||||
|
WriteSendList();
|
||||||
|
return WC24_OK;
|
||||||
|
}
|
||||||
|
|
||||||
std::string_view WC24SendList::GetMailFlag() const
|
std::string_view WC24SendList::GetMailFlag() const
|
||||||
{
|
{
|
||||||
ASSERT(!IsDisabled());
|
ASSERT(!IsDisabled());
|
||||||
|
|
|
@ -3,13 +3,16 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "Common/CommonPaths.h"
|
#include "Common/CommonPaths.h"
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/Swap.h"
|
#include "Common/Swap.h"
|
||||||
#include "Core/IOS/Network/KD/Mail/MailCommon.h"
|
#include "Core/IOS/Network/KD/Mail/MailCommon.h"
|
||||||
|
#include "Core/IOS/Network/KD/Mail/WC24FriendList.h"
|
||||||
#include "Core/IOS/Network/KD/NWC24Config.h"
|
#include "Core/IOS/Network/KD/NWC24Config.h"
|
||||||
|
|
||||||
namespace IOS::HLE
|
namespace IOS::HLE
|
||||||
|
@ -34,11 +37,56 @@ public:
|
||||||
bool IsDisabled() const;
|
bool IsDisabled() const;
|
||||||
|
|
||||||
std::string_view GetMailFlag() const;
|
std::string_view GetMailFlag() const;
|
||||||
|
u32 GetNumberOfMail() const;
|
||||||
|
std::vector<u32> GetMailToSend() const;
|
||||||
|
u32 GetEntryId(u32 entry_index) const;
|
||||||
|
u32 GetMailSize(u32 index) const;
|
||||||
|
ErrorCode DeleteMessage(u32 index);
|
||||||
|
std::string GetMailPath(u32 index) const;
|
||||||
|
u32 GetNextEntryId() const;
|
||||||
|
u32 GetNextEntryIndex() const;
|
||||||
|
std::optional<u32> GetNextFreeEntryIndex() const;
|
||||||
|
|
||||||
|
ErrorCode AddRegistrationMessages(const WC24FriendList& friend_list, u64 sender);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr u32 MAX_ENTRIES = 127;
|
static constexpr u32 MAX_ENTRIES = 127;
|
||||||
static constexpr u32 SEND_LIST_SIZE = 16384;
|
static constexpr u32 SEND_LIST_SIZE = 16384;
|
||||||
|
|
||||||
|
// Format for the message Wii Mail sends when trying to register a Wii Friend.
|
||||||
|
// Most fields can be static such as the X-Wii-AppId which is the Wii Menu,
|
||||||
|
// X-Wii-Cmd which is the registration command, and the attached file which is
|
||||||
|
// just 128 bytes of base64 encoded 0 bytes. That file is supposed to be friend profile data which
|
||||||
|
// is written to nwc24fl.bin, although it has been observed to always be 0.
|
||||||
|
static constexpr char MAIL_REGISTRATION_STRING[] =
|
||||||
|
"MAIL FROM: {0:016d}@wii.com\r\n"
|
||||||
|
"RCPT TO: {1:016d}wii.com\r\n"
|
||||||
|
"DATA\r\n"
|
||||||
|
"Date: {2:%a, %d %b %Y %X} GMT\r\n"
|
||||||
|
"From: {0:016d}@wii.com\r\n"
|
||||||
|
"To: {1:016d}@wii.com\r\n"
|
||||||
|
"Message-Id: <00002000B0DF6BB47FE0303E0DB0D@wii.com>\r\n"
|
||||||
|
"Subject: WC24 Cmd Message\r\n"
|
||||||
|
"X-Wii-AppId: 0-00000001-0001\r\n"
|
||||||
|
"X-Wii-Cmd: 80010001\r\n"
|
||||||
|
"MIME-Version: 1.0\r\n"
|
||||||
|
"Content-Type: multipart/mixed;\r\n "
|
||||||
|
"boundary=\"Boundary-NWC24-041B6CE500012\"\r\n"
|
||||||
|
"--Boundary-NWC24-041B6CE500012\r\n"
|
||||||
|
"Content-Type: text/plain; charset=us-ascii\r\n"
|
||||||
|
"Content-Transfer-Encoding: 7bit\r\n"
|
||||||
|
"WC24 Cmd Message\r\n"
|
||||||
|
"--Boundary-NWC24-041B6CE500012\r\n"
|
||||||
|
"Content-Type: application/octet-stream;\r\n "
|
||||||
|
"name=a0000018.dat\r\n"
|
||||||
|
"Content-Transfer-Encoding: base64\r\n"
|
||||||
|
"Content-Disposition: attachment;\r\n "
|
||||||
|
"filename=a0000018.dat\r\n\r\n "
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
|
||||||
|
"\r\n\r\n"
|
||||||
|
"--Boundary-NWC24-041B6CE500012--";
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
struct SendList final
|
struct SendList final
|
||||||
{
|
{
|
||||||
|
|
|
@ -246,4 +246,16 @@ void NWC24Config::SetPassword(std::string_view password)
|
||||||
std::strncpy(m_data.paswd, password.data(), std::size(m_data.paswd));
|
std::strncpy(m_data.paswd, password.data(), std::size(m_data.paswd));
|
||||||
m_data.paswd[MAX_PASSWORD_LENGTH - 1] = '\0';
|
m_data.paswd[MAX_PASSWORD_LENGTH - 1] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string NWC24Config::GetSendURL() const
|
||||||
|
{
|
||||||
|
const size_t size = strnlen(m_data.http_urls[4], MAX_URL_LENGTH);
|
||||||
|
return {m_data.http_urls[4], size};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view NWC24Config::GetPassword() const
|
||||||
|
{
|
||||||
|
const size_t size = strnlen(m_data.paswd, MAX_PASSWORD_LENGTH);
|
||||||
|
return {m_data.paswd, size};
|
||||||
|
}
|
||||||
} // namespace IOS::HLE::NWC24
|
} // namespace IOS::HLE::NWC24
|
||||||
|
|
|
@ -34,6 +34,8 @@ enum ErrorCode : s32
|
||||||
WC24_ERR_ID_REGISTERED = -36,
|
WC24_ERR_ID_REGISTERED = -36,
|
||||||
WC24_ERR_DISABLED = -39,
|
WC24_ERR_DISABLED = -39,
|
||||||
WC24_ERR_ID_NOT_REGISTERED = -44,
|
WC24_ERR_ID_NOT_REGISTERED = -44,
|
||||||
|
WC24_MSG_DAMAGED = -71,
|
||||||
|
WC24_MSG_TOO_BIG = -72
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class NWC24CreationStage : u32
|
enum class NWC24CreationStage : u32
|
||||||
|
@ -72,6 +74,8 @@ public:
|
||||||
|
|
||||||
std::string_view GetMlchkid() const;
|
std::string_view GetMlchkid() const;
|
||||||
std::string GetCheckURL() const;
|
std::string GetCheckURL() const;
|
||||||
|
std::string GetSendURL() const;
|
||||||
|
std::string_view GetPassword() const;
|
||||||
|
|
||||||
NWC24CreationStage CreationStage() const;
|
NWC24CreationStage CreationStage() const;
|
||||||
void SetCreationStage(NWC24CreationStage creation_stage);
|
void SetCreationStage(NWC24CreationStage creation_stage);
|
||||||
|
|
|
@ -155,7 +155,7 @@ s32 NWC24MakeUserID(u64* nwc24_id, u32 hollywood_id, u16 id_ctr, HardwareModel h
|
||||||
|
|
||||||
NetKDRequestDevice::NetKDRequestDevice(EmulationKernel& ios, const std::string& device_name)
|
NetKDRequestDevice::NetKDRequestDevice(EmulationKernel& ios, const std::string& device_name)
|
||||||
: EmulationDevice(ios, device_name), m_config{ios.GetFS()}, m_dl_list{ios.GetFS()},
|
: EmulationDevice(ios, device_name), m_config{ios.GetFS()}, m_dl_list{ios.GetFS()},
|
||||||
m_send_list{ios.GetFS()}
|
m_send_list{ios.GetFS()}, m_friend_list{ios.GetFS()}
|
||||||
{
|
{
|
||||||
// Enable all NWC24 permissions
|
// Enable all NWC24 permissions
|
||||||
m_scheduler_buffer[1] = Common::swap32(-1);
|
m_scheduler_buffer[1] = Common::swap32(-1);
|
||||||
|
@ -275,6 +275,12 @@ void NetKDRequestDevice::SchedulerWorker(const SchedulerEvent event)
|
||||||
{
|
{
|
||||||
LogError(ErrorType::CheckMail, code);
|
LogError(ErrorType::CheckMail, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code = KDSendMail();
|
||||||
|
if (code != NWC24::WC24_OK)
|
||||||
|
{
|
||||||
|
LogError(ErrorType::SendMail, code);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,6 +321,15 @@ void NetKDRequestDevice::LogError(ErrorType error_type, s32 error_code)
|
||||||
case ErrorType::CheckMail:
|
case ErrorType::CheckMail:
|
||||||
new_code = -(102200 - error_code);
|
new_code = -(102200 - error_code);
|
||||||
break;
|
break;
|
||||||
|
case ErrorType::SendMail:
|
||||||
|
new_code = -(105000 - error_code);
|
||||||
|
break;
|
||||||
|
case ErrorType::ReceiveMail:
|
||||||
|
new_code = -(100300 - error_code);
|
||||||
|
break;
|
||||||
|
case ErrorType::CGI:
|
||||||
|
new_code = -(error_code + 110000);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::lock_guard lg(m_scheduler_buffer_lock);
|
std::lock_guard lg(m_scheduler_buffer_lock);
|
||||||
|
@ -498,6 +513,133 @@ NWC24::ErrorCode NetKDRequestDevice::DetermineSubtask(u16 entry_index,
|
||||||
return NWC24::WC24_ERR_INVALID_VALUE;
|
return NWC24::WC24_ERR_INVALID_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NWC24::ErrorCode NetKDRequestDevice::KDSendMail()
|
||||||
|
{
|
||||||
|
bool success = false;
|
||||||
|
Common::ScopeGuard exit_guard([&] {
|
||||||
|
std::lock_guard lg(m_scheduler_buffer_lock);
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
// m_scheduler_buffer[11] contains the amount of times we have sent for mail this IOS
|
||||||
|
// session.
|
||||||
|
m_scheduler_buffer[14] = Common::swap32(Common::swap32(m_scheduler_buffer[14]) + 1);
|
||||||
|
}
|
||||||
|
m_scheduler_buffer[4] = static_cast<u32>(CurrentFunction::None);
|
||||||
|
|
||||||
|
m_send_list.WriteSendList();
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lg(m_scheduler_buffer_lock);
|
||||||
|
m_scheduler_buffer[4] = Common::swap32(static_cast<u32>(CurrentFunction::Send));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_send_list.ReadSendList();
|
||||||
|
const std::string auth =
|
||||||
|
fmt::format("mlid=w{}\r\npasswd={}", m_config.Id(), m_config.GetPassword());
|
||||||
|
std::vector<Common::HttpRequest::Multiform> multiform = {{"mlid", auth}};
|
||||||
|
|
||||||
|
std::vector<u32> mails = m_send_list.GetMailToSend();
|
||||||
|
for (const u32 file_index : mails)
|
||||||
|
{
|
||||||
|
const u32 entry_id = m_send_list.GetEntryId(file_index);
|
||||||
|
const u32 mail_size = m_send_list.GetMailSize(file_index);
|
||||||
|
if (mail_size > MAX_MAIL_SIZE)
|
||||||
|
{
|
||||||
|
WARN_LOG_FMT(IOS_WC24,
|
||||||
|
"NET_KD_REQ: IOCTL_NWC24_SEND_MAIL_NOW: Mail at index {} was too large to send.",
|
||||||
|
entry_id);
|
||||||
|
LogError(ErrorType::SendMail, NWC24::WC24_MSG_TOO_BIG);
|
||||||
|
|
||||||
|
NWC24::ErrorCode res = m_send_list.DeleteMessage(file_index);
|
||||||
|
if (res != NWC24::WC24_OK)
|
||||||
|
{
|
||||||
|
LogError(ErrorType::SendMail, res);
|
||||||
|
}
|
||||||
|
mails.erase(std::remove(mails.begin(), mails.end(), file_index), mails.end());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> mail_data(mail_size);
|
||||||
|
NWC24::ErrorCode res = NWC24::ReadFromVFF(
|
||||||
|
NWC24::Mail::SEND_BOX_PATH, m_send_list.GetMailPath(file_index), m_ios.GetFS(), mail_data);
|
||||||
|
if (res != NWC24::WC24_OK)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "Reading mail at index {} failed with error code {}.", entry_id,
|
||||||
|
static_cast<s32>(res));
|
||||||
|
LogError(ErrorType::SendMail, NWC24::WC24_MSG_DAMAGED);
|
||||||
|
res = m_send_list.DeleteMessage(file_index);
|
||||||
|
if (res != NWC24::WC24_OK)
|
||||||
|
{
|
||||||
|
LogError(ErrorType::SendMail, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
mails.erase(std::remove(mails.begin(), mails.end(), file_index), mails.end());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string mail_str = {mail_data.begin(), mail_data.end()};
|
||||||
|
|
||||||
|
multiform.push_back({fmt::format("m{}", entry_id), mail_str});
|
||||||
|
}
|
||||||
|
|
||||||
|
const Common::HttpRequest::Response response =
|
||||||
|
m_http.PostMultiform(m_config.GetSendURL(), multiform);
|
||||||
|
|
||||||
|
if (!response)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_SEND_MAIL_NOW: Failed to request data at {}.",
|
||||||
|
m_config.GetSendURL());
|
||||||
|
return NWC24::WC24_ERR_SERVER;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now check if any mail failed to save to the server.
|
||||||
|
const std::string response_str = {response->begin(), response->end()};
|
||||||
|
const std::string code = GetValueFromCGIResponse(response_str, "cd");
|
||||||
|
if (code != "100")
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(
|
||||||
|
IOS_WC24,
|
||||||
|
"NET_KD_REQ: IOCTL_NWC24_CHECK_MAIL_NOW: Mail server returned non-success code: {}", code);
|
||||||
|
return NWC24::WC24_ERR_SERVER;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse in order to delete from bottom to top of the send list.
|
||||||
|
// We do this to ensure that new entries can be written as close to the beginning of the file as
|
||||||
|
// possible.
|
||||||
|
for (auto it = mails.rbegin(); it != mails.rend(); ++it)
|
||||||
|
{
|
||||||
|
const u32 entry_id = m_send_list.GetEntryId(*it);
|
||||||
|
Common::ScopeGuard delete_guard([&] {
|
||||||
|
NWC24::ErrorCode res = m_send_list.DeleteMessage(*it);
|
||||||
|
if (res != NWC24::WC24_OK)
|
||||||
|
{
|
||||||
|
LogError(ErrorType::SendMail, res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const std::string value = GetValueFromCGIResponse(response_str, fmt::format("cd{}", entry_id));
|
||||||
|
|
||||||
|
s32 cgi_code{};
|
||||||
|
const bool did_parse = TryParse(value, &cgi_code);
|
||||||
|
if (!did_parse)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "Mail server returned invalid CGI response code.");
|
||||||
|
LogError(ErrorType::CGI, NWC24::WC24_ERR_SERVER);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cgi_code != 100)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "Mail server failed to save mail at index {}", entry_id);
|
||||||
|
LogError(ErrorType::CGI, cgi_code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
return NWC24::WC24_OK;
|
||||||
|
}
|
||||||
|
|
||||||
NWC24::ErrorCode NetKDRequestDevice::KDDownload(const u16 entry_index,
|
NWC24::ErrorCode NetKDRequestDevice::KDDownload(const u16 entry_index,
|
||||||
const std::optional<u8> subtask_id)
|
const std::optional<u8> subtask_id)
|
||||||
{
|
{
|
||||||
|
@ -622,8 +764,8 @@ NWC24::ErrorCode NetKDRequestDevice::KDDownload(const u16 entry_index,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NWC24::ErrorCode reply = IOS::HLE::NWC24::OpenVFF(m_dl_list.GetVFFPath(entry_index), content_name,
|
NWC24::ErrorCode reply = IOS::HLE::NWC24::WriteToVFF(m_dl_list.GetVFFPath(entry_index),
|
||||||
m_ios.GetFS(), file_data);
|
content_name, m_ios.GetFS(), file_data);
|
||||||
|
|
||||||
if (reply != NWC24::WC24_OK)
|
if (reply != NWC24::WC24_OK)
|
||||||
{
|
{
|
||||||
|
@ -657,6 +799,13 @@ IPCReply NetKDRequestDevice::HandleNWC24CheckMailNow(const IOCtlRequest& request
|
||||||
return IPCReply(IPC_SUCCESS);
|
return IPCReply(IPC_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IPCReply NetKDRequestDevice::HandleNWC24SendMailNow(const IOCtlRequest& request)
|
||||||
|
{
|
||||||
|
const NWC24::ErrorCode reply = KDSendMail();
|
||||||
|
WriteReturnValue(reply, request.buffer_out);
|
||||||
|
return IPCReply(IPC_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
IPCReply NetKDRequestDevice::HandleNWC24DownloadNowEx(const IOCtlRequest& request)
|
IPCReply NetKDRequestDevice::HandleNWC24DownloadNowEx(const IOCtlRequest& request)
|
||||||
{
|
{
|
||||||
if (m_dl_list.IsDisabled() || !m_dl_list.ReadDlList())
|
if (m_dl_list.IsDisabled() || !m_dl_list.ReadDlList())
|
||||||
|
@ -981,6 +1130,9 @@ std::optional<IPCReply> NetKDRequestDevice::IOCtl(const IOCtlRequest& request)
|
||||||
case IOCTL_NWC24_DOWNLOAD_NOW_EX:
|
case IOCTL_NWC24_DOWNLOAD_NOW_EX:
|
||||||
return LaunchAsyncTask(&NetKDRequestDevice::HandleNWC24DownloadNowEx, request);
|
return LaunchAsyncTask(&NetKDRequestDevice::HandleNWC24DownloadNowEx, request);
|
||||||
|
|
||||||
|
case IOCTL_NWC24_SEND_MAIL_NOW:
|
||||||
|
return LaunchAsyncTask(&NetKDRequestDevice::HandleNWC24SendMailNow, request);
|
||||||
|
|
||||||
case IOCTL_NWC24_REQUEST_SHUTDOWN:
|
case IOCTL_NWC24_REQUEST_SHUTDOWN:
|
||||||
{
|
{
|
||||||
if (request.buffer_in == 0 || request.buffer_in % 4 != 0 || request.buffer_in_size < 8 ||
|
if (request.buffer_in == 0 || request.buffer_in % 4 != 0 || request.buffer_in_size < 8 ||
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include "Common/HttpRequest.h"
|
#include "Common/HttpRequest.h"
|
||||||
#include "Common/WorkQueueThread.h"
|
#include "Common/WorkQueueThread.h"
|
||||||
#include "Core/IOS/Device.h"
|
#include "Core/IOS/Device.h"
|
||||||
|
#include "Core/IOS/Network/KD/Mail/WC24FriendList.h"
|
||||||
#include "Core/IOS/Network/KD/Mail/WC24Send.h"
|
#include "Core/IOS/Network/KD/Mail/WC24Send.h"
|
||||||
#include "Core/IOS/Network/KD/NWC24Config.h"
|
#include "Core/IOS/Network/KD/NWC24Config.h"
|
||||||
#include "Core/IOS/Network/KD/NWC24DL.h"
|
#include "Core/IOS/Network/KD/NWC24DL.h"
|
||||||
|
@ -72,6 +73,9 @@ private:
|
||||||
Client,
|
Client,
|
||||||
Server,
|
Server,
|
||||||
CheckMail,
|
CheckMail,
|
||||||
|
SendMail,
|
||||||
|
ReceiveMail,
|
||||||
|
CGI,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class SchedulerEvent
|
enum class SchedulerEvent
|
||||||
|
@ -80,14 +84,18 @@ private:
|
||||||
Download,
|
Download,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
IPCReply HandleNWC24SendMailNow(const IOCtlRequest& request);
|
||||||
NWC24::ErrorCode KDCheckMail(u32* mail_flag, u32* interval);
|
NWC24::ErrorCode KDCheckMail(u32* mail_flag, u32* interval);
|
||||||
IPCReply HandleRequestRegisterUserId(const IOCtlRequest& request);
|
IPCReply HandleRequestRegisterUserId(const IOCtlRequest& request);
|
||||||
|
NWC24::ErrorCode KDSendMail();
|
||||||
|
|
||||||
void LogError(ErrorType error_type, s32 error_code);
|
void LogError(ErrorType error_type, s32 error_code);
|
||||||
void SchedulerTimer();
|
void SchedulerTimer();
|
||||||
void SchedulerWorker(SchedulerEvent event);
|
void SchedulerWorker(SchedulerEvent event);
|
||||||
NWC24::ErrorCode DetermineDownloadTask(u16* entry_index, std::optional<u8>* subtask_id) const;
|
NWC24::ErrorCode DetermineDownloadTask(u16* entry_index, std::optional<u8>* subtask_id) const;
|
||||||
NWC24::ErrorCode DetermineSubtask(u16 entry_index, std::optional<u8>* subtask_id) const;
|
NWC24::ErrorCode DetermineSubtask(u16 entry_index, std::optional<u8>* subtask_id) const;
|
||||||
|
|
||||||
|
static constexpr u32 MAX_MAIL_SIZE = 208952;
|
||||||
static std::string GetValueFromCGIResponse(const std::string& response, const std::string& key);
|
static std::string GetValueFromCGIResponse(const std::string& response, const std::string& key);
|
||||||
static constexpr std::array<u8, 20> MAIL_CHECK_KEY = {0xce, 0x4c, 0xf2, 0x9a, 0x3d, 0x6b, 0xe1,
|
static constexpr std::array<u8, 20> MAIL_CHECK_KEY = {0xce, 0x4c, 0xf2, 0x9a, 0x3d, 0x6b, 0xe1,
|
||||||
0xc2, 0x61, 0x91, 0x72, 0xb5, 0xcb, 0x29,
|
0xc2, 0x61, 0x91, 0x72, 0xb5, 0xcb, 0x29,
|
||||||
|
@ -98,6 +106,7 @@ private:
|
||||||
NWC24::NWC24Config m_config;
|
NWC24::NWC24Config m_config;
|
||||||
NWC24::NWC24Dl m_dl_list;
|
NWC24::NWC24Dl m_dl_list;
|
||||||
NWC24::Mail::WC24SendList m_send_list;
|
NWC24::Mail::WC24SendList m_send_list;
|
||||||
|
NWC24::Mail::WC24FriendList m_friend_list;
|
||||||
Common::WorkQueueThread<AsyncTask> m_work_queue;
|
Common::WorkQueueThread<AsyncTask> m_work_queue;
|
||||||
Common::WorkQueueThread<std::function<void()>> m_scheduler_work_queue;
|
Common::WorkQueueThread<std::function<void()>> m_scheduler_work_queue;
|
||||||
std::mutex m_async_reply_lock;
|
std::mutex m_async_reply_lock;
|
||||||
|
|
|
@ -237,6 +237,48 @@ static ErrorCode WriteFile(const std::string& filename, const std::vector<u8>& t
|
||||||
return WC24_OK;
|
return WC24_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ErrorCode ReadFile(const std::string& filename, std::vector<u8>& out)
|
||||||
|
{
|
||||||
|
FIL src{};
|
||||||
|
const auto open_error_code = f_open(&src, filename.c_str(), FA_READ);
|
||||||
|
if (open_error_code != FR_OK)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "Failed to open file {} in VFF", filename);
|
||||||
|
return WC24_ERR_FILE_OPEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::ScopeGuard vff_close_guard{[&] { f_close(&src); }};
|
||||||
|
|
||||||
|
u32 size = static_cast<u32>(out.size());
|
||||||
|
u32 read_size{};
|
||||||
|
const auto read_error_code = f_read(&src, out.data(), size, &read_size);
|
||||||
|
if (read_error_code != FR_OK)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "Failed to read file {} in VFF: {}", filename,
|
||||||
|
static_cast<u32>(read_error_code));
|
||||||
|
return WC24_ERR_FILE_READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (read_size != size)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "Failed to read bytes of file {} to VFF ({} != {})", filename,
|
||||||
|
read_size, size);
|
||||||
|
return WC24_ERR_FILE_READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
// As prior operations did not fail, dismiss the guard and handle a potential error with f_close.
|
||||||
|
vff_close_guard.Dismiss();
|
||||||
|
|
||||||
|
const auto close_error_code = f_close(&src);
|
||||||
|
if (close_error_code != FR_OK)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "Failed to close file {} in VFF", filename);
|
||||||
|
return WC24_ERR_FILE_CLOSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return WC24_OK;
|
||||||
|
}
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
class VffFatFsCallbacks : public Common::FatFsCallbacks
|
class VffFatFsCallbacks : public Common::FatFsCallbacks
|
||||||
|
@ -258,7 +300,7 @@ public:
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ErrorCode OpenVFF(const std::string& path, const std::string& filename,
|
ErrorCode WriteToVFF(const std::string& path, const std::string& filename,
|
||||||
const std::shared_ptr<FS::FileSystem>& fs, const std::vector<u8>& data)
|
const std::shared_ptr<FS::FileSystem>& fs, const std::vector<u8>& data)
|
||||||
{
|
{
|
||||||
VffFatFsCallbacks callbacks;
|
VffFatFsCallbacks callbacks;
|
||||||
|
@ -286,6 +328,8 @@ ErrorCode OpenVFF(const std::string& path, const std::string& filename,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Common::ScopeGuard unmount_guard{[] { f_unmount(""); }};
|
||||||
|
|
||||||
const FRESULT vff_mount_error_code = vff_mount(callbacks.m_vff, &fatfs);
|
const FRESULT vff_mount_error_code = vff_mount(callbacks.m_vff, &fatfs);
|
||||||
if (vff_mount_error_code != FR_OK)
|
if (vff_mount_error_code != FR_OK)
|
||||||
{
|
{
|
||||||
|
@ -295,8 +339,6 @@ ErrorCode OpenVFF(const std::string& path, const std::string& filename,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Common::ScopeGuard unmount_guard{[] { f_unmount(""); }};
|
|
||||||
|
|
||||||
const auto write_error_code = WriteFile(filename, data);
|
const auto write_error_code = WriteFile(filename, data);
|
||||||
if (write_error_code != WC24_OK)
|
if (write_error_code != WC24_OK)
|
||||||
{
|
{
|
||||||
|
@ -312,4 +354,108 @@ ErrorCode OpenVFF(const std::string& path, const std::string& filename,
|
||||||
|
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ErrorCode ReadFromVFF(const std::string& path, const std::string& filename,
|
||||||
|
const std::shared_ptr<FS::FileSystem>& fs, std::vector<u8>& out)
|
||||||
|
{
|
||||||
|
VffFatFsCallbacks callbacks;
|
||||||
|
ErrorCode return_value;
|
||||||
|
Common::RunInFatFsContext(callbacks, [&]() {
|
||||||
|
auto temp = fs->OpenFile(PID_KD, PID_KD, path, FS::Mode::ReadWrite);
|
||||||
|
if (!temp)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "Failed to open VFF at: {}", path);
|
||||||
|
return_value = WC24_ERR_NOT_FOUND;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks.m_vff = &*temp;
|
||||||
|
|
||||||
|
FATFS fatfs{};
|
||||||
|
const FRESULT fatfs_mount_error_code = f_mount(&fatfs, "", 0);
|
||||||
|
if (fatfs_mount_error_code != FR_OK)
|
||||||
|
{
|
||||||
|
// The VFF is most likely broken.
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "Failed to mount VFF at: {}", path);
|
||||||
|
return_value = WC24_ERR_BROKEN;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::ScopeGuard unmount_guard{[] { f_unmount(""); }};
|
||||||
|
|
||||||
|
const FRESULT vff_mount_error_code = vff_mount(callbacks.m_vff, &fatfs);
|
||||||
|
if (vff_mount_error_code != FR_OK)
|
||||||
|
{
|
||||||
|
// The VFF is most likely broken.
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "Failed to mount VFF at: {}", path);
|
||||||
|
return_value = WC24_ERR_BROKEN;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ErrorCode read_error_code = ReadFile(filename, out);
|
||||||
|
if (read_error_code != WC24_OK)
|
||||||
|
{
|
||||||
|
return_value = read_error_code;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return_value = WC24_OK;
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode DeleteFileFromVFF(const std::string& path, const std::string& filename,
|
||||||
|
const std::shared_ptr<FS::FileSystem>& fs)
|
||||||
|
{
|
||||||
|
VffFatFsCallbacks callbacks;
|
||||||
|
ErrorCode return_value;
|
||||||
|
Common::RunInFatFsContext(callbacks, [&]() {
|
||||||
|
auto temp = fs->OpenFile(PID_KD, PID_KD, path, FS::Mode::ReadWrite);
|
||||||
|
if (!temp)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "Failed to open VFF at: {}", path);
|
||||||
|
return_value = WC24_ERR_NOT_FOUND;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks.m_vff = &*temp;
|
||||||
|
|
||||||
|
FATFS fatfs{};
|
||||||
|
const FRESULT fatfs_mount_error_code = f_mount(&fatfs, "", 0);
|
||||||
|
if (fatfs_mount_error_code != FR_OK)
|
||||||
|
{
|
||||||
|
// The VFF is most likely broken.
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "Failed to mount VFF at: {}", path);
|
||||||
|
return_value = WC24_ERR_BROKEN;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Common::ScopeGuard unmount_guard{[] { f_unmount(""); }};
|
||||||
|
|
||||||
|
const FRESULT vff_mount_error_code = vff_mount(callbacks.m_vff, &fatfs);
|
||||||
|
if (vff_mount_error_code != FR_OK)
|
||||||
|
{
|
||||||
|
// The VFF is most likely broken.
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "Failed to mount VFF at: {}", path);
|
||||||
|
return_value = WC24_ERR_BROKEN;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FRESULT unlink_code = f_unlink(filename.c_str());
|
||||||
|
if (unlink_code != FR_OK)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(IOS_WC24, "Failed to delete file {} in VFF at: {} Code: {}", filename, path,
|
||||||
|
static_cast<u32>(unlink_code));
|
||||||
|
return_value = WC24_ERR_BROKEN;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return_value = WC24_OK;
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
} // namespace IOS::HLE::NWC24
|
} // namespace IOS::HLE::NWC24
|
||||||
|
|
|
@ -22,8 +22,12 @@ namespace NWC24
|
||||||
constexpr u16 SECTOR_SIZE = 512;
|
constexpr u16 SECTOR_SIZE = 512;
|
||||||
constexpr u16 VF_LITTLE_ENDIAN = 0xFFFE;
|
constexpr u16 VF_LITTLE_ENDIAN = 0xFFFE;
|
||||||
constexpr u16 VF_BIG_ENDIAN = 0xFEFF;
|
constexpr u16 VF_BIG_ENDIAN = 0xFEFF;
|
||||||
ErrorCode OpenVFF(const std::string& path, const std::string& filename,
|
ErrorCode WriteToVFF(const std::string& path, const std::string& filename,
|
||||||
const std::shared_ptr<FS::FileSystem>& fs, const std::vector<u8>& data);
|
const std::shared_ptr<FS::FileSystem>& fs, const std::vector<u8>& data);
|
||||||
|
ErrorCode ReadFromVFF(const std::string& path, const std::string& filename,
|
||||||
|
const std::shared_ptr<FS::FileSystem>& fs, std::vector<u8>& out);
|
||||||
|
ErrorCode DeleteFileFromVFF(const std::string& path, const std::string& filename,
|
||||||
|
const std::shared_ptr<FS::FileSystem>& fs);
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
struct VFFHeader final
|
struct VFFHeader final
|
||||||
|
|
|
@ -366,6 +366,7 @@
|
||||||
<ClInclude Include="Core\IOS\Network\KD\VFF\VFFUtil.h" />
|
<ClInclude Include="Core\IOS\Network\KD\VFF\VFFUtil.h" />
|
||||||
<ClInclude Include="Core\IOS\Network\KD\WC24File.h" />
|
<ClInclude Include="Core\IOS\Network\KD\WC24File.h" />
|
||||||
<ClInclude Include="Core\IOS\Network\KD\Mail\MailCommon.h" />
|
<ClInclude Include="Core\IOS\Network\KD\Mail\MailCommon.h" />
|
||||||
|
<ClInclude Include="Core\IOS\Network\KD\Mail\WC24FriendList.h" />
|
||||||
<ClInclude Include="Core\IOS\Network\KD\Mail\WC24Send.h" />
|
<ClInclude Include="Core\IOS\Network\KD\Mail\WC24Send.h" />
|
||||||
<ClInclude Include="Core\IOS\Network\MACUtils.h" />
|
<ClInclude Include="Core\IOS\Network\MACUtils.h" />
|
||||||
<ClInclude Include="Core\IOS\Network\NCD\Manage.h" />
|
<ClInclude Include="Core\IOS\Network\NCD\Manage.h" />
|
||||||
|
@ -1017,6 +1018,7 @@
|
||||||
<ClCompile Include="Core\IOS\Network\KD\NWC24Config.cpp" />
|
<ClCompile Include="Core\IOS\Network\KD\NWC24Config.cpp" />
|
||||||
<ClCompile Include="Core\IOS\Network\KD\NWC24DL.cpp" />
|
<ClCompile Include="Core\IOS\Network\KD\NWC24DL.cpp" />
|
||||||
<ClCompile Include="Core\IOS\Network\KD\VFF\VFFUtil.cpp" />
|
<ClCompile Include="Core\IOS\Network\KD\VFF\VFFUtil.cpp" />
|
||||||
|
<ClCompile Include="Core\IOS\Network\KD\Mail\WC24FriendList.cpp" />
|
||||||
<ClCompile Include="Core\IOS\Network\KD\Mail\WC24Send.cpp" />
|
<ClCompile Include="Core\IOS\Network\KD\Mail\WC24Send.cpp" />
|
||||||
<ClCompile Include="Core\IOS\Network\MACUtils.cpp" />
|
<ClCompile Include="Core\IOS\Network\MACUtils.cpp" />
|
||||||
<ClCompile Include="Core\IOS\Network\NCD\Manage.cpp" />
|
<ClCompile Include="Core\IOS\Network\NCD\Manage.cpp" />
|
||||||
|
|
Loading…
Reference in New Issue