Merge pull request #12108 from noahpistilli/kd-check-mail

IOS/KD: Implement NWC24_CHECK_MAIL_NOW
This commit is contained in:
Admiral H. Curtiss 2023-09-03 19:52:26 +02:00 committed by GitHub
commit 143a13622f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 569 additions and 18 deletions

View File

@ -29,6 +29,8 @@ add_library(common
Crypto/bn.h Crypto/bn.h
Crypto/ec.cpp Crypto/ec.cpp
Crypto/ec.h Crypto/ec.h
Crypto/HMAC.cpp
Crypto/HMAC.h
Crypto/SHA1.cpp Crypto/SHA1.cpp
Crypto/SHA1.h Crypto/SHA1.h
Debug/MemoryPatches.cpp Debug/MemoryPatches.cpp

View File

@ -0,0 +1,27 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mbedtls/hmac_drbg.h>
#include "Common/Crypto/HMAC.h"
#include "Common/ScopeGuard.h"
namespace Common::HMAC
{
bool HMACWithSHA1(std::span<const u8> key, std::span<const u8> msg, u8* out)
{
mbedtls_md_context_t ctx;
Common::ScopeGuard guard{[&ctx] { mbedtls_md_free(&ctx); }};
mbedtls_md_init(&ctx);
if (mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), 1))
return false;
if (mbedtls_md_hmac_starts(&ctx, key.data(), key.size()) ||
mbedtls_md_hmac_update(&ctx, msg.data(), msg.size()) || mbedtls_md_hmac_finish(&ctx, out))
{
return false;
}
return true;
}
} // namespace Common::HMAC

View File

@ -0,0 +1,14 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Common/CommonTypes.h"
#include <span>
namespace Common::HMAC
{
// HMAC with the SHA1 message digest. Excepted output length is 20 bytes.
bool HMACWithSHA1(std::span<const u8> key, std::span<const u8> msg, u8* out);
} // namespace Common::HMAC

View File

@ -27,6 +27,7 @@ public:
explicit Impl(std::chrono::milliseconds timeout_ms, ProgressCallback callback); explicit Impl(std::chrono::milliseconds timeout_ms, ProgressCallback callback);
bool IsValid() const; bool IsValid() const;
std::string GetHeaderValue(std::string_view name) const;
void SetCookies(const std::string& cookies); void SetCookies(const std::string& cookies);
void UseIPv4(); void UseIPv4();
void FollowRedirects(long max); void FollowRedirects(long max);
@ -41,6 +42,7 @@ public:
private: private:
static inline std::once_flag s_curl_was_initialized; static inline std::once_flag s_curl_was_initialized;
ProgressCallback m_callback; ProgressCallback m_callback;
Headers m_response_headers;
std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> m_curl{nullptr, curl_easy_cleanup}; std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> m_curl{nullptr, curl_easy_cleanup};
std::string m_error_string; std::string m_error_string;
}; };
@ -82,6 +84,11 @@ s32 HttpRequest::GetLastResponseCode() const
return m_impl->GetLastResponseCode(); return m_impl->GetLastResponseCode();
} }
std::string HttpRequest::GetHeaderValue(std::string_view name) const
{
return m_impl->GetHeaderValue(name);
}
HttpRequest::Response HttpRequest::Get(const std::string& url, const Headers& headers, HttpRequest::Response HttpRequest::Get(const std::string& url, const Headers& headers,
AllowedReturnCodes codes) AllowedReturnCodes codes)
{ {
@ -173,6 +180,17 @@ void HttpRequest::Impl::FollowRedirects(long max)
curl_easy_setopt(m_curl.get(), CURLOPT_MAXREDIRS, max); curl_easy_setopt(m_curl.get(), CURLOPT_MAXREDIRS, max);
} }
std::string HttpRequest::Impl::GetHeaderValue(std::string_view name) const
{
for (const auto& [key, value] : m_response_headers)
{
if (key == name)
return value.value();
}
return {};
}
std::string HttpRequest::Impl::EscapeComponent(const std::string& string) std::string HttpRequest::Impl::EscapeComponent(const std::string& string)
{ {
char* escaped = curl_easy_escape(m_curl.get(), string.c_str(), static_cast<int>(string.size())); char* escaped = curl_easy_escape(m_curl.get(), string.c_str(), static_cast<int>(string.size()));
@ -190,10 +208,26 @@ static size_t CurlWriteCallback(char* data, size_t size, size_t nmemb, void* use
return actual_size; return actual_size;
} }
static size_t header_callback(char* buffer, size_t size, size_t nitems, void* userdata)
{
auto* headers = static_cast<HttpRequest::Headers*>(userdata);
std::string_view full_buffer = std::string_view{buffer, nitems};
const size_t colon_pos = full_buffer.find(':');
if (colon_pos == std::string::npos)
return nitems * size;
const std::string_view key = full_buffer.substr(0, colon_pos);
const std::string_view value = StripWhitespace(full_buffer.substr(colon_pos + 1));
headers->emplace(std::string{key}, std::string{value});
return nitems * size;
}
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)
{ {
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)
@ -215,6 +249,9 @@ HttpRequest::Response HttpRequest::Impl::Fetch(const std::string& url, Method me
} }
curl_easy_setopt(m_curl.get(), CURLOPT_HTTPHEADER, list); curl_easy_setopt(m_curl.get(), CURLOPT_HTTPHEADER, list);
curl_easy_setopt(m_curl.get(), CURLOPT_HEADERFUNCTION, header_callback);
curl_easy_setopt(m_curl.get(), CURLOPT_HEADERDATA, static_cast<void*>(&m_response_headers));
std::vector<u8> buffer; std::vector<u8> buffer;
curl_easy_setopt(m_curl.get(), CURLOPT_WRITEFUNCTION, CurlWriteCallback); curl_easy_setopt(m_curl.get(), CURLOPT_WRITEFUNCTION, CurlWriteCallback);
curl_easy_setopt(m_curl.get(), CURLOPT_WRITEDATA, &buffer); curl_easy_setopt(m_curl.get(), CURLOPT_WRITEDATA, &buffer);

