* 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 <geoster3d@gmail.com>
This commit is contained in:
PabloMK7 2023-07-27 18:19:33 +02:00 committed by GitHub
parent 539a1a0b6e
commit 71235bd678
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1901 additions and 228 deletions

View File

@ -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{});
}

View File

@ -24,7 +24,7 @@ private:
QVBoxLayout* layout;
QtMiiSelector* mii_selector;
u32 return_code = 0;
std::vector<HLE::Applets::MiiData> miis;
std::vector<Mii::MiiData> miis;
friend class QtMiiSelector;
};

View File

@ -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

View File

@ -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<HLE::Applets::MiiData> LoadMiis() {
std::vector<HLE::Applets::MiiData> miis;
std::vector<Mii::MiiData> LoadMiis() {
std::vector<Mii::MiiData> 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<HLE::Applets::MiiData> 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<u8, sizeof(mii)> 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<HLE::Applets::MiiData> 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

View File

@ -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<HLE::Applets::MiiData> LoadMiis();
std::vector<Mii::MiiData> LoadMiis();
class DefaultMiiSelector final : public MiiSelector {
public:

View File

@ -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;

View File

@ -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<u8, 0x6> creator_mac;
u16_be padding;
u16_be mii_information;
std::array<u16_le, 0xA> 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<u8, 0x7> unknown3;
std::array<u16_le, 0xA> 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<u16_le, 0xC> guest_mii_name;
};
static_assert(sizeof(MiiResult) == 0x84, "MiiResult structure has incorrect size");

9
src/core/hle/mii.cpp Normal file
View File

@ -0,0 +1,9 @@
#include <boost/crc.hpp>
#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

295
src/core/hle/mii.h Normal file
View File

@ -0,0 +1,295 @@
#pragma once
#include <boost/serialization/base_object.hpp>
#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<u8, 6> 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<u16_le, 10> 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<u16_le, 10> author_name{}; ///< Name of Mii's author (Encoded using UTF16)
private:
template <class Archive>
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 <class Archive>
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

View File

@ -4,6 +4,7 @@
#include <array>
#include <cstring>
#include <ctime>
#include <vector>
#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<u16>(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<Module> 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<Kernel::Event>();
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<u32>(frd->my_account_data.my_pref_public_mode);
rb.Push<u32>(frd->my_account_data.my_pref_public_game_name);
rb.Push<u32>(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<FriendProfile>(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<std::array<u16_le, FRIEND_SCREEN_NAME_SIZE>>(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<Mii::ChecksummedMiiData>(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<u32>(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<TitleData>(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<TitleData>(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<u32>(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<std::array<u16_le, FRIEND_COMMENT_SIZE>>(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<u32>();
std::vector<u8> pass_buf(pass_len);
strncpy(reinterpret_cast<char*>(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<u32>();
const u32 offset = rp.Pop<u32>();
const u32 frd_count = rp.Pop<u32>();
std::vector<u8> 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<u8> buffer(sizeof(FriendKey) * (end - start), 0);
FriendKey* buffer_ptr = reinterpret_cast<FriendKey*>(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<u32>(0); // 0 friends
rb.Push<u32>(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<u32>();
const std::vector<u8> frd_keys = rp.PopStaticBuffer();
ASSERT(frd_keys.size() == count * sizeof(FriendKey));
std::vector<u8> 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<u32>();
const u32 font_count = rp.Pop<u32>();
const u32 count = rp.Pop<u32>();
rp.Pop<u32>(); // replace_unknown_characters
rp.Pop<u32>(); // replace_bad_words
const std::vector<u8> 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<const FriendKey*>(frd_keys.data());
std::vector<u8> screen_names(sizeof(u16) * name_count, 0);
std::vector<u8> 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<u32>();
const std::vector<u8> 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<const FriendKey*>(frd_keys.data());
std::vector<u8> out_mii_vector(count * sizeof(Mii::ChecksummedMiiData));
Mii::ChecksummedMiiData* out_mii_data =
reinterpret_cast<Mii::ChecksummedMiiData*>(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<u8> frd_keys = rp.PopStaticBuffer();
ASSERT(frd_keys.size() == count * sizeof(FriendKey));
std::vector<u8> buffer(sizeof(Profile) * count, 0);
std::vector<u8> buffer(sizeof(FriendProfile) * count, 0);
const FriendKey* friend_keys_data = reinterpret_cast<const FriendKey*>(frd_keys.data());
FriendProfile* out_friend_profiles = reinterpret_cast<FriendProfile*>(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<u32>();
const std::vector<u8> frd_keys = rp.PopStaticBuffer();
ASSERT(frd_keys.size() == count * sizeof(FriendKey));
std::vector<u8> buffer(sizeof(u8) * count, 0);
const FriendKey* friend_keys_data = reinterpret_cast<const FriendKey*>(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<u32>();
const std::vector<u8> 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<u8> buffer(1 * count, 0);
std::vector<u8> buffer(sizeof(u32) * count, 0);
const FriendKey* friend_keys_data = reinterpret_cast<const FriendKey*>(frd_keys.data());
u32* out_attribute_flags = reinterpret_cast<u32*>(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<u32>();
const std::vector<u8> 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<const FriendKey*>(frd_keys.data());
std::vector<u8> 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<u32>();
const std::vector<u8> frd_keys = rp.PopStaticBuffer();
ASSERT(frd_keys.size() == count * sizeof(FriendKey));
struct ScreenName {
// 20 bytes according to 3dbrew
std::array<char16_t, 10> 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<u8> buffer(sizeof(TitleData) * count, 0);
const FriendKey* friend_keys_data = reinterpret_cast<const FriendKey*>(frd_keys.data());
TitleData* out_friend_fav_title = reinterpret_cast<TitleData*>(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<u32>();
rp.Pop<u32>(); // replace_unknown_characters
rp.Pop<u32>(); // replace_bad_words
const std::vector<u8> 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<const FriendKey*>(frd_keys.data());
std::vector<u8> out_info_vector(count * sizeof(FriendInfo));
FriendInfo* out_info_data = reinterpret_cast<FriendInfo*>(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<u64>();
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<u8> new_game_mode_description = rp.PopStaticBuffer();
u32 copy_size = std::min<u32>(static_cast<u32>(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<GameMode>();
const std::vector<u8> new_game_mode_description = rp.PopStaticBuffer();
frd->my_presence.game_mode = game_mode;
u32 copy_size = std::min<u32>(static_cast<u32>(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<Kernel::Event>();
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<u32>();
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<u32>();
// LOG_WARNING(Service_FRD, "(STUBBED) called");
std::vector<u8> empty(24 * count);
IPC::RequestBuilder rb = rp.MakeBuilder(3, 2);
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(0);
rb.Push<u32>(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<u32>();
struct ScreenNameIPC {
// 24 bytes
std::array<char16_t, 12> name;
};
auto screenName = rp.PopRaw<ScreenNameIPC>();
auto sdkMajor = rp.Pop<u32>();
auto sdkMinor = rp.Pop<u32>();
auto processID = rp.PopPID();
auto process = frd->system.Kernel().GetProcessById(processID);
auto event = rp.PopObject<Kernel::Event>();
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<Service::FS::FS_USER>("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<char*>(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<char*>(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<u8> 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<char16_t*>(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<u8> 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<u32>();
@ -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) {

View File

@ -6,6 +6,7 @@
#include <memory>
#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 <class Archive>
@ -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 <class Archive>
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<u8, 20> app_args{};
private:
template <class Archive>
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<u16_le, FRIEND_GAME_MODE_DESCRIPTION_SIZE> description{};
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& title;
ar& description;
}
friend class boost::serialization::access;
};
struct MyPresence {
GameMode game_mode{};
std::array<u16_le, FRIEND_GAME_MODE_DESCRIPTION_SIZE> description{};
private:
template <class Archive>
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 <class Archive>
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<char, 32> server_address{};
u16_le server_port{};
u16_le padding1{};
u32_le unused{};
std::array<char, 256> auth_token{};
u64_le server_time{};
void Init() {
memset(this, 0, sizeof(*this));
}
private:
template <class Archive>
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<u16_le, 11> user_name{};
u8 is_bad_word{};
u8 unknown{};
u32_le bad_word_ver{};
private:
template <class Archive>
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<u8, 3> padding{};
private:
template <class Archive>
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<u8, 3> 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<u16_le, FRIEND_SCREEN_NAME_SIZE> screen_name;
u8 font_region{};
u8 padding4{};
Mii::ChecksummedMiiData mii_data{};
private:
template <class Archive>
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<char, 0x20> password{};
std::array<char, 0x10> pid_HMAC{};
std::array<char, 0x10> serial_number{};
std::array<u8, 6> mac_address{};
u16_le padding2{};
std::array<u8, 0x110> device_cert{};
std::array<char, 0x20> nasc_url{};
std::array<u8, 0x4E0> ctr_common_prod_cert{};
std::array<u8, 0x4C0> 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<u16_le, FRIEND_SCREEN_NAME_SIZE> my_screen_name{};
u16_le padding4{};
Mii::ChecksummedMiiData my_mii_data{};
u32_le padding5{};
TitleData my_fav_game{};
std::array<u16_le, FRIEND_COMMENT_SIZE> my_comment{};
u16_le my_friend_count{};
u32_le padding6{};
std::array<FriendInfo, FRIEND_LIST_SIZE> friend_info{};
AccountDataV1() {
Init();
}
AccountDataV1& operator=(const AccountDataV1&) = default;
AccountDataV1& operator=(AccountDataV1&&) = default;
u32 GenerateChecksum() {
u32 checksum = 0;
u8* data = reinterpret_cast<u8*>(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<const FriendInfo*> 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 <class Archive>
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<Kernel::Event> notif_event;
static const u32 fpd_version = 16;
template <class Archive>
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;

View File

@ -11,47 +11,47 @@ namespace Service::FRD {
FRD_A::FRD_A(std::shared_ptr<Module> 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<Module> 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);
}

View File

@ -11,47 +11,47 @@ namespace Service::FRD {
FRD_U::FRD_U(std::shared_ptr<Module> 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"},

View File

@ -670,6 +670,27 @@ void FS_USER::GetFormatInfo(Kernel::HLERequestContext& ctx) {
rb.Push<bool>(format_info->duplicate_data != 0);
}
void FS_USER::GetProductInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x82E, 1, 0);
u32 process_id = rp.Pop<u32>();
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<ProductInfo>(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<u64>();
u32 secure_value_slot = rp.Pop<u32>();
@ -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<u32>();
@ -823,7 +844,85 @@ void FS_USER::GetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
rb.Push<u64>(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<u32>();
u64 value = rp.Pop<u64>();
// 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<u32>();
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<bool>(false); // indicates that the secure value doesn't exist
rb.Push<bool>(false); // looks like a boolean value, purpose unknown
rb.Push<u64>(0); // the secure value
}
void FS_USER::SetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x875, 6, 0);
auto archive_handle = rp.PopRaw<ArchiveHandle>();
u32 secure_value_slot = rp.Pop<u32>();
u64 value = rp.Pop<u64>();
bool flush = rp.Pop<bool>();
// 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<ArchiveHandle>();
u32 secure_value_slot = rp.Pop<u32>();
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<bool>(false); // indicates that the secure value doesn't exist
rb.Push<bool>(false); // looks like a boolean value, purpose unknown
rb.Push<u64>(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<u16> 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"},

View File

@ -39,12 +39,22 @@ class FS_USER final : public ServiceFramework<FS_USER, ClientSlot> {
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<u8, 0x10> 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<u16> GetSpecialContentIndexFromGameCard(u64 title_id, SpecialContentType type);
@ -610,6 +664,8 @@ private:
std::unordered_map<u32, ProgramInfo> program_info_map;
std::string current_gamecard_path;
std::unordered_map<u32, ProductInfo> product_info_map;
u32 priority = -1; ///< For SetPriority and GetPriority service functions
Core::System& system;

View File

@ -933,6 +933,10 @@ HTTP_C::HTTP_C() : ServiceFramework("http:C", 32) {
DecryptClCertA();
}
std::shared_ptr<HTTP_C> GetService(Core::System& system) {
return system.ServiceManager().GetService<HTTP_C>("http:C");
}
void InstallInterfaces(Core::System& system) {
auto& service_manager = system.ServiceManager();
std::make_shared<HTTP_C>()->InstallAsService(service_manager);

View File

@ -252,6 +252,16 @@ class HTTP_C final : public ServiceFramework<HTTP_C, SessionData> {
public:
HTTP_C();
struct ClCertAData {
std::vector<u8> certificate;
std::vector<u8> 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<ClientCertContext::Handle, std::shared_ptr<ClientCertContext>> client_certs;
struct {
std::vector<u8> certificate;
std::vector<u8> private_key;
bool init = false;
} ClCertA;
ClCertAData ClCertA;
private:
template <class Archive>
@ -452,6 +458,8 @@ private:
friend class boost::serialization::access;
};
std::shared_ptr<HTTP_C> GetService(Core::System& system);
void InstallInterfaces(Core::System& system);
} // namespace Service::HTTP

View File

@ -282,7 +282,7 @@ ResultStatus AppLoader_THREEDSX::Load(std::shared_ptr<Kernel::Process>& process)
// On real HW this is done with FS:Reg, but we can be lazy
auto fs_user =
Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>("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);

View File

@ -152,7 +152,15 @@ ResultStatus AppLoader_NCCH::LoadExec(std::shared_ptr<Kernel::Process>& process)
auto fs_user =
Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>(
"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;

View File

@ -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)

View File

@ -0,0 +1,203 @@
#include <memory>
#include <boost/algorithm/string/replace.hpp>
#include <cryptopp/base64.h>
#include <fmt/format.h>
#include "common/file_util.h"
#include "nasc.h"
namespace NetworkClient::NASC {
constexpr std::size_t TIMEOUT_SECONDS = 15;
void NASCClient::Initialize(const std::vector<u8>& cert, const std::vector<u8>& 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<u8>& 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<httplib::SSLClient> 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<httplib::SSLClient>(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<u8>(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<const byte*>(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

View File

@ -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 <httplib.h>
#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<u8>& cert, const std::vector<u8>& 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<u8>(value.cbegin(), value.cend()));
}
void SetParameter(const std::string& key, const std::vector<u8>& 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<u8>& cert, const std::vector<u8>& key);
httplib::Params parameters;
void SetParameterImpl(const std::string& key, const std::vector<u8>& 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