mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-03 00:38:58 +00:00
Add support for title.db, and use title.db to augment TMD finding
Also added support for system title CIA building, and fixed various minor issues. Also moved MakeMagic to Common.
This commit is contained in:
@@ -78,3 +78,12 @@ bool CheckedMemcpy(void* dest, T& container, std::ptrdiff_t offset, std::size_t
|
||||
std::memcpy(dest, container.data() + offset, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
consteval u32 MakeMagic(char a, char b, char c, char d) {
|
||||
return a | b << 8 | c << 16 | d << 24;
|
||||
}
|
||||
|
||||
consteval u64 MakeMagic(char a, char b, char c, char d, char e, char f, char g, char h) {
|
||||
return u64(a) | u64(b) << 8 | u64(c) << 16 | u64(d) << 24 | u64(e) << 32 | u64(f) << 40 |
|
||||
u64(g) << 48 | u64(h) << 56;
|
||||
}
|
||||
|
||||
@@ -40,3 +40,4 @@
|
||||
#define SEED_DB "seeddb.bin"
|
||||
#define AES_KEYS "aes_keys.txt"
|
||||
#define CERTS_DB "certs.db"
|
||||
#define TITLE_DB "title.db"
|
||||
|
||||
@@ -26,6 +26,8 @@ add_library(core STATIC
|
||||
quick_decryptor.cpp
|
||||
quick_decryptor.h
|
||||
result_status.h
|
||||
title_db.cpp
|
||||
title_db.h
|
||||
)
|
||||
|
||||
target_link_libraries(core PRIVATE common cryptopp)
|
||||
|
||||
@@ -9,10 +9,6 @@
|
||||
|
||||
namespace Core {
|
||||
|
||||
constexpr u32 MakeMagic(char a, char b, char c, char d) {
|
||||
return a | b << 8 | c << 16 | d << 24;
|
||||
}
|
||||
|
||||
DPFSContainer::DPFSContainer(DPFSDescriptor descriptor_, u8 level1_selector_,
|
||||
std::vector<u32_le> data_)
|
||||
: descriptor(descriptor_), level1_selector(level1_selector_), data(std::move(data_)) {
|
||||
|
||||
+107
-65
@@ -17,6 +17,7 @@
|
||||
#include "core/ncch/seed_db.h"
|
||||
#include "core/ncch/smdh.h"
|
||||
#include "core/ncch/title_metadata.h"
|
||||
#include "core/title_db.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
@@ -56,6 +57,33 @@ bool SDMCImporter::Init() {
|
||||
decryptor = std::make_unique<SDMCDecryptor>(config.sdmc_path);
|
||||
cia_builder = std::make_unique<CIABuilder>();
|
||||
|
||||
// Load SDMC Title DB
|
||||
{
|
||||
DataContainer container(decryptor->DecryptFile("/dbs/title.db"));
|
||||
std::vector<std::vector<u8>> data;
|
||||
if (container.IsGood() && container.GetIVFCLevel4Data(data)) {
|
||||
sdmc_title_db = std::make_unique<TitleDB>(std::move(data[0]));
|
||||
}
|
||||
}
|
||||
if (!sdmc_title_db || !sdmc_title_db->IsGood()) {
|
||||
LOG_WARNING(Core, "SDMC title.db invalid");
|
||||
sdmc_title_db.reset();
|
||||
}
|
||||
|
||||
// Load NAND Title DB
|
||||
if (!config.nand_title_db_path.empty()) {
|
||||
FileUtil::IOFile file(config.nand_title_db_path, "rb");
|
||||
DataContainer container(file.GetData());
|
||||
std::vector<std::vector<u8>> data;
|
||||
if (container.IsGood() && container.GetIVFCLevel4Data(data)) {
|
||||
nand_title_db = std::make_unique<TitleDB>(std::move(data[0]));
|
||||
}
|
||||
}
|
||||
if (!nand_title_db || !nand_title_db->IsGood()) {
|
||||
LOG_WARNING(Core, "NAND title.db invalid");
|
||||
nand_title_db.reset();
|
||||
}
|
||||
|
||||
FileUtil::SetUserPath(config.user_path);
|
||||
return true;
|
||||
}
|
||||
@@ -426,17 +454,22 @@ static std::string FindTMD(const std::string& path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (virtual_name.substr(virtual_name.size() - 3) == "tmd" &&
|
||||
if (virtual_name.size() == 12 &&
|
||||
virtual_name.substr(virtual_name.size() - 4) == ".tmd" &&
|
||||
std::regex_match(virtual_name.substr(0, 8), title_regex)) {
|
||||
|
||||
title_metadata = virtual_name;
|
||||
return false;
|
||||
// We would like to find the TMD with the smallest content ID,
|
||||
// as that would be the finalized version, not the version
|
||||
// pending installation
|
||||
title_metadata =
|
||||
title_metadata.empty() ? virtual_name : std::min(title_metadata, virtual_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (ret) { // TMD not found
|
||||
if (title_metadata.empty()) { // TMD not found
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -447,16 +480,39 @@ static std::string FindTMD(const std::string& path) {
|
||||
return path + title_metadata;
|
||||
}
|
||||
|
||||
static bool LoadTMD(const std::string& sdmc_path, const std::string& path, SDMCDecryptor& decryptor,
|
||||
TitleMetadata& out) {
|
||||
bool SDMCImporter::LoadTMD(ContentType type, u64 id, TitleMetadata& out) const {
|
||||
const bool is_nand = type == ContentType::SystemTitle;
|
||||
|
||||
const auto tmd = FindTMD(sdmc_path + path.substr(1));
|
||||
if (tmd.empty()) {
|
||||
return false;
|
||||
auto& title_db = is_nand ? nand_title_db : sdmc_title_db;
|
||||
const auto physical_path =
|
||||
is_nand ? fmt::format("{}{:08x}/{:08x}/content/", config.system_titles_path, (id >> 32),
|
||||
(id & 0xFFFFFFFF))
|
||||
: fmt::format("{}title/{:08x}/{:08x}/content/", config.sdmc_path, (id >> 32),
|
||||
(id & 0xFFFFFFFF));
|
||||
|
||||
std::string tmd_path;
|
||||
if (title_db && title_db->titles.count(id)) {
|
||||
tmd_path =
|
||||
fmt::format("{}{:08x}.tmd", physical_path, title_db->titles.at(id).tmd_content_id);
|
||||
} else {
|
||||
LOG_WARNING(Core, "Title {:016x} does not exist in title.db", id);
|
||||
tmd_path = FindTMD(physical_path);
|
||||
if (tmd_path.empty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return out.Load(decryptor.DecryptFile(tmd.substr(sdmc_path.size() - 1))) ==
|
||||
ResultStatus::Success;
|
||||
if (is_nand) {
|
||||
FileUtil::IOFile file(tmd_path, "rb");
|
||||
if (!file || file.GetSize() > 1024 * 1024) {
|
||||
LOG_ERROR(Core, "Could not open {} or file too big", tmd_path);
|
||||
return false;
|
||||
}
|
||||
return out.Load(file.GetData()) == ResultStatus::Success;
|
||||
} else {
|
||||
return out.Load(decryptor->DecryptFile(tmd_path.substr(config.sdmc_path.size() - 1))) ==
|
||||
ResultStatus::Success;
|
||||
}
|
||||
}
|
||||
|
||||
// English short title name, extdata id, encryption, seed, icon
|
||||
@@ -513,16 +569,14 @@ bool SDMCImporter::DumpCXI(const ContentSpecifier& specifier, const std::string&
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto content_path = fmt::format("/title/{:08x}/{:08x}/content/", specifier.id >> 32,
|
||||
(specifier.id & 0xFFFFFFFF));
|
||||
TitleMetadata tmd;
|
||||
if (!LoadTMD(config.sdmc_path, content_path, *decryptor, tmd)) {
|
||||
LOG_ERROR(Core, "Could not load tmd");
|
||||
if (!LoadTMD(specifier.type, specifier.id, tmd)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto boot_content_path =
|
||||
fmt::format("{}{:08x}.app", content_path, tmd.GetBootContentID());
|
||||
fmt::format("/title/{:08x}/{:08x}/content/{:08x}.app", specifier.id >> 32,
|
||||
(specifier.id & 0xFFFFFFFF), tmd.GetBootContentID());
|
||||
dump_cxi_ncch = std::make_unique<NCCHContainer>(
|
||||
std::make_shared<SDMCFile>(config.sdmc_path, boot_content_path, "rb"));
|
||||
|
||||
@@ -548,32 +602,35 @@ bool SDMCImporter::BuildCIA(const ContentSpecifier& specifier, const std::string
|
||||
}
|
||||
|
||||
if (specifier.type != ContentType::Application && specifier.type != ContentType::Update &&
|
||||
specifier.type != ContentType::DLC) {
|
||||
specifier.type != ContentType::DLC && specifier.type != ContentType::SystemTitle) {
|
||||
|
||||
LOG_ERROR(Core, "Unsupported specifier type {}", static_cast<int>(specifier.type));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load TMD
|
||||
const auto path = fmt::format("/title/{:08x}/{:08x}/content/", (specifier.id >> 32),
|
||||
(specifier.id & 0xFFFFFFFF));
|
||||
TitleMetadata tmd;
|
||||
if (!LoadTMD(config.sdmc_path, path, *decryptor, tmd)) {
|
||||
LOG_ERROR(Core, "Failed to load TMD from {}", path);
|
||||
if (!LoadTMD(specifier.type, specifier.id, tmd)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto physical_path = config.sdmc_path + path.substr(1);
|
||||
bool ret = cia_builder->Init(destination, std::move(tmd), config.certs_db_path,
|
||||
const bool is_nand = specifier.type == ContentType::SystemTitle;
|
||||
const auto physical_path =
|
||||
is_nand ? fmt::format("{}{:08x}/{:08x}/content/", config.system_titles_path,
|
||||
(specifier.id >> 32), (specifier.id & 0xFFFFFFFF))
|
||||
: fmt::format("{}title/{:08x}/{:08x}/content/", config.sdmc_path,
|
||||
(specifier.id >> 32), (specifier.id & 0xFFFFFFFF));
|
||||
|
||||
bool ret = cia_builder->Init(destination, tmd, config.certs_db_path,
|
||||
FileUtil::GetDirectoryTreeSize(physical_path), callback);
|
||||
if (!ret) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const FileUtil::DirectoryEntryCallable DirectoryEntryCallback =
|
||||
[this, specifier, path, &DirectoryEntryCallback](u64* /*num_entries_out*/,
|
||||
const std::string& directory,
|
||||
const std::string& virtual_name) {
|
||||
[this, tmd, is_nand, specifier, &DirectoryEntryCallback](u64* /*num_entries_out*/,
|
||||
const std::string& directory,
|
||||
const std::string& virtual_name) {
|
||||
if (FileUtil::IsDirectory(directory + virtual_name + "/")) {
|
||||
if (virtual_name == "cmd") {
|
||||
return true; // Skip cmd (not used in Citra)
|
||||
@@ -592,9 +649,22 @@ bool SDMCImporter::BuildCIA(const ContentSpecifier& specifier, const std::string
|
||||
ASSERT(match.size() >= 2);
|
||||
|
||||
const u32 id = static_cast<u32>(std::stoul(match[1], nullptr, 16));
|
||||
const auto relative_path = directory.substr(config.sdmc_path.size() - 1) + virtual_name;
|
||||
NCCHContainer ncch(std::make_shared<SDMCFile>(config.sdmc_path, relative_path, "rb"));
|
||||
return cia_builder->AddContent(id, ncch);
|
||||
if (!tmd.HasContentID(id)) {
|
||||
LOG_WARNING(Core, "Ignoring content {} (not in TMD)", directory + virtual_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_nand) {
|
||||
NCCHContainer ncch(
|
||||
std::make_shared<FileUtil::IOFile>(directory + virtual_name, "rb"));
|
||||
return cia_builder->AddContent(id, ncch);
|
||||
} else {
|
||||
const auto relative_path =
|
||||
directory.substr(config.sdmc_path.size() - 1) + virtual_name;
|
||||
NCCHContainer ncch(
|
||||
std::make_shared<SDMCFile>(config.sdmc_path, relative_path, "rb"));
|
||||
return cia_builder->AddContent(id, ncch);
|
||||
}
|
||||
};
|
||||
|
||||
if (!FileUtil::ForeachDirectoryEntry(nullptr, physical_path, DirectoryEntryCallback)) {
|
||||
@@ -633,12 +703,8 @@ void SDMCImporter::ListTitle(std::vector<ContentSpecifier>& out) const {
|
||||
|
||||
if (FileUtil::Exists(directory + virtual_name + "/content/")) {
|
||||
do {
|
||||
const auto content_path =
|
||||
fmt::format("/title/{:08x}/{}/content/", high_id, virtual_name);
|
||||
|
||||
TitleMetadata tmd;
|
||||
if (!LoadTMD(sdmc_path, content_path, *decryptor, tmd)) {
|
||||
LOG_WARNING(Core, "Could not load tmd from {}", content_path);
|
||||
if (!LoadTMD(type, id, tmd)) {
|
||||
out.push_back({type, id, FileUtil::Exists(citra_path + "content/"),
|
||||
FileUtil::GetDirectoryTreeSize(directory + virtual_name +
|
||||
"/content/")});
|
||||
@@ -646,7 +712,8 @@ void SDMCImporter::ListTitle(std::vector<ContentSpecifier>& out) const {
|
||||
}
|
||||
|
||||
const auto boot_content_path =
|
||||
fmt::format("{}{:08x}.app", content_path, tmd.GetBootContentID());
|
||||
fmt::format("/title/{:08x}/{}/content/{:08x}.app", high_id,
|
||||
virtual_name, tmd.GetBootContentID());
|
||||
NCCHContainer ncch(
|
||||
std::make_shared<SDMCFile>(sdmc_path, boot_content_path, "rb"));
|
||||
if (ncch.Load() != ResultStatus::Success) {
|
||||
@@ -692,39 +759,14 @@ void SDMCImporter::ListTitle(std::vector<ContentSpecifier>& out) const {
|
||||
ProcessDirectory(ContentType::DLC, 0x0004008c);
|
||||
}
|
||||
|
||||
static bool LoadNandTMD(const std::string& path, TitleMetadata& out) {
|
||||
|
||||
const auto tmd = FindTMD(path);
|
||||
if (tmd.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FileUtil::IOFile file(tmd, "rb");
|
||||
if (!file) {
|
||||
LOG_ERROR(Core, "Could not open file {}", tmd);
|
||||
return false;
|
||||
}
|
||||
if (file.GetSize() >= 1024 * 1024) { // Too big
|
||||
LOG_ERROR(Core, "TMD {} too big", tmd);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<u8> data(file.GetSize());
|
||||
if (file.ReadBytes(data.data(), data.size()) != data.size()) {
|
||||
LOG_ERROR(Core, "Could not read from {}", tmd);
|
||||
return false;
|
||||
}
|
||||
return out.Load(std::move(data)) == ResultStatus::Success;
|
||||
}
|
||||
|
||||
// TODO: Simplify.
|
||||
void SDMCImporter::ListNandTitle(std::vector<ContentSpecifier>& out) const {
|
||||
const auto ProcessDirectory = [&out,
|
||||
const auto ProcessDirectory = [this, &out,
|
||||
&system_titles_path = config.system_titles_path](u64 high_id) {
|
||||
FileUtil::ForeachDirectoryEntry(
|
||||
nullptr, fmt::format("{}{:08x}/", system_titles_path, high_id),
|
||||
[high_id, &out](u64* /*num_entries_out*/, const std::string& directory,
|
||||
const std::string& virtual_name) {
|
||||
[this, high_id, &out](u64* /*num_entries_out*/, const std::string& directory,
|
||||
const std::string& virtual_name) {
|
||||
if (!FileUtil::IsDirectory(directory + virtual_name + "/")) {
|
||||
return true;
|
||||
}
|
||||
@@ -742,8 +784,7 @@ void SDMCImporter::ListNandTitle(std::vector<ContentSpecifier>& out) const {
|
||||
if (FileUtil::Exists(content_path)) {
|
||||
do {
|
||||
TitleMetadata tmd;
|
||||
if (!LoadNandTMD(content_path, tmd)) {
|
||||
LOG_WARNING(Core, "Could not load tmd from {}", content_path);
|
||||
if (!LoadTMD(ContentType::SystemTitle, id, tmd)) {
|
||||
out.push_back({ContentType::SystemTitle, id,
|
||||
FileUtil::Exists(citra_path + "content/"),
|
||||
FileUtil::GetDirectoryTreeSize(content_path)});
|
||||
@@ -1085,6 +1126,7 @@ std::vector<Config> LoadPresetConfig(std::string mount_point) {
|
||||
LOAD_DATA(movable_sed_path, MOVABLE_SED);
|
||||
LOAD_DATA(bootrom_path, BOOTROM9);
|
||||
LOAD_DATA(certs_db_path, CERTS_DB);
|
||||
LOAD_DATA(nand_title_db_path, TITLE_DB);
|
||||
LOAD_DATA(safe_mode_firm_path, "firm/");
|
||||
LOAD_DATA(seed_db_path, SEED_DB);
|
||||
LOAD_DATA(secret_sector_path, SECRET_SECTOR);
|
||||
|
||||
+10
-2
@@ -15,6 +15,8 @@ namespace Core {
|
||||
|
||||
class CIABuilder;
|
||||
class SDMCDecryptor;
|
||||
class TitleDB;
|
||||
class TitleMetadata;
|
||||
|
||||
/**
|
||||
* Type of an importable content.
|
||||
@@ -71,7 +73,8 @@ struct Config {
|
||||
std::string movable_sed_path; ///< Path to movable.sed
|
||||
std::string bootrom_path; ///< Path to bootrom (boot9.bin) (Sysdata 0)
|
||||
|
||||
std::string certs_db_path; ///< Path to certs.db. Used while building CIA.
|
||||
std::string certs_db_path; ///< Path to certs.db. Used while building CIA.
|
||||
std::string nand_title_db_path; ///< Path to NAND title.db. Entirely optional.
|
||||
|
||||
// The following system files are optional for importing and are only copied so that Citra
|
||||
// will be able to decrypt imported encrypted ROMs.
|
||||
@@ -90,7 +93,7 @@ struct Config {
|
||||
};
|
||||
|
||||
// Version of the current dumper.
|
||||
constexpr int CurrentDumperVersion = 3;
|
||||
constexpr int CurrentDumperVersion = 4;
|
||||
|
||||
class SDMCFile;
|
||||
class NCCHContainer;
|
||||
@@ -190,6 +193,8 @@ private:
|
||||
void DeleteSystemArchive(u64 id) const;
|
||||
void DeleteSysdata(u64 id) const;
|
||||
|
||||
bool LoadTMD(ContentType type, u64 id, TitleMetadata& out) const;
|
||||
|
||||
bool is_good{};
|
||||
Config config;
|
||||
std::unique_ptr<SDMCDecryptor> decryptor;
|
||||
@@ -197,6 +202,9 @@ private:
|
||||
|
||||
// The NCCH used to dump CXIs.
|
||||
std::unique_ptr<NCCHContainer> dump_cxi_ncch;
|
||||
|
||||
std::unique_ptr<TitleDB> sdmc_title_db{};
|
||||
std::unique_ptr<TitleDB> nand_title_db{};
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,10 +12,6 @@
|
||||
|
||||
namespace Core {
|
||||
|
||||
constexpr u32 MakeMagic(char a, char b, char c, char d) {
|
||||
return a | b << 8 | c << 16 | d << 24;
|
||||
}
|
||||
|
||||
InnerFAT::~InnerFAT() = default;
|
||||
|
||||
bool InnerFAT::IsGood() const {
|
||||
|
||||
@@ -175,7 +175,7 @@ bool CIABuilder::AddContent(u16 content_id, NCCHContainer& ncch) {
|
||||
file->SetHashEnabled(false);
|
||||
|
||||
// DLCs do not have a meta
|
||||
if (tmd_chunk.index != TMDContentIndex::Main || (tmd.GetTitleID() & 0x0004008c'00000000)) {
|
||||
if (tmd_chunk.index != TMDContentIndex::Main || (tmd.GetTitleID() >> 32) == 0x0004008c) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,6 @@
|
||||
|
||||
namespace Core {
|
||||
|
||||
constexpr u32 MakeMagic(char a, char b, char c, char d) {
|
||||
return a | b << 8 | c << 16 | d << 24;
|
||||
}
|
||||
|
||||
static const int kMaxSections = 8; ///< Maximum number of sections (files) in an ExeFs
|
||||
static const int kBlockSize = 0x200; ///< Size of ExeFS blocks (in bytes)
|
||||
|
||||
|
||||
@@ -4,15 +4,12 @@
|
||||
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/ncch/smdh.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
constexpr u32 MakeMagic(char a, char b, char c, char d) {
|
||||
return a | b << 8 | c << 16 | d << 24;
|
||||
}
|
||||
|
||||
// 8x8 Z-Order coordinate from 2D coordinates
|
||||
static constexpr u32 MortonInterleave(u32 x, u32 y) {
|
||||
constexpr u32 xlut[] = {0x00, 0x01, 0x04, 0x05, 0x10, 0x11, 0x14, 0x15};
|
||||
|
||||
@@ -207,6 +207,13 @@ const TitleMetadata::ContentChunk& TitleMetadata::GetContentChunkByID(u32 conten
|
||||
return *it;
|
||||
}
|
||||
|
||||
bool TitleMetadata::HasContentID(u32 content_id) const {
|
||||
const auto it =
|
||||
std::find_if(tmd_chunks.begin(), tmd_chunks.end(),
|
||||
[content_id](const ContentChunk& chunk) { return chunk.id == content_id; });
|
||||
return it != tmd_chunks.end();
|
||||
}
|
||||
|
||||
void TitleMetadata::AddContentChunk(const ContentChunk& chunk) {
|
||||
tmd_chunks.push_back(chunk);
|
||||
}
|
||||
|
||||
@@ -127,6 +127,7 @@ public:
|
||||
|
||||
ContentChunk& GetContentChunkByID(u32 content_id);
|
||||
const ContentChunk& GetContentChunkByID(u32 content_id) const;
|
||||
bool HasContentID(u32 content_id) const;
|
||||
|
||||
void SetTitleID(u64 title_id);
|
||||
void SetTitleType(u32 type);
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright 2021 threeSD Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/title_db.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
TitleDB::TitleDB(std::vector<u8> data) {
|
||||
is_good = Init(std::move(data));
|
||||
}
|
||||
|
||||
bool TitleDB::IsGood() const {
|
||||
return is_good;
|
||||
}
|
||||
|
||||
// Note: Title DB is actually a degenerate version of the Inner FAT.
|
||||
// We are simplifying things as much as possible and not actually dealing with FAT nodes.
|
||||
bool TitleDB::Init(std::vector<u8> data) {
|
||||
// Read header, FAT header and filesystem information
|
||||
TRY(CheckedMemcpy(&header, data, 0, sizeof(header)), LOG_ERROR(Core, "File size is too small"));
|
||||
|
||||
if (header.db_magic != MakeMagic('N', 'A', 'N', 'D', 'T', 'D', 'B', 0) &&
|
||||
header.db_magic != MakeMagic('T', 'E', 'M', 'P', 'T', 'D', 'B', 0)) {
|
||||
|
||||
LOG_ERROR(Core, "File is invalid, decryption errors may have happened.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.fat_header.magic != MakeMagic('B', 'D', 'R', 'I') ||
|
||||
header.fat_header.version != 0x30000) {
|
||||
|
||||
LOG_ERROR(Core, "File is invalid, decryption errors may have happened.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.fat_header.image_block_size != 0x80 ||
|
||||
header.fs_info.data_region_block_size != 0x80) { // This simplifies things
|
||||
LOG_ERROR(Core, "Unexpected block size (this may be a bug)");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read file entry table
|
||||
file_entry_table.resize(header.fs_info.maximum_file_count + 1); // including head
|
||||
|
||||
auto file_entry_table_pos = TitleDBPreheaderSize + header.fs_info.data_region_offset +
|
||||
header.fs_info.file_entry_table.duplicate.block_index *
|
||||
static_cast<std::size_t>(header.fs_info.data_region_block_size);
|
||||
|
||||
TRY(CheckedMemcpy(file_entry_table.data(), data, file_entry_table_pos,
|
||||
file_entry_table.size() * sizeof(TitleDBFileEntryTableEntry)),
|
||||
LOG_ERROR(Core, "File is too small"));
|
||||
|
||||
// Read directory entry table for first file index
|
||||
auto first_file_index_pos =
|
||||
TitleDBPreheaderSize + header.fs_info.data_region_offset +
|
||||
header.fs_info.directory_entry_table.duplicate.block_index *
|
||||
static_cast<std::size_t>(header.fs_info.data_region_block_size) +
|
||||
0x20 /* sizeof TitleDB's directory entry (to skip head) */ +
|
||||
0x0C /* offset of first_file_index in directory entry of Title DB */;
|
||||
|
||||
if (data.size() < first_file_index_pos + 4) {
|
||||
LOG_ERROR(Core, "File size is too small");
|
||||
return false;
|
||||
}
|
||||
|
||||
const u32 first_file_index = *reinterpret_cast<u32_le*>(data.data() + first_file_index_pos);
|
||||
LOG_INFO(Core, "First file index is {}", first_file_index);
|
||||
u32 cur = first_file_index;
|
||||
while (cur != 0) {
|
||||
if (!LoadTitleInfo(data, cur)) {
|
||||
return false;
|
||||
}
|
||||
cur = file_entry_table[cur].next_sibling_index;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TitleDB::LoadTitleInfo(const std::vector<u8>& data, u32 index) {
|
||||
auto entry = file_entry_table[index];
|
||||
u32 block = entry.data_block_index;
|
||||
if (block == 0x80000000) { // empty file
|
||||
LOG_ERROR(Core, "Entry is an empty file");
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 file_size = entry.file_size;
|
||||
if (file_size != sizeof(TitleInfoEntry)) {
|
||||
LOG_ERROR(Core, "Entry has incorrect size {}", file_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
TitleInfoEntry title;
|
||||
const auto offset = TitleDBPreheaderSize + header.fs_info.data_region_offset +
|
||||
block * header.fs_info.data_region_block_size;
|
||||
TRY(CheckedMemcpy(&title, data, offset, sizeof(TitleInfoEntry)),
|
||||
LOG_ERROR(Core, "File size is too small"));
|
||||
|
||||
titles.emplace(entry.title_id, title);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2021 threeSD Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/inner_fat.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
struct TitleDBHeader {
|
||||
u64_le db_magic;
|
||||
INSERT_PADDING_BYTES(0x78);
|
||||
FATHeader fat_header;
|
||||
FileSystemInformation fs_info;
|
||||
};
|
||||
constexpr std::size_t TitleDBPreheaderSize = 0x80;
|
||||
static_assert(sizeof(TitleDBHeader) ==
|
||||
TitleDBPreheaderSize + sizeof(FATHeader) + sizeof(FileSystemInformation),
|
||||
"TitleDB preheader has incorrect size");
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct TitleDBFileEntryTableEntry {
|
||||
u32_le parent_directory_index;
|
||||
u64_le title_id;
|
||||
u32_le next_sibling_index;
|
||||
INSERT_PADDING_BYTES(4);
|
||||
u32_le data_block_index;
|
||||
u64_le file_size;
|
||||
INSERT_PADDING_BYTES(8);
|
||||
u32_le next_hash_bucket_entry;
|
||||
};
|
||||
static_assert(sizeof(TitleDBFileEntryTableEntry) == 0x2c,
|
||||
"TitleDBFileEntryTableEntry has incorrect size");
|
||||
#pragma pack(pop)
|
||||
|
||||
struct TitleInfoEntry {
|
||||
u64_le title_size;
|
||||
u32_le title_type;
|
||||
u32_le title_version;
|
||||
u32_le flags0;
|
||||
u32_le tmd_content_id;
|
||||
u32_le cmd_content_id;
|
||||
u32_le flags1;
|
||||
u32_le extdata_id_low;
|
||||
INSERT_PADDING_BYTES(4);
|
||||
u64_le flags2;
|
||||
std::array<u8, 0x10> product_code;
|
||||
INSERT_PADDING_BYTES(0x40);
|
||||
};
|
||||
static_assert(sizeof(TitleInfoEntry) == 0x80, "TitleInfoEntry has incorrect size");
|
||||
|
||||
class TitleDB {
|
||||
public:
|
||||
explicit TitleDB(std::vector<u8> data);
|
||||
bool IsGood() const;
|
||||
|
||||
std::unordered_map<u64, TitleInfoEntry> titles;
|
||||
|
||||
private:
|
||||
bool Init(std::vector<u8> data);
|
||||
bool LoadTitleInfo(const std::vector<u8>& data, u32 index);
|
||||
|
||||
bool is_good = false;
|
||||
TitleDBHeader header;
|
||||
std::vector<TitleDBFileEntryTableEntry> file_entry_table;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
@@ -843,19 +843,20 @@ void ImportDialog::StartBatchBuildingCIA() {
|
||||
to_import.begin(), to_import.end(), [](const Core::ContentSpecifier& specifier) {
|
||||
return specifier.type != Core::ContentType::Application &&
|
||||
specifier.type != Core::ContentType::Update &&
|
||||
specifier.type != Core::ContentType::DLC;
|
||||
specifier.type != Core::ContentType::DLC &&
|
||||
specifier.type != Core::ContentType::SystemTitle;
|
||||
});
|
||||
if (removed_iter == to_import.begin()) { // No Titles selected
|
||||
QMessageBox::critical(this, tr("threeSD"),
|
||||
tr("The contents selected are not supported.<br>You can only build "
|
||||
"CIAs from Applications, Updates and DLCs."));
|
||||
"CIAs from Applications, Updates, DLCs and System Titles."));
|
||||
return;
|
||||
}
|
||||
if (removed_iter != to_import.end()) { // Some non-Titles selected
|
||||
QMessageBox::warning(
|
||||
this, tr("threeSD"),
|
||||
tr("Some contents selected are not supported and will be ignored.<br>Only "
|
||||
"Applications, Updates and DLCs will be built as CIAs."));
|
||||
"Applications, Updates, DLCs and System Titles will be built as CIAs."));
|
||||
}
|
||||
|
||||
to_import.erase(removed_iter, to_import.end());
|
||||
|
||||
Reference in New Issue
Block a user