View File

@ -40,6 +40,7 @@ public:
void FollowRedirects(long max = 1); void FollowRedirects(long max = 1);
s32 GetLastResponseCode() const; s32 GetLastResponseCode() const;
std::string EscapeComponent(const std::string& string); std::string EscapeComponent(const std::string& string);
std::string GetHeaderValue(std::string_view name) const;
Response Get(const std::string& url, const Headers& headers = {}, Response Get(const std::string& url, const Headers& headers = {},
AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only); AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only);
Response Post(const std::string& url, const std::vector<u8>& payload, const Headers& headers = {}, Response Post(const std::string& url, const std::vector<u8>& payload, const Headers& headers = {},

View File

@ -693,4 +693,9 @@ bool CaseInsensitiveEquals(std::string_view a, std::string_view b)
return std::equal(a.begin(), a.end(), b.begin(), return std::equal(a.begin(), a.end(), b.begin(),
[](char ca, char cb) { return Common::ToLower(ca) == Common::ToLower(cb); }); [](char ca, char cb) { return Common::ToLower(ca) == Common::ToLower(cb); });
} }
std::string BytesToHexString(std::span<const u8> bytes)
{
return fmt::format("{:02x}", fmt::join(bytes, ""));
}
} // namespace Common } // namespace Common

View File

@ -11,6 +11,7 @@
#include <iomanip> #include <iomanip>
#include <limits> #include <limits>
#include <locale> #include <locale>
#include <span>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
@ -313,4 +314,5 @@ std::string GetEscapedHtml(std::string html);
void ToLower(std::string* str); void ToLower(std::string* str);
void ToUpper(std::string* str); void ToUpper(std::string* str);
bool CaseInsensitiveEquals(std::string_view a, std::string_view b); bool CaseInsensitiveEquals(std::string_view a, std::string_view b);
std::string BytesToHexString(std::span<const u8> bytes);
} // namespace Common } // namespace Common

View File

