From 34d352b3ccfe4e89d31fa4bbee0acc44c51e3dd0 Mon Sep 17 00:00:00 2001 From: Pengfei Date: Wed, 1 Sep 2021 18:29:35 +0800 Subject: [PATCH] Use Config Savegame to determine system language and change display title accordingly --- src/core/CMakeLists.txt | 2 ++ src/core/file_sys/config_savegame.cpp | 37 +++++++++++++++++++ src/core/file_sys/config_savegame.h | 48 +++++++++++++++++++++++++ src/core/file_sys/data/savegame.h | 1 + src/core/importer.cpp | 51 ++++++++++++++++++++++++--- src/core/importer.h | 9 +++++ src/frontend/title_info_dialog.cpp | 2 +- 7 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 src/core/file_sys/config_savegame.cpp create mode 100644 src/core/file_sys/config_savegame.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index b2f3b09..bdd603b 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -12,6 +12,8 @@ add_library(core STATIC file_sys/certificate.cpp file_sys/certificate.h file_sys/cia_common.h + file_sys/config_savegame.cpp + file_sys/config_savegame.h file_sys/data/data_container.cpp file_sys/data/data_container.h file_sys/data/extdata.cpp diff --git a/src/core/file_sys/config_savegame.cpp b/src/core/file_sys/config_savegame.cpp new file mode 100644 index 0000000..de21bde --- /dev/null +++ b/src/core/file_sys/config_savegame.cpp @@ -0,0 +1,37 @@ +// Copyright 2021 Pengfei Zhu +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "common/logging/log.h" +#include "core/file_sys/config_savegame.h" + +namespace Core { + +ConfigSavegame::ConfigSavegame() = default; +ConfigSavegame::~ConfigSavegame() = default; + +bool ConfigSavegame::Init(std::vector data) { + if (data.size() != ConfigSavegameSize) { + LOG_ERROR(Core, "Config savegame is of incorrect size"); + return false; + } + std::memcpy(&header, data.data(), sizeof(header)); + return true; +} + +u8 ConfigSavegame::GetSystemLanguage() const { + static constexpr u32 LanguageBlockID = 0x000A0002; + + const auto iter = std::find_if( + header.block_entries.begin(), header.block_entries.end(), + [](const ConfigSavegameBlockEntry& entry) { return entry.block_id == LanguageBlockID; }); + if (iter == header.block_entries.end()) { + LOG_ERROR(Core, "Cannot find Language config block, returning default (English)"); + return 1; + } + return static_cast(iter->offset_or_data); +} + +} // namespace Core diff --git a/src/core/file_sys/config_savegame.h b/src/core/file_sys/config_savegame.h new file mode 100644 index 0000000..d519796 --- /dev/null +++ b/src/core/file_sys/config_savegame.h @@ -0,0 +1,48 @@ +// Copyright 2014 Citra Emulator Project / 2021 Pengfei Zhu +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "common/swap.h" + +namespace Core { + +constexpr u32 ConfigSavegameSize = 0x8000; +/// The maximum number of block entries that can exist in the config file +constexpr u32 ConfigSavegameMaxEntries = 1479; + +/// Block header in the config savedata file +struct ConfigSavegameBlockEntry { + u32_le block_id; ///< The id of the current block + u32_le offset_or_data; ///< This is the absolute offset to the block data if the size is greater + /// than 4 bytes, otherwise it contains the data itself + u16_le size; ///< The size of the block + u16_le flags; ///< The flags of the block, possibly used for access control +}; + +struct ConfigSavegameHeader { + u16_le total_entries; ///< The total number of set entries in the config file + u16_le data_entries_offset; ///< The offset where the data for the blocks start + /// The block headers, the maximum possible value is 1479 as per hardware + std::array block_entries; + u32_le unknown; ///< This field is unknown, possibly padding, 0 has been observed in hardware +}; +static_assert(sizeof(ConfigSavegameHeader) == 0x455C, + "Config Savegame header must be exactly 0x455C bytes"); + +// This is not the config savegame itself, but rather the `config` file inside. +class ConfigSavegame { +public: + explicit ConfigSavegame(); + ~ConfigSavegame(); + + bool Init(std::vector data); + u8 GetSystemLanguage() const; + +private: + ConfigSavegameHeader header; +}; + +} // namespace Core diff --git a/src/core/file_sys/data/savegame.h b/src/core/file_sys/data/savegame.h index 495b08e..327e4e4 100644 --- a/src/core/file_sys/data/savegame.h +++ b/src/core/file_sys/data/savegame.h @@ -25,6 +25,7 @@ private: friend class Archive; friend class InnerFAT; + friend class SDMCImporter; // Needed to read config savegame }; } // namespace Core diff --git a/src/core/importer.cpp b/src/core/importer.cpp index 1b09efa..642f536 100644 --- a/src/core/importer.cpp +++ b/src/core/importer.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include #include @@ -14,6 +15,7 @@ #include "core/db/seed_db.h" #include "core/db/title_db.h" #include "core/file_sys/certificate.h" +#include "core/file_sys/config_savegame.h" #include "core/file_sys/data/data_container.h" #include "core/file_sys/data/extdata.h" #include "core/file_sys/data/savegame.h" @@ -76,6 +78,8 @@ bool SDMCImporter::Init() { } } + LoadSystemLanguage(); + // Create children sdmc_decryptor = std::make_unique(config.sdmc_path); cia_builder = std::make_unique(config, ticket_db); @@ -94,6 +98,41 @@ bool SDMCImporter::Init() { return true; } +void SDMCImporter::LoadSystemLanguage() { + FileUtil::IOFile file(nand_config.data_path + "sysdata/00010017/00000000", "rb"); + if (!file) { + LOG_ERROR(Core, "Could not open config savegame"); + return; + } + + DataContainer container(file.GetData()); + std::vector> raw_data; + if (!container.IsGood() || !container.GetIVFCLevel4Data(raw_data)) { + return; + } + + Savegame save(raw_data); + // Find index of the 'config' file + for (std::size_t i = 0; i < save.file_entry_table.size(); ++i) { + if (std::strncmp(save.file_entry_table[i].name.data(), "config", 16) != 0) { + continue; + } + + // Load 'config' file + std::vector data; + if (!save.GetFileData(data, i)) { + return; + } + + ConfigSavegame config; + if (!config.Init(std::move(data))) { + return; + } + system_language = static_cast(config.GetSystemLanguage()); + break; + } +} + bool SDMCImporter::IsGood() const { return is_good; } @@ -438,7 +477,7 @@ struct TitleData { u64 extdata_id; std::vector icon; }; -static TitleData LoadTitleData(NCCHContainer& ncch) { +static TitleData LoadTitleData(NCCHContainer& ncch, SMDH::TitleLanguage language) { static const std::unordered_map NamedTitles{{ // System Applications (to avoid confusion) {0x00040010'2002c800, "New 3DS HOME Menu manual (JPN)"}, @@ -501,7 +540,11 @@ static TitleData LoadTitleData(NCCHContainer& ncch) { SMDH smdh; std::memcpy(&smdh, smdh_buffer.data(), smdh_buffer.size()); if (!NamedTitles.count(program_id)) { // Name was not overridden - title_name = Common::UTF16BufferToUTF8(smdh.GetShortTitle(SMDH::TitleLanguage::English)); + title_name = Common::UTF16BufferToUTF8(smdh.GetShortTitle(language)); + if (title_name.empty()) { // No title in the language for some reason + title_name = + Common::UTF16BufferToUTF8(smdh.GetShortTitle(SMDH::TitleLanguage::English)); + } } return TitleData{std::move(title_name), extdata_id, smdh.GetIcon(false)}; } @@ -803,7 +846,7 @@ void SDMCImporter::ListTitle(std::vector& out) const { break; } - const auto& [name, extdata_id, icon] = LoadTitleData(ncch); + const auto& [name, extdata_id, icon] = LoadTitleData(ncch, system_language); const auto size = FileUtil::GetDirectoryTreeSize(directory + virtual_name + "/content/") + TitleSizeAllowance; @@ -879,7 +922,7 @@ void SDMCImporter::ListNandTitle(std::vector& out) const { break; } - const auto& [name, extdata_id, icon] = LoadTitleData(ncch); + const auto& [name, extdata_id, icon] = LoadTitleData(ncch, system_language); const auto size = FileUtil::GetDirectoryTreeSize(directory + virtual_name + "/content/") + TitleSizeAllowance; diff --git a/src/core/importer.h b/src/core/importer.h index 1eb9897..b32a837 100644 --- a/src/core/importer.h +++ b/src/core/importer.h @@ -13,6 +13,7 @@ #include "common/progress_callback.h" #include "core/file_decryptor.h" #include "core/file_sys/cia_common.h" +#include "core/file_sys/smdh.h" namespace Core { @@ -196,8 +197,13 @@ public: return ticket_db; } + SMDH::TitleLanguage GetSystemLanguage() const { + return system_language; + } + private: bool Init(); + void LoadSystemLanguage(); // Impl of ImportContent without deleting mechanism. bool ImportContentImpl( @@ -230,6 +236,9 @@ private: bool is_good{}; Config config; Config::NandConfig nand_config; // Main NAND config + // System language, determined from config savegame. Used to return the title's names. + SMDH::TitleLanguage system_language{SMDH::TitleLanguage::English}; + std::unique_ptr sdmc_decryptor; FileDecryptor file_decryptor; diff --git a/src/frontend/title_info_dialog.cpp b/src/frontend/title_info_dialog.cpp index 47abc4c..112c479 100644 --- a/src/frontend/title_info_dialog.cpp +++ b/src/frontend/title_info_dialog.cpp @@ -145,7 +145,7 @@ void TitleInfoDialog::InitializeLanguageComboBox() { } ui->languageComboBox->addItem(tr(LanguageNames.at(i)), static_cast(i)); - if (i == 1) { // English + if (i == static_cast(importer.GetSystemLanguage())) { ui->languageComboBox->setCurrentIndex(ui->languageComboBox->count() - 1); } }