diff --git a/src/citra_qt/configuration/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp index 653957ca7..c6de61f46 100644 --- a/src/citra_qt/configuration/configure_system.cpp +++ b/src/citra_qt/configuration/configure_system.cpp @@ -3,12 +3,14 @@ // Refer to the license.txt file included. #include +#include #include #include #include #include #include "citra_qt/configuration/configuration_shared.h" #include "citra_qt/configuration/configure_system.h" +#include "common/file_util.h" #include "common/settings.h" #include "core/core.h" #include "core/hle/service/am/am.h" @@ -236,6 +238,24 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent) &ConfigureSystem::RefreshConsoleID); connect(ui->button_start_download, &QPushButton::clicked, this, &ConfigureSystem::DownloadFromNUS); + + connect(ui->button_secure_info, &QPushButton::clicked, this, [this] { + ui->button_secure_info->setEnabled(false); + const QString file_path_qtstr = + QFileDialog::getOpenFileName(this, tr("Select SecureInfo_A"), QString(), + tr("SecureInfo_A (SecureInfo_A);;All Files (*.*)")); + ui->button_secure_info->setEnabled(true); + InstallSecureData(file_path_qtstr.toStdString(), cfg->GetSecureInfoAPath()); + }); + connect(ui->button_friend_code_seed, &QPushButton::clicked, this, [this] { + ui->button_friend_code_seed->setEnabled(false); + const QString file_path_qtstr = QFileDialog::getOpenFileName( + this, tr("Select LocalFriendCodeSeed_B"), QString(), + tr("LocalFriendCodeSeed_B (LocalFriendCodeSeed_B);;All Files (*.*)")); + ui->button_friend_code_seed->setEnabled(true); + InstallSecureData(file_path_qtstr.toStdString(), cfg->GetLocalFriendCodeSeedBPath()); + }); + for (u8 i = 0; i < country_names.size(); i++) { if (std::strcmp(country_names.at(i), "") != 0) { ui->combo_country->addItem(tr(country_names.at(i)), i); @@ -304,6 +324,7 @@ void ConfigureSystem::SetConfiguration() { ReadSystemSettings(); ui->group_system_settings->setEnabled(enabled); + ui->group_real_console_unique_data->setEnabled(enabled); if (enabled) { ui->label_disable_info->hide(); } @@ -354,6 +375,9 @@ void ConfigureSystem::ReadSystemSettings() { // set firmware download region ui->combo_download_region->setCurrentIndex(static_cast(cfg->GetRegionValue())); + + // Refresh secure data status + RefreshSecureDataStatus(); } void ConfigureSystem::ApplyConfiguration() { @@ -522,6 +546,41 @@ void ConfigureSystem::RefreshConsoleID() { tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper())); } +void ConfigureSystem::InstallSecureData(const std::string& from_path, const std::string& to_path) { + std::string from = + FileUtil::SanitizePath(from_path, FileUtil::DirectorySeparator::PlatformDefault); + std::string to = FileUtil::SanitizePath(to_path, FileUtil::DirectorySeparator::PlatformDefault); + if (from.empty() || from == to) { + return; + } + FileUtil::CreateFullPath(to_path); + FileUtil::Copy(from, to); + cfg->InvalidateSecureData(); + RefreshSecureDataStatus(); +} + +void ConfigureSystem::RefreshSecureDataStatus() { + auto status_to_str = [](Service::CFG::SecureDataLoadStatus status) { + switch (status) { + case Service::CFG::SecureDataLoadStatus::Loaded: + return "Loaded"; + case Service::CFG::SecureDataLoadStatus::NotFound: + return "Not Found"; + case Service::CFG::SecureDataLoadStatus::Invalid: + return "Invalid"; + case Service::CFG::SecureDataLoadStatus::IOError: + return "IO Error"; + default: + return ""; + } + }; + + ui->label_secure_info_status->setText( + tr((std::string("Status: ") + status_to_str(cfg->LoadSecureInfoAFile())).c_str())); + ui->label_friend_code_seed_status->setText( + tr((std::string("Status: ") + status_to_str(cfg->LoadLocalFriendCodeSeedBFile())).c_str())); +} + void ConfigureSystem::RetranslateUI() { ui->retranslateUi(this); } diff --git a/src/citra_qt/configuration/configure_system.h b/src/citra_qt/configuration/configure_system.h index 8a258bc2b..68819bf98 100644 --- a/src/citra_qt/configuration/configure_system.h +++ b/src/citra_qt/configuration/configure_system.h @@ -46,6 +46,9 @@ private: void UpdateInitTicks(int init_ticks_type); void RefreshConsoleID(); + void InstallSecureData(const std::string& from_path, const std::string& to_path); + void RefreshSecureDataStatus(); + void SetupPerGameUI(); void DownloadFromNUS(); diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index b93265c6b..50dc2d5bd 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -217,12 +217,39 @@ void Module::Interface::GetRegion(Kernel::HLERequestContext& ctx) { void Module::Interface::SecureInfoGetByte101(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - LOG_DEBUG(Service_CFG, "(STUBBED) called"); + u8 ret = 0; + if (cfg->secure_info_a_loaded) { + ret = cfg->secure_info_a.unknown; + } IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); - rb.Push(ResultSuccess); - // According to 3dbrew this is normally 0. - rb.Push(0); + rb.Push(RESULT_SUCCESS); + rb.Push(ret); +} + +void Module::Interface::SecureInfoGetSerialNo(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + [[maybe_unused]] u32 out_size = rp.Pop(); + auto out_buffer = rp.PopMappedBuffer(); + + if (out_buffer.GetSize() < sizeof(SecureInfoA::serial_number)) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultCode(ErrorDescription::InvalidSize, ErrorModule::Config, + ErrorSummary::WrongArgument, ErrorLevel::Permanent)); + } + // Never happens on real hardware, but may happen if user didn't supply a dump. + // Always make sure to have available both secure data kinds or error otherwise. + if (!cfg->secure_info_a_loaded || !cfg->local_friend_code_seed_b_loaded) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::Config, + ErrorSummary::InvalidState, ErrorLevel::Permanent)); + } + + out_buffer.Write(&cfg->secure_info_a.serial_number, 0, sizeof(SecureInfoA::serial_number)); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); + rb.PushMappedBuffer(out_buffer); } void Module::Interface::SetUUIDClockSequence(Kernel::HLERequestContext& ctx) { @@ -365,6 +392,43 @@ void Module::Interface::UpdateConfigNANDSavegame(Kernel::HLERequestContext& ctx) rb.Push(cfg->UpdateConfigNANDSavegame()); } +void Module::Interface::GetLocalFriendCodeSeedData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + [[maybe_unused]] u32 out_size = rp.Pop(); + auto out_buffer = rp.PopMappedBuffer(); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + if (out_buffer.GetSize() < sizeof(LocalFriendCodeSeedB)) { + rb.Push(ResultCode(ErrorDescription::InvalidSize, ErrorModule::Config, + ErrorSummary::WrongArgument, ErrorLevel::Permanent)); + } + // Never happens on real hardware, but may happen if user didn't supply a dump. + // Always make sure to have available both secure data kinds or error otherwise. + if (!cfg->secure_info_a_loaded || !cfg->local_friend_code_seed_b_loaded) { + rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::Config, + ErrorSummary::InvalidState, ErrorLevel::Permanent)); + } + + out_buffer.Write(&cfg->local_friend_code_seed_b, 0, sizeof(LocalFriendCodeSeedB)); + rb.Push(RESULT_SUCCESS); +} + +void Module::Interface::GetLocalFriendCodeSeed(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + // Never happens on real hardware, but may happen if user didn't supply a dump. + // Always make sure to have available both secure data kinds or error otherwise. + if (!cfg->secure_info_a_loaded || !cfg->local_friend_code_seed_b_loaded) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::Config, + ErrorSummary::InvalidState, ErrorLevel::Permanent)); + } + + IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(cfg->local_friend_code_seed_b.friend_code_seed); +} + void Module::Interface::FormatConfig(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); @@ -506,8 +570,16 @@ Result Module::UpdateConfigNANDSavegame() { return ResultSuccess; } -Result Module::FormatConfig() { - Result res = DeleteConfigNANDSaveFile(); +std::string Module::GetLocalFriendCodeSeedBPath() { + return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "rw/sys/LocalFriendCodeSeed_B"; +} + +std::string Module::GetSecureInfoAPath() { + return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "rw/sys/SecureInfo_A"; +} + +ResultCode Module::FormatConfig() { + ResultCode res = DeleteConfigNANDSaveFile(); // The delete command fails if the file doesn't exist, so we have to check that too if (!res.IsSuccess() && res != FileSys::ResultFileNotFound) { return res; @@ -579,6 +651,55 @@ Result Module::LoadConfigNANDSaveFile() { return FormatConfig(); } +void Module::InvalidateSecureData() { + secure_info_a_loaded = local_friend_code_seed_b_loaded = false; +} + +SecureDataLoadStatus Module::LoadSecureInfoAFile() { + if (secure_info_a_loaded) { + return SecureDataLoadStatus::Loaded; + } + std::string file_path = GetSecureInfoAPath(); + if (!FileUtil::Exists(file_path)) { + return SecureDataLoadStatus::NotFound; + } + FileUtil::IOFile file(file_path, "rb"); + if (!file.IsOpen()) { + return SecureDataLoadStatus::IOError; + } + if (file.GetSize() != sizeof(SecureInfoA)) { + return SecureDataLoadStatus::Invalid; + } + if (file.ReadBytes(&secure_info_a, sizeof(SecureInfoA)) != sizeof(SecureInfoA)) { + return SecureDataLoadStatus::IOError; + } + secure_info_a_loaded = true; + return SecureDataLoadStatus::Loaded; +} + +SecureDataLoadStatus Module::LoadLocalFriendCodeSeedBFile() { + if (local_friend_code_seed_b_loaded) { + return SecureDataLoadStatus::Loaded; + } + std::string file_path = GetLocalFriendCodeSeedBPath(); + if (!FileUtil::Exists(file_path)) { + return SecureDataLoadStatus::NotFound; + } + FileUtil::IOFile file(file_path, "rb"); + if (!file.IsOpen()) { + return SecureDataLoadStatus::IOError; + } + if (file.GetSize() != sizeof(LocalFriendCodeSeedB)) { + return SecureDataLoadStatus::Invalid; + } + if (file.ReadBytes(&local_friend_code_seed_b, sizeof(LocalFriendCodeSeedB)) != + sizeof(LocalFriendCodeSeedB)) { + return SecureDataLoadStatus::IOError; + } + local_friend_code_seed_b_loaded = true; + return SecureDataLoadStatus::Loaded; +} + void Module::LoadMCUConfig() { FileUtil::IOFile mcu_data_file( fmt::format("{}/mcu.dat", FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir)), "rb"); @@ -616,6 +737,8 @@ Module::Module(Core::System& system_) : system(system_) { SetEULAVersion(default_version); UpdateConfigNANDSavegame(); } + LoadSecureInfoAFile(); + LoadLocalFriendCodeSeedBFile(); } Module::~Module() = default; diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h index 60bdcb9d5..7bd6a1310 100644 --- a/src/core/hle/service/cfg/cfg.h +++ b/src/core/hle/service/cfg/cfg.h @@ -176,6 +176,28 @@ enum class AccessFlag : u16 { }; DECLARE_ENUM_FLAG_OPERATORS(AccessFlag); +struct SecureInfoA { + std::array signature; + u8 region; + u8 unknown; + std::array serial_number; +}; +static_assert(sizeof(SecureInfoA) == 0x111); + +struct LocalFriendCodeSeedB { + std::array signature; + u64 unknown; + u64 friend_code_seed; +}; +static_assert(sizeof(LocalFriendCodeSeedB) == 0x110); + +enum class SecureDataLoadStatus { + Loaded, + NotFound, + Invalid, + IOError, +}; + class Module final { public: Module(Core::System& system_); @@ -230,6 +252,18 @@ public: */ void SecureInfoGetByte101(Kernel::HLERequestContext& ctx); + /** + * CFG::SecureInfoGetSerialNo service function + * Inputs: + * 1 : Buffer Size + * 2-3: Output mapped buffer + * Outputs: + * 0 : Result Header code + * 1 : Result of function, 0 on success, otherwise error code + * 2-3 : Output mapped buffer + */ + void SecureInfoGetSerialNo(Kernel::HLERequestContext& ctx); + /** * CFG::SetUUIDClockSequence service function * Inputs: @@ -345,6 +379,27 @@ public: */ void UpdateConfigNANDSavegame(Kernel::HLERequestContext& ctx); + /** + * CFG::GetLocalFriendCodeSeedData service function + * Inputs: + * 1 : Buffer Size + * 2-3: Output mapped buffer + * Outputs: + * 0 : Result Header code + * 1 : Result of function, 0 on success, otherwise error code + */ + void GetLocalFriendCodeSeedData(Kernel::HLERequestContext& ctx); + + /** + * CFG::GetLocalFriendCodeSeed service function + * Inputs: + * Outputs: + * 0 : Result Header code + * 1 : Result of function, 0 on success, otherwise error code + * 2-3 : Friend code seed + */ + void GetLocalFriendCodeSeed(Kernel::HLERequestContext& ctx); + /** * CFG::FormatConfig service function * Inputs: @@ -575,6 +630,34 @@ public: */ Result UpdateConfigNANDSavegame(); + /** + * Invalidates the loaded secure data so that it is loaded again. + */ + void InvalidateSecureData(); + /** + * Loads the LocalFriendCodeSeed_B file from NAND. + * @returns LocalFriendCodeSeedBLoadStatus indicating the file load status. + */ + SecureDataLoadStatus LoadSecureInfoAFile(); + + /** + * Loads the LocalFriendCodeSeed_B file from NAND. + * @returns LocalFriendCodeSeedBLoadStatus indicating the file load status. + */ + SecureDataLoadStatus LoadLocalFriendCodeSeedBFile(); + + /** + * Gets the SecureInfo_A path in the host filesystem + * @returns std::string SecureInfo_A path in the host filesystem + */ + std::string GetSecureInfoAPath(); + + /** + * Gets the LocalFriendCodeSeed_B path in the host filesystem + * @returns std::string LocalFriendCodeSeed_B path in the host filesystem + */ + std::string GetLocalFriendCodeSeedBPath(); + /** * Saves MCU specific data */ @@ -590,6 +673,10 @@ private: std::array cfg_config_file_buffer; std::unique_ptr cfg_system_save_data_archive; u32 preferred_region_code = 0; + bool secure_info_a_loaded = false; + SecureInfoA secure_info_a; + bool local_friend_code_seed_b_loaded = false; + LocalFriendCodeSeedB local_friend_code_seed_b; bool preferred_region_chosen = false; MCUData mcu_data{}; diff --git a/src/core/hle/service/cfg/cfg_i.cpp b/src/core/hle/service/cfg/cfg_i.cpp index 5223f8d08..aa7c17c3b 100644 --- a/src/core/hle/service/cfg/cfg_i.cpp +++ b/src/core/hle/service/cfg/cfg_i.cpp @@ -28,11 +28,11 @@ CFG_I::CFG_I(std::shared_ptr cfg) : Module::Interface(std::move(cfg), "c {0x0401, &CFG_I::GetSystemConfig, "GetSystemConfig"}, {0x0402, &CFG_I::SetSystemConfig, "SetSystemConfig"}, {0x0403, &CFG_I::UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"}, - {0x0404, nullptr, "GetLocalFriendCodeSeedData"}, - {0x0405, nullptr, "GetLocalFriendCodeSeed"}, + {0x0404, &CFG_I::GetLocalFriendCodeSeedData, "GetLocalFriendCodeSeedData"}, + {0x0405, &CFG_I::GetLocalFriendCodeSeed, "GetLocalFriendCodeSeed"}, {0x0406, &CFG_I::GetRegion, "GetRegion"}, {0x0407, &CFG_I::SecureInfoGetByte101, "SecureInfoGetByte101"}, - {0x0408, nullptr, "SecureInfoGetSerialNo"}, + {0x0408, &CFG_I::SecureInfoGetSerialNo, "SecureInfoGetSerialNo"}, {0x0409, nullptr, "UpdateConfigBlk00040003"}, // cfg:i {0x0801, &CFG_I::GetSystemConfig, "GetSystemConfig"}, diff --git a/src/core/hle/service/cfg/cfg_s.cpp b/src/core/hle/service/cfg/cfg_s.cpp index 3b2a50ff4..81aa5f487 100644 --- a/src/core/hle/service/cfg/cfg_s.cpp +++ b/src/core/hle/service/cfg/cfg_s.cpp @@ -28,11 +28,11 @@ CFG_S::CFG_S(std::shared_ptr cfg) : Module::Interface(std::move(cfg), "c {0x0401, &CFG_S::GetSystemConfig, "GetSystemConfig"}, {0x0402, &CFG_S::SetSystemConfig, "SetSystemConfig"}, {0x0403, &CFG_S::UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"}, - {0x0404, nullptr, "GetLocalFriendCodeSeedData"}, - {0x0405, nullptr, "GetLocalFriendCodeSeed"}, + {0x0404, &CFG_S::GetLocalFriendCodeSeedData, "GetLocalFriendCodeSeedData"}, + {0x0405, &CFG_S::GetLocalFriendCodeSeed, "GetLocalFriendCodeSeed"}, {0x0406, &CFG_S::GetRegion, "GetRegion"}, {0x0407, &CFG_S::SecureInfoGetByte101, "SecureInfoGetByte101"}, - {0x0408, nullptr, "SecureInfoGetSerialNo"}, + {0x0408, &CFG_S::SecureInfoGetSerialNo, "SecureInfoGetSerialNo"}, {0x0409, nullptr, "UpdateConfigBlk00040003"}, {0x040D, &CFG_S::SetUUIDClockSequence, "SetUUIDClockSequence"}, {0x040E, &CFG_S::GetUUIDClockSequence, "GetUUIDClockSequence"},