@ -378,6 +378,9 @@ add_library(core
IOS/Network/KD/VFF/VFFUtil.cpp IOS/Network/KD/VFF/VFFUtil.cpp
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/WC24Send.cpp
IOS/Network/KD/Mail/WC24Send.h
IOS/Network/MACUtils.cpp IOS/Network/MACUtils.cpp
IOS/Network/MACUtils.h IOS/Network/MACUtils.h
IOS/Network/NCD/Manage.cpp IOS/Network/NCD/Manage.cpp

View File

@ -0,0 +1,74 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "Common/CommonTypes.h"
#include <array>
namespace IOS::HLE::NWC24::Mail
{
constexpr u32 MAIL_LIST_MAGIC = 0x57635466; // WcTf
#pragma pack(push, 1)
struct MailListHeader final
{
u32 magic; // 'WcTf' 0x57635466
u32 version; // 4 in Wii Menu 4.x
u32 number_of_mail;
u32 total_entries;
u32 total_size_of_messages;
u32 filesize;
u32 next_entry_id;
u32 next_entry_offset;
u32 unk1;
u32 vff_free_space;
std::array<u8, 48> unk2;
std::array<char, 40> mail_flag;
};
static_assert(sizeof(MailListHeader) == 128);
struct MultipartEntry final
{
u32 offset;
u32 size;
};
struct MailListEntry final
{
u32 id;
u32 flag;
u32 msg_size;
u32 app_id;
u32 header_length;
u32 tag;
u32 wii_cmd;
// Never validated
u32 crc32;
u64 from_friend_code;
u32 minutes_since_1900;
u32 padding;
u8 always_1;
u8 number_of_multipart_entries;
u16 app_group;
u32 packed_from;
u32 packed_to;
u32 packed_subject;
u32 packed_charset;
u32 packed_transfer_encoding;
u32 message_offset;
// Set to message_length if content transfer encoding is not base64.
u32 encoded_length;
std::array<MultipartEntry, 2> multipart_entries;
std::array<u32, 2> multipart_sizes;
std::array<u32, 2> multipart_content_types;
u32 message_length;
u32 dwc_id;
u32 always_0x80000000;
u32 padding3;
};
static_assert(sizeof(MailListEntry) == 128);
#pragma pack(pop)
} // namespace IOS::HLE::NWC24::Mail

View File

@ -0,0 +1,68 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/IOS/Network/KD/Mail/WC24Send.h"
#include "Core/IOS/FS/FileSystem.h"
#include "Core/IOS/Uids.h"
namespace IOS::HLE::NWC24::Mail
{
constexpr const char SEND_LIST_PATH[] = "/" WII_WC24CONF_DIR "/mbox"
"/wc24send.ctl";
WC24SendList::WC24SendList(std::shared_ptr<FS::FileSystem> fs) : m_fs{std::move(fs)}
{
ReadSendList();
}
void WC24SendList::ReadSendList()
{
const auto file = m_fs->OpenFile(PID_KD, PID_KD, SEND_LIST_PATH, FS::Mode::Read);
if (!file || !file->Read(&m_data, 1))
return;
if (file->GetStatus()->size != SEND_LIST_SIZE)
{
ERROR_LOG_FMT(IOS_WC24, "The WC24 Send list file is not the correct size.");
return;
}
const s32 file_error = CheckSendList();
if (!file_error)
ERROR_LOG_FMT(IOS_WC24, "There is an error in the Send List for WC24 mail");
}
void WC24SendList::WriteSendList() const
{
constexpr FS::Modes public_modes{FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite};
m_fs->CreateFullPath(PID_KD, PID_KD, SEND_LIST_PATH, 0, public_modes);
const auto file = m_fs->CreateAndOpenFile(PID_KD, PID_KD, SEND_LIST_PATH, public_modes);
if (!file || !file->Write(&m_data, 1))
ERROR_LOG_FMT(IOS_WC24, "Failed to open or write WC24 Send list file");
}
bool WC24SendList::CheckSendList() const
{
// 'WcTf' magic
if (Common::swap32(m_data.header.magic) != MAIL_LIST_MAGIC)
{
ERROR_LOG_FMT(IOS_WC24, "Send 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, "Send List version mismatch");
return false;
}
return true;
}
std::string_view WC24SendList::GetMailFlag() const
{
return {m_data.header.mail_flag.data(), m_data.header.mail_flag.size()};
}
} // namespace IOS::HLE::NWC24::Mail

View File

@ -0,0 +1,52 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string_view>
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Common/Swap.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 SEND_BOX_PATH[] = "/" WII_WC24CONF_DIR "/mbox"
"/wc24send.mbx";
class WC24SendList final
{
public:
explicit WC24SendList(std::shared_ptr<FS::FileSystem> fs);
void ReadSendList();
bool CheckSendList() const;
void WriteSendList() const;
std::string_view GetMailFlag() const;
private:
static constexpr u32 MAX_ENTRIES = 127;
static constexpr u32 SEND_LIST_SIZE = 16384;
#pragma pack(push, 1)
struct SendList final
{
MailListHeader header;
std::array<MailListEntry, MAX_ENTRIES> entries;
};
static_assert(sizeof(SendList) == SEND_LIST_SIZE);
#pragma pack(pop)
SendList m_data;
std::shared_ptr<FS::FileSystem> m_fs;
};
} // namespace NWC24::Mail
} // namespace IOS::HLE

View File

@ -216,4 +216,16 @@ void NWC24Config::SetEmail(const char* email)
strncpy(m_data.email, email, MAX_EMAIL_LENGTH); strncpy(m_data.email, email, MAX_EMAIL_LENGTH);
m_data.email[MAX_EMAIL_LENGTH - 1] = '\0'; m_data.email[MAX_EMAIL_LENGTH - 1] = '\0';
} }
std::string_view NWC24Config::GetMlchkid() const
{
const size_t size = strnlen(m_data.mlchkid, MAX_MLCHKID_LENGTH);
return {m_data.mlchkid, size};
}
std::string NWC24Config::GetCheckURL() const
{
const size_t size = strnlen(m_data.http_urls[1], MAX_URL_LENGTH);
return {m_data.http_urls[1], size};
}
} // namespace IOS::HLE::NWC24 } // namespace IOS::HLE::NWC24

