mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-06 16:49:09 +00:00
Add proper support for CIA title keys
1. Support for `ticket.db` 2. Support for `gm9/support/encTitleKeys.bin` 3. Set the title key and common key index when building standard CIA
This commit is contained in:
@@ -41,3 +41,5 @@
|
|||||||
#define AES_KEYS "aes_keys.txt"
|
#define AES_KEYS "aes_keys.txt"
|
||||||
#define CERTS_DB "certs.db"
|
#define CERTS_DB "certs.db"
|
||||||
#define TITLE_DB "title.db"
|
#define TITLE_DB "title.db"
|
||||||
|
#define TICKET_DB "ticket.db"
|
||||||
|
#define ENC_TITLE_KEYS_BIN "encTitleKeys.bin"
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ add_library(core STATIC
|
|||||||
savegame.h
|
savegame.h
|
||||||
title_db.cpp
|
title_db.cpp
|
||||||
title_db.h
|
title_db.h
|
||||||
|
title_keys_bin.cpp
|
||||||
|
title_keys_bin.h
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(core PRIVATE common cryptopp)
|
target_link_libraries(core PRIVATE common cryptopp)
|
||||||
|
|||||||
@@ -73,12 +73,7 @@ bool SDMCImporter::Init() {
|
|||||||
|
|
||||||
// Load NAND Title DB
|
// Load NAND Title DB
|
||||||
if (!config.nand_title_db_path.empty()) {
|
if (!config.nand_title_db_path.empty()) {
|
||||||
FileUtil::IOFile file(config.nand_title_db_path, "rb");
|
nand_title_db = std::make_unique<TitleDB>(config.nand_title_db_path);
|
||||||
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()) {
|
if (!nand_title_db || !nand_title_db->IsGood()) {
|
||||||
LOG_WARNING(Core, "NAND title.db invalid");
|
LOG_WARNING(Core, "NAND title.db invalid");
|
||||||
@@ -680,7 +675,7 @@ bool SDMCImporter::BuildCIA(const ContentSpecifier& specifier, std::string desti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ret = cia_builder->Init(destination, tmd, config.certs_db_path,
|
bool ret = cia_builder->Init(destination, tmd, config,
|
||||||
FileUtil::GetDirectoryTreeSize(physical_path), callback);
|
FileUtil::GetDirectoryTreeSize(physical_path), callback);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1186,6 +1181,7 @@ std::vector<Config> LoadPresetConfig(std::string mount_point) {
|
|||||||
LOAD_DATA(bootrom_path, BOOTROM9);
|
LOAD_DATA(bootrom_path, BOOTROM9);
|
||||||
LOAD_DATA(certs_db_path, CERTS_DB);
|
LOAD_DATA(certs_db_path, CERTS_DB);
|
||||||
LOAD_DATA(nand_title_db_path, TITLE_DB);
|
LOAD_DATA(nand_title_db_path, TITLE_DB);
|
||||||
|
LOAD_DATA(ticket_db_path, TICKET_DB);
|
||||||
LOAD_DATA(safe_mode_firm_path, "firm/");
|
LOAD_DATA(safe_mode_firm_path, "firm/");
|
||||||
LOAD_DATA(seed_db_path, SEED_DB);
|
LOAD_DATA(seed_db_path, SEED_DB);
|
||||||
LOAD_DATA(secret_sector_path, SECRET_SECTOR);
|
LOAD_DATA(secret_sector_path, SECRET_SECTOR);
|
||||||
@@ -1195,6 +1191,12 @@ std::vector<Config> LoadPresetConfig(std::string mount_point) {
|
|||||||
LOAD_DATA(nand_data_path, "data/");
|
LOAD_DATA(nand_data_path, "data/");
|
||||||
#undef LOAD_DATA
|
#undef LOAD_DATA
|
||||||
|
|
||||||
|
// encTitleKeys.bin
|
||||||
|
if (FileUtil::Exists(mount_point + "gm9/support/" ENC_TITLE_KEYS_BIN)) {
|
||||||
|
config_template.enc_title_keys_bin_path =
|
||||||
|
mount_point + "gm9/support/" ENC_TITLE_KEYS_BIN;
|
||||||
|
}
|
||||||
|
|
||||||
// Load version
|
// Load version
|
||||||
if (FileUtil::Exists(mount_point + "threeSD/version.txt")) {
|
if (FileUtil::Exists(mount_point + "threeSD/version.txt")) {
|
||||||
std::ifstream stream;
|
std::ifstream stream;
|
||||||
|
|||||||
+5
-2
@@ -72,9 +72,12 @@ struct Config {
|
|||||||
// Necessary system files keys are loaded from.
|
// Necessary system files keys are loaded from.
|
||||||
std::string movable_sed_path; ///< Path to movable.sed
|
std::string movable_sed_path; ///< Path to movable.sed
|
||||||
std::string bootrom_path; ///< Path to bootrom (boot9.bin) (Sysdata 0)
|
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.
|
// Optional, used while building CIA, but usually missing these files won't hinder CIA building.
|
||||||
std::string nand_title_db_path; ///< Path to NAND title.db. Entirely optional.
|
std::string nand_title_db_path; ///< Path to NAND title.db. Entirely optional.
|
||||||
|
std::string ticket_db_path; ///< Path to ticket.db. Entirely optional.
|
||||||
|
std::string enc_title_keys_bin_path; ///< Path to encTitleKeys.bin. Entireley optional.
|
||||||
|
|
||||||
// The following system files are optional for importing and are only copied so that Citra
|
// The following system files are optional for importing and are only copied so that Citra
|
||||||
// will be able to decrypt imported encrypted ROMs.
|
// will be able to decrypt imported encrypted ROMs.
|
||||||
|
|||||||
@@ -4,9 +4,12 @@
|
|||||||
|
|
||||||
#include <cryptopp/sha.h>
|
#include <cryptopp/sha.h>
|
||||||
#include "common/alignment.h"
|
#include "common/alignment.h"
|
||||||
|
#include "core/importer.h"
|
||||||
#include "core/ncch/cia_builder.h"
|
#include "core/ncch/cia_builder.h"
|
||||||
#include "core/ncch/ticket.h"
|
#include "core/ncch/ticket.h"
|
||||||
#include "core/ncch/title_metadata.h"
|
#include "core/ncch/title_metadata.h"
|
||||||
|
#include "core/title_db.h"
|
||||||
|
#include "core/title_keys_bin.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
@@ -43,9 +46,8 @@ private:
|
|||||||
CIABuilder::CIABuilder() = default;
|
CIABuilder::CIABuilder() = default;
|
||||||
CIABuilder::~CIABuilder() = default;
|
CIABuilder::~CIABuilder() = default;
|
||||||
|
|
||||||
bool CIABuilder::Init(const std::string& destination, TitleMetadata tmd_,
|
bool CIABuilder::Init(const std::string& destination, TitleMetadata tmd_, const Config& config,
|
||||||
const std::string& certs_db_path, std::size_t total_size_,
|
std::size_t total_size_, const Common::ProgressCallback& callback_) {
|
||||||
const Common::ProgressCallback& callback_) {
|
|
||||||
|
|
||||||
header = {};
|
header = {};
|
||||||
meta = {};
|
meta = {};
|
||||||
@@ -69,7 +71,7 @@ bool CIABuilder::Init(const std::string& destination, TitleMetadata tmd_,
|
|||||||
// Cert
|
// Cert
|
||||||
cert_offset = Common::AlignUp(header.header_size, CIA_ALIGNMENT);
|
cert_offset = Common::AlignUp(header.header_size, CIA_ALIGNMENT);
|
||||||
header.cert_size = CIA_CERT_SIZE;
|
header.cert_size = CIA_CERT_SIZE;
|
||||||
if (!WriteCert(certs_db_path)) {
|
if (!WriteCert(config.certs_db_path)) {
|
||||||
LOG_ERROR(Core, "Could not write cert to file {}", destination);
|
LOG_ERROR(Core, "Could not write cert to file {}", destination);
|
||||||
file.reset();
|
file.reset();
|
||||||
return false;
|
return false;
|
||||||
@@ -77,13 +79,7 @@ bool CIABuilder::Init(const std::string& destination, TitleMetadata tmd_,
|
|||||||
|
|
||||||
// Ticket
|
// Ticket
|
||||||
ticket_offset = Common::AlignUp(cert_offset + header.cert_size, CIA_ALIGNMENT);
|
ticket_offset = Common::AlignUp(cert_offset + header.cert_size, CIA_ALIGNMENT);
|
||||||
|
if (!WriteTicket(config.ticket_db_path, config.enc_title_keys_bin_path)) {
|
||||||
const auto fake_ticket = BuildFakeTicket(tmd.GetTitleID()).GetData();
|
|
||||||
header.tik_size = fake_ticket.size();
|
|
||||||
|
|
||||||
file->Seek(ticket_offset, SEEK_SET);
|
|
||||||
if (file->WriteBytes(fake_ticket.data(), fake_ticket.size()) != fake_ticket.size()) {
|
|
||||||
LOG_ERROR(Core, "Could not write ticket to file {}", destination);
|
|
||||||
file.reset();
|
file.reset();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -144,6 +140,42 @@ bool CIABuilder::WriteCert(const std::string& certs_db_path) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CIABuilder::WriteTicket(const std::string& ticket_db_path,
|
||||||
|
const std::string& enc_title_keys_bin_path) {
|
||||||
|
const auto title_id = tmd.GetTitleID();
|
||||||
|
Ticket ticket = BuildFakeTicket(title_id);
|
||||||
|
|
||||||
|
// Fill in common_key_index and title_key from either ticket.db (installed tickets)
|
||||||
|
// or GM9 support files (encTitleKeys.bin) found on the SD card
|
||||||
|
if (TicketDB ticket_db(ticket_db_path);
|
||||||
|
ticket_db.IsGood() && ticket_db.tickets.count(title_id)) { // ticket.db
|
||||||
|
|
||||||
|
const auto& legit_ticket = ticket_db.tickets.at(title_id);
|
||||||
|
ticket.body.common_key_index = legit_ticket.body.common_key_index;
|
||||||
|
ticket.body.title_key = legit_ticket.body.title_key;
|
||||||
|
|
||||||
|
} else if (TitleKeysBin enc_title_keys(enc_title_keys_bin_path);
|
||||||
|
enc_title_keys.IsGood() && enc_title_keys.entries.count(title_id)) { // support files
|
||||||
|
|
||||||
|
const auto& entry = enc_title_keys.entries.at(title_id);
|
||||||
|
ticket.body.common_key_index = entry.common_key_index;
|
||||||
|
ticket.body.title_key = entry.title_key;
|
||||||
|
} else {
|
||||||
|
LOG_WARNING(Core, "Could not find title key for {:016x}", title_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ticket_data = ticket.GetData();
|
||||||
|
header.tik_size = ticket_data.size();
|
||||||
|
|
||||||
|
file->Seek(ticket_offset, SEEK_SET);
|
||||||
|
if (file->WriteBytes(ticket_data.data(), ticket_data.size()) != ticket_data.size()) {
|
||||||
|
LOG_ERROR(Core, "Could not write ticket");
|
||||||
|
file.reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool CIABuilder::AddContent(u16 content_id, NCCHContainer& ncch) {
|
bool CIABuilder::AddContent(u16 content_id, NCCHContainer& ncch) {
|
||||||
file->Seek(written, SEEK_SET); // To enforce alignment
|
file->Seek(written, SEEK_SET); // To enforce alignment
|
||||||
file->SetHashEnabled(true);
|
file->SetHashEnabled(true);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ constexpr std::size_t CIA_HEADER_SIZE = 0x2020;
|
|||||||
constexpr std::size_t CIA_CERT_SIZE = 0xA00;
|
constexpr std::size_t CIA_CERT_SIZE = 0xA00;
|
||||||
constexpr std::size_t CIA_METADATA_SIZE = 0x3AC0;
|
constexpr std::size_t CIA_METADATA_SIZE = 0x3AC0;
|
||||||
|
|
||||||
|
class Config;
|
||||||
class HashedFile;
|
class HashedFile;
|
||||||
|
|
||||||
class CIABuilder {
|
class CIABuilder {
|
||||||
@@ -33,7 +34,7 @@ public:
|
|||||||
* Initializes the building of the CIA.
|
* Initializes the building of the CIA.
|
||||||
* @return true on success, false otherwise
|
* @return true on success, false otherwise
|
||||||
*/
|
*/
|
||||||
bool Init(const std::string& destination, TitleMetadata tmd, const std::string& certs_db_path,
|
bool Init(const std::string& destination, TitleMetadata tmd, const Config& config,
|
||||||
std::size_t total_size, const Common::ProgressCallback& callback);
|
std::size_t total_size, const Common::ProgressCallback& callback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -90,6 +91,7 @@ private:
|
|||||||
static_assert(sizeof(Metadata) == CIA_METADATA_SIZE, "CIA Metadata structure size is wrong");
|
static_assert(sizeof(Metadata) == CIA_METADATA_SIZE, "CIA Metadata structure size is wrong");
|
||||||
|
|
||||||
bool WriteCert(const std::string& certs_db_path);
|
bool WriteCert(const std::string& certs_db_path);
|
||||||
|
bool WriteTicket(const std::string& ticket_db_path, const std::string& enc_title_keys_bin_path);
|
||||||
|
|
||||||
Header header{};
|
Header header{};
|
||||||
Metadata meta{};
|
Metadata meta{};
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ Ticket BuildFakeTicket(u64 title_id) {
|
|||||||
Ticket ticket{};
|
Ticket ticket{};
|
||||||
ticket.signature_type = 0x10004; // RSA_2048 SHA256
|
ticket.signature_type = 0x10004; // RSA_2048 SHA256
|
||||||
|
|
||||||
|
ticket.signature.resize(GetSignatureSize(ticket.signature_type));
|
||||||
std::memset(ticket.signature.data(), 0xFF, ticket.signature.size());
|
std::memset(ticket.signature.data(), 0xFF, ticket.signature.size());
|
||||||
|
|
||||||
auto& body = ticket.body;
|
auto& body = ticket.body;
|
||||||
@@ -80,18 +81,8 @@ Ticket BuildFakeTicket(u64 title_id) {
|
|||||||
body.common_key_index = 0x00;
|
body.common_key_index = 0x00;
|
||||||
body.audit = 0x01;
|
body.audit = 0x01;
|
||||||
std::memcpy(body.content_index.data(), TicketContentIndex.data(), TicketContentIndex.size());
|
std::memcpy(body.content_index.data(), TicketContentIndex.data(), TicketContentIndex.size());
|
||||||
// GodMode9 by default sets all remaining 0x80 bytes to 0xFF, but legit tickets only set 0x20
|
// GodMode9 by default sets all remaining 0x80 bytes to 0xFF
|
||||||
std::memset(body.content_index.data() + TicketContentIndex.size(), 0xFF, 0x20);
|
std::memset(body.content_index.data() + TicketContentIndex.size(), 0xFF, 0x80);
|
||||||
return ticket;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ticket BuildStandardTicket(u64 title_id, Ticket legit_ticket) {
|
|
||||||
Ticket ticket = BuildFakeTicket(title_id);
|
|
||||||
|
|
||||||
// Put in the title key from the legit ticket
|
|
||||||
ticket.body.title_key.swap(legit_ticket.body.title_key);
|
|
||||||
ticket.body.common_key_index = legit_ticket.body.common_key_index;
|
|
||||||
|
|
||||||
return ticket;
|
return ticket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,5 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ticket BuildFakeTicket(u64 title_id);
|
Ticket BuildFakeTicket(u64 title_id);
|
||||||
Ticket BuildStandardTicket(u64 title_id, Ticket legit_ticket);
|
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
// 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 "common/file_util.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "core/data_container.h"
|
||||||
#include "core/title_db.h"
|
#include "core/title_db.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
@@ -11,6 +13,17 @@ TitleDB::TitleDB(std::vector<u8> data) {
|
|||||||
is_good = Init(std::move(data));
|
is_good = Init(std::move(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TitleDB::TitleDB(const std::string& path) {
|
||||||
|
FileUtil::IOFile file(path, "rb");
|
||||||
|
DataContainer container(file.GetData());
|
||||||
|
std::vector<std::vector<u8>> data;
|
||||||
|
if (container.IsGood() && container.GetIVFCLevel4Data(data)) {
|
||||||
|
is_good = Init(std::move(data[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TitleDB::~TitleDB() = default;
|
||||||
|
|
||||||
bool TitleDB::IsGood() const {
|
bool TitleDB::IsGood() const {
|
||||||
return is_good;
|
return is_good;
|
||||||
}
|
}
|
||||||
@@ -63,4 +76,67 @@ bool TitleDB::LoadTitleInfo(u32 index) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TicketDB::TicketDB(std::vector<u8> data) {
|
||||||
|
is_good = Init(std::move(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
TicketDB::TicketDB(const std::string& path) {
|
||||||
|
FileUtil::IOFile file(path, "rb");
|
||||||
|
DataContainer container(file.GetData());
|
||||||
|
std::vector<std::vector<u8>> data;
|
||||||
|
if (container.IsGood() && container.GetIVFCLevel4Data(data)) {
|
||||||
|
is_good = Init(std::move(data[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TicketDB::~TicketDB() = default;
|
||||||
|
|
||||||
|
bool TicketDB::IsGood() const {
|
||||||
|
return is_good;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TicketDB::Init(std::vector<u8> data) {
|
||||||
|
if (!InnerFAT_TicketDB::Init({std::move(data)})) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 cur = directory_entry_table[1].first_file_index;
|
||||||
|
while (cur != 0) {
|
||||||
|
if (!LoadTicket(cur)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
cur = file_entry_table[cur].next_sibling_index;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TicketDB::CheckMagic() const {
|
||||||
|
if (header.pre_header.db_magic != MakeMagic('T', 'I', 'C', 'K')) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TicketDB::LoadTicket(u32 index) {
|
||||||
|
std::vector<u8> data;
|
||||||
|
if (!GetFileData(data, index)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ticket ticket;
|
||||||
|
if (!ticket.Load(std::move(data), 8)) { // there is a 8-byte header
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
tickets.emplace(file_entry_table[index].title_id, ticket);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
#include "core/inner_fat.hpp"
|
#include "core/inner_fat.hpp"
|
||||||
|
#include "core/ncch/ticket.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
@@ -69,6 +70,9 @@ using InnerFAT_TitleDB = InnerFAT<TitleDB, TitleDBPreheader, TitleDBDirectoryEnt
|
|||||||
class TitleDB final : public InnerFAT_TitleDB {
|
class TitleDB final : public InnerFAT_TitleDB {
|
||||||
public:
|
public:
|
||||||
explicit TitleDB(std::vector<u8> data);
|
explicit TitleDB(std::vector<u8> data);
|
||||||
|
explicit TitleDB(const std::string& path);
|
||||||
|
~TitleDB();
|
||||||
|
|
||||||
bool IsGood() const;
|
bool IsGood() const;
|
||||||
|
|
||||||
std::unordered_map<u64, TitleInfoEntry> titles;
|
std::unordered_map<u64, TitleInfoEntry> titles;
|
||||||
@@ -83,4 +87,32 @@ private:
|
|||||||
friend InnerFAT_TitleDB;
|
friend InnerFAT_TitleDB;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct TicketDBPreheader {
|
||||||
|
u32_le db_magic;
|
||||||
|
INSERT_PADDING_BYTES(0x0C);
|
||||||
|
};
|
||||||
|
static_assert(sizeof(TicketDBPreheader) == 0x10, "TicketDB pre-header has incorrect size");
|
||||||
|
|
||||||
|
class TicketDB;
|
||||||
|
using InnerFAT_TicketDB = InnerFAT<TicketDB, TicketDBPreheader, TitleDBDirectoryEntryTableEntry,
|
||||||
|
TitleDBFileEntryTableEntry>;
|
||||||
|
class TicketDB final : public InnerFAT_TicketDB {
|
||||||
|
public:
|
||||||
|
explicit TicketDB(std::vector<u8> data);
|
||||||
|
explicit TicketDB(const std::string& path);
|
||||||
|
~TicketDB();
|
||||||
|
|
||||||
|
bool IsGood() const;
|
||||||
|
|
||||||
|
std::unordered_map<u64, Ticket> tickets;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool Init(std::vector<u8> data);
|
||||||
|
bool CheckMagic() const;
|
||||||
|
bool LoadTicket(u32 index);
|
||||||
|
|
||||||
|
bool is_good = false;
|
||||||
|
friend InnerFAT_TicketDB;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// Copyright 2021 threeSD Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "common/file_util.h"
|
||||||
|
#include "core/title_keys_bin.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
TitleKeysBin::TitleKeysBin(const std::string& path) {
|
||||||
|
is_good = Load(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
TitleKeysBin::~TitleKeysBin() = default;
|
||||||
|
|
||||||
|
bool TitleKeysBin::IsGood() const {
|
||||||
|
return is_good;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TitleKeysBin::Load(const std::string& path) {
|
||||||
|
FileUtil::IOFile file(path, "rb");
|
||||||
|
if (!file) {
|
||||||
|
LOG_ERROR(Core, "Could not open file {}", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TitleKeysBinHeader header;
|
||||||
|
if (file.ReadBytes(&header, sizeof(header)) != sizeof(header)) {
|
||||||
|
LOG_ERROR(Core, "Could not read header from {}", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < header.num_entries; ++i) {
|
||||||
|
TitleKeysBinEntry entry;
|
||||||
|
if (file.ReadBytes(&entry, sizeof(entry)) != sizeof(entry)) {
|
||||||
|
LOG_ERROR(Core, "Could not read entry {} from {}", i, path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
entries.emplace(entry.title_id, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.Tell() != file.GetSize()) {
|
||||||
|
LOG_ERROR(Core, "File {} has redundant data, may be corrupted");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Core
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// 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 "common/common_funcs.h"
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/swap.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
struct TitleKeysBinHeader {
|
||||||
|
u32_le num_entries;
|
||||||
|
INSERT_PADDING_BYTES(12);
|
||||||
|
};
|
||||||
|
static_assert(sizeof(TitleKeysBinHeader) == 16);
|
||||||
|
|
||||||
|
struct TitleKeysBinEntry {
|
||||||
|
u32_be common_key_index;
|
||||||
|
INSERT_PADDING_BYTES(4);
|
||||||
|
u64_be title_id;
|
||||||
|
std::array<u8, 16> title_key;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(TitleKeysBinEntry) == 32);
|
||||||
|
|
||||||
|
// GM9 support files encTitleKeys.bin and decTitleKeys.bin.
|
||||||
|
class TitleKeysBin {
|
||||||
|
public:
|
||||||
|
explicit TitleKeysBin(const std::string& path);
|
||||||
|
~TitleKeysBin();
|
||||||
|
|
||||||
|
bool IsGood() const;
|
||||||
|
|
||||||
|
std::unordered_map<u64, TitleKeysBinEntry> entries;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool Load(const std::string& path);
|
||||||
|
bool is_good = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Core
|
||||||
Reference in New Issue
Block a user