mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-11-14 13:27:45 -07:00
Merge pull request #12108 from noahpistilli/kd-check-mail
IOS/KD: Implement NWC24_CHECK_MAIL_NOW
This commit is contained in:
commit
143a13622f
@ -29,6 +29,8 @@ add_library(common
|
||||
Crypto/bn.h
|
||||
Crypto/ec.cpp
|
||||
Crypto/ec.h
|
||||
Crypto/HMAC.cpp
|
||||
Crypto/HMAC.h
|
||||
Crypto/SHA1.cpp
|
||||
Crypto/SHA1.h
|
||||
Debug/MemoryPatches.cpp
|
||||
|
27
Source/Core/Common/Crypto/HMAC.cpp
Normal file
27
Source/Core/Common/Crypto/HMAC.cpp
Normal 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
|
14
Source/Core/Common/Crypto/HMAC.h
Normal file
14
Source/Core/Common/Crypto/HMAC.h
Normal 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
|
@ -27,6 +27,7 @@ public:
|
||||
explicit Impl(std::chrono::milliseconds timeout_ms, ProgressCallback callback);
|
||||
|
||||
bool IsValid() const;
|
||||
std::string GetHeaderValue(std::string_view name) const;
|
||||
void SetCookies(const std::string& cookies);
|
||||
void UseIPv4();
|
||||
void FollowRedirects(long max);
|
||||
@ -41,6 +42,7 @@ public:
|
||||
private:
|
||||
static inline std::once_flag s_curl_was_initialized;
|
||||
ProgressCallback m_callback;
|
||||
Headers m_response_headers;
|
||||
std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> m_curl{nullptr, curl_easy_cleanup};
|
||||
std::string m_error_string;
|
||||
};
|
||||
@ -82,6 +84,11 @@ s32 HttpRequest::GetLastResponseCode() const
|
||||
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,
|
||||
AllowedReturnCodes codes)
|
||||
{
|
||||
@ -173,6 +180,17 @@ void HttpRequest::Impl::FollowRedirects(long 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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
const Headers& headers, const u8* payload,
|
||||
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_URL, url.c_str());
|
||||
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_HEADERFUNCTION, header_callback);
|
||||
curl_easy_setopt(m_curl.get(), CURLOPT_HEADERDATA, static_cast<void*>(&m_response_headers));
|
||||
|
||||
std::vector<u8> buffer;
|
||||
curl_easy_setopt(m_curl.get(), CURLOPT_WRITEFUNCTION, CurlWriteCallback);
|
||||
curl_easy_setopt(m_curl.get(), CURLOPT_WRITEDATA, &buffer);
|
||||
|
@ -40,6 +40,7 @@ public:
|
||||
void FollowRedirects(long max = 1);
|
||||
s32 GetLastResponseCode() const;
|
||||
std::string EscapeComponent(const std::string& string);
|
||||
std::string GetHeaderValue(std::string_view name) const;
|
||||
Response Get(const std::string& url, const Headers& headers = {},
|
||||
AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only);
|
||||
Response Post(const std::string& url, const std::vector<u8>& payload, const Headers& headers = {},
|
||||
|
@ -693,4 +693,9 @@ bool CaseInsensitiveEquals(std::string_view a, std::string_view b)
|
||||
return std::equal(a.begin(), a.end(), b.begin(),
|
||||
[](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
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <locale>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
@ -313,4 +314,5 @@ std::string GetEscapedHtml(std::string html);
|
||||
void ToLower(std::string* str);
|
||||
void ToUpper(std::string* str);
|
||||
bool CaseInsensitiveEquals(std::string_view a, std::string_view b);
|
||||
std::string BytesToHexString(std::span<const u8> bytes);
|
||||
} // namespace Common
|
||||
|
@ -378,6 +378,9 @@ add_library(core
|
||||
IOS/Network/KD/VFF/VFFUtil.cpp
|
||||
IOS/Network/KD/VFF/VFFUtil.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.h
|
||||
IOS/Network/NCD/Manage.cpp
|
||||
|
74
Source/Core/Core/IOS/Network/KD/Mail/MailCommon.h
Normal file
74
Source/Core/Core/IOS/Network/KD/Mail/MailCommon.h
Normal 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
|
68
Source/Core/Core/IOS/Network/KD/Mail/WC24Send.cpp
Normal file
68
Source/Core/Core/IOS/Network/KD/Mail/WC24Send.cpp
Normal 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
|
52
Source/Core/Core/IOS/Network/KD/Mail/WC24Send.h
Normal file
52
Source/Core/Core/IOS/Network/KD/Mail/WC24Send.h
Normal 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
|
@ -216,4 +216,16 @@ void NWC24Config::SetEmail(const char* email)
|
||||
strncpy(m_data.email, email, MAX_EMAIL_LENGTH);
|
||||
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
|
||||
|
@ -69,6 +69,9 @@ public:
|
||||
u32 Checksum() const;
|
||||
void SetChecksum(u32 checksum);
|
||||
|
||||
std::string_view GetMlchkid() const;
|
||||
std::string GetCheckURL() const;
|
||||
|
||||
NWC24CreationStage CreationStage() const;
|
||||
void SetCreationStage(NWC24CreationStage creation_stage);
|
||||
|
||||
@ -92,6 +95,7 @@ private:
|
||||
MAX_URL_LENGTH = 0x80,
|
||||
MAX_EMAIL_LENGTH = 0x40,
|
||||
MAX_PASSWORD_LENGTH = 0x20,
|
||||
MAX_MLCHKID_LENGTH = 0x24,
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
@ -104,7 +108,7 @@ private:
|
||||
NWC24CreationStage creation_stage;
|
||||
char email[MAX_EMAIL_LENGTH];
|
||||
char paswd[MAX_PASSWORD_LENGTH];
|
||||
char mlchkid[0x24];
|
||||
char mlchkid[MAX_MLCHKID_LENGTH];
|
||||
char http_urls[URL_COUNT][MAX_URL_LENGTH];
|
||||
u8 reserved[0xDC];
|
||||
u32 enable_booting;
|
||||
|
@ -12,11 +12,14 @@
|
||||
#include "Common/BitUtils.h"
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Crypto/HMAC.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/NandPaths.h"
|
||||
#include "Common/SettingsHandler.h"
|
||||
|
||||
#include "Common/Random.h"
|
||||
#include "Common/ScopeGuard.h"
|
||||
#include "Core/CommonTitles.h"
|
||||
#include "Core/HW/Memmap.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
|
||||
|
||||
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
|
||||
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_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()
|
||||
@ -168,6 +178,16 @@ NetKDRequestDevice::~NetKDRequestDevice()
|
||||
auto socket_manager = GetEmulationKernel().GetSocketManager();
|
||||
if (socket_manager)
|
||||
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()
|
||||
@ -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)
|
||||
{
|
||||
s32 new_code{};
|
||||
@ -200,6 +292,9 @@ void NetKDRequestDevice::LogError(ErrorType error_type, s32 error_code)
|
||||
case ErrorType::Server:
|
||||
new_code = -(117000 + error_code);
|
||||
break;
|
||||
case ErrorType::CheckMail:
|
||||
new_code = -(102200 - error_code);
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
const std::optional<u8> subtask_id)
|
||||
{
|
||||
@ -308,6 +497,21 @@ NWC24::ErrorCode NetKDRequestDevice::KDDownload(const u16 entry_index,
|
||||
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)
|
||||
{
|
||||
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)
|
||||
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 =
|
||||
Common::GetTitleDataPath(Titles::SYSTEM_MENU) + "/" WII_SETTING;
|
||||
@ -467,19 +671,19 @@ std::optional<IPCReply> NetKDRequestDevice::IOCtl(const IOCtlRequest& request)
|
||||
if (!area.empty() && !model.empty())
|
||||
{
|
||||
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 u32 hollywood_id = m_ios.GetIOSC().GetDeviceId();
|
||||
u64 user_id = 0;
|
||||
|
||||
const s32 ret = NWC24MakeUserID(&user_id, hollywood_id, id_ctr, hardware_model, area_code);
|
||||
config.SetId(user_id);
|
||||
config.IncrementIdGen();
|
||||
config.SetCreationStage(NWC24::NWC24CreationStage::Generated);
|
||||
config.SetChecksum(config.CalculateNwc24ConfigChecksum());
|
||||
config.WriteConfig();
|
||||
config.WriteCBK();
|
||||
m_config.SetId(user_id);
|
||||
m_config.IncrementIdGen();
|
||||
m_config.SetCreationStage(NWC24::NWC24CreationStage::Generated);
|
||||
m_config.SetChecksum(m_config.CalculateNwc24ConfigChecksum());
|
||||
m_config.WriteConfig();
|
||||
m_config.WriteCBK();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
else if (config.IsGenerated())
|
||||
else if (m_config.IsGenerated())
|
||||
{
|
||||
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);
|
||||
}
|
||||
memory.Write_U64(config.Id(), request.buffer_out + 4);
|
||||
memory.Write_U32(u32(config.CreationStage()), request.buffer_out + 0xC);
|
||||
memory.Write_U64(m_config.Id(), request.buffer_out + 4);
|
||||
memory.Write_U32(u32(m_config.CreationStage()), request.buffer_out + 0xC);
|
||||
break;
|
||||
|
||||
case IOCTL_NWC24_GET_SCHEDULER_STAT:
|
||||
@ -514,8 +718,8 @@ std::optional<IPCReply> NetKDRequestDevice::IOCtl(const IOCtlRequest& request)
|
||||
request.buffer_out_size);
|
||||
|
||||
// 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
|
||||
// recorded as we do not have a scheduler.
|
||||
// KD among other things.
|
||||
std::lock_guard lg(m_scheduler_buffer_lock);
|
||||
const u32 out_size = std::min(request.buffer_out_size, 256U);
|
||||
memory.CopyToEmu(request.buffer_out, m_scheduler_buffer.data(), out_size);
|
||||
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");
|
||||
break;
|
||||
|
||||
case IOCTL_NWC24_CHECK_MAIL_NOW:
|
||||
return LaunchAsyncTask(&NetKDRequestDevice::HandleNWC24CheckMailNow, request);
|
||||
|
||||
case IOCTL_NWC24_DOWNLOAD_NOW_EX:
|
||||
return LaunchAsyncTask(&NetKDRequestDevice::HandleNWC24DownloadNowEx, request);
|
||||
|
||||
|
@ -9,9 +9,11 @@
|
||||
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Event.h"
|
||||
#include "Common/HttpRequest.h"
|
||||
#include "Common/WorkQueueThread.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/NWC24DL.h"
|
||||
|
||||
@ -26,6 +28,7 @@ public:
|
||||
NetKDRequestDevice(EmulationKernel& ios, const std::string& device_name);
|
||||
IPCReply HandleNWC24DownloadNowEx(const IOCtlRequest& request);
|
||||
NWC24::ErrorCode KDDownload(const u16 entry_index, const std::optional<u8> subtask_id);
|
||||
IPCReply HandleNWC24CheckMailNow(const IOCtlRequest& request);
|
||||
~NetKDRequestDevice() override;
|
||||
|
||||
std::optional<IPCReply> IOCtl(const IOCtlRequest& request) override;
|
||||
@ -51,19 +54,48 @@ private:
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
enum class CurrentFunction : u32
|
||||
{
|
||||
None = 0,
|
||||
Account = 1,
|
||||
Check = 2,
|
||||
Receive = 3,
|
||||
Send = 5,
|
||||
Save = 6,
|
||||
Download = 7,
|
||||
};
|
||||
|
||||
enum class ErrorType
|
||||
{
|
||||
Account,
|
||||
KD_Download,
|
||||
Client,
|
||||
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::Mail::WC24SendList m_send_list;
|
||||
Common::WorkQueueThread<AsyncTask> m_work_queue;
|
||||
Common::WorkQueueThread<std::function<void()>> m_scheduler_work_queue;
|
||||
std::mutex m_async_reply_lock;
|
||||
std::mutex m_scheduler_buffer_lock;
|
||||
std::queue<AsyncReply> m_async_replies;
|
||||
@ -71,5 +103,11 @@ private:
|
||||
std::array<u32, 256> m_scheduler_buffer{};
|
||||
// TODO: Maybe move away from Common::HttpRequest?
|
||||
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
|
||||
|
@ -35,6 +35,7 @@
|
||||
<ClInclude Include="Common\Crypto\AES.h" />
|
||||
<ClInclude Include="Common\Crypto\bn.h" />
|
||||
<ClInclude Include="Common\Crypto\ec.h" />
|
||||
<ClInclude Include="Common\Crypto\HMAC.h" />
|
||||
<ClInclude Include="Common\Crypto\SHA1.h" />
|
||||
<ClInclude Include="Common\Debug\MemoryPatches.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\VFF\VFFUtil.h" />
|
||||
<ClInclude Include="Core\IOS\Network\KD\WC24File.h" />
|
||||
<ClInclude Include="Core\IOS\Network\KD\Mail\MailCommon.h" />
|
||||
<ClInclude Include="Core\IOS\Network\KD\Mail\WC24Send.h" />
|
||||
<ClInclude Include="Core\IOS\Network\MACUtils.h" />
|
||||
<ClInclude Include="Core\IOS\Network\NCD\Manage.h" />
|
||||
<ClInclude Include="Core\IOS\Network\NCD\WiiNetConfig.h" />
|
||||
@ -762,6 +765,7 @@
|
||||
<ClCompile Include="Common\Crypto\AES.cpp" />
|
||||
<ClCompile Include="Common\Crypto\bn.cpp" />
|
||||
<ClCompile Include="Common\Crypto\ec.cpp" />
|
||||
<ClCompile Include="Common\Crypto\HMAC.cpp" />
|
||||
<ClCompile Include="Common\Crypto\SHA1.cpp" />
|
||||
<ClCompile Include="Common\Debug\MemoryPatches.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\NWC24DL.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\NCD\Manage.cpp" />
|
||||
<ClCompile Include="Core\IOS\Network\NCD\WiiNetConfig.cpp" />
|
||||
|
Loading…
Reference in New Issue
Block a user