View File

@ -69,6 +69,9 @@ public:
u32 Checksum() const; u32 Checksum() const;
void SetChecksum(u32 checksum); void SetChecksum(u32 checksum);
std::string_view GetMlchkid() const;
std::string GetCheckURL() const;
NWC24CreationStage CreationStage() const; NWC24CreationStage CreationStage() const;
void SetCreationStage(NWC24CreationStage creation_stage); void SetCreationStage(NWC24CreationStage creation_stage);
@ -92,6 +95,7 @@ private:
MAX_URL_LENGTH = 0x80, MAX_URL_LENGTH = 0x80,
MAX_EMAIL_LENGTH = 0x40, MAX_EMAIL_LENGTH = 0x40,
MAX_PASSWORD_LENGTH = 0x20, MAX_PASSWORD_LENGTH = 0x20,
MAX_MLCHKID_LENGTH = 0x24,
}; };
#pragma pack(push, 1) #pragma pack(push, 1)
@ -104,7 +108,7 @@ private:
NWC24CreationStage creation_stage; NWC24CreationStage creation_stage;
char email[MAX_EMAIL_LENGTH]; char email[MAX_EMAIL_LENGTH];
char paswd[MAX_PASSWORD_LENGTH]; char paswd[MAX_PASSWORD_LENGTH];
char mlchkid[0x24]; char mlchkid[MAX_MLCHKID_LENGTH];
char http_urls[URL_COUNT][MAX_URL_LENGTH]; char http_urls[URL_COUNT][MAX_URL_LENGTH];
u8 reserved[0xDC]; u8 reserved[0xDC];
u32 enable_booting; u32 enable_booting;

View File

