From 813d0c2a302cd5b7cb23ed0307d8984df3df4a21 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Tue, 5 Mar 2024 14:52:17 +0100 Subject: [PATCH] Add console unique data (SecureInfo, LocalFriendCodeSeed, CTCert) (#6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add console unique secure data * Add CTCert and DeviceID support * Fix AM_U::GetDeviceID Co-authored-by: Daniel López Guimaraes <112760654+DaniElectra@users.noreply.github.com> * Update to latest master changes. --------- Co-authored-by: Daniel López Guimaraes <112760654+DaniElectra@users.noreply.github.com> --- .../configuration/configure_system.cpp | 85 ++++++++++++ src/citra_qt/configuration/configure_system.h | 10 ++ .../configuration/configure_system.ui | 117 ++++++++++++++++ src/core/hle/service/am/am.cpp | 99 +++++++++++++- src/core/hle/service/am/am.h | 64 +++++++++ src/core/hle/service/am/am_net.cpp | 4 +- src/core/hle/service/am/am_sys.cpp | 2 +- src/core/hle/service/am/am_u.cpp | 2 +- src/core/hle/service/cfg/cfg.cpp | 129 +++++++++++++++++- src/core/hle/service/cfg/cfg.h | 87 ++++++++++++ src/core/hle/service/cfg/cfg_i.cpp | 6 +- src/core/hle/service/cfg/cfg_s.cpp | 6 +- 12 files changed, 597 insertions(+), 14 deletions(-) diff --git a/src/citra_qt/configuration/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp index 653957ca7..971f9f520 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,32 @@ 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/B"), QString(), + tr("SecureInfo_A/B (SecureInfo_A SecureInfo_B);;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_A/B"), QString(), + tr("LocalFriendCodeSeed_A/B (LocalFriendCodeSeed_A " + "LocalFriendCodeSeed_B);;All Files (*.*)")); + ui->button_friend_code_seed->setEnabled(true); + InstallSecureData(file_path_qtstr.toStdString(), cfg->GetLocalFriendCodeSeedBPath()); + }); + connect(ui->button_ct_cert, &QPushButton::clicked, this, [this] { + ui->button_ct_cert->setEnabled(false); + const QString file_path_qtstr = QFileDialog::getOpenFileName( + this, tr("Select CTCert"), QString(), tr("CTCert.bin (*.bin);;All Files (*.*)")); + ui->button_ct_cert->setEnabled(true); + InstallCTCert(file_path_qtstr.toStdString()); + }); + 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 +332,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 +383,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 +554,59 @@ 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::InstallCTCert(const std::string& from_path) { + std::string from = + FileUtil::SanitizePath(from_path, FileUtil::DirectorySeparator::PlatformDefault); + std::string to = FileUtil::SanitizePath(Service::AM::Module::GetCTCertPath(), + FileUtil::DirectorySeparator::PlatformDefault); + if (from.empty() || from == to) { + return; + } + FileUtil::Copy(from, to); + 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 ""; + } + }; + + Service::AM::CTCert ct_cert; + + 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())); + ui->label_ct_cert_status->setText( + tr((std::string("Status: ") + status_to_str(static_cast( + Service::AM::Module::LoadCTCertFile(ct_cert)))) + .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..9db21ba1c 100644 --- a/src/citra_qt/configuration/configure_system.h +++ b/src/citra_qt/configuration/configure_system.h @@ -20,6 +20,12 @@ namespace Core { class System; } +namespace Service { +namespace AM { +class Module; +} // namespace AM +} // namespace Service + namespace Service { namespace CFG { class Module; @@ -46,6 +52,10 @@ private: void UpdateInitTicks(int init_ticks_type); void RefreshConsoleID(); + void InstallSecureData(const std::string& from_path, const std::string& to_path); + void InstallCTCert(const std::string& from_path); + void RefreshSecureDataStatus(); + void SetupPerGameUI(); void DownloadFromNUS(); diff --git a/src/citra_qt/configuration/configure_system.ui b/src/citra_qt/configuration/configure_system.ui index 81f764b05..20f585637 100644 --- a/src/citra_qt/configuration/configure_system.ui +++ b/src/citra_qt/configuration/configure_system.ui @@ -506,6 +506,123 @@ + + + + Real Console Unique Data + + + + + + SecureInfo_A/B + + + + + + + + + + + + + + + + + + 0 + 0 + + + + Qt::RightToLeft + + + Choose + + + + + + + + + + LocalFriendCodeSeed_A/B + + + + + + + + + + + + + + + + + + 0 + 0 + + + + Qt::RightToLeft + + + Choose + + + + + + + + + + CTCert + + + + + + + + + + + + + + + + + + 0 + 0 + + + + Qt::RightToLeft + + + Choose + + + + + + + + + diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index a741e3488..1807fc185 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -80,6 +80,35 @@ struct TicketInfo { static_assert(sizeof(TicketInfo) == 0x18, "Ticket info structure size is wrong"); +bool CTCert::IsValid() const { + constexpr std::string_view expected_issuer_prod = "Nintendo CA - G3_NintendoCTR2prod"; + constexpr std::string_view expected_issuer_dev = "Nintendo CA - G3_NintendoCTR2dev"; + constexpr u32 expected_signature_type = 0x010005; + + return signature_type == expected_signature_type && + (std::string(issuer.data()) == expected_issuer_prod || + std::string(issuer.data()) == expected_issuer_dev); + + return false; +} + +u32 CTCert::GetDeviceID() const { + constexpr std::string_view key_id_prefix = "CT"; + + const std::string key_id_str(key_id.data()); + if (key_id_str.starts_with(key_id_prefix)) { + const std::string device_id = + key_id_str.substr(key_id_prefix.size(), key_id_str.find('-') - key_id_prefix.size()); + char* end_ptr; + const u32 device_id_value = std::strtoul(device_id.c_str(), &end_ptr, 16); + if (*end_ptr == '\0') { + return device_id_value; + } + } + // Error + return 0; +} + class CIAFile::DecryptionState { public: std::vector::Decryption> content; @@ -1213,6 +1242,21 @@ void Module::Interface::GetTicketList(Kernel::HLERequestContext& ctx) { ticket_list_count, ticket_index); } +void Module::Interface::GetDeviceID(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + const u32 deviceID = am->ct_cert.IsValid() ? am->ct_cert.GetDeviceID() : 0; + + if (deviceID == 0) { + LOG_ERROR(Service_AM, "Invalid or missing CTCert"); + } + + IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); + rb.Push(ResultSuccess); + rb.Push(0); + rb.Push(deviceID); +} + void Module::Interface::NeedsCleanup(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const auto media_type = rp.Pop(); @@ -1802,13 +1846,66 @@ void Module::serialize(Archive& ar, const unsigned int) { } SERIALIZE_IMPL(Module) -Module::Module(Core::System& system) : system(system) { +void Module::Interface::GetDeviceCert(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + [[maybe_unused]] u32 size = rp.Pop(); + auto buffer = rp.PopMappedBuffer(); + + if (!am->ct_cert.IsValid()) { + LOG_ERROR(Service_AM, "Invalid or missing CTCert"); + } + + buffer.Write(&am->ct_cert, 0, std::min(sizeof(CTCert), buffer.GetSize())); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + rb.Push(ResultSuccess); + rb.Push(0); + rb.PushMappedBuffer(buffer); +} + +std::string Module::GetCTCertPath() { + return FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + "CTCert.bin"; +} + +CTCertLoadStatus Module::LoadCTCertFile(CTCert& output) { + if (output.IsValid()) { + return CTCertLoadStatus::Loaded; + } + std::string file_path = GetCTCertPath(); + if (!FileUtil::Exists(file_path)) { + return CTCertLoadStatus::NotFound; + } + FileUtil::IOFile file(file_path, "rb"); + if (!file.IsOpen()) { + return CTCertLoadStatus::IOError; + } + if (file.GetSize() != sizeof(CTCert)) { + return CTCertLoadStatus::Invalid; + } + if (file.ReadBytes(&output, sizeof(CTCert)) != sizeof(CTCert)) { + return CTCertLoadStatus::IOError; + } + if (!output.IsValid()) { + output = CTCert(); + return CTCertLoadStatus::Invalid; + } + return CTCertLoadStatus::Loaded; +} + +Module::Module(Core::System& _system) : system(_system) { ScanForAllTitles(); + LoadCTCertFile(ct_cert); system_updater_mutex = system.Kernel().CreateMutex(false, "AM::SystemUpdaterMutex"); } Module::~Module() = default; +std::shared_ptr GetModule(Core::System& system) { + auto am = system.ServiceManager().GetService("am:u"); + if (!am) + return nullptr; + return am->GetModule(); +} + void InstallInterfaces(Core::System& system) { auto& service_manager = system.ServiceManager(); auto am = std::make_shared(system); diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 09fe9dc87..26b9b1056 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -68,6 +68,31 @@ enum class InstallStatus : u32 { ErrorEncrypted, }; +enum class CTCertLoadStatus { + Loaded, + NotFound, + Invalid, + IOError, +}; + +struct CTCert { + u32_be signature_type{}; + std::array signature_r{}; + std::array signature_s{}; + INSERT_PADDING_BYTES(0x40){}; + std::array issuer{}; + u32_be key_type{}; + std::array key_id{}; + u32_be expiration_time{}; + std::array public_key_x{}; + std::array public_key_y{}; + INSERT_PADDING_BYTES(0x3C){}; + + bool IsValid() const; + u32 GetDeviceID() const; +}; +static_assert(sizeof(CTCert) == 0x180, "Invalid CTCert size."); + // Title ID valid length constexpr std::size_t TITLE_ID_VALID_LENGTH = 16; @@ -216,6 +241,10 @@ public: Interface(std::shared_ptr am, const char* name, u32 max_session); ~Interface(); + std::shared_ptr GetModule() const { + return am; + } + protected: /** * AM::GetNumPrograms service function @@ -415,6 +444,16 @@ public: */ void GetTicketList(Kernel::HLERequestContext& ctx); + /** + * AM::GetDeviceID service function + * Inputs: + * Outputs: + * 1 : Result, 0 on success, otherwise error code + * 2 : Unknown + * 3 : DeviceID + */ + void GetDeviceID(Kernel::HLERequestContext& ctx); + /** * AM::NeedsCleanup service function * Inputs: @@ -702,10 +741,32 @@ public: */ void EndImportTicket(Kernel::HLERequestContext& ctx); + /** + * AM::GetDeviceCert service function + * Inputs: + * Outputs: + * 1 : Result, 0 on success, otherwise error code + * 2 : Unknown + * 3-4 : Device cert + */ + void GetDeviceCert(Kernel::HLERequestContext& ctx); + protected: std::shared_ptr am; }; + /** + * Gets the CTCert.bin path in the host filesystem + * @returns std::string CTCert.bin path in the host filesystem + */ + static std::string GetCTCertPath(); + + /** + * Loads the CTCert.bin file from the filesystem. + * @returns CTCertLoadStatus indicating the file load status. + */ + static CTCertLoadStatus LoadCTCertFile(CTCert& output); + private: /** * Scans the for titles in a storage medium for listing. @@ -722,12 +783,15 @@ private: bool cia_installing = false; std::array, 3> am_title_list; std::shared_ptr system_updater_mutex; + CTCert ct_cert{}; template void serialize(Archive& ar, const unsigned int); friend class boost::serialization::access; }; +std::shared_ptr GetModule(Core::System& system); + void InstallInterfaces(Core::System& system); } // namespace Service::AM diff --git a/src/core/hle/service/am/am_net.cpp b/src/core/hle/service/am/am_net.cpp index fed0ab912..eb9470752 100644 --- a/src/core/hle/service/am/am_net.cpp +++ b/src/core/hle/service/am/am_net.cpp @@ -19,7 +19,7 @@ AM_NET::AM_NET(std::shared_ptr am) : Module::Interface(std::move(am), "a {0x0007, &AM_NET::DeleteTicket, "DeleteTicket"}, {0x0008, &AM_NET::GetNumTickets, "GetNumTickets"}, {0x0009, &AM_NET::GetTicketList, "GetTicketList"}, - {0x000A, nullptr, "GetDeviceID"}, + {0x000A, &AM_NET::GetDeviceID, "GetDeviceID"}, {0x000B, nullptr, "GetNumImportTitleContexts"}, {0x000C, nullptr, "GetImportTitleContextList"}, {0x000D, nullptr, "GetImportTitleContexts"}, @@ -103,7 +103,7 @@ AM_NET::AM_NET(std::shared_ptr am) : Module::Interface(std::move(am), "a {0x0815, nullptr, "GetCurrentImportContentContexts"}, {0x0816, nullptr, "Sign"}, {0x0817, nullptr, "Verify"}, - {0x0818, nullptr, "GetDeviceCert"}, + {0x0818, &AM_NET::GetDeviceCert, "GetDeviceCert"}, {0x0819, nullptr, "ImportCertificates"}, {0x081A, nullptr, "ImportCertificate"}, {0x081B, nullptr, "CommitImportTitlesAndUpdateFirmwareAuto"}, diff --git a/src/core/hle/service/am/am_sys.cpp b/src/core/hle/service/am/am_sys.cpp index 48bc39cbb..a4e39e6d7 100644 --- a/src/core/hle/service/am/am_sys.cpp +++ b/src/core/hle/service/am/am_sys.cpp @@ -19,7 +19,7 @@ AM_SYS::AM_SYS(std::shared_ptr am) : Module::Interface(std::move(am), "a {0x0007, &AM_SYS::DeleteTicket, "DeleteTicket"}, {0x0008, &AM_SYS::GetNumTickets, "GetNumTickets"}, {0x0009, &AM_SYS::GetTicketList, "GetTicketList"}, - {0x000A, nullptr, "GetDeviceID"}, + {0x000A, &AM_SYS::GetDeviceID, "GetDeviceID"}, {0x000B, nullptr, "GetNumImportTitleContexts"}, {0x000C, nullptr, "GetImportTitleContextList"}, {0x000D, nullptr, "GetImportTitleContexts"}, diff --git a/src/core/hle/service/am/am_u.cpp b/src/core/hle/service/am/am_u.cpp index b1d8450a5..79a8fad95 100644 --- a/src/core/hle/service/am/am_u.cpp +++ b/src/core/hle/service/am/am_u.cpp @@ -19,7 +19,7 @@ AM_U::AM_U(std::shared_ptr am) : Module::Interface(std::move(am), "am:u" {0x0007, &AM_U::DeleteTicket, "DeleteTicket"}, {0x0008, &AM_U::GetNumTickets, "GetNumTickets"}, {0x0009, &AM_U::GetTicketList, "GetTicketList"}, - {0x000A, nullptr, "GetDeviceID"}, + {0x000A, &AM_U::GetDeviceID, "GetDeviceID"}, {0x000B, nullptr, "GetNumImportTitleContexts"}, {0x000C, nullptr, "GetImportTitleContextList"}, {0x000D, nullptr, "GetImportTitleContexts"}, diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index b93265c6b..d118250ba 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(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(Result(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(Result(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(ResultSuccess); + 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(Result(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(Result(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::InvalidState, + ErrorLevel::Permanent)); + } + + out_buffer.Write(&cfg->local_friend_code_seed_b, 0, sizeof(LocalFriendCodeSeedB)); + rb.Push(ResultSuccess); +} + +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(Result(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::InvalidState, + ErrorLevel::Permanent)); + } + + IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); + rb.Push(ResultSuccess); + 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,6 +570,14 @@ Result Module::UpdateConfigNANDSavegame() { return ResultSuccess; } +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"; +} + Result Module::FormatConfig() { Result res = DeleteConfigNANDSaveFile(); // The delete command fails if the file doesn't exist, so we have to check that too @@ -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"},