diff --git a/src/citra_qt/configuration/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp index f0ae442d6..01129b7ae 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" @@ -234,6 +236,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); @@ -298,6 +318,7 @@ void ConfigureSystem::SetConfiguration() { ASSERT_MSG(cfg, "CFG Module missing!"); ReadSystemSettings(); ui->group_system_settings->setEnabled(false); + ui->group_real_console_unique_data->setEnabled(false); } else { // This tab is enabled only when game is not running (i.e. all service are not initialized). cfg = std::make_shared(); @@ -351,6 +372,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() { @@ -501,6 +525,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 86b6b7016..9f107cb4b 100644 --- a/src/citra_qt/configuration/configure_system.h +++ b/src/citra_qt/configuration/configure_system.h @@ -45,6 +45,9 @@ private: void UpdateInitTime(int init_clock); void RefreshConsoleID(); + void InstallSecureData(const std::string& from_path, const std::string& to_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 728db6c76..1f5197d2b 100644 --- a/src/citra_qt/configuration/configure_system.ui +++ b/src/citra_qt/configuration/configure_system.ui @@ -1,503 +1,584 @@ - ConfigureSystem - - - - 0 - 0 - 535 - 619 - - - - Form - - - - - - - - System Settings - - - - - - Enable New 3DS mode - - - - - - - - 0 - 0 - - - - 10 - - - - - - - Username - - - - - - - Birthday - - - - - + ConfigureSystem + + + + 0 + 0 + 535 + 619 + + + + Form + + + + - - - - January - - - - - February - - - - - March - - - - - April - - - - - May - - - - - June - - - - - July - - - - - August - - - - - September - - - - - October - - - - - November - - - - - December - - - - - - - - - - - - - Language - - - - - - - Note: this can be overridden when region setting is auto-select - - - - Japanese (日本語) - - - - - English - - - - - French (français) - - - - - German (Deutsch) - - - - - Italian (italiano) - - - - - Spanish (español) - - - - - Simplified Chinese (简体中文) - - - - - Korean (한국어) - - - - - Dutch (Nederlands) - - - - - Portuguese (português) - - - - - Russian (Русский) - - - - - Traditional Chinese (正體中文) - - - - - - - - Sound output mode - - - - - - - - Mono - - - - - Stereo - - - - - Surround - - - - - - - - Country - - - - - - - - - - Clock - - - - - - - - System Clock - - - - - Fixed Time - - - - - - - - Startup time - - - - - - - yyyy-MM-ddTHH:mm:ss - - - - - - - Offset time - - - - - - - - - days - - - -2147483648 - - - 2147483647 - - - - - - - HH:mm:ss - - - - - - - - - Play Coins: - - - - - - - 300 - - - - - - - Run System Setup when Home Menu is launched - - - - - - - Console ID: - - - - - - - - 0 - 0 - - - - Qt::RightToLeft - - - Regenerate - - - - - - - 3GX Plugin Loader: - - - - - - - Enable 3GX plugin loader - - - - - - - Allow games to change plugin loader state - - - - - - - Download System Files from Nitendo servers - - - - - - - - - - - Minimal + + + System Settings - - - - Old 3DS - - - - - New 3DS - - + + + + + Enable New 3DS mode + + + + + + + + 0 + 0 + + + + 10 + + + + + + + Username + + + + + + + Birthday + + + + + + + + + + January + + + + + February + + + + + March + + + + + April + + + + + May + + + + + June + + + + + July + + + + + August + + + + + September + + + + + October + + + + + November + + + + + December + + + + + + + + + + + + + Language + + + + + + + Note: this can be overridden when region setting is auto-select + + + + Japanese (日本語) + + + + + English + + + + + French (français) + + + + + German (Deutsch) + + + + + Italian (italiano) + + + + + Spanish (español) + + + + + Simplified Chinese (简体中文) + + + + + Korean (한국어) + + + + + Dutch (Nederlands) + + + + + Portuguese (português) + + + + + Russian (Русский) + + + + + Traditional Chinese (正體中文) + + + + + + + + Sound output mode + + + + + + + + Mono + + + + + Stereo + + + + + Surround + + + + + + + + Country + + + + + + + + + + Clock + + + + + + + + System Clock + + + + + Fixed Time + + + + + + + + Startup time + + + + + + + yyyy-MM-ddTHH:mm:ss + + + + + + + Offset time + + + + + + + + + days + + + -2147483648 + + + 2147483647 + + + + + + + HH:mm:ss + + + + + + + + + Play Coins: + + + + + + + 300 + + + + + + + Run System Setup when Home Menu is launched + + + + + + + Console ID: + + + + + + + + 0 + 0 + + + + Qt::RightToLeft + + + Regenerate + + + + + + + 3GX Plugin Loader: + + + + + + + Enable 3GX plugin loader + + + + + + + Allow games to change plugin loader state + + + + + + + Download System Files from Nitendo servers + + + + + + + + + + + Minimal + + + + + Old 3DS + + + + + New 3DS + + + + + + + + + JPN + + + + + USA + + + + + EUR + + + + + AUS + + + + + CHN + + + + + KOR + + + + + TWN + + + + + + + + + 0 + 0 + + + + Qt::RightToLeft + + + Download + + + + + + + - - - - - - JPN + + + + + Real Console Unique Data - - - - USA - - - - - EUR - - - - - AUS - - - - - CHN - - - - - KOR - - - - - TWN - - + + + + + SecureInfo_A + + + + + + + + + + + + + + + + + + 0 + 0 + + + + Qt::RightToLeft + + + Choose + + + + + + + + + + LocalFriendCodeSeed_B + + + + + + + + + + + + + + + + + + 0 + 0 + + + + Qt::RightToLeft + + + Choose + + + + + + + - - - - - - 0 - 0 - - - - Qt::RightToLeft - - - Download - + + + + + System settings are available only when game is not running. + + + true + - - - - - - - - - - - System settings are available only when game is not running. - - - true - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + - - - - - toggle_new_3ds - edit_username - combo_birthmonth - combo_birthday - combo_language - combo_sound - combo_country - combo_init_clock - edit_init_time - spinBox_play_coins - button_regenerate_console_id - - - + + + toggle_new_3ds + edit_username + combo_birthmonth + combo_birthday + combo_language + combo_sound + combo_country + combo_init_clock + edit_init_time + spinBox_play_coins + button_regenerate_console_id + + + diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 8fbbca114..4ac067d1a 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -212,12 +212,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(RESULT_SUCCESS); - // 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(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::GetTransferableId(Kernel::HLERequestContext& ctx) { @@ -342,6 +369,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); @@ -484,6 +548,14 @@ ResultCode Module::UpdateConfigNANDSavegame() { return RESULT_SUCCESS; } +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 @@ -557,6 +629,55 @@ ResultCode 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; +} + Module::Module() { LoadConfigNANDSaveFile(); // Check the config savegame EULA Version and update it to 0x7F7F if necessary @@ -569,6 +690,8 @@ Module::Module() { 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 738709829..2d0b69e81 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(); @@ -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::GetTransferableId service function * Inputs: @@ -324,6 +358,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: @@ -538,11 +593,43 @@ public: */ ResultCode 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(); + private: static constexpr u32 CONFIG_SAVEFILE_SIZE = 0x8000; 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; template void serialize(Archive& ar, const unsigned int); 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 e75ca8548..4ad653f61 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"}, // clang-format on };