@ -12,11 +12,14 @@
#include "Common/BitUtils.h" #include "Common/BitUtils.h"
#include "Common/CommonPaths.h" #include "Common/CommonPaths.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Crypto/HMAC.h"
#include "Common/FileUtil.h" #include "Common/FileUtil.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/NandPaths.h" #include "Common/NandPaths.h"
#include "Common/SettingsHandler.h" #include "Common/SettingsHandler.h"
#include "Common/Random.h"
#include "Common/ScopeGuard.h"
#include "Core/CommonTitles.h" #include "Core/CommonTitles.h"
#include "Core/HW/Memmap.h" #include "Core/HW/Memmap.h"
#include "Core/IOS/FS/FileSystem.h" #include "Core/IOS/FS/FileSystem.h"
@ -149,7 +152,8 @@ s32 NWC24MakeUserID(u64* nwc24_id, u32 hollywood_id, u16 id_ctr, HardwareModel h
} // Anonymous namespace } // Anonymous namespace
NetKDRequestDevice::NetKDRequestDevice(EmulationKernel& ios, const std::string& device_name) NetKDRequestDevice::NetKDRequestDevice(EmulationKernel& ios, const std::string& device_name)
: EmulationDevice(ios, device_name), 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()}
{ {
// Enable all NWC24 permissions // Enable all NWC24 permissions
m_scheduler_buffer[1] = Common::swap32(-1); m_scheduler_buffer[1] = Common::swap32(-1);
@ -161,6 +165,12 @@ NetKDRequestDevice::NetKDRequestDevice(EmulationKernel& ios, const std::string&
m_async_replies.emplace(AsyncReply{task.request, reply.return_value}); m_async_replies.emplace(AsyncReply{task.request, reply.return_value});
} }
}); });
m_handle_mail = !ios.GetIOSC().IsUsingDefaultId();
m_scheduler_work_queue.Reset("WiiConnect24 Scheduler Worker",
[](std::function<void()> task) { task(); });
m_scheduler_timer_thread = std::thread([this] { SchedulerTimer(); });
} }
NetKDRequestDevice::~NetKDRequestDevice() NetKDRequestDevice::~NetKDRequestDevice()
@ -168,6 +178,16 @@ NetKDRequestDevice::~NetKDRequestDevice()
auto socket_manager = GetEmulationKernel().GetSocketManager(); auto socket_manager = GetEmulationKernel().GetSocketManager();
if (socket_manager) if (socket_manager)
socket_manager->Clean(); socket_manager->Clean();
{
std::lock_guard lg(m_scheduler_lock);
if (!m_scheduler_timer_thread.joinable())
return;
m_shutdown_event.Set();
}
m_scheduler_timer_thread.join();
} }
void NetKDRequestDevice::Update() void NetKDRequestDevice::Update()
@ -183,6 +203,78 @@ void NetKDRequestDevice::Update()
} }
} }
void NetKDRequestDevice::SchedulerTimer()
{
u32 mail_time_state = 0;
u32 download_time_state = 0;
Common::SetCurrentThreadName("KD Scheduler Timer");
while (true)
{
{
std::lock_guard lg(m_scheduler_lock);
if (m_mail_span <= mail_time_state && m_handle_mail)
{
m_scheduler_work_queue.EmplaceItem([this] { SchedulerWorker(SchedulerEvent::Mail); });
INFO_LOG_FMT(IOS_WC24, "NET_KD_REQ: Dispatching Mail Task from Scheduler");
mail_time_state = 0;
}
if (m_download_span <= download_time_state)
{
INFO_LOG_FMT(IOS_WC24, "NET_KD_REQ: Dispatching Download Task from Scheduler");
m_scheduler_work_queue.EmplaceItem([this] { SchedulerWorker(SchedulerEvent::Download); });
download_time_state = 0;
}
}
if (m_shutdown_event.WaitFor(std::chrono::minutes{1}))
return;
mail_time_state++;
download_time_state++;
}
}
void NetKDRequestDevice::SchedulerWorker(const SchedulerEvent event)
{
if (event == SchedulerEvent::Download)
{
// TODO: Implement downloader part of scheduler
return;
}
else
{
if (!m_config.IsRegistered())
return;
u32 mail_flag{};
u32 interval{};
NWC24::ErrorCode code = KDCheckMail(&mail_flag, &interval);
if (code != NWC24::WC24_OK)
{
LogError(ErrorType::CheckMail, code);
}
}
}
std::string NetKDRequestDevice::GetValueFromCGIResponse(const std::string& response,
const std::string& key)
{
const std::vector<std::string> raw_fields = SplitString(response, '\n');
for (const std::string& field : raw_fields)
{
const std::vector<std::string> key_value = SplitString(field, '=');
if (key_value.size() != 2)
continue;
if (key_value[0] == key)
return std::string{StripWhitespace(key_value[1])};
}
return {};
}
void NetKDRequestDevice::LogError(ErrorType error_type, s32 error_code) void NetKDRequestDevice::LogError(ErrorType error_type, s32 error_code)
{ {
s32 new_code{}; s32 new_code{};
@ -200,6 +292,9 @@ void NetKDRequestDevice::LogError(ErrorType error_type, s32 error_code)
case ErrorType::Server: case ErrorType::Server:
new_code = -(117000 + error_code); new_code = -(117000 + error_code);
break; break;
case ErrorType::CheckMail:
new_code = -(102200 - error_code);
break;
} }
std::lock_guard lg(m_scheduler_buffer_lock); std::lock_guard lg(m_scheduler_buffer_lock);
@ -211,6 +306,100 @@ void NetKDRequestDevice::LogError(ErrorType error_type, s32 error_code)
m_scheduler_buffer[2] = Common::swap32(new_code); m_scheduler_buffer[2] = Common::swap32(new_code);
} }
NWC24::ErrorCode NetKDRequestDevice::KDCheckMail(u32* mail_flag, u32* interval)
{
bool success = false;
Common::ScopeGuard state_guard([&] {
std::lock_guard lg(m_scheduler_buffer_lock);
if (success)
{
// m_scheduler_buffer[11] contains the amount of times we have checked for mail this IOS
// session.
m_scheduler_buffer[11] = Common::swap32(Common::swap32(m_scheduler_buffer[11]) + 1);
}
m_scheduler_buffer[4] = static_cast<u32>(CurrentFunction::None);
});
{
std::lock_guard lg(m_scheduler_buffer_lock);
m_scheduler_buffer[4] = Common::swap32(static_cast<u32>(CurrentFunction::Check));
}
u64 random_number{};
Common::Random::Generate(&random_number, sizeof(u64));
const std::string form_data(
fmt::format("mlchkid={}&chlng={}", m_config.GetMlchkid(), random_number));
const Common::HttpRequest::Response response = m_http.Post(m_config.GetCheckURL(), form_data);
if (!response)
{
ERROR_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_CHECK_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_CHECK_MAIL_NOW: Mail server returned non-success code: {}", code);
return NWC24::WC24_ERR_SERVER;
}
const std::string server_hmac = GetValueFromCGIResponse(response_str, "res");
const std::string str_mail_flag = GetValueFromCGIResponse(response_str, "mail.flag");
const std::string str_interval = GetValueFromCGIResponse(response_str, "interval");
DEBUG_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_CHECK_MAIL_NOW: Server HMAC: {}", server_hmac);
// On a real Wii, a response to a challenge is expected and would be verified by KD.
const std::string hmac_message =
fmt::format("{}\nw{}\n{}\n{}", random_number, m_config.Id(), str_mail_flag, str_interval);
std::array<u8, 20> hashed{};
Common::HMAC::HMACWithSHA1(
MAIL_CHECK_KEY,
std::span<const u8>(reinterpret_cast<const u8*>(hmac_message.data()), hmac_message.size()),
hashed.data());
// On a real Wii, strncmp is used to compare both hashes. This means that it is a case-sensitive
// comparison. KD will generate a lowercase hash as well as expect a lowercase hash from the
// server.
if (Common::BytesToHexString(hashed) != server_hmac)
{
ERROR_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_CHECK_MAIL_NOW: Server HMAC is invalid.");
return NWC24::WC24_ERR_SERVER;
}
*mail_flag = std::strncmp(str_mail_flag.data(), m_send_list.GetMailFlag().data(), 22) != 0;
{
std::lock_guard scheduler_lg(m_scheduler_lock);
bool did_parse = TryParse(m_http.GetHeaderValue("X-Wii-Mail-Check-Span"), interval);
if (did_parse)
{
if (*interval == 0)
{
*interval = 1;
}
m_mail_span = *interval;
}
did_parse = TryParse(m_http.GetHeaderValue("X-Wii-Download-Span"), &m_download_span);
if (did_parse)
{
if (m_download_span == 0)
{
m_download_span = 1;
}
}
}
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)
{ {
@ -308,6 +497,21 @@ NWC24::ErrorCode NetKDRequestDevice::KDDownload(const u16 entry_index,
return reply; return reply;
} }
IPCReply NetKDRequestDevice::HandleNWC24CheckMailNow(const IOCtlRequest& request)
{
auto& system = GetSystem();
auto& memory = system.GetMemory();
u32 mail_flag{};
u32 interval{};
const NWC24::ErrorCode reply = KDCheckMail(&mail_flag, &interval);
WriteReturnValue(reply, request.buffer_out);
memory.Write_U32(mail_flag, request.buffer_out + 4);
memory.Write_U32(interval, request.buffer_out + 8);
return IPCReply(IPC_SUCCESS);
}
IPCReply NetKDRequestDevice::HandleNWC24DownloadNowEx(const IOCtlRequest& request) IPCReply NetKDRequestDevice::HandleNWC24DownloadNowEx(const IOCtlRequest& request)
{ {
m_dl_list.ReadDlList(); m_dl_list.ReadDlList();
@ -446,7 +650,7 @@ std::optional<IPCReply> NetKDRequestDevice::IOCtl(const IOCtlRequest& request)
case IOCTL_NWC24_REQUEST_GENERATED_USER_ID: // (Input: none, Output: 32 bytes) case IOCTL_NWC24_REQUEST_GENERATED_USER_ID: // (Input: none, Output: 32 bytes)
INFO_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_REQUEST_GENERATED_USER_ID"); INFO_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_REQUEST_GENERATED_USER_ID");
if (config.IsCreated()) if (m_config.IsCreated())
{ {
const std::string settings_file_path = const std::string settings_file_path =
Common::GetTitleDataPath(Titles::SYSTEM_MENU) + "/" WII_SETTING; Common::GetTitleDataPath(Titles::SYSTEM_MENU) + "/" WII_SETTING;
@ -467,19 +671,19 @@ std::optional<IPCReply> NetKDRequestDevice::IOCtl(const IOCtlRequest& request)
if (!area.empty() && !model.empty()) if (!area.empty() && !model.empty())
{ {
const u8 area_code = GetAreaCode(area); const u8 area_code = GetAreaCode(area);
const u8 id_ctr = u8(config.IdGen()); const u8 id_ctr = u8(m_config.IdGen());
const HardwareModel hardware_model = GetHardwareModel(model); const HardwareModel hardware_model = GetHardwareModel(model);
const u32 hollywood_id = m_ios.GetIOSC().GetDeviceId(); const u32 hollywood_id = m_ios.GetIOSC().GetDeviceId();
u64 user_id = 0; u64 user_id = 0;
const s32 ret = NWC24MakeUserID(&user_id, hollywood_id, id_ctr, hardware_model, area_code); const s32 ret = NWC24MakeUserID(&user_id, hollywood_id, id_ctr, hardware_model, area_code);
config.SetId(user_id); m_config.SetId(user_id);
config.IncrementIdGen(); m_config.IncrementIdGen();
config.SetCreationStage(NWC24::NWC24CreationStage::Generated); m_config.SetCreationStage(NWC24::NWC24CreationStage::Generated);
config.SetChecksum(config.CalculateNwc24ConfigChecksum()); m_config.SetChecksum(m_config.CalculateNwc24ConfigChecksum());
config.WriteConfig(); m_config.WriteConfig();
config.WriteCBK(); m_config.WriteCBK();
WriteReturnValue(ret, request.buffer_out); WriteReturnValue(ret, request.buffer_out);
} }
@ -489,16 +693,16 @@ std::optional<IPCReply> NetKDRequestDevice::IOCtl(const IOCtlRequest& request)
WriteReturnValue(NWC24::WC24_ERR_FATAL, request.buffer_out); WriteReturnValue(NWC24::WC24_ERR_FATAL, request.buffer_out);
} }
} }
else if (config.IsGenerated()) else if (m_config.IsGenerated())
{ {
WriteReturnValue(NWC24::WC24_ERR_ID_GENERATED, request.buffer_out); WriteReturnValue(NWC24::WC24_ERR_ID_GENERATED, request.buffer_out);
} }
else if (config.IsRegistered()) else if (m_config.IsRegistered())
{ {
WriteReturnValue(NWC24::WC24_ERR_ID_REGISTERED, request.buffer_out); WriteReturnValue(NWC24::WC24_ERR_ID_REGISTERED, request.buffer_out);
} }
memory.Write_U64(config.Id(), request.buffer_out + 4); memory.Write_U64(m_config.Id(), request.buffer_out + 4);
memory.Write_U32(u32(config.CreationStage()), request.buffer_out + 0xC); memory.Write_U32(u32(m_config.CreationStage()), request.buffer_out + 0xC);
break; break;
case IOCTL_NWC24_GET_SCHEDULER_STAT: case IOCTL_NWC24_GET_SCHEDULER_STAT:
@ -514,8 +718,8 @@ std::optional<IPCReply> NetKDRequestDevice::IOCtl(const IOCtlRequest& request)
request.buffer_out_size); request.buffer_out_size);
// On a real Wii, GetSchedulerStat copies memory containing a list of error codes recorded by // On a real Wii, GetSchedulerStat copies memory containing a list of error codes recorded by
// KD among other things. In most instances there will never be more than one error code // KD among other things.
// recorded as we do not have a scheduler. std::lock_guard lg(m_scheduler_buffer_lock);
const u32 out_size = std::min(request.buffer_out_size, 256U); const u32 out_size = std::min(request.buffer_out_size, 256U);
memory.CopyToEmu(request.buffer_out, m_scheduler_buffer.data(), out_size); memory.CopyToEmu(request.buffer_out, m_scheduler_buffer.data(), out_size);
break; break;
@ -525,6 +729,9 @@ std::optional<IPCReply> NetKDRequestDevice::IOCtl(const IOCtlRequest& request)
INFO_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_SAVE_MAIL_NOW - NI"); INFO_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_SAVE_MAIL_NOW - NI");
break; break;
case IOCTL_NWC24_CHECK_MAIL_NOW:
return LaunchAsyncTask(&NetKDRequestDevice::HandleNWC24CheckMailNow, request);
case IOCTL_NWC24_DOWNLOAD_NOW_EX: case IOCTL_NWC24_DOWNLOAD_NOW_EX:
return LaunchAsyncTask(&NetKDRequestDevice::HandleNWC24DownloadNowEx, request); return LaunchAsyncTask(&NetKDRequestDevice::HandleNWC24DownloadNowEx, request);

