Skip to content

Commit

Permalink
IOS/KD: Implement NWC24_CHECK_MAIL_NOW
Browse files Browse the repository at this point in the history
  • Loading branch information
noahpistilli committed Aug 20, 2023
1 parent f2b8baa commit 34abdf4
Show file tree
Hide file tree
Showing 16 changed files with 518 additions and 15 deletions.
2 changes: 2 additions & 0 deletions Source/Core/Common/CMakeLists.txt
Expand Up @@ -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
Expand Down
27 changes: 27 additions & 0 deletions Source/Core/Common/Crypto/HMAC.cpp
@@ -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(const u8* key, const size_t key_len, const u8* msg, const size_t msg_len, 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, key_len) || mbedtls_md_hmac_update(&ctx, msg, msg_len) ||
mbedtls_md_hmac_finish(&ctx, out))
{
return false;
}

return true;
}
} // namespace Common::HMAC
12 changes: 12 additions & 0 deletions Source/Core/Common/Crypto/HMAC.h
@@ -0,0 +1,12 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "Common/CommonTypes.h"

namespace Common::HMAC
{
bool HMACWithSHA1(const u8* key, const size_t key_len, const u8* msg, const size_t msg_len,
u8* out);
} // namespace Common::HMAC
37 changes: 37 additions & 0 deletions Source/Core/Common/HttpRequest.cpp
Expand Up @@ -27,6 +27,7 @@ class HttpRequest::Impl final
explicit Impl(std::chrono::milliseconds timeout_ms, ProgressCallback callback);

bool IsValid() const;
std::string GetHeaderValue(const std::string& name) const;
void SetCookies(const std::string& cookies);
void UseIPv4();
void FollowRedirects(long max);
Expand All @@ -41,6 +42,7 @@ class HttpRequest::Impl final
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;
};
Expand Down Expand Up @@ -82,6 +84,11 @@ s32 HttpRequest::GetLastResponseCode() const
return m_impl->GetLastResponseCode();
}

std::string HttpRequest::GetHeaderValue(const std::string& name) const
{
return m_impl->GetHeaderValue(name);
}

HttpRequest::Response HttpRequest::Get(const std::string& url, const Headers& headers,
AllowedReturnCodes codes)
{
Expand Down Expand Up @@ -173,6 +180,17 @@ void HttpRequest::Impl::FollowRedirects(long max)
curl_easy_setopt(m_curl.get(), CURLOPT_MAXREDIRS, max);
}

std::string HttpRequest::Impl::GetHeaderValue(const std::string& 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()));
Expand All @@ -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);
const std::string full_header{buffer, nitems};
const size_t colon_pos = full_header.find(':');
if (colon_pos == std::string::npos)
return nitems * size;

const std::string key = full_header.substr(0, colon_pos);
const std::string value = std::string{StripWhitespace(full_header.substr(colon_pos + 1))};

headers->insert(headers->end(), {key, 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)
Expand All @@ -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, &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);
Expand Down
1 change: 1 addition & 0 deletions Source/Core/Common/HttpRequest.h
Expand Up @@ -40,6 +40,7 @@ class HttpRequest final
void FollowRedirects(long max = 1);
s32 GetLastResponseCode() const;
std::string EscapeComponent(const std::string& string);
std::string GetHeaderValue(const std::string& 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 = {},
Expand Down
5 changes: 5 additions & 0 deletions Source/Core/Common/StringUtil.cpp
Expand Up @@ -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(const std::vector<u8>& bytes)
{
return fmt::format("{:02x}", fmt::join(bytes, ""));
}
} // namespace Common
1 change: 1 addition & 0 deletions Source/Core/Common/StringUtil.h
Expand Up @@ -313,4 +313,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(const std::vector<u8>& bytes);
} // namespace Common
3 changes: 3 additions & 0 deletions Source/Core/Core/CMakeLists.txt
Expand Up @@ -376,6 +376,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
Expand Down
70 changes: 70 additions & 0 deletions Source/Core/Core/IOS/Network/KD/Mail/MailCommon.h
@@ -0,0 +1,70 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "Common/CommonTypes.h"

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 unk2;
u32 vff_free_space;
u8 unk3[48];
char mail_flag[40];
};

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;
MultipartEntry multipart_entries[2];
u32 multipart_sizes[2];
u32 multipart_content_types[2];
u32 message_length;
u32 dwc_id;
u32 always_0x80000000;
u32 padding3;
};

#pragma pack(pop)
} // namespace IOS::HLE::NWC24::Mail
62 changes: 62 additions & 0 deletions Source/Core/Core/IOS/Network/KD/Mail/WC24Send.cpp
@@ -0,0 +1,62 @@
// 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;

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};
}
} // namespace IOS::HLE::NWC24::Mail
50 changes: 50 additions & 0 deletions Source/Core/Core/IOS/Network/KD/Mail/WC24Send.h
@@ -0,0 +1,50 @@
// 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;

#pragma pack(push, 1)
struct SendList final
{
MailListHeader header;
MailListEntry entries[MAX_ENTRIES];
};
#pragma pack(pop)

SendList m_data;
std::shared_ptr<FS::FileSystem> m_fs;
};
} // namespace NWC24::Mail
} // namespace IOS::HLE
12 changes: 12 additions & 0 deletions Source/Core/Core/IOS/Network/KD/NWC24Config.cpp
Expand Up @@ -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
6 changes: 5 additions & 1 deletion Source/Core/Core/IOS/Network/KD/NWC24Config.h
Expand Up @@ -69,6 +69,9 @@ class NWC24Config final
u32 Checksum() const;
void SetChecksum(u32 checksum);

std::string_view GetMlchkid() const;
std::string GetCheckURL() const;

NWC24CreationStage CreationStage() const;
void SetCreationStage(NWC24CreationStage creation_stage);

Expand All @@ -92,6 +95,7 @@ class NWC24Config final
MAX_URL_LENGTH = 0x80,
MAX_EMAIL_LENGTH = 0x40,
MAX_PASSWORD_LENGTH = 0x20,
MAX_MLCHKID_LENGTH = 0x24,
};

#pragma pack(push, 1)
Expand All @@ -104,7 +108,7 @@ class NWC24Config final
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;
Expand Down

0 comments on commit 34abdf4

Please sign in to comment.