mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-02 16:49:04 +00:00
Use Config Savegame to determine system language and change display title accordingly
This commit is contained in:
@@ -12,6 +12,8 @@ add_library(core STATIC
|
|||||||
file_sys/certificate.cpp
|
file_sys/certificate.cpp
|
||||||
file_sys/certificate.h
|
file_sys/certificate.h
|
||||||
file_sys/cia_common.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.cpp
|
||||||
file_sys/data/data_container.h
|
file_sys/data/data_container.h
|
||||||
file_sys/data/extdata.cpp
|
file_sys/data/extdata.cpp
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2021 Pengfei Zhu
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#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<u8> 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<u8>(iter->offset_or_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Core
|
||||||
@@ -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 <array>
|
||||||
|
#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<ConfigSavegameBlockEntry, ConfigSavegameMaxEntries> 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<u8> data);
|
||||||
|
u8 GetSystemLanguage() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ConfigSavegameHeader header;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Core
|
||||||
@@ -25,6 +25,7 @@ private:
|
|||||||
|
|
||||||
friend class Archive<Savegame>;
|
friend class Archive<Savegame>;
|
||||||
friend class InnerFAT<Savegame>;
|
friend class InnerFAT<Savegame>;
|
||||||
|
friend class SDMCImporter; // Needed to read config savegame
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|||||||
+47
-4
@@ -2,6 +2,7 @@
|
|||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include <cryptopp/sha.h>
|
#include <cryptopp/sha.h>
|
||||||
@@ -14,6 +15,7 @@
|
|||||||
#include "core/db/seed_db.h"
|
#include "core/db/seed_db.h"
|
||||||
#include "core/db/title_db.h"
|
#include "core/db/title_db.h"
|
||||||
#include "core/file_sys/certificate.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/data_container.h"
|
||||||
#include "core/file_sys/data/extdata.h"
|
#include "core/file_sys/data/extdata.h"
|
||||||
#include "core/file_sys/data/savegame.h"
|
#include "core/file_sys/data/savegame.h"
|
||||||
@@ -76,6 +78,8 @@ bool SDMCImporter::Init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LoadSystemLanguage();
|
||||||
|
|
||||||
// Create children
|
// Create children
|
||||||
sdmc_decryptor = std::make_unique<SDMCDecryptor>(config.sdmc_path);
|
sdmc_decryptor = std::make_unique<SDMCDecryptor>(config.sdmc_path);
|
||||||
cia_builder = std::make_unique<CIABuilder>(config, ticket_db);
|
cia_builder = std::make_unique<CIABuilder>(config, ticket_db);
|
||||||
@@ -94,6 +98,41 @@ bool SDMCImporter::Init() {
|
|||||||
return true;
|
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<std::vector<u8>> 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<u8> data;
|
||||||
|
if (!save.GetFileData(data, i)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigSavegame config;
|
||||||
|
if (!config.Init(std::move(data))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
system_language = static_cast<SMDH::TitleLanguage>(config.GetSystemLanguage());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool SDMCImporter::IsGood() const {
|
bool SDMCImporter::IsGood() const {
|
||||||
return is_good;
|
return is_good;
|
||||||
}
|
}
|
||||||
@@ -438,7 +477,7 @@ struct TitleData {
|
|||||||
u64 extdata_id;
|
u64 extdata_id;
|
||||||
std::vector<u16> icon;
|
std::vector<u16> icon;
|
||||||
};
|
};
|
||||||
static TitleData LoadTitleData(NCCHContainer& ncch) {
|
static TitleData LoadTitleData(NCCHContainer& ncch, SMDH::TitleLanguage language) {
|
||||||
static const std::unordered_map<u64, const char*> NamedTitles{{
|
static const std::unordered_map<u64, const char*> NamedTitles{{
|
||||||
// System Applications (to avoid confusion)
|
// System Applications (to avoid confusion)
|
||||||
{0x00040010'2002c800, "New 3DS HOME Menu manual (JPN)"},
|
{0x00040010'2002c800, "New 3DS HOME Menu manual (JPN)"},
|
||||||
@@ -501,7 +540,11 @@ static TitleData LoadTitleData(NCCHContainer& ncch) {
|
|||||||
SMDH smdh;
|
SMDH smdh;
|
||||||
std::memcpy(&smdh, smdh_buffer.data(), smdh_buffer.size());
|
std::memcpy(&smdh, smdh_buffer.data(), smdh_buffer.size());
|
||||||
if (!NamedTitles.count(program_id)) { // Name was not overridden
|
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)};
|
return TitleData{std::move(title_name), extdata_id, smdh.GetIcon(false)};
|
||||||
}
|
}
|
||||||
@@ -803,7 +846,7 @@ void SDMCImporter::ListTitle(std::vector<ContentSpecifier>& out) const {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& [name, extdata_id, icon] = LoadTitleData(ncch);
|
const auto& [name, extdata_id, icon] = LoadTitleData(ncch, system_language);
|
||||||
const auto size =
|
const auto size =
|
||||||
FileUtil::GetDirectoryTreeSize(directory + virtual_name + "/content/") +
|
FileUtil::GetDirectoryTreeSize(directory + virtual_name + "/content/") +
|
||||||
TitleSizeAllowance;
|
TitleSizeAllowance;
|
||||||
@@ -879,7 +922,7 @@ void SDMCImporter::ListNandTitle(std::vector<ContentSpecifier>& out) const {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& [name, extdata_id, icon] = LoadTitleData(ncch);
|
const auto& [name, extdata_id, icon] = LoadTitleData(ncch, system_language);
|
||||||
const auto size =
|
const auto size =
|
||||||
FileUtil::GetDirectoryTreeSize(directory + virtual_name + "/content/") +
|
FileUtil::GetDirectoryTreeSize(directory + virtual_name + "/content/") +
|
||||||
TitleSizeAllowance;
|
TitleSizeAllowance;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "common/progress_callback.h"
|
#include "common/progress_callback.h"
|
||||||
#include "core/file_decryptor.h"
|
#include "core/file_decryptor.h"
|
||||||
#include "core/file_sys/cia_common.h"
|
#include "core/file_sys/cia_common.h"
|
||||||
|
#include "core/file_sys/smdh.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
@@ -196,8 +197,13 @@ public:
|
|||||||
return ticket_db;
|
return ticket_db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SMDH::TitleLanguage GetSystemLanguage() const {
|
||||||
|
return system_language;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool Init();
|
bool Init();
|
||||||
|
void LoadSystemLanguage();
|
||||||
|
|
||||||
// Impl of ImportContent without deleting mechanism.
|
// Impl of ImportContent without deleting mechanism.
|
||||||
bool ImportContentImpl(
|
bool ImportContentImpl(
|
||||||
@@ -230,6 +236,9 @@ private:
|
|||||||
bool is_good{};
|
bool is_good{};
|
||||||
Config config;
|
Config config;
|
||||||
Config::NandConfig nand_config; // Main NAND 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<SDMCDecryptor> sdmc_decryptor;
|
std::unique_ptr<SDMCDecryptor> sdmc_decryptor;
|
||||||
FileDecryptor file_decryptor;
|
FileDecryptor file_decryptor;
|
||||||
|
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ void TitleInfoDialog::InitializeLanguageComboBox() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ui->languageComboBox->addItem(tr(LanguageNames.at(i)), static_cast<int>(i));
|
ui->languageComboBox->addItem(tr(LanguageNames.at(i)), static_cast<int>(i));
|
||||||
if (i == 1) { // English
|
if (i == static_cast<u8>(importer.GetSystemLanguage())) {
|
||||||
ui->languageComboBox->setCurrentIndex(ui->languageComboBox->count() - 1);
|
ui->languageComboBox->setCurrentIndex(ui->languageComboBox->count() - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user