From 71235bd67833ff92652cc575e923651c5b2889d8 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Thu, 27 Jul 2023 18:19:33 +0200 Subject: [PATCH] Online (#2) * Implement SOC_U::GetHostByName and partial SOC_U::GetNetworkOpt * Implement AC::GetWifiStatus, and get proper network interface. * Minor fixes * More minor fixes * Even more fixes * Fix Get/Set SockOpt * Implement SendToOther * Apply suggestions and fix timer advance * Fix variable name * WIP implementation * More implemented friends/mii/fs stuff * Add more sockopt values and fix send/recv flags. * Temporary disable GetFriendPresence * Fix dontwait logic * Fix linux build * Add missing header for linux * Remove TCP_STDURG * frd stuff * Fix poll and add more 3ds <-> platform conversions * Finish implementing all platform <-> 3ds conversion. * Disable UDP connreset and fix poll again. * Fix compile issues * Apply suggestions * Fix compiler issues * Fix compiler errors (again) * Fix GetAddrInfo * Use cert from nand files instead of account. * Implement more frd functionality. * common: Add thread pool from yuzu * Is really useful for asynchronous operations like shader compilation and custom textures, will be used in following PRs * core: Improve ImageInterface * Provide a default implementation so frontends don't have to duplicate code registering the lodepng version * Add a dds version too which we will use in the next commit * rasterizer_cache: Rewrite custom textures * There's just too much to talk about here, look at the PR description for more details * rasterizer_cache: Implement basic pack configuration file * custom_tex_manager: Flip dumped textures * custom_tex_manager: Optimize custom texture hashing * If no convertions are needed then we can hash the decoded data directly removing the needed for duplicate decode * custom_tex_manager: Implement asynchronous texture loading * The file loading and decoding is offloaded into worker threads, while the upload itself still occurs in the main thread to avoid having to manage shared contexts * Address review comments * custom_tex_manager: Introduce custom material support * Remove files that were not added properly * fix submodules * Fix submodules again * fix more files --------- Co-authored-by: GPUCode --- src/citra_qt/applets/mii_selector.cpp | 6 +- src/citra_qt/applets/mii_selector.h | 2 +- src/core/CMakeLists.txt | 2 + src/core/frontend/applets/mii_selector.cpp | 10 +- src/core/frontend/applets/mii_selector.h | 6 +- src/core/hle/applets/mii_selector.cpp | 47 +- src/core/hle/applets/mii_selector.h | 57 +- src/core/hle/mii.cpp | 9 + src/core/hle/mii.h | 295 ++++++++++ src/core/hle/service/frd/frd.cpp | 637 +++++++++++++++++++-- src/core/hle/service/frd/frd.h | 417 +++++++++++++- src/core/hle/service/frd/frd_a.cpp | 75 ++- src/core/hle/service/frd/frd_u.cpp | 58 +- src/core/hle/service/fs/fs_user.cpp | 123 +++- src/core/hle/service/fs/fs_user.h | 64 ++- src/core/hle/service/http_c.cpp | 4 + src/core/hle/service/http_c.h | 18 +- src/core/loader/3dsx.cpp | 2 +- src/core/loader/ncch.cpp | 10 +- src/network/CMakeLists.txt | 4 +- src/network/network_clients/nasc.cpp | 203 +++++++ src/network/network_clients/nasc.h | 80 +++ 22 files changed, 1901 insertions(+), 228 deletions(-) create mode 100644 src/core/hle/mii.cpp create mode 100644 src/core/hle/mii.h create mode 100644 src/network/network_clients/nasc.cpp create mode 100644 src/network/network_clients/nasc.h diff --git a/src/citra_qt/applets/mii_selector.cpp b/src/citra_qt/applets/mii_selector.cpp index 2099e675e6..f0bcbbae54 100644 --- a/src/citra_qt/applets/mii_selector.cpp +++ b/src/citra_qt/applets/mii_selector.cpp @@ -27,7 +27,8 @@ QtMiiSelectorDialog::QtMiiSelectorDialog(QWidget* parent, QtMiiSelector* mii_sel ? tr("Mii Selector") : QString::fromStdString(config.title)); - miis.push_back(HLE::Applets::MiiSelector::GetStandardMiiResult().selected_mii_data); + miis.push_back( + HLE::Applets::MiiSelector::GetStandardMiiResult().selected_mii_data.GetMiiData()); combobox->addItem(tr("Standard Mii")); for (const auto& mii : Frontend::LoadMiis()) { miis.push_back(mii); @@ -67,6 +68,5 @@ void QtMiiSelector::OpenDialog() { dialog.return_code, index); const auto mii_data = dialog.miis.at(index); - Finalize(dialog.return_code, - dialog.return_code == 0 ? std::move(mii_data) : HLE::Applets::MiiData{}); + Finalize(dialog.return_code, dialog.return_code == 0 ? std::move(mii_data) : Mii::MiiData{}); } diff --git a/src/citra_qt/applets/mii_selector.h b/src/citra_qt/applets/mii_selector.h index c345493443..6b88674660 100644 --- a/src/citra_qt/applets/mii_selector.h +++ b/src/citra_qt/applets/mii_selector.h @@ -24,7 +24,7 @@ private: QVBoxLayout* layout; QtMiiSelector* mii_selector; u32 return_code = 0; - std::vector miis; + std::vector miis; friend class QtMiiSelector; }; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 186f50fe3c..ae71ccc37b 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -184,6 +184,8 @@ add_library(citra_core STATIC hle/kernel/wait_object.h hle/lock.cpp hle/lock.h + hle/mii.h + hle/mii.cpp hle/result.h hle/romfs.cpp hle/romfs.h diff --git a/src/core/frontend/applets/mii_selector.cpp b/src/core/frontend/applets/mii_selector.cpp index 6325e7a044..fcbc18213b 100644 --- a/src/core/frontend/applets/mii_selector.cpp +++ b/src/core/frontend/applets/mii_selector.cpp @@ -11,12 +11,12 @@ namespace Frontend { -void MiiSelector::Finalize(u32 return_code, HLE::Applets::MiiData mii) { +void MiiSelector::Finalize(u32 return_code, Mii::MiiData mii) { data = {return_code, mii}; } -std::vector LoadMiis() { - std::vector miis; +std::vector LoadMiis() { + std::vector miis; std::string nand_directory{FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)}; FileSys::ArchiveFactory_ExtSaveData extdata_archive_factory(nand_directory, true); @@ -36,7 +36,7 @@ std::vector LoadMiis() { u32 saved_miis_offset = 0x8; // The Mii Maker has a 100 Mii limit on the 3ds for (int i = 0; i < 100; ++i) { - HLE::Applets::MiiData mii; + Mii::MiiData mii; std::array mii_raw; file->Read(saved_miis_offset, sizeof(mii), mii_raw.data()); std::memcpy(&mii, mii_raw.data(), sizeof(mii)); @@ -53,7 +53,7 @@ std::vector LoadMiis() { void DefaultMiiSelector::Setup(const Frontend::MiiSelectorConfig& config) { MiiSelector::Setup(config); - Finalize(0, HLE::Applets::MiiSelector::GetStandardMiiResult().selected_mii_data); + Finalize(0, HLE::Applets::MiiSelector::GetStandardMiiResult().selected_mii_data.GetMiiData()); } } // namespace Frontend diff --git a/src/core/frontend/applets/mii_selector.h b/src/core/frontend/applets/mii_selector.h index 0cb0950559..7dc404d371 100644 --- a/src/core/frontend/applets/mii_selector.h +++ b/src/core/frontend/applets/mii_selector.h @@ -25,7 +25,7 @@ struct MiiSelectorConfig { struct MiiSelectorData { u32 return_code; - HLE::Applets::MiiData mii; + Mii::MiiData mii; }; class MiiSelector { @@ -43,14 +43,14 @@ public: * Stores the data so that the HLE applet in core can * send this to the calling application */ - void Finalize(u32 return_code, HLE::Applets::MiiData mii); + void Finalize(u32 return_code, Mii::MiiData mii); protected: MiiSelectorConfig config; MiiSelectorData data; }; -std::vector LoadMiis(); +std::vector LoadMiis(); class DefaultMiiSelector final : public MiiSelector { public: diff --git a/src/core/hle/applets/mii_selector.cpp b/src/core/hle/applets/mii_selector.cpp index f4ff0b408f..573f9d0526 100644 --- a/src/core/hle/applets/mii_selector.cpp +++ b/src/core/hle/applets/mii_selector.cpp @@ -71,9 +71,6 @@ void MiiSelector::Update() { const MiiSelectorData& data = frontend_applet->ReceiveData(); result.return_code = data.return_code; result.selected_mii_data = data.mii; - // Calculate the checksum of the selected Mii, see https://www.3dbrew.org/wiki/Mii#Checksum - result.mii_data_checksum = boost::crc<16, 0x1021, 0, 0, false, false>( - &result.selected_mii_data, sizeof(HLE::Applets::MiiData) + sizeof(result.unknown1)); result.selected_guest_mii_index = 0xFFFFFFFF; // TODO(Subv): We're finalizing the applet immediately after it's started, @@ -92,29 +89,31 @@ MiiResult MiiSelector::GetStandardMiiResult() { // This data was obtained by writing the returned buffer in AppletManager::GlanceParameter of // the LLEd Mii picker of version system version 11.8.0 to a file and then matching the values // to the members of the MiiResult struct - MiiData mii_data; - mii_data.mii_id = 0x03001030; + Mii::MiiData mii_data; + mii_data.magic = 0x03; + mii_data.mii_options.raw = 0x00; + mii_data.mii_pos.raw = 0x10; + mii_data.console_identity.raw = 0x30; mii_data.system_id = 0xD285B6B300C8850A; - mii_data.specialness_and_creation_date = 0x98391EE4; - mii_data.creator_mac = {0x40, 0xF4, 0x07, 0xB7, 0x37, 0x10}; - mii_data.padding = 0x0; - mii_data.mii_information = 0xA600; + mii_data.mii_id = 0x98391EE4; + mii_data.mac = {0x40, 0xF4, 0x07, 0xB7, 0x37, 0x10}; + mii_data.pad = 0x0000; + mii_data.mii_details.raw = 0xA600; mii_data.mii_name = {'C', 'i', 't', 'r', 'a', 0x0, 0x0, 0x0, 0x0, 0x0}; - mii_data.width_height = 0x4040; - mii_data.appearance_bits1.raw = 0x0; - mii_data.appearance_bits2.raw = 0x0; + mii_data.height = 0x40; + mii_data.width = 0x40; + mii_data.face_style.raw = 0x00; + mii_data.face_details.raw = 0x00; mii_data.hair_style = 0x21; - mii_data.appearance_bits3.hair_color.Assign(0x1); - mii_data.appearance_bits3.flip_hair.Assign(0x0); - mii_data.unknown1 = 0x02684418; - mii_data.appearance_bits4.eyebrow_style.Assign(0x6); - mii_data.appearance_bits4.eyebrow_color.Assign(0x1); - mii_data.appearance_bits5.eyebrow_scale.Assign(0x4); - mii_data.appearance_bits5.eyebrow_yscale.Assign(0x3); - mii_data.appearance_bits6 = 0x4614; - mii_data.unknown2 = 0x81121768; - mii_data.allow_copying = 0x0D; - mii_data.unknown3 = {0x0, 0x0, 0x29, 0x0, 0x52, 0x48, 0x50}; + mii_data.hair_details.raw = 0x01; + mii_data.eye_details.raw = 0x02684418; + mii_data.eyebrow_details.raw = 0x26344614; + mii_data.nose_details.raw = 0x8112; + mii_data.mouth_details.raw = 0x1768; + mii_data.mustache_details.raw = 0x0D00; + mii_data.beard_details.raw = 0x0029; + mii_data.glasses_details.raw = 0x0052; + mii_data.mole_details.raw = 0x4850; mii_data.author_name = {'f', 'l', 'T', 'o', 'b', 'i', 0x0, 0x0, 0x0, 0x0}; MiiResult result; @@ -122,8 +121,6 @@ MiiResult MiiSelector::GetStandardMiiResult() { result.is_guest_mii_selected = 0x0; result.selected_guest_mii_index = 0xFFFFFFFF; result.selected_mii_data = mii_data; - result.unknown1 = 0x0; - result.mii_data_checksum = 0x056C; result.guest_mii_name.fill(0x0); return result; diff --git a/src/core/hle/applets/mii_selector.h b/src/core/hle/applets/mii_selector.h index 50cf1bf4a9..63d7298de5 100644 --- a/src/core/hle/applets/mii_selector.h +++ b/src/core/hle/applets/mii_selector.h @@ -9,6 +9,7 @@ #include "common/common_types.h" #include "core/hle/applets/applet.h" #include "core/hle/kernel/shared_memory.h" +#include "core/hle/mii.h" #include "core/hle/result.h" #include "core/hle/service/apt/apt.h" @@ -44,65 +45,11 @@ ASSERT_REG_POSITION(initially_selected_mii_index, 0x90); ASSERT_REG_POSITION(guest_mii_whitelist, 0x94); #undef ASSERT_REG_POSITION -#pragma pack(push, 1) -struct MiiData { - u32_be mii_id; - u64_be system_id; - u32_be specialness_and_creation_date; - std::array creator_mac; - u16_be padding; - u16_be mii_information; - std::array mii_name; - u16_be width_height; - union { - u8 raw; - - BitField<0, 1, u8> disable_sharing; - BitField<1, 4, u8> face_shape; - BitField<5, 3, u8> skin_color; - } appearance_bits1; - union { - u8 raw; - - BitField<0, 4, u8> wrinkles; - BitField<4, 4, u8> makeup; - } appearance_bits2; - u8 hair_style; - union { - u8 raw; - - BitField<0, 3, u8> hair_color; - BitField<3, 1, u8> flip_hair; - } appearance_bits3; - u32_be unknown1; - union { - u8 raw; - - BitField<0, 5, u8> eyebrow_style; - BitField<5, 3, u8> eyebrow_color; - } appearance_bits4; - union { - u8 raw; - - BitField<0, 4, u8> eyebrow_scale; - BitField<4, 3, u8> eyebrow_yscale; - } appearance_bits5; - u16_be appearance_bits6; - u32_be unknown2; - u8 allow_copying; - std::array unknown3; - std::array author_name; -}; -static_assert(sizeof(MiiData) == 0x5C, "MiiData structure has incorrect size"); -#pragma pack(pop) - struct MiiResult { u32_be return_code; u32_be is_guest_mii_selected; u32_be selected_guest_mii_index; - MiiData selected_mii_data; - u16_be unknown1; - u16_be mii_data_checksum; + Mii::ChecksummedMiiData selected_mii_data; std::array guest_mii_name; }; static_assert(sizeof(MiiResult) == 0x84, "MiiResult structure has incorrect size"); diff --git a/src/core/hle/mii.cpp b/src/core/hle/mii.cpp new file mode 100644 index 0000000000..be7ff183e4 --- /dev/null +++ b/src/core/hle/mii.cpp @@ -0,0 +1,9 @@ +#include +#include "core/hle/mii.h" + +namespace Mii { +u16 ChecksummedMiiData::CalcChecksum() { + // Calculate the checksum of the selected Mii, see https://www.3dbrew.org/wiki/Mii#Checksum + return boost::crc<16, 0x1021, 0, 0, false, false>(this, offsetof(ChecksummedMiiData, crc16)); +} +} // namespace Mii \ No newline at end of file diff --git a/src/core/hle/mii.h b/src/core/hle/mii.h new file mode 100644 index 0000000000..800836cb42 --- /dev/null +++ b/src/core/hle/mii.h @@ -0,0 +1,295 @@ +#pragma once + +#include +#include "common/bit_field.h" +#include "common/common_types.h" + +namespace Mii { + +#pragma pack(push, 1) +// Reference: https://github.com/devkitPro/libctru/blob/master/libctru/include/3ds/mii.h +class MiiData { +public: + u8 magic{}; ///< Always 3? + + /// Mii options + union { + u8 raw; + + BitField<0, 1, u8> allow_copying; ///< True if copying is allowed + BitField<1, 1, u8> is_private_name; ///< Private name? + BitField<2, 2, u8> region_lock; ///< Region lock (0=no lock, 1=JPN, 2=USA, 3=EUR) + BitField<4, 2, u8> char_set; ///< Character set (0=JPN+USA+EUR, 1=CHN, 2=KOR, 3=TWN) + } mii_options{}; + + /// Mii position in Mii selector or Mii maker + union { + u8 raw; + + BitField<0, 4, u8> page_index; ///< Page index of Mii + BitField<4, 4, u8> slot_index; ///< Slot offset of Mii on its Page + } mii_pos{}; + + /// Console Identity + union { + u8 raw; + + BitField<0, 4, u8> unknown0; ///< Mabye padding (always seems to be 0)? + BitField<4, 3, u8> + origin_console; ///< Console that the Mii was created on (1=WII, 2=DSI, 3=3DS) + } console_identity{}; + + u64_be system_id{}; ///< Identifies the system that the Mii was created on (Determines pants) + u32_be mii_id{}; ///< ID of Mii + std::array mac{}; ///< Creator's system's full MAC address + u16 pad{}; ///< Padding + + /// Mii details + union { + u16_be raw; + + BitField<0, 1, u16> sex; ///< Sex of Mii (False=Male, True=Female) + BitField<1, 4, u16> bday_month; ///< Month of Mii's birthday + BitField<5, 5, u16> bday_day; ///< Day of Mii's birthday + BitField<10, 4, u16> shirt_color; ///< Color of Mii's shirt + BitField<14, 1, u16> favorite; ///< Whether the Mii is one of your 10 favorite Mii's + } mii_details{}; + + std::array mii_name{}; ///< Name of Mii (Encoded using UTF16) + u8 height{}; ///< How tall the Mii is + u8 width{}; ///< How wide the Mii is + + /// Face style + union { + u8 raw; + + BitField<0, 1, u8> disable_sharing; ///< Whether or not Sharing of the Mii is allowed + BitField<1, 4, u8> shape; ///< Face shape + BitField<5, 3, u8> skin_color; ///< Color of skin + } face_style{}; + + /// Face details + union { + u8 raw; + + BitField<0, 4, u8> wrinkles; + BitField<4, 4, u8> makeup; + } face_details{}; + + u8 hair_style{}; + + /// Hair details + union { + u8 raw; + + BitField<0, 3, u8> color; + BitField<3, 1, u8> flip; + } hair_details{}; + + /// Eye details + union { + u32_be raw; + + BitField<0, 6, u32> style; + BitField<6, 3, u32> color; + BitField<9, 4, u32> scale; + BitField<13, 3, u32> yscale; + BitField<16, 5, u32> rotation; + BitField<21, 4, u32> xspacing; + BitField<25, 5, u32> yposition; + } eye_details{}; + + /// Eyebrow details + union { + u32_be raw; + + BitField<0, 5, u32> style; + BitField<5, 3, u32> color; + BitField<8, 4, u32> scale; + BitField<12, 3, u32> yscale; + BitField<15, 1, u32> pad; + BitField<16, 5, u32> rotation; + BitField<21, 4, u32> xspacing; + BitField<25, 5, u32> yposition; + } eyebrow_details{}; + + /// Nose details + union { + u16_be raw; + + BitField<0, 5, u16> style; + BitField<5, 4, u16> scale; + BitField<9, 5, u16> yposition; + } nose_details{}; + + /// Mouth details + union { + u16_be raw; + + BitField<0, 6, u16> style; + BitField<6, 3, u16> color; + BitField<9, 4, u16> scale; + BitField<13, 3, u16> yscale; + } mouth_details{}; + + /// Mustache details + union { + u16_be raw; + + BitField<0, 5, u16> mouth_yposition; + BitField<5, 3, u16> mustach_style; + BitField<8, 2, u16> pad; + } mustache_details{}; + + /// Beard details + union { + u16_be raw; + + BitField<0, 3, u16> style; + BitField<3, 3, u16> color; + BitField<6, 4, u16> scale; + BitField<10, 5, u16> ypos; + } beard_details{}; + + /// Glasses details + union { + u16_be raw; + + BitField<0, 4, u16> style; + BitField<4, 3, u16> color; + BitField<7, 4, u16> scale; + BitField<11, 5, u16> ypos; + } glasses_details{}; + + /// Mole details + union { + u16_be raw; + + BitField<0, 1, u16> enable; + BitField<1, 5, u16> scale; + BitField<6, 5, u16> xpos; + BitField<11, 5, u16> ypos; + } mole_details{}; + + std::array author_name{}; ///< Name of Mii's author (Encoded using UTF16) +private: + template + void serialize(Archive& ar, const unsigned int) { + ar& magic; + ar& mii_options.raw; + ar& mii_pos.raw; + ar& console_identity.raw; + u64 system_id_ = system_id; + ar& system_id_; + system_id = system_id_; + u32 mii_id_ = mii_id; + ar& mii_id_; + mii_id = mii_id_; + ar& mac; + ar& pad; + u16 mii_details_ = mii_details.raw; + ar& mii_details_; + mii_details.raw = mii_details_; + ar& mii_name; + ar& height; + ar& width; + ar& face_style.raw; + ar& face_details.raw; + ar& hair_style; + ar& hair_details.raw; + u32 eye_details_ = eye_details.raw; + ar& eye_details_; + eye_details.raw = eye_details_; + u32 eyebrow_details_ = eyebrow_details.raw; + ar& eyebrow_details_; + eyebrow_details.raw = eyebrow_details_; + u16 nose_details_ = nose_details.raw; + ar& nose_details_; + nose_details.raw = nose_details_; + u16 mouth_details_ = mouth_details.raw; + ar& mouth_details_; + mouth_details.raw = mouth_details_; + u16 mustache_details_ = mustache_details.raw; + ar& mustache_details_; + mustache_details.raw = mustache_details_; + u16 beard_details_ = beard_details.raw; + ar& beard_details_; + beard_details.raw = beard_details_; + u16 glasses_details_ = glasses_details.raw; + ar& glasses_details_; + glasses_details.raw = glasses_details_; + u16 mole_details_ = mole_details.raw; + ar& mole_details_; + mole_details.raw = mole_details_; + ar& author_name; + } + friend class boost::serialization::access; +}; + +static_assert(sizeof(MiiData) == 0x5C, "MiiData structure has incorrect size"); + +class ChecksummedMiiData { +public: + ChecksummedMiiData() { + FixChecksum(); + } + ChecksummedMiiData(const ChecksummedMiiData& data) = default; + ChecksummedMiiData(ChecksummedMiiData&& data) = default; + ChecksummedMiiData& operator=(const ChecksummedMiiData&) = default; + ChecksummedMiiData& operator=(ChecksummedMiiData&&) = default; + + ChecksummedMiiData(const MiiData& data) : mii_data(data) { + FixChecksum(); + } + + ChecksummedMiiData(MiiData&& data) : mii_data(data) { + FixChecksum(); + } + + ChecksummedMiiData& operator=(const MiiData& data) { + mii_data = data; + FixChecksum(); + return *this; + } + + ChecksummedMiiData& operator=(MiiData&& data) { + mii_data = std::move(data); + FixChecksum(); + return *this; + } + + MiiData& GetMiiData() { + return mii_data; + } + + bool IsChecksumValid() { + return crc16 == CalcChecksum(); + } + + u16 CalcChecksum(); + +private: + MiiData mii_data{}; + u16_be unknown{0}; + u16_be crc16; + + void FixChecksum() { + crc16 = CalcChecksum(); + } + + template + void serialize(Archive& ar, const unsigned int) { + ar& mii_data; + u16 unknown_ = unknown; + ar& unknown_; + unknown = unknown_; + u16 crc16_ = crc16; + ar& crc16_; + crc16 = crc16_; + } + friend class boost::serialization::access; +}; +#pragma pack(pop) +static_assert(sizeof(ChecksummedMiiData) == 0x60, + "ChecksummedMiiData structure has incorrect size"); +} // namespace Mii \ No newline at end of file diff --git a/src/core/hle/service/frd/frd.cpp b/src/core/hle/service/frd/frd.cpp index e3629a9963..22b385ee99 100644 --- a/src/core/hle/service/frd/frd.cpp +++ b/src/core/hle/service/frd/frd.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "common/assert.h" #include "common/logging/log.h" @@ -15,16 +16,103 @@ #include "core/hle/service/frd/frd.h" #include "core/hle/service/frd/frd_a.h" #include "core/hle/service/frd/frd_u.h" +#include "core/hle/service/fs/fs_user.h" +#include "core/hle/service/http_c.h" +#include "network/network_clients/nasc.h" SERVICE_CONSTRUCT_IMPL(Service::FRD::Module) namespace Service::FRD { +void AccountDataV1::Init() { + Core::System& system = Core::System::GetInstance(); + auto cfg = Service::CFG::GetModule(system); + ASSERT_MSG(cfg, "CFG Module missing!"); + + auto username = cfg->GetUsername(); + auto usernamelen = std::min(username.length(), device_name.user_name.size() - 1); + for (size_t i = 0; i < usernamelen; i++) { + device_name.user_name[i] = static_cast(username[i]); + } + device_name.user_name[usernamelen] = '\0'; + device_name.bad_word_ver = 0x12; + my_profile.area = cfg->GetStateCode(); + my_profile.country = cfg->GetCountryCode(); + my_profile.language = cfg->GetSystemLanguage(); + my_profile.region = cfg->GetRegionValue(); + my_profile.platform = 2; + + GenerateChecksum(); +} + Module::Interface::Interface(std::shared_ptr frd, const char* name, u32 max_session) : ServiceFramework(name, max_session), frd(std::move(frd)) {} Module::Interface::~Interface() = default; +void Module::Interface::HasLoggedIn(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x1, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + + rb.Push(RESULT_SUCCESS); + rb.Push(frd->has_logged_in); +} + +void Module::Interface::IsOnline(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x2, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + + // This is actually different than HasLoggedIn, + // but return the same value until it is figured out. + rb.Push(RESULT_SUCCESS); + rb.Push(frd->has_logged_in); + + LOG_WARNING(Service_FRD, "(STUBBED) called"); +} + +void Module::Interface::Login(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x3, 0, 2); + auto event = rp.PopObject(); + + frd->has_logged_in = true; + event->Signal(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + +void Module::Interface::Logout(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x4, 0, 0); + + frd->has_logged_in = false; + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + +void Module::Interface::GetMyFriendKey(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x5, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(5, 0); + rb.Push(RESULT_SUCCESS); + rb.PushRaw(frd->my_account_data.my_key); +} + +void Module::Interface::GetMyPreference(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x6, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(4, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(frd->my_account_data.my_pref_public_mode); + rb.Push(frd->my_account_data.my_pref_public_game_name); + rb.Push(frd->my_account_data.my_pref_public_played_game); +} + +void Module::Interface::GetMyProfile(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x7, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); + rb.Push(RESULT_SUCCESS); + rb.PushRaw(frd->my_account_data.my_profile); +} + void Module::Interface::GetMyPresence(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); @@ -34,23 +122,177 @@ void Module::Interface::GetMyPresence(Kernel::HLERequestContext& ctx) { IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(RESULT_SUCCESS); rb.PushStaticBuffer(std::move(buffer), 0); +} + +void Module::Interface::GetMyScreenName(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x9, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(7, 0); + rb.Push(RESULT_SUCCESS); + rb.PushRaw>(frd->my_account_data.my_screen_name); +} + +void Module::Interface::GetMyMii(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0xA, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(25, 0); + rb.Push(RESULT_SUCCESS); + rb.PushRaw(frd->my_account_data.my_mii_data); +} + +void Module::Interface::GetMyLocalAccountId(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0xB, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(1); // Assume using account ID 1 LOG_WARNING(Service_FRD, "(STUBBED) called"); } +void Module::Interface::GetMyPlayingGame(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0xC, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(5, 0); + + TitleData title_data{}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw(title_data); + + LOG_WARNING(Service_FRD, "(STUBBED) called"); +} + +void Module::Interface::GetMyFavoriteGame(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0xD, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(5, 0); + rb.Push(RESULT_SUCCESS); + rb.PushRaw(frd->my_account_data.my_fav_game); +} + +void Module::Interface::GetMyNcPrincipalId(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0xE, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(0); // Stubbed + + LOG_WARNING(Service_FRD, "(STUBBED) called"); +} + +void Module::Interface::GetMyComment(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0xF, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(10, 0); + rb.Push(RESULT_SUCCESS); + rb.PushRaw>(frd->my_account_data.my_comment); +} + +void Module::Interface::GetMyPassword(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x10, 1, 0); + u32 pass_len = rp.Pop(); + std::vector pass_buf(pass_len); + + strncpy(reinterpret_cast(pass_buf.data()), frd->my_account_data.password.data(), + pass_len - 1); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); + rb.PushStaticBuffer(std::move(pass_buf), 0); +} + void Module::Interface::GetFriendKeyList(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - const u32 unknown = rp.Pop(); + const u32 offset = rp.Pop(); const u32 frd_count = rp.Pop(); - std::vector buffer(sizeof(FriendKey) * frd_count, 0); + u32 start = offset; + u32 end = std::min(offset + frd_count, (u32)frd->my_account_data.my_friend_count); + std::vector buffer(sizeof(FriendKey) * (end - start), 0); + FriendKey* buffer_ptr = reinterpret_cast(buffer.data()); + u32 count = 0; + while (start < end) { + if (frd->my_account_data.friend_info[start].friend_key.friend_id) { + buffer_ptr[count++] = frd->my_account_data.friend_info[start].friend_key; + } else { + break; + } + ++start; + } IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); rb.Push(RESULT_SUCCESS); - rb.Push(0); // 0 friends + rb.Push(count); + rb.PushStaticBuffer(std::move(buffer), 0); +} + +void Module::Interface::GetFriendPresence(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x12, 1, 2); + const u32 count = rp.Pop(); + const std::vector frd_keys = rp.PopStaticBuffer(); + ASSERT(frd_keys.size() == count * sizeof(FriendKey)); + + std::vector buffer(sizeof(FriendPresence) * count, 0); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); rb.PushStaticBuffer(std::move(buffer), 0); - LOG_WARNING(Service_FRD, "(STUBBED) called, unknown={}, frd_count={}", unknown, frd_count); + LOG_WARNING(Service_FRD, "(STUBBED) called, count={}", count); +} + +void Module::Interface::GetFriendScreenName(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x12, 5, 2); + const u32 name_count = rp.Pop(); + const u32 font_count = rp.Pop(); + const u32 count = rp.Pop(); + rp.Pop(); // replace_unknown_characters + rp.Pop(); // replace_bad_words + const std::vector frd_keys = rp.PopStaticBuffer(); + ASSERT(frd_keys.size() == count * sizeof(FriendKey)); + + const u32 actual_count = std::min(count, name_count / FRIEND_SCREEN_NAME_SIZE); + const FriendKey* friend_keys_data = reinterpret_cast(frd_keys.data()); + std::vector screen_names(sizeof(u16) * name_count, 0); + std::vector fonts(font_count, 0); + + for (u32 i = 0; i < actual_count; i++) { + auto friend_info = frd->my_account_data.GetFriendInfo(friend_keys_data[i]); + if (friend_info.has_value()) { + memcpy(screen_names.data() + i * FRIEND_SCREEN_NAME_SIZE * sizeof(u16), + friend_info.value()->screen_name.data(), FRIEND_SCREEN_NAME_SIZE * sizeof(u16)); + if (i < font_count) { + fonts[i] = friend_info.value()->font_region; + } + } + } + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); + rb.Push(RESULT_SUCCESS); + rb.PushStaticBuffer(std::move(screen_names), 0); + rb.PushStaticBuffer(std::move(fonts), 1); +} + +void Module::Interface::GetFriendMii(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x14, 1, 4); + const u32 count = rp.Pop(); + const std::vector frd_keys = rp.PopStaticBuffer(); + ASSERT(frd_keys.size() == count * sizeof(FriendKey)); + auto out_mii_buffer = rp.PopMappedBuffer(); + ASSERT(out_mii_buffer.GetSize() == count * sizeof(Mii::ChecksummedMiiData)); + + const FriendKey* friend_keys_data = reinterpret_cast(frd_keys.data()); + std::vector out_mii_vector(count * sizeof(Mii::ChecksummedMiiData)); + Mii::ChecksummedMiiData* out_mii_data = + reinterpret_cast(out_mii_vector.data()); + + for (u32 i = 0; i < count; i++) { + auto friend_info = frd->my_account_data.GetFriendInfo(friend_keys_data[i]); + if (friend_info.has_value()) { + out_mii_data[i] = friend_info.value()->mii_data; + } else { + out_mii_data[i] = Mii::ChecksummedMiiData(); + } + } + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); + + out_mii_buffer.Write(out_mii_vector.data(), 0, out_mii_vector.size()); + rb.PushMappedBuffer(out_mii_buffer); } void Module::Interface::GetFriendProfile(Kernel::HLERequestContext& ctx) { @@ -59,60 +301,172 @@ void Module::Interface::GetFriendProfile(Kernel::HLERequestContext& ctx) { const std::vector frd_keys = rp.PopStaticBuffer(); ASSERT(frd_keys.size() == count * sizeof(FriendKey)); - std::vector buffer(sizeof(Profile) * count, 0); + std::vector buffer(sizeof(FriendProfile) * count, 0); + const FriendKey* friend_keys_data = reinterpret_cast(frd_keys.data()); + FriendProfile* out_friend_profiles = reinterpret_cast(buffer.data()); + for (u32 i = 0; i < count; i++) { + auto friend_info = frd->my_account_data.GetFriendInfo(friend_keys_data[i]); + if (friend_info.has_value()) { + out_friend_profiles[i] = friend_info.value()->friend_profile; + } else { + out_friend_profiles[i] = FriendProfile(); + } + } IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(RESULT_SUCCESS); rb.PushStaticBuffer(std::move(buffer), 0); +} - LOG_WARNING(Service_FRD, "(STUBBED) called, count={}", count); +void Module::Interface::GetFriendRelationship(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x16, 1, 2); + const u32 count = rp.Pop(); + const std::vector frd_keys = rp.PopStaticBuffer(); + ASSERT(frd_keys.size() == count * sizeof(FriendKey)); + + std::vector buffer(sizeof(u8) * count, 0); + const FriendKey* friend_keys_data = reinterpret_cast(frd_keys.data()); + for (u32 i = 0; i < count; i++) { + auto friend_info = frd->my_account_data.GetFriendInfo(friend_keys_data[i]); + if (friend_info.has_value()) { + buffer[i] = friend_info.value()->relationship; + } else { + buffer[i] = 0; + } + } + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); + rb.PushStaticBuffer(std::move(buffer), 0); } void Module::Interface::GetFriendAttributeFlags(Kernel::HLERequestContext& ctx) { + auto RelationshipToAttribute = [](u8 relationship) -> u32 { + // TODO (PabloMK7): Figure out what those values actually mean + switch (relationship) { + case 0: + case 2: + return 0; + case 3: + case 4: + return 1; + case 1: + return 3; + default: + break; + } + return 0; + }; IPC::RequestParser rp(ctx); const u32 count = rp.Pop(); const std::vector frd_keys = rp.PopStaticBuffer(); ASSERT(frd_keys.size() == count * sizeof(FriendKey)); - // TODO:(mailwl) figure out AttributeFlag size and zero all buffer. Assume 1 byte - std::vector buffer(1 * count, 0); + std::vector buffer(sizeof(u32) * count, 0); + const FriendKey* friend_keys_data = reinterpret_cast(frd_keys.data()); + u32* out_attribute_flags = reinterpret_cast(buffer.data()); + for (u32 i = 0; i < count; i++) { + auto friend_info = frd->my_account_data.GetFriendInfo(friend_keys_data[i]); + if (friend_info.has_value()) { + out_attribute_flags[i] = RelationshipToAttribute(friend_info.value()->relationship); + } else { + out_attribute_flags[i] = 0; + } + } + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(RESULT_SUCCESS); rb.PushStaticBuffer(std::move(buffer), 0); - - LOG_WARNING(Service_FRD, "(STUBBED) called, count={}", count); } -void Module::Interface::GetMyFriendKey(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx); - IPC::RequestBuilder rb = rp.MakeBuilder(5, 0); +void Module::Interface::GetFriendPlayingGame(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x18, 1, 4); + const u32 count = rp.Pop(); + const std::vector frd_keys = rp.PopStaticBuffer(); + ASSERT(frd_keys.size() == count * sizeof(FriendKey)); + auto out_game_buffer = rp.PopMappedBuffer(); + ASSERT(out_game_buffer.GetSize() == count * sizeof(FriendPlayingGame)); + + const FriendKey* friend_keys_data = reinterpret_cast(frd_keys.data()); + std::vector out_game_vector(count * sizeof(FriendPlayingGame), 0); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(RESULT_SUCCESS); - rb.PushRaw(frd->my_friend_key); + + out_game_buffer.Write(out_game_vector.data(), 0, out_game_vector.size()); + rb.PushMappedBuffer(out_game_buffer); LOG_WARNING(Service_FRD, "(STUBBED) called"); } -void Module::Interface::GetMyScreenName(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx); - IPC::RequestBuilder rb = rp.MakeBuilder(7, 0); +void Module::Interface::GetFriendFavoriteGame(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x19, 1, 2); + const u32 count = rp.Pop(); + const std::vector frd_keys = rp.PopStaticBuffer(); + ASSERT(frd_keys.size() == count * sizeof(FriendKey)); - struct ScreenName { - // 20 bytes according to 3dbrew - std::array name; - }; - - auto cfg = Service::CFG::GetModule(frd->system); - ASSERT_MSG(cfg, "CFG Module missing!"); - auto username = cfg->GetUsername(); - ASSERT_MSG(username.length() <= 10, "Username longer than expected!"); - ScreenName screen_name{}; - std::memcpy(screen_name.name.data(), username.data(), username.length() * sizeof(char16_t)); + std::vector buffer(sizeof(TitleData) * count, 0); + const FriendKey* friend_keys_data = reinterpret_cast(frd_keys.data()); + TitleData* out_friend_fav_title = reinterpret_cast(buffer.data()); + for (u32 i = 0; i < count; i++) { + auto friend_info = frd->my_account_data.GetFriendInfo(friend_keys_data[i]); + if (friend_info.has_value()) { + out_friend_fav_title[i] = friend_info.value()->favorite_game; + } else { + out_friend_fav_title[i] = TitleData(); + } + } + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(RESULT_SUCCESS); - rb.PushRaw(screen_name); - rb.Push(0); + rb.PushStaticBuffer(std::move(buffer), 0); +} - LOG_INFO(Service_FRD, "returning the username defined in cfg"); +void Module::Interface::GetFriendInfo(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x1A, 3, 4); + const u32 count = rp.Pop(); + rp.Pop(); // replace_unknown_characters + rp.Pop(); // replace_bad_words + const std::vector frd_keys = rp.PopStaticBuffer(); + ASSERT(frd_keys.size() == count * sizeof(FriendKey)); + auto out_info_buffer = rp.PopMappedBuffer(); + ASSERT(out_info_buffer.GetSize() == count * sizeof(FriendInfo)); + + const FriendKey* friend_keys_data = reinterpret_cast(frd_keys.data()); + std::vector out_info_vector(count * sizeof(FriendInfo)); + FriendInfo* out_info_data = reinterpret_cast(out_info_vector.data()); + + for (u32 i = 0; i < count; i++) { + auto friend_info = frd->my_account_data.GetFriendInfo(friend_keys_data[i]); + if (friend_info.has_value()) { + out_info_data[i] = *friend_info.value(); + } else { + out_info_data[i] = FriendInfo(); + } + } + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); + + out_info_buffer.Write(out_info_vector.data(), 0, out_info_vector.size()); + rb.PushMappedBuffer(out_info_buffer); +} + +void Module::Interface::IsIncludedInFriendList(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x1B, 2, 0); + u64_le friendCode = rp.Pop(); + + bool is_included = false; + for (u32 i = 0; i < frd->my_account_data.my_friend_count; i++) { + if (frd->my_account_data.friend_info[i].friend_key.friend_code == friendCode) { + is_included = true; + break; + } + } + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(is_included); } void Module::Interface::UnscrambleLocalFriendCode(Kernel::HLERequestContext& ctx) { @@ -146,6 +500,207 @@ void Module::Interface::UnscrambleLocalFriendCode(Kernel::HLERequestContext& ctx rb.PushStaticBuffer(std::move(unscrambled_friend_codes), 0); } +void Module::Interface::UpdateGameModeDescription(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x1D, 0, 2); + const std::vector new_game_mode_description = rp.PopStaticBuffer(); + + u32 copy_size = std::min(static_cast(new_game_mode_description.size()), + FRIEND_GAME_MODE_DESCRIPTION_SIZE * sizeof(u16)); + memcpy(frd->my_presence.description.data(), new_game_mode_description.data(), copy_size); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + +void Module::Interface::UpdateGameMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x1E, 11, 2); + auto game_mode = rp.PopRaw(); + const std::vector new_game_mode_description = rp.PopStaticBuffer(); + + frd->my_presence.game_mode = game_mode; + u32 copy_size = std::min(static_cast(new_game_mode_description.size()), + FRIEND_GAME_MODE_DESCRIPTION_SIZE * sizeof(u16)); + memcpy(frd->my_presence.description.data(), new_game_mode_description.data(), copy_size); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + +void Module::Interface::AttachToEventNotification(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x20, 0, 2); + frd->notif_event = rp.PopObject(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + +void Module::Interface::SetNotificationMask(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x21, 1, 0); + frd->notif_event_mask = rp.Pop(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + +void Module::Interface::GetEventNotification(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x22, 1, 0); + u32 count = rp.Pop(); + + // LOG_WARNING(Service_FRD, "(STUBBED) called"); + std::vector empty(24 * count); + IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); + rb.Push(RESULT_SUCCESS); + rb.Push(0); + rb.Push(0); + rb.PushStaticBuffer(empty, 0); +} + +void Module::Interface::GetLastResponseResult(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x23, 0, 0); + + LOG_WARNING(Service_FRD, "(STUBBED) called"); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + +void Module::Interface::RequestGameAuthentication(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x28, 9, 4); + u32 gameID = rp.Pop(); + + struct ScreenNameIPC { + // 24 bytes + std::array name; + }; + auto screenName = rp.PopRaw(); + auto sdkMajor = rp.Pop(); + auto sdkMinor = rp.Pop(); + auto processID = rp.PopPID(); + auto process = frd->system.Kernel().GetProcessById(processID); + auto event = rp.PopObject(); + + event->Signal(); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + auto http_c = HTTP::GetService(frd->system); + + if (!http_c) { + LOG_ERROR(Service_FRD, "http:C module not found!"); + rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::Friends, + ErrorSummary::InvalidState, ErrorLevel::Status)); + return; + } + + auto clcert = http_c->GetClCertA(); + + if (!clcert.init) { + LOG_ERROR(Service_FRD, "ClCertA missing!"); + rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::Friends, + ErrorSummary::InvalidState, ErrorLevel::Status)); + return; + } + + if (frd->my_account_data.password[0] == '\0' || frd->my_account_data.pid_HMAC[0] == '\0') { + LOG_ERROR(Service_FRD, "no account data is present!"); + rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::Friends, + ErrorSummary::InvalidState, ErrorLevel::Status)); + return; + } + auto fs_user = + Core::System::GetInstance().ServiceManager().GetService("fs:USER"); + Service::FS::FS_USER::ProductInfo product_info; + + if (!fs_user->GetProductInfo(processID, product_info)) { + LOG_ERROR(Service_FRD, "no game product info is available!"); + rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::Friends, + ErrorSummary::InvalidState, ErrorLevel::Status)); + return; + } + + std::string nasc_url = + std::string(reinterpret_cast(frd->my_account_data.nasc_url.data())); + NetworkClient::NASC::NASCClient nasc_client(nasc_url, clcert.certificate, clcert.private_key); + nasc_client.SetParameter("gameid", fmt::format("{:08X}", gameID)); + nasc_client.SetParameter("sdkver", fmt::format("{:03d}{:03d}", (u8)sdkMajor, (u8)sdkMinor)); + nasc_client.SetParameter("titleid", fmt::format("{:016X}", process->codeset->program_id)); + nasc_client.SetParameter( + "gamecd", std::string(reinterpret_cast(product_info.product_code.data() + 6))); + nasc_client.SetParameter("gamever", fmt::format("{:04X}", product_info.remaster_version)); + nasc_client.SetParameter("mediatype", 1); + char makercd[3]; + makercd[0] = (product_info.maker_code >> 8); + makercd[1] = (product_info.maker_code & 0xFF); + makercd[2] = '\0'; + nasc_client.SetParameter("makercd", std::string(makercd)); + nasc_client.SetParameter("unitcd", (int)frd->my_account_data.my_profile.platform); + const u8* mac = frd->my_account_data.mac_address.data(); + nasc_client.SetParameter("macadr", fmt::format("{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", mac[0], + mac[1], mac[2], mac[3], mac[4], mac[5])); + nasc_client.SetParameter("bssid", std::string("000000000000")); + nasc_client.SetParameter("apinfo", std::string("01:0000000000")); + + { + time_t raw_time; + struct tm* time_info; + char time_buffer[80]; + + time(&raw_time); + time_info = localtime(&raw_time); + + strftime(time_buffer, sizeof(time_buffer), "%y%m%d%H%M%S", time_info); + nasc_client.SetParameter("devtime", std::string(time_buffer)); + } + + std::vector device_cert(sizeof(frd->my_account_data.device_cert)); + memcpy(device_cert.data(), frd->my_account_data.device_cert.data(), device_cert.size()); + nasc_client.SetParameter("fcdcert", device_cert); + auto device_name = Common::UTF16ToUTF8( + reinterpret_cast(frd->my_account_data.device_name.user_name.data())); + nasc_client.SetParameter("devname", device_name); + nasc_client.SetParameter("servertype", std::string("L1")); + nasc_client.SetParameter("fpdver", fmt::format("{:04X}", Module::fpd_version)); + nasc_client.SetParameter("lang", + fmt::format("{:02X}", frd->my_account_data.my_profile.language)); + nasc_client.SetParameter("region", + fmt::format("{:02X}", frd->my_account_data.my_profile.region)); + nasc_client.SetParameter("csnum", std::string(frd->my_account_data.serial_number.data())); + nasc_client.SetParameter("uidhmac", std::string(frd->my_account_data.pid_HMAC.data())); + nasc_client.SetParameter("userid", (int)frd->my_account_data.my_key.friend_id); + nasc_client.SetParameter("action", std::string("LOGIN")); + nasc_client.SetParameter("ingamesn", std::string("")); + + LOG_INFO(Service_FRD, "Performing NASC request to: {}", nasc_url); + NetworkClient::NASC::NASCClient::NASCResult nasc_result = nasc_client.Perform(); + frd->last_game_auth_data.Init(); + frd->last_game_auth_data.result = nasc_result.result; + if (nasc_result.result != 1) { + LOG_ERROR(Service_FRD, "NASC Error: {}", nasc_result.log_message); + if (nasc_result.result != 0) { + frd->last_game_auth_data.http_status_code = nasc_result.http_status; + } + } else { + frd->last_game_auth_data.http_status_code = nasc_result.http_status; + strncpy(frd->last_game_auth_data.server_address.data(), nasc_result.server_address.c_str(), + sizeof(frd->last_game_auth_data.server_address) - 1); + frd->last_game_auth_data.server_port = nasc_result.server_port; + strncpy(frd->last_game_auth_data.auth_token.data(), nasc_result.auth_token.c_str(), + sizeof(frd->last_game_auth_data.auth_token) - 1); + frd->last_game_auth_data.server_time = nasc_result.time_stamp; + } + + rb.Push(RESULT_SUCCESS); +} + +void Module::Interface::GetGameAuthenticationData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x29, 0, 0); + + std::vector out_auth_data(sizeof(GameAuthenticationData)); + memcpy(out_auth_data.data(), &frd->last_game_auth_data, sizeof(GameAuthenticationData)); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); + rb.PushStaticBuffer(out_auth_data, 0); +} + void Module::Interface::SetClientSdkVersion(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 version = rp.Pop(); @@ -157,7 +712,25 @@ void Module::Interface::SetClientSdkVersion(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_FRD, "(STUBBED) called, version: 0x{:08X}", version); } -Module::Module(Core::System& system) : system(system){}; +Module::Module(Core::System& system) : system(system) { + + std::string account_file = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + "user.3dsac"; + if (FileUtil::Exists(account_file)) { + FileUtil::IOFile file(account_file, "rb"); + if (file.IsOpen() && file.GetSize() == sizeof(AccountDataV1)) { + file.ReadBytes(&my_account_data, sizeof(AccountDataV1)); + if (!my_account_data.IsValid()) { + LOG_ERROR(Service_FRD, "Invalid or corrupted user account file, using default"); + my_account_data = AccountDataV1(); + } + } else { + LOG_ERROR(Service_FRD, "Failed to open user account file, using default"); + } + } else { + my_account_data = AccountDataV1(); + LOG_INFO(Service_FRD, "No user account file found, using default"); + } +}; Module::~Module() = default; void InstallInterfaces(Core::System& system) { diff --git a/src/core/hle/service/frd/frd.h b/src/core/hle/service/frd/frd.h index 16902e45da..f485c12f7f 100644 --- a/src/core/hle/service/frd/frd.h +++ b/src/core/hle/service/frd/frd.h @@ -6,6 +6,7 @@ #include #include "common/common_types.h" +#include "core/hle/mii.h" #include "core/hle/service/service.h" namespace Core { @@ -14,10 +15,19 @@ class System; namespace Service::FRD { +constexpr u32 FRIEND_SCREEN_NAME_SIZE = 0xB; ///< 11-short UTF-16 screen name +constexpr u32 FRIEND_COMMENT_SIZE = 0x11; ///< 17-short UTF-16 comment +constexpr u32 FRIEND_GAME_MODE_DESCRIPTION_SIZE = 0x80; ///< 128-short UTF-16 description +constexpr u32 FRIEND_LIST_SIZE = 0x64; + struct FriendKey { - u32 friend_id; - u32 unknown; - u64 friend_code; + u32_le friend_id{}; + u32_le unknown{}; + u64_le friend_code{}; + + bool operator==(const FriendKey& other) { + return friend_id == other.friend_id; + } private: template @@ -29,25 +39,303 @@ private: friend class boost::serialization::access; }; -struct MyPresence { - u8 unknown[0x12C]; +struct TitleData { + u64_le tid{}; + u32_le version{}; + u32_le unk{}; private: template void serialize(Archive& ar, const unsigned int) { - ar& unknown; + ar& tid; + ar& version; + ar& unk; } friend class boost::serialization::access; }; -struct Profile { - u8 region; - u8 country; - u8 area; - u8 language; - u32 unknown; +struct GameMode { + u32_le join_flags{}; + u32_le type{}; + u32_le game_ID{}; + u32_le game_mode{}; + u32_le host_principal_ID{}; + u32_le gathering_ID{}; + std::array app_args{}; + +private: + template + void serialize(Archive& ar, const unsigned int) { + ar& join_flags; + ar& type; + ar& game_ID; + ar& game_mode; + ar& host_principal_ID; + ar& gathering_ID; + ar& app_args; + } + friend class boost::serialization::access; }; +struct FriendPlayingGame { + TitleData title{}; + std::array description{}; + +private: + template + void serialize(Archive& ar, const unsigned int) { + ar& title; + ar& description; + } + friend class boost::serialization::access; +}; + +struct MyPresence { + GameMode game_mode{}; + std::array description{}; + +private: + template + void serialize(Archive& ar, const unsigned int) { + ar& game_mode; + ar& description; + } + friend class boost::serialization::access; +}; + +struct FriendPresence { + GameMode game_mode{}; + u8 is_online{}; + u8 unknown{}; + u8 is_valid{}; + +private: + template + void serialize(Archive& ar, const unsigned int) { + ar& game_mode; + ar& is_online; + ar& unknown; + ar& is_valid; + } + friend class boost::serialization::access; +}; + +struct GameAuthenticationData { + s32_le result{}; + s32_le http_status_code{}; + std::array server_address{}; + u16_le server_port{}; + u16_le padding1{}; + u32_le unused{}; + std::array auth_token{}; + u64_le server_time{}; + + void Init() { + memset(this, 0, sizeof(*this)); + } + +private: + template + void serialize(Archive& ar, const unsigned int) { + ar& result; + ar& http_status_code; + ar& server_address; + ar& server_port; + ar& padding1; + ar& unused; + ar& auth_token; + ar& server_time; + } + friend class boost::serialization::access; +}; + +struct UserNameData { + std::array user_name{}; + u8 is_bad_word{}; + u8 unknown{}; + u32_le bad_word_ver{}; + +private: + template + void serialize(Archive& ar, const unsigned int) { + ar& user_name; + ar& is_bad_word; + ar& unknown; + ar& bad_word_ver; + } + friend class boost::serialization::access; +}; + +struct FriendProfile { + u8 region{}; + u8 country{}; + u8 area{}; + u8 language{}; + u8 platform{}; + std::array padding{}; + +private: + template + void serialize(Archive& ar, const unsigned int) { + ar& region; + ar& country; + ar& area; + ar& language; + ar& platform; + ar& padding; + } + friend class boost::serialization::access; +}; + +struct FriendInfo { + FriendKey friend_key{}; + s64_le account_creation_timestamp{}; + u8 relationship{}; + std::array padding1{}; + u32_le padding2{}; + FriendProfile friend_profile{}; + TitleData favorite_game{}; + u32_le principal_ID{}; + u16_le comment[FRIEND_COMMENT_SIZE]{}; + u16_le padding3{}; + s64_le last_online_timestamp{}; + std::array screen_name; + u8 font_region{}; + u8 padding4{}; + Mii::ChecksummedMiiData mii_data{}; + +private: + template + void serialize(Archive& ar, const unsigned int) { + ar& friend_key; + ar& account_creation_timestamp; + ar& relationship; + ar& padding1; + ar& padding2; + ar& friend_profile; + ar& favorite_game; + ar& principal_ID; + ar& comment; + ar& padding3; + ar& last_online_timestamp; + ar& screen_name; + ar& font_region; + ar& padding4; + ar& mii_data; + } + friend class boost::serialization::access; +}; + +struct AccountDataV1 { + static constexpr u32 MAGIC_VALUE = 0x54444146; + static constexpr u32 VERSION_VALUE = 0x1; + + u32_le magic{MAGIC_VALUE}; + u32_le version{VERSION_VALUE}; + u32_le reserved{}; + u32_le checksum{}; + UserNameData device_name{}; + u32_le padding1{}; + std::array password{}; + std::array pid_HMAC{}; + std::array serial_number{}; + std::array mac_address{}; + u16_le padding2{}; + std::array device_cert{}; + std::array nasc_url{}; + std::array ctr_common_prod_cert{}; + std::array ctr_common_prod_key{}; + FriendKey my_key{}; + u8 my_pref_public_mode{}; + u8 my_pref_public_game_name{}; + u8 my_pref_public_played_game{}; + u8 padding3{}; + FriendProfile my_profile{}; + std::array my_screen_name{}; + u16_le padding4{}; + Mii::ChecksummedMiiData my_mii_data{}; + u32_le padding5{}; + TitleData my_fav_game{}; + std::array my_comment{}; + u16_le my_friend_count{}; + u32_le padding6{}; + std::array friend_info{}; + + AccountDataV1() { + Init(); + } + + AccountDataV1& operator=(const AccountDataV1&) = default; + AccountDataV1& operator=(AccountDataV1&&) = default; + + u32 GenerateChecksum() { + u32 checksum = 0; + u8* data = reinterpret_cast(this); + for (int i = 0; i < sizeof(AccountDataV1); i++) { + checksum = data[i] + checksum * 0x65; + } + return checksum; + } + + bool IsValid() { + if (magic != MAGIC_VALUE || version != VERSION_VALUE) { + return false; + } + u32 checksum_backup = checksum; + checksum = 0; + bool good_checksum = GenerateChecksum() == checksum_backup; + checksum = checksum_backup; + return good_checksum; + } + + std::optional GetFriendInfo(const FriendKey& key) { + for (int i = 0; i < my_friend_count; i++) { + if (friend_info[i].friend_key == key) { + return &friend_info[i]; + } + } + return std::nullopt; + } + +private: + void Init(); + template + void serialize(Archive& ar, const unsigned int) { + ar& magic; + ar& version; + ar& reserved; + ar& checksum; + ar& device_name; + ar& padding1; + ar& password; + ar& pid_HMAC; + ar& serial_number; + ar& mac_address; + ar& padding2; + ar& device_cert; + ar& nasc_url; + ar& ctr_common_prod_cert; + ar& ctr_common_prod_key; + ar& my_key; + ar& my_pref_public_mode; + ar& my_pref_public_game_name; + ar& my_pref_public_played_game; + ar& padding3; + ar& my_profile; + ar& my_screen_name; + ar& padding4; + ar& my_mii_data; + ar& padding5; + ar& my_fav_game; + ar& my_comment; + ar& my_friend_count; + ar& padding6; + ar& friend_info; + } + friend class boost::serialization::access; +}; +static_assert(sizeof(AccountDataV1) == 25496, "AccountDataV1 has wrong size"); + class Module final { public: explicit Module(Core::System& system); @@ -59,6 +347,28 @@ public: ~Interface(); protected: + void HasLoggedIn(Kernel::HLERequestContext& ctx); + + void IsOnline(Kernel::HLERequestContext& ctx); + + void Login(Kernel::HLERequestContext& ctx); + + void Logout(Kernel::HLERequestContext& ctx); + + /** + * FRD::GetMyFriendKey service function + * Inputs: + * none + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2-5 : FriendKey + */ + void GetMyFriendKey(Kernel::HLERequestContext& ctx); + + void GetMyPreference(Kernel::HLERequestContext& ctx); + + void GetMyProfile(Kernel::HLERequestContext& ctx); + /** * FRD::GetMyPresence service function * Inputs: @@ -69,6 +379,28 @@ public: */ void GetMyPresence(Kernel::HLERequestContext& ctx); + /** + * FRD::GetMyScreenName service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : UTF16 encoded name (max 11 symbols) + */ + void GetMyScreenName(Kernel::HLERequestContext& ctx); + + void GetMyMii(Kernel::HLERequestContext& ctx); + + void GetMyLocalAccountId(Kernel::HLERequestContext& ctx); + + void GetMyPlayingGame(Kernel::HLERequestContext& ctx); + + void GetMyFavoriteGame(Kernel::HLERequestContext& ctx); + + void GetMyNcPrincipalId(Kernel::HLERequestContext& ctx); + + void GetMyComment(Kernel::HLERequestContext& ctx); + + void GetMyPassword(Kernel::HLERequestContext& ctx); + /** * FRD::GetFriendKeyList service function * Inputs: @@ -81,6 +413,12 @@ public: */ void GetFriendKeyList(Kernel::HLERequestContext& ctx); + void GetFriendPresence(Kernel::HLERequestContext& ctx); + + void GetFriendScreenName(Kernel::HLERequestContext& ctx); + + void GetFriendMii(Kernel::HLERequestContext& ctx); + /** * FRD::GetFriendProfile service function * Inputs: @@ -94,6 +432,8 @@ public: */ void GetFriendProfile(Kernel::HLERequestContext& ctx); + void GetFriendRelationship(Kernel::HLERequestContext& ctx); + /** * FRD::GetFriendAttributeFlags service function * Inputs: @@ -106,23 +446,13 @@ public: */ void GetFriendAttributeFlags(Kernel::HLERequestContext& ctx); - /** - * FRD::GetMyFriendKey service function - * Inputs: - * none - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2-5 : FriendKey - */ - void GetMyFriendKey(Kernel::HLERequestContext& ctx); + void GetFriendPlayingGame(Kernel::HLERequestContext& ctx); - /** - * FRD::GetMyScreenName service function - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : UTF16 encoded name (max 11 symbols) - */ - void GetMyScreenName(Kernel::HLERequestContext& ctx); + void GetFriendFavoriteGame(Kernel::HLERequestContext& ctx); + + void GetFriendInfo(Kernel::HLERequestContext& ctx); + + void IsIncludedInFriendList(Kernel::HLERequestContext& ctx); /** * FRD::UnscrambleLocalFriendCode service function @@ -137,6 +467,22 @@ public: */ void UnscrambleLocalFriendCode(Kernel::HLERequestContext& ctx); + void UpdateGameModeDescription(Kernel::HLERequestContext& ctx); + + void UpdateGameMode(Kernel::HLERequestContext& ctx); + + void AttachToEventNotification(Kernel::HLERequestContext& ctx); + + void SetNotificationMask(Kernel::HLERequestContext& ctx); + + void GetEventNotification(Kernel::HLERequestContext& ctx); + + void GetLastResponseResult(Kernel::HLERequestContext& ctx); + + void RequestGameAuthentication(Kernel::HLERequestContext& ctx); + + void GetGameAuthenticationData(Kernel::HLERequestContext& ctx); + /** * FRD::SetClientSdkVersion service function * Inputs: @@ -151,13 +497,22 @@ public: }; private: - FriendKey my_friend_key = {0, 0, 0ull}; - MyPresence my_presence = {}; + AccountDataV1 my_account_data; + GameAuthenticationData last_game_auth_data{}; + MyPresence my_presence{}; + Core::System& system; + bool has_logged_in = false; + u32 notif_event_mask = 0xF7; + std::shared_ptr notif_event; + + static const u32 fpd_version = 16; + template void serialize(Archive& ar, const unsigned int) { - ar& my_friend_key; + ar& my_account_data; + ar& last_game_auth_data; ar& my_presence; } friend class boost::serialization::access; diff --git a/src/core/hle/service/frd/frd_a.cpp b/src/core/hle/service/frd/frd_a.cpp index 4fcf04b94f..36937b5331 100644 --- a/src/core/hle/service/frd/frd_a.cpp +++ b/src/core/hle/service/frd/frd_a.cpp @@ -11,47 +11,47 @@ namespace Service::FRD { FRD_A::FRD_A(std::shared_ptr frd) : Module::Interface(std::move(frd), "frd:a", 8) { static const FunctionInfo functions[] = { - {0x0001, nullptr, "HasLoggedIn"}, - {0x0002, nullptr, "IsOnline"}, - {0x0003, nullptr, "Login"}, - {0x0004, nullptr, "Logout"}, + {0x0001, &FRD_A::HasLoggedIn, "HasLoggedIn"}, + {0x0002, &FRD_A::IsOnline, "IsOnline"}, + {0x0003, &FRD_A::Login, "Login"}, + {0x0004, &FRD_A::Logout, "Logout"}, {0x0005, &FRD_A::GetMyFriendKey, "GetMyFriendKey"}, - {0x0006, nullptr, "GetMyPreference"}, - {0x0007, nullptr, "GetMyProfile"}, + {0x0006, &FRD_A::GetMyPreference, "GetMyPreference"}, + {0x0007, &FRD_A::GetMyProfile, "GetMyProfile"}, {0x0008, &FRD_A::GetMyPresence, "GetMyPresence"}, {0x0009, &FRD_A::GetMyScreenName, "GetMyScreenName"}, - {0x000A, nullptr, "GetMyMii"}, - {0x000B, nullptr, "GetMyLocalAccountId"}, - {0x000C, nullptr, "GetMyPlayingGame"}, - {0x000D, nullptr, "GetMyFavoriteGame"}, - {0x000E, nullptr, "GetMyNcPrincipalId"}, - {0x000F, nullptr, "GetMyComment"}, - {0x0010, nullptr, "GetMyPassword"}, + {0x000A, &FRD_A::GetMyMii, "GetMyMii"}, + {0x000B, &FRD_A::GetMyLocalAccountId, "GetMyLocalAccountId"}, + {0x000C, &FRD_A::GetMyPlayingGame, "GetMyPlayingGame"}, + {0x000D, &FRD_A::GetMyFavoriteGame, "GetMyFavoriteGame"}, + {0x000E, &FRD_A::GetMyNcPrincipalId, "GetMyNcPrincipalId"}, + {0x000F, &FRD_A::GetMyComment, "GetMyComment"}, + {0x0010, &FRD_A::GetMyPassword, "GetMyPassword"}, {0x0011, &FRD_A::GetFriendKeyList, "GetFriendKeyList"}, - {0x0012, nullptr, "GetFriendPresence"}, - {0x0013, nullptr, "GetFriendScreenName"}, - {0x0014, nullptr, "GetFriendMii"}, + {0x0012, &FRD_A::GetFriendPresence, "GetFriendPresence"}, + {0x0013, &FRD_A::GetFriendScreenName, "GetFriendScreenName"}, + {0x0014, &FRD_A::GetFriendMii, "GetFriendMii"}, {0x0015, &FRD_A::GetFriendProfile, "GetFriendProfile"}, - {0x0016, nullptr, "GetFriendRelationship"}, + {0x0016, &FRD_A::GetFriendRelationship, "GetFriendRelationship"}, {0x0017, &FRD_A::GetFriendAttributeFlags, "GetFriendAttributeFlags"}, - {0x0018, nullptr, "GetFriendPlayingGame"}, - {0x0019, nullptr, "GetFriendFavoriteGame"}, - {0x001A, nullptr, "GetFriendInfo"}, - {0x001B, nullptr, "IsIncludedInFriendList"}, + {0x0018, &FRD_A::GetFriendPlayingGame, "GetFriendPlayingGame"}, + {0x0019, &FRD_A::GetFriendFavoriteGame, "GetFriendFavoriteGame"}, + {0x001A, &FRD_A::GetFriendInfo, "GetFriendInfo"}, + {0x001B, &FRD_A::IsIncludedInFriendList, "IsIncludedInFriendList"}, {0x001C, &FRD_A::UnscrambleLocalFriendCode, "UnscrambleLocalFriendCode"}, - {0x001D, nullptr, "UpdateGameModeDescription"}, - {0x001E, nullptr, "UpdateGameMode"}, + {0x001D, &FRD_A::UpdateGameModeDescription, "UpdateGameModeDescription"}, + {0x001E, &FRD_A::UpdateGameMode, "UpdateGameMode"}, {0x001F, nullptr, "SendInvitation"}, - {0x0020, nullptr, "AttachToEventNotification"}, - {0x0021, nullptr, "SetNotificationMask"}, - {0x0022, nullptr, "GetEventNotification"}, - {0x0023, nullptr, "GetLastResponseResult"}, + {0x0020, &FRD_A::AttachToEventNotification, "AttachToEventNotification"}, + {0x0021, &FRD_A::SetNotificationMask, "SetNotificationMask"}, + {0x0022, &FRD_A::GetEventNotification, "GetEventNotification"}, + {0x0023, &FRD_A::GetLastResponseResult, "GetLastResponseResult"}, {0x0024, nullptr, "PrincipalIdToFriendCode"}, {0x0025, nullptr, "FriendCodeToPrincipalId"}, {0x0026, nullptr, "IsValidFriendCode"}, {0x0027, nullptr, "ResultToErrorCode"}, - {0x0028, nullptr, "RequestGameAuthentication"}, - {0x0029, nullptr, "GetGameAuthenticationData"}, + {0x0028, &FRD_A::RequestGameAuthentication, "RequestGameAuthentication"}, + {0x0029, &FRD_A::GetGameAuthenticationData, "GetGameAuthenticationData"}, {0x002A, nullptr, "RequestServiceLocator"}, {0x002B, nullptr, "GetServiceLocatorData"}, {0x002C, nullptr, "DetectNatProperties"}, @@ -64,6 +64,23 @@ FRD_A::FRD_A(std::shared_ptr frd) : Module::Interface(std::move(frd), "f {0x0033, nullptr, "GetMyApproachContext"}, {0x0034, nullptr, "AddFriendWithApproach"}, {0x0035, nullptr, "DecryptApproachContext"}, + // frd:a commands start here + {0x0401, nullptr, "CreateLocalAccount"}, + {0x0402, nullptr, "DeleteConfig"}, + {0x0403, nullptr, "SetLocalAccountId"}, + {0x0404, nullptr, "ResetAccountConfig"}, + {0x0405, nullptr, "HasUserData"}, + {0x0406, nullptr, "AddFriendOnline"}, + {0x0407, nullptr, "AddFriendOffline"}, + {0x0408, nullptr, "SetFriendDisplayName"}, + {0x0409, nullptr, "RemoveFriend"}, + {0x040A, nullptr, "SetPresenseGameKey"}, + {0x040B, nullptr, "SetPrivacySettings"}, + {0x040C, nullptr, "SetMyData"}, + {0x040D, nullptr, "SetMyFavoriteGame"}, + {0x040E, nullptr, "SetMyNCPrincipalId"}, + {0x040F, nullptr, "SetPersonalComment"}, + {0x0410, nullptr, "DecryptApproachContext"}, }; RegisterHandlers(functions); } diff --git a/src/core/hle/service/frd/frd_u.cpp b/src/core/hle/service/frd/frd_u.cpp index 46e5f23c3d..9262a1f0b8 100644 --- a/src/core/hle/service/frd/frd_u.cpp +++ b/src/core/hle/service/frd/frd_u.cpp @@ -11,47 +11,47 @@ namespace Service::FRD { FRD_U::FRD_U(std::shared_ptr frd) : Module::Interface(std::move(frd), "frd:u", 8) { static const FunctionInfo functions[] = { - {0x0001, nullptr, "HasLoggedIn"}, - {0x0002, nullptr, "IsOnline"}, - {0x0003, nullptr, "Login"}, - {0x0004, nullptr, "Logout"}, + {0x0001, &FRD_U::HasLoggedIn, "HasLoggedIn"}, + {0x0002, &FRD_U::IsOnline, "IsOnline"}, + {0x0003, &FRD_U::Login, "Login"}, + {0x0004, &FRD_U::Logout, "Logout"}, {0x0005, &FRD_U::GetMyFriendKey, "GetMyFriendKey"}, - {0x0006, nullptr, "GetMyPreference"}, - {0x0007, nullptr, "GetMyProfile"}, + {0x0006, &FRD_U::GetMyPreference, "GetMyPreference"}, + {0x0007, &FRD_U::GetMyProfile, "GetMyProfile"}, {0x0008, &FRD_U::GetMyPresence, "GetMyPresence"}, {0x0009, &FRD_U::GetMyScreenName, "GetMyScreenName"}, - {0x000A, nullptr, "GetMyMii"}, - {0x000B, nullptr, "GetMyLocalAccountId"}, - {0x000C, nullptr, "GetMyPlayingGame"}, - {0x000D, nullptr, "GetMyFavoriteGame"}, - {0x000E, nullptr, "GetMyNcPrincipalId"}, - {0x000F, nullptr, "GetMyComment"}, - {0x0010, nullptr, "GetMyPassword"}, + {0x000A, &FRD_U::GetMyMii, "GetMyMii"}, + {0x000B, &FRD_U::GetMyLocalAccountId, "GetMyLocalAccountId"}, + {0x000C, &FRD_U::GetMyPlayingGame, "GetMyPlayingGame"}, + {0x000D, &FRD_U::GetMyFavoriteGame, "GetMyFavoriteGame"}, + {0x000E, &FRD_U::GetMyNcPrincipalId, "GetMyNcPrincipalId"}, + {0x000F, &FRD_U::GetMyComment, "GetMyComment"}, + {0x0010, &FRD_U::GetMyPassword, "GetMyPassword"}, {0x0011, &FRD_U::GetFriendKeyList, "GetFriendKeyList"}, - {0x0012, nullptr, "GetFriendPresence"}, - {0x0013, nullptr, "GetFriendScreenName"}, - {0x0014, nullptr, "GetFriendMii"}, + {0x0012, &FRD_U::GetFriendPresence, "GetFriendPresence"}, + {0x0013, &FRD_U::GetFriendScreenName, "GetFriendScreenName"}, + {0x0014, &FRD_U::GetFriendMii, "GetFriendMii"}, {0x0015, &FRD_U::GetFriendProfile, "GetFriendProfile"}, - {0x0016, nullptr, "GetFriendRelationship"}, + {0x0016, &FRD_U::GetFriendRelationship, "GetFriendRelationship"}, {0x0017, &FRD_U::GetFriendAttributeFlags, "GetFriendAttributeFlags"}, - {0x0018, nullptr, "GetFriendPlayingGame"}, - {0x0019, nullptr, "GetFriendFavoriteGame"}, - {0x001A, nullptr, "GetFriendInfo"}, - {0x001B, nullptr, "IsIncludedInFriendList"}, + {0x0018, &FRD_U::GetFriendPlayingGame, "GetFriendPlayingGame"}, + {0x0019, &FRD_U::GetFriendFavoriteGame, "GetFriendFavoriteGame"}, + {0x001A, &FRD_U::GetFriendInfo, "GetFriendInfo"}, + {0x001B, &FRD_U::IsIncludedInFriendList, "IsIncludedInFriendList"}, {0x001C, &FRD_U::UnscrambleLocalFriendCode, "UnscrambleLocalFriendCode"}, - {0x001D, nullptr, "UpdateGameModeDescription"}, - {0x001E, nullptr, "UpdateGameMode"}, + {0x001D, &FRD_U::UpdateGameModeDescription, "UpdateGameModeDescription"}, + {0x001E, &FRD_U::UpdateGameMode, "UpdateGameMode"}, {0x001F, nullptr, "SendInvitation"}, - {0x0020, nullptr, "AttachToEventNotification"}, - {0x0021, nullptr, "SetNotificationMask"}, - {0x0022, nullptr, "GetEventNotification"}, - {0x0023, nullptr, "GetLastResponseResult"}, + {0x0020, &FRD_U::AttachToEventNotification, "AttachToEventNotification"}, + {0x0021, &FRD_U::SetNotificationMask, "SetNotificationMask"}, + {0x0022, &FRD_U::GetEventNotification, "GetEventNotification"}, + {0x0023, &FRD_U::GetLastResponseResult, "GetLastResponseResult"}, {0x0024, nullptr, "PrincipalIdToFriendCode"}, {0x0025, nullptr, "FriendCodeToPrincipalId"}, {0x0026, nullptr, "IsValidFriendCode"}, {0x0027, nullptr, "ResultToErrorCode"}, - {0x0028, nullptr, "RequestGameAuthentication"}, - {0x0029, nullptr, "GetGameAuthenticationData"}, + {0x0028, &FRD_U::RequestGameAuthentication, "RequestGameAuthentication"}, + {0x0029, &FRD_U::GetGameAuthenticationData, "GetGameAuthenticationData"}, {0x002A, nullptr, "RequestServiceLocator"}, {0x002B, nullptr, "GetServiceLocatorData"}, {0x002C, nullptr, "DetectNatProperties"}, diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index f32a00df3c..0765ac9c85 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -670,6 +670,27 @@ void FS_USER::GetFormatInfo(Kernel::HLERequestContext& ctx) { rb.Push(format_info->duplicate_data != 0); } +void FS_USER::GetProductInfo(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x82E, 1, 0); + + u32 process_id = rp.Pop(); + + LOG_DEBUG(Service_FS, "process_id={}", process_id); + + IPC::RequestBuilder rb = rp.MakeBuilder(6, 0); + + ProductInfo product_info; + if (!GetProductInfo(process_id, product_info)) { + rb.Push(ResultCode(FileSys::ErrCodes::ArchiveNotMounted, ErrorModule::FS, + ErrorSummary::NotFound, ErrorLevel::Status)); + rb.Skip(5, false); + return; + } + + rb.Push(RESULT_SUCCESS); + rb.PushRaw(product_info); +} + void FS_USER::GetProgramLaunchInfo(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); @@ -782,7 +803,7 @@ void FS_USER::AddSeed(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } -void FS_USER::SetSaveDataSecureValue(Kernel::HLERequestContext& ctx) { +void FS_USER::ObsoletedSetSaveDataSecureValue(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u64 value = rp.Pop(); u32 secure_value_slot = rp.Pop(); @@ -801,7 +822,7 @@ void FS_USER::SetSaveDataSecureValue(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } -void FS_USER::GetSaveDataSecureValue(Kernel::HLERequestContext& ctx) { +void FS_USER::ObsoletedGetSaveDataSecureValue(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 secure_value_slot = rp.Pop(); @@ -823,7 +844,85 @@ void FS_USER::GetSaveDataSecureValue(Kernel::HLERequestContext& ctx) { rb.Push(0); // the secure value } -void FS_USER::Register(u32 process_id, u64 program_id, const std::string& filepath) { +void FS_USER::SetThisSaveDataSecureValue(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x86E, 3, 0); + u32 secure_value_slot = rp.Pop(); + u64 value = rp.Pop(); + + // TODO: Generate and Save the Secure Value + + LOG_WARNING(Service_FS, + "(STUBBED) called, value=0x{:016x} secure_value_slot=0x{:08X}", + value, secure_value_slot); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + rb.Push(RESULT_SUCCESS); +} + +void FS_USER::GetThisSaveDataSecureValue(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x86F, 1, 0); + + u32 secure_value_slot = rp.Pop(); + + LOG_WARNING( + Service_FS, + "(STUBBED) called secure_value_slot=0x{:08X}", + secure_value_slot); + + IPC::RequestBuilder rb = rp.MakeBuilder(5, 0); + + rb.Push(RESULT_SUCCESS); + + // TODO: Implement Secure Value Lookup & Generation + + rb.Push(false); // indicates that the secure value doesn't exist + rb.Push(false); // looks like a boolean value, purpose unknown + rb.Push(0); // the secure value +} + +void FS_USER::SetSaveDataSecureValue(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x875, 6, 0); + auto archive_handle = rp.PopRaw(); + u32 secure_value_slot = rp.Pop(); + u64 value = rp.Pop(); + bool flush = rp.Pop(); + + // TODO: Generate and Save the Secure Value + + LOG_WARNING(Service_FS, + "(STUBBED) called, value=0x{:016x} secure_value_slot=0x{:04X} " + "archive_handle=0x{:08X} flush={}", + value, secure_value_slot, archive_handle, flush); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + rb.Push(RESULT_SUCCESS); +} + +void FS_USER::GetSaveDataSecureValue(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x876, 3, 0); + + auto archive_handle = rp.PopRaw(); + u32 secure_value_slot = rp.Pop(); + + LOG_WARNING( + Service_FS, + "(STUBBED) called secure_value_slot=0x{:08X} archive_handle=0x{:08X}", + secure_value_slot, archive_handle); + + IPC::RequestBuilder rb = rp.MakeBuilder(5, 0); + + rb.Push(RESULT_SUCCESS); + + // TODO: Implement Secure Value Lookup & Generation + + rb.Push(false); // indicates that the secure value doesn't exist + rb.Push(false); // looks like a boolean value, purpose unknown + rb.Push(0); // the secure value +} + +void FS_USER::RegisterProgramInfo(u32 process_id, u64 program_id, const std::string& filepath) { const MediaType media_type = GetMediaTypeFromPath(filepath); program_info_map.insert_or_assign(process_id, ProgramInfo{program_id, media_type}); if (media_type == MediaType::GameCard) { @@ -835,6 +934,20 @@ std::string FS_USER::GetCurrentGamecardPath() const { return current_gamecard_path; } +void FS_USER::RegisterProductInfo(u32 process_id, const ProductInfo& product_info) { + product_info_map.insert_or_assign(process_id, product_info); +} + +bool FS_USER::GetProductInfo(u32 process_id, ProductInfo& out_product_info) { + auto it = product_info_map.find(process_id); + if (it != product_info_map.end()) { + out_product_info = it->second; + return true; + } else { + return false; + } +} + ResultVal FS_USER::GetSpecialContentIndexFromGameCard(u64 title_id, SpecialContentType type) { // TODO(B3N30) check if on real 3DS NCSD is checked if partition exists @@ -997,6 +1110,10 @@ FS_USER::FS_USER(Core::System& system) {0x0868, nullptr, "GetMediaType"}, {0x0869, nullptr, "GetNandEraseCount"}, {0x086A, nullptr, "ReadNandReport"}, + {0x086E, &FS_USER::SetThisSaveDataSecureValue, "SetThisSaveDataSecureValue" }, + {0x086F, &FS_USER::GetThisSaveDataSecureValue, "GetThisSaveDataSecureValue" }, + {0x0875, &FS_USER::SetSaveDataSecureValue, "SetSaveDataSecureValue" }, + {0x0876, &FS_USER::GetSaveDataSecureValue, "GetSaveDataSecureValue" }, {0x087A, &FS_USER::AddSeed, "AddSeed"}, {0x087D, &FS_USER::GetNumSeeds, "GetNumSeeds"}, {0x0886, nullptr, "CheckUpdatedDat"}, diff --git a/src/core/hle/service/fs/fs_user.h b/src/core/hle/service/fs/fs_user.h index 3962b8d6a0..d6ecf484df 100644 --- a/src/core/hle/service/fs/fs_user.h +++ b/src/core/hle/service/fs/fs_user.h @@ -39,12 +39,22 @@ class FS_USER final : public ServiceFramework { public: explicit FS_USER(Core::System& system); - // On real HW this is part of FS:Reg. But since that module is only used by loader and pm, which - // we HLEed, we can just directly use it here - void Register(u32 process_id, u64 program_id, const std::string& filepath); + // On real HW this is part of FSReg (FSReg:Register). But since that module is only used by + // loader and pm, which we HLEed, we can just directly use it here + void RegisterProgramInfo(u32 process_id, u64 program_id, const std::string& filepath); std::string GetCurrentGamecardPath() const; + struct ProductInfo { + std::array product_code; + u16_le maker_code; + u16_le remaster_version; + }; + + void RegisterProductInfo(u32 process_id, const ProductInfo& product_info); + + bool GetProductInfo(u32 process_id, ProductInfo& out_product_info); + private: void Initialize(Kernel::HLERequestContext& ctx); @@ -490,6 +500,17 @@ private: */ void GetFormatInfo(Kernel::HLERequestContext& ctx); + /** + * FS_User::GetProductInfo service function. + * Inputs: + * 0 : 0x082E0040 + * 1 : Process ID + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2-6 : Product info + */ + void GetProductInfo(Kernel::HLERequestContext& ctx); + /** * FS_User::GetProgramLaunchInfo service function. * Inputs: @@ -581,7 +602,7 @@ private: * 0 : 0x08650140 * 1 : Result of function, 0 on success, otherwise error code */ - void SetSaveDataSecureValue(Kernel::HLERequestContext& ctx); + void ObsoletedSetSaveDataSecureValue(Kernel::HLERequestContext& ctx); /** * FS_User::GetSaveDataSecureValue service function. @@ -596,6 +617,39 @@ private: * 2 : If Secure Value doesn't exist, 0, if it exists, 1 * 3-4 : Secure Value */ + void ObsoletedGetSaveDataSecureValue(Kernel::HLERequestContext& ctx); + + void SetThisSaveDataSecureValue(Kernel::HLERequestContext& ctx); + + void GetThisSaveDataSecureValue(Kernel::HLERequestContext& ctx); + + /** + * FS_User::SetSaveDataSecureValue service function. + * Inputs: + * 0 : 0x08750180 + * 1-2 : Archive + * 3 : Secure Value Slot + * 4 : value + * 5 : flush + * Outputs: + * 0 : header + * 1 : Result of function, 0 on success, otherwise error code + */ + void SetSaveDataSecureValue(Kernel::HLERequestContext& ctx); + + /** + * FS_User::GetSaveDataSecureValue service function. + * Inputs: + * 0 : 0x087600C0 + * 1-2 : Archive + * 2 : Secure Value slot + * Outputs: + * 0 : Header + * 1 : Result of function, 0 on success, otherwise error code + * 2 : If Secure Value doesn't exist, 0, if it exists, 1 + * 3 : unknown + * 4-5 : Secure Value + */ void GetSaveDataSecureValue(Kernel::HLERequestContext& ctx); static ResultVal GetSpecialContentIndexFromGameCard(u64 title_id, SpecialContentType type); @@ -610,6 +664,8 @@ private: std::unordered_map program_info_map; std::string current_gamecard_path; + std::unordered_map product_info_map; + u32 priority = -1; ///< For SetPriority and GetPriority service functions Core::System& system; diff --git a/src/core/hle/service/http_c.cpp b/src/core/hle/service/http_c.cpp index 094f908bad..8f04b54efe 100644 --- a/src/core/hle/service/http_c.cpp +++ b/src/core/hle/service/http_c.cpp @@ -933,6 +933,10 @@ HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) { DecryptClCertA(); } +std::shared_ptr GetService(Core::System& system) { + return system.ServiceManager().GetService("http:C"); +} + void InstallInterfaces(Core::System& system) { auto& service_manager = system.ServiceManager(); std::make_shared()->InstallAsService(service_manager); diff --git a/src/core/hle/service/http_c.h b/src/core/hle/service/http_c.h index 490e066481..54cbd15f7d 100644 --- a/src/core/hle/service/http_c.h +++ b/src/core/hle/service/http_c.h @@ -252,6 +252,16 @@ class HTTP_C final : public ServiceFramework { public: HTTP_C(); + struct ClCertAData { + std::vector certificate; + std::vector private_key; + bool init = false; + }; + + const ClCertAData& GetClCertA() const { + return ClCertA; + } + private: /** * HTTP_C::Initialize service function @@ -427,11 +437,7 @@ private: /// Global list of ClientCert contexts currently opened. std::unordered_map> client_certs; - struct { - std::vector certificate; - std::vector private_key; - bool init = false; - } ClCertA; + ClCertAData ClCertA; private: template @@ -452,6 +458,8 @@ private: friend class boost::serialization::access; }; +std::shared_ptr GetService(Core::System& system); + void InstallInterfaces(Core::System& system); } // namespace Service::HTTP diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index 1a7bd44947..b100533f93 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp @@ -282,7 +282,7 @@ ResultStatus AppLoader_THREEDSX::Load(std::shared_ptr& process) // On real HW this is done with FS:Reg, but we can be lazy auto fs_user = Core::System::GetInstance().ServiceManager().GetService("fs:USER"); - fs_user->Register(process->GetObjectId(), process->codeset->program_id, filepath); + fs_user->RegisterProgramInfo(process->GetObjectId(), process->codeset->program_id, filepath); process->Run(48, Kernel::DEFAULT_STACK_SIZE); diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 73b06529c4..ca1fd916c1 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -152,7 +152,15 @@ ResultStatus AppLoader_NCCH::LoadExec(std::shared_ptr& process) auto fs_user = Core::System::GetInstance().ServiceManager().GetService( "fs:USER"); - fs_user->Register(process->process_id, process->codeset->program_id, filepath); + fs_user->RegisterProgramInfo(process->process_id, process->codeset->program_id, filepath); + + Service::FS::FS_USER::ProductInfo product_info{}; + memcpy(product_info.product_code.data(), overlay_ncch->ncch_header.product_code, + product_info.product_code.size()); + std::memcpy(&product_info.remaster_version, + overlay_ncch->exheader_header.codeset_info.flags.remaster_version, 2); + product_info.maker_code = overlay_ncch->ncch_header.maker_code; + fs_user->RegisterProductInfo(process->process_id, product_info); process->Run(priority, stack_size); return ResultStatus::Success; diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 18dc7e6324..23deb2ff20 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -14,6 +14,8 @@ add_library(network STATIC room_member.h verify_user.cpp verify_user.h + network_clients/nasc.h + network_clients/nasc.cpp ) create_target_directory_groups(network) @@ -26,7 +28,7 @@ if (ENABLE_WEB_SERVICE) endif() endif() -target_link_libraries(network PRIVATE citra_common enet Boost::serialization) +target_link_libraries(network PRIVATE citra_common enet Boost::serialization cryptopp) set_target_properties(network PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO}) if (CITRA_USE_PRECOMPILED_HEADERS) diff --git a/src/network/network_clients/nasc.cpp b/src/network/network_clients/nasc.cpp new file mode 100644 index 0000000000..0129f595ec --- /dev/null +++ b/src/network/network_clients/nasc.cpp @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include "common/file_util.h" +#include "nasc.h" + +namespace NetworkClient::NASC { + +constexpr std::size_t TIMEOUT_SECONDS = 15; + +void NASCClient::Initialize(const std::vector& cert, const std::vector& key) { + Clear(); + + const unsigned char* tmpCertPtr = cert.data(); + const unsigned char* tmpKeyPtr = key.data(); + + client_cert = d2i_X509(nullptr, &tmpCertPtr, (long)cert.size()); + client_priv_key = d2i_PrivateKey(EVP_PKEY_RSA, nullptr, &tmpKeyPtr, (long)key.size()); +} + +void NASCClient::SetParameterImpl(const std::string& key, const std::vector& value) { + using namespace CryptoPP; + using Name::EncodingLookupArray; + using Name::InsertLineBreaks; + using Name::Pad; + using Name::PaddingByte; + + std::string out; + Base64Encoder encoder; + AlgorithmParameters params = + MakeParameters(EncodingLookupArray(), (const byte*)base64_dict.data())( + InsertLineBreaks(), false)(Pad(), true)(PaddingByte(), (byte)'*'); + + encoder.IsolatedInitialize(params); + encoder.Attach(new StringSink(out)); + encoder.Put(value.data(), value.size()); + encoder.MessageEnd(); + + parameters.emplace(key, out); +} + +NASCClient::NASCResult NASCClient::Perform() { + std::unique_ptr cli; + httplib::Request request; + NASCResult res; + + if (client_cert == nullptr || client_priv_key == nullptr) { + res.log_message = "Missing or invalid client certificate or key."; + return res; + } + + cli = std::make_unique(nasc_url.c_str(), 443, client_cert, client_priv_key); + cli->set_connection_timeout(TIMEOUT_SECONDS); + cli->set_read_timeout(TIMEOUT_SECONDS); + cli->set_write_timeout(TIMEOUT_SECONDS); + cli->enable_server_certificate_verification(false); + + if (!cli->is_valid()) { + res.log_message = fmt::format("Invalid URL \"{}\"", nasc_url); + return res; + } + + std::string header_param; + + if (GetParameter(parameters, "gameid", header_param)) { + request.set_header("X-GameId", header_param); + } + header_param.clear(); + if (GetParameter(parameters, "fpdver", header_param)) { + request.set_header("User-Agent", fmt::format("CTR FPD/{}", header_param)); + } + request.set_header("Content-Type", "application/x-www-form-urlencoded"); + + request.method = "POST"; + request.path = "/ac"; + request.body = httplib::detail::params_to_query_str(parameters); + boost::replace_all(request.body, "*", "%2A"); + + httplib::Result result = cli->send(request); + if (!result) { + res.log_message = + fmt::format("Request to \"{}\" returned error {}", nasc_url, (int)result.error()); + return res; + } + + httplib::Response response = result.value(); + + res.http_status = response.status; + if (response.status != 200) { + res.log_message = + fmt::format("Request to \"{}\" returned status {}", nasc_url, response.status); + return res; + } + + auto content_type = response.headers.find("content-type"); + if (content_type == response.headers.end() || + content_type->second.find("text/plain") == content_type->second.npos) { + res.log_message = "Unknown response body from NASC server"; + return res; + } + + httplib::Params out_parameters; + httplib::detail::parse_query_text(response.body, out_parameters); + + int nasc_result; + if (!GetParameter(out_parameters, "returncd", nasc_result)) { + res.log_message = "NASC response missing \"returncd\""; + return res; + } + + res.result = static_cast(nasc_result); + if (nasc_result != 1) { + res.log_message = fmt::format("NASC login failed with code 002-{:04d}", nasc_result); + return res; + } + + std::string locator; + if (!GetParameter(out_parameters, "locator", locator)) { + res.log_message = "NASC response missing \"locator\""; + return res; + } + + auto delimiter = locator.find(":"); + if (delimiter == locator.npos) { + res.log_message = "NASC response \"locator\" missing port delimiter"; + return res; + } + res.server_address = locator.substr(0, delimiter); + std::string port_str = locator.substr(delimiter + 1); + try { + res.server_port = (u16)std::stoi(port_str); + } catch (std::exception) { + } + + auto token = out_parameters.find("token"); + if (token == out_parameters.end()) { + res.log_message = "NASC response missing \"locator\""; + return res; + } + + res.auth_token = token->second; + + long long server_time; + if (!GetParameter(out_parameters, "datetime", server_time)) { + res.log_message = "NASC response missing \"datetime\""; + return res; + } + res.time_stamp = server_time; + return res; +} + +bool NASCClient::GetParameter(const httplib::Params& param, const std::string& key, + std::string& out) { + using namespace CryptoPP; + using Name::DecodingLookupArray; + using Name::Pad; + using Name::PaddingByte; + + auto field = param.find(key); + if (field == param.end()) + return false; + + Base64Decoder decoder; + int lookup[256]; + Base64Decoder::InitializeDecodingLookupArray(lookup, (const byte*)base64_dict.data(), 64, + false); + AlgorithmParameters params = MakeParameters(DecodingLookupArray(), (const int*)lookup); + + decoder.IsolatedInitialize(params); + decoder.Attach(new StringSink(out)); + decoder.Put(reinterpret_cast(field->second.data()), field->second.size()); + decoder.MessageEnd(); + return true; +} + +bool NASCClient::GetParameter(const httplib::Params& param, const std::string& key, int& out) { + std::string out_str; + if (!GetParameter(param, key, out_str)) { + return false; + } + try { + out = std::stoi(out_str); + return true; + } catch (std::exception) { + return false; + } +} + +bool NASCClient::GetParameter(const httplib::Params& param, const std::string& key, + long long& out) { + std::string out_str; + if (!GetParameter(param, key, out_str)) { + return false; + } + try { + out = std::stoll(out_str); + return true; + } catch (std::exception) { + return false; + } +} +} // namespace NetworkClient::NASC diff --git a/src/network/network_clients/nasc.h b/src/network/network_clients/nasc.h new file mode 100644 index 0000000000..c7c84e400a --- /dev/null +++ b/src/network/network_clients/nasc.h @@ -0,0 +1,80 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "common/common_types.h" +#include "map" +#include "string" +#include "vector" + +namespace NetworkClient::NASC { +class NASCClient { +public: + struct NASCResult { + u8 result = 0; + int http_status; + std::string log_message; + + std::string server_address; + u16 server_port; + std::string auth_token; + u64 time_stamp; + }; + + NASCClient(const std::string& url, const std::vector& cert, const std::vector& key) + : nasc_url(url) { + Initialize(cert, key); + } + + ~NASCClient() { + if (client_cert) { + X509_free(client_cert); + client_cert = nullptr; + } + if (client_priv_key) { + EVP_PKEY_free(client_priv_key); + client_priv_key = nullptr; + } + } + + void Clear() { + parameters.clear(); + } + + + void SetParameter(const std::string& key, int value) { + SetParameter(key, std::to_string(value)); + } + + void SetParameter(const std::string& key, const std::string& value) { + SetParameter(key, std::vector(value.cbegin(), value.cend())); + } + + void SetParameter(const std::string& key, const std::vector& value) { + SetParameterImpl(key, value); + } + + NASCResult Perform(); + +private: + const std::string base64_dict = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-"; + + std::string nasc_url; + X509* client_cert = nullptr; + EVP_PKEY* client_priv_key = nullptr; + + void Initialize(const std::vector& cert, const std::vector& key); + + httplib::Params parameters; + + void SetParameterImpl(const std::string& key, const std::vector& value); + + bool GetParameter(const httplib::Params& param, const std::string& key, std::string& out); + bool GetParameter(const httplib::Params& param, const std::string& key, int& out); + bool GetParameter(const httplib::Params& param, const std::string& key, long long& out); +}; +} // namespace NetworkClient::NASC