View File

@ -9,9 +9,11 @@
#include "Common/CommonPaths.h" #include "Common/CommonPaths.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Event.h"
#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/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"
@ -26,6 +28,7 @@ public:
NetKDRequestDevice(EmulationKernel& ios, const std::string& device_name); NetKDRequestDevice(EmulationKernel& ios, const std::string& device_name);
IPCReply HandleNWC24DownloadNowEx(const IOCtlRequest& request); IPCReply HandleNWC24DownloadNowEx(const IOCtlRequest& request);
NWC24::ErrorCode KDDownload(const u16 entry_index, const std::optional<u8> subtask_id); NWC24::ErrorCode KDDownload(const u16 entry_index, const std::optional<u8> subtask_id);
IPCReply HandleNWC24CheckMailNow(const IOCtlRequest& request);
~NetKDRequestDevice() override; ~NetKDRequestDevice() override;
std::optional<IPCReply> IOCtl(const IOCtlRequest& request) override; std::optional<IPCReply> IOCtl(const IOCtlRequest& request) override;
@ -51,19 +54,48 @@ private:
return std::nullopt; return std::nullopt;
} }
enum class CurrentFunction : u32
{
None = 0,
Account = 1,
Check = 2,
Receive = 3,
Send = 5,
Save = 6,
Download = 7,
};
enum class ErrorType enum class ErrorType
{ {
Account, Account,
KD_Download, KD_Download,
Client, Client,
Server, Server,
CheckMail,
}; };
void LogError(ErrorType error_type, s32 error_code); enum class SchedulerEvent
{
Mail,
Download,
};
NWC24::NWC24Config config; NWC24::ErrorCode KDCheckMail(u32* mail_flag, u32* interval);
void LogError(ErrorType error_type, s32 error_code);
void SchedulerTimer();
void SchedulerWorker(SchedulerEvent event);
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,
0xc2, 0x61, 0x91, 0x72, 0xb5, 0xcb, 0x29,
0x8c, 0x89, 0x72, 0xd4, 0x50, 0xad};
NWC24::NWC24Config m_config;
NWC24::NWC24Dl m_dl_list; NWC24::NWC24Dl m_dl_list;
NWC24::Mail::WC24SendList m_send_list;
Common::WorkQueueThread<AsyncTask> m_work_queue; Common::WorkQueueThread<AsyncTask> m_work_queue;
Common::WorkQueueThread<std::function<void()>> m_scheduler_work_queue;
std::mutex m_async_reply_lock; std::mutex m_async_reply_lock;
std::mutex m_scheduler_buffer_lock; std::mutex m_scheduler_buffer_lock;
std::queue<AsyncReply> m_async_replies; std::queue<AsyncReply> m_async_replies;
@ -71,5 +103,11 @@ private:
std::array<u32, 256> m_scheduler_buffer{}; std::array<u32, 256> m_scheduler_buffer{};
// TODO: Maybe move away from Common::HttpRequest? // TODO: Maybe move away from Common::HttpRequest?
Common::HttpRequest m_http{std::chrono::minutes{1}}; Common::HttpRequest m_http{std::chrono::minutes{1}};
u32 m_download_span = 2;
u32 m_mail_span = 1;
bool m_handle_mail;
Common::Event m_shutdown_event;
std::mutex m_scheduler_lock;
std::thread m_scheduler_timer_thread;
}; };
} // namespace IOS::HLE } // namespace IOS::HLE

