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 <geoster3d@gmail.com>
This commit is contained in:
parent
539a1a0b6e
commit
71235bd678
@ -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{});
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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
9
src/core/hle/mii.cpp
Normal 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
295
src/core/hle/mii.h
Normal 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
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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"},
|
||||
|
@ -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"},
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
203
src/network/network_clients/nasc.cpp
Normal file
203
src/network/network_clients/nasc.cpp
Normal 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
|
80
src/network/network_clients/nasc.h
Normal file
80
src/network/network_clients/nasc.h
Normal 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
|
Loading…
Reference in New Issue
Block a user