View File

@ -35,6 +35,7 @@
<ClInclude Include="Common\Crypto\AES.h" /> <ClInclude Include="Common\Crypto\AES.h" />
<ClInclude Include="Common\Crypto\bn.h" /> <ClInclude Include="Common\Crypto\bn.h" />
<ClInclude Include="Common\Crypto\ec.h" /> <ClInclude Include="Common\Crypto\ec.h" />
<ClInclude Include="Common\Crypto\HMAC.h" />
<ClInclude Include="Common\Crypto\SHA1.h" /> <ClInclude Include="Common\Crypto\SHA1.h" />
<ClInclude Include="Common\Debug\MemoryPatches.h" /> <ClInclude Include="Common\Debug\MemoryPatches.h" />
<ClInclude Include="Common\Debug\Threads.h" /> <ClInclude Include="Common\Debug\Threads.h" />
@ -362,6 +363,8 @@
<ClInclude Include="Core\IOS\Network\KD\NWC24DL.h" /> <ClInclude Include="Core\IOS\Network\KD\NWC24DL.h" />
<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\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" />
<ClInclude Include="Core\IOS\Network\NCD\WiiNetConfig.h" /> <ClInclude Include="Core\IOS\Network\NCD\WiiNetConfig.h" />
@ -762,6 +765,7 @@
<ClCompile Include="Common\Crypto\AES.cpp" /> <ClCompile Include="Common\Crypto\AES.cpp" />
<ClCompile Include="Common\Crypto\bn.cpp" /> <ClCompile Include="Common\Crypto\bn.cpp" />
<ClCompile Include="Common\Crypto\ec.cpp" /> <ClCompile Include="Common\Crypto\ec.cpp" />
<ClCompile Include="Common\Crypto\HMAC.cpp" />
<ClCompile Include="Common\Crypto\SHA1.cpp" /> <ClCompile Include="Common\Crypto\SHA1.cpp" />
<ClCompile Include="Common\Debug\MemoryPatches.cpp" /> <ClCompile Include="Common\Debug\MemoryPatches.cpp" />
<ClCompile Include="Common\Debug\Watches.cpp" /> <ClCompile Include="Common\Debug\Watches.cpp" />
@ -1005,6 +1009,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\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" />
<ClCompile Include="Core\IOS\Network\NCD\WiiNetConfig.cpp" /> <ClCompile Include="Core\IOS\Network\NCD\WiiNetConfig.cpp" />