mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-04 00:38:47 +00:00
lots of important fixes
- DPFS container is fixed - SD Savegame is fixed - added slot0x25KeyX load - added regex for titile ID - added aes_keys.txt import - sd savegame listing is fixed (uninitialized won't be listed any more) - error handling is improved (removed asserts and replaced with return values) - UI is now functional - config is now checked in main
This commit is contained in:
+33
-14
@@ -25,7 +25,7 @@ u8 DPFSContainer::GetBit(u8 level, u8 selector, u64 index) const {
|
||||
return (data[(descriptor.levels[level].offset + selector * descriptor.levels[level].size) / 4 +
|
||||
index / 32] >>
|
||||
(31 - (index % 32))) &
|
||||
1;
|
||||
static_cast<u32_le>(1);
|
||||
}
|
||||
|
||||
u8 DPFSContainer::GetByte(u8 level, u8 selector, u64 index) const {
|
||||
@@ -38,9 +38,9 @@ u8 DPFSContainer::GetByte(u8 level, u8 selector, u64 index) const {
|
||||
std::vector<u8> DPFSContainer::GetLevel3Data() const {
|
||||
std::vector<u8> level3_data(descriptor.levels[2].size);
|
||||
for (std::size_t i = 0; i < level3_data.size(); i++) {
|
||||
auto level2_bit_index = i / std::pow(2, descriptor.levels[1].block_size);
|
||||
auto level2_bit_index = i / std::pow(2, descriptor.levels[2].block_size);
|
||||
auto level1_bit_index =
|
||||
(level2_bit_index / 8) / std::pow(2, descriptor.levels[0].block_size);
|
||||
(level2_bit_index / 8) / std::pow(2, descriptor.levels[1].block_size);
|
||||
auto level2_selector = GetBit(0, level1_selector, level1_bit_index);
|
||||
auto level3_selector = GetBit(1, level2_selector, level2_bit_index);
|
||||
level3_data[i] = GetByte(2, level3_selector, i);
|
||||
@@ -49,27 +49,38 @@ std::vector<u8> DPFSContainer::GetLevel3Data() const {
|
||||
}
|
||||
|
||||
DataContainer::DataContainer(std::vector<u8> data_) : data(std::move(data_)) {
|
||||
ASSERT_MSG(data.size() >= 0x200, "Data size is too small");
|
||||
if (data.size() < 0x200) {
|
||||
LOG_ERROR(Core, "Data size {:X} is too small", data.size());
|
||||
is_good = false;
|
||||
return;
|
||||
}
|
||||
|
||||
u32_le magic;
|
||||
std::memcpy(&magic, data.data() + 0x100, sizeof(u32_le));
|
||||
if (magic == MakeMagic('D', 'I', 'S', 'A')) {
|
||||
InitAsDISA();
|
||||
is_good = InitAsDISA();
|
||||
} else if (magic == MakeMagic('D', 'I', 'F', 'F')) {
|
||||
InitAsDIFF();
|
||||
is_good = InitAsDIFF();
|
||||
} else {
|
||||
// TODO: Add error handling
|
||||
UNREACHABLE_MSG("Unknown magic");
|
||||
LOG_ERROR(Core, "Unknown magic 0x{:08x}", magic);
|
||||
is_good = false;
|
||||
}
|
||||
}
|
||||
|
||||
DataContainer::~DataContainer() = default;
|
||||
|
||||
void DataContainer::InitAsDISA() {
|
||||
bool DataContainer::IsGood() const {
|
||||
return is_good;
|
||||
}
|
||||
|
||||
bool DataContainer::InitAsDISA() {
|
||||
DISAHeader header;
|
||||
std::memcpy(&header, data.data() + 0x100, sizeof(header));
|
||||
|
||||
ASSERT_MSG(header.version == 0x40000, "DISA Version is not correct");
|
||||
if (header.version != 0x40000) {
|
||||
LOG_ERROR(Core, "DISA Version {:x} is not correct", header.version);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.active_partition_table == 0) { // primary
|
||||
partition_table_offset = header.primary_partition_table_offset;
|
||||
@@ -86,13 +97,18 @@ void DataContainer::InitAsDISA() {
|
||||
partition_descriptors = {header.partition_descriptors[0]};
|
||||
partitions = {header.partitions[0]};
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DataContainer::InitAsDIFF() {
|
||||
bool DataContainer::InitAsDIFF() {
|
||||
DIFFHeader header;
|
||||
std::memcpy(&header, data.data() + 0x100, sizeof(header));
|
||||
|
||||
ASSERT_MSG(header.version == 0x30000, "DIFF Version is not correct");
|
||||
if (header.version != 0x30000) {
|
||||
LOG_ERROR(Core, "DIFF Version {:x} is not correct", header.version);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.active_partition_table == 0) { // primary
|
||||
partition_table_offset = header.primary_partition_table_offset;
|
||||
@@ -103,6 +119,8 @@ void DataContainer::InitAsDIFF() {
|
||||
partition_count = 1;
|
||||
partition_descriptors = {{/* offset */ 0, /* size */ header.partition_table_size}};
|
||||
partitions = {header.partition_A};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<u8> DataContainer::GetPartitionData(u8 index) const {
|
||||
@@ -132,11 +150,12 @@ std::vector<u8> DataContainer::GetPartitionData(u8 index) const {
|
||||
std::memcpy(&dpfs_descriptor, data.data() + partition_descriptor_offset + difi.dpfs.offset,
|
||||
sizeof(dpfs_descriptor));
|
||||
|
||||
std::vector<u32> partition_data(partitions[index].size / 4);
|
||||
std::vector<u32_le> partition_data(partitions[index].size / 4);
|
||||
std::memcpy(partition_data.data(), data.data() + partitions[index].offset,
|
||||
partitions[index].size);
|
||||
|
||||
DPFSContainer dpfs_container(dpfs_descriptor, difi.dpfs_level1_selector, partition_data);
|
||||
DPFSContainer dpfs_container(dpfs_descriptor, difi.dpfs_level1_selector,
|
||||
std::move(partition_data));
|
||||
auto ivfc_data = dpfs_container.GetLevel3Data();
|
||||
|
||||
std::vector<u8> result(ivfc_data.data() + ivfc_descriptor.levels[3].offset,
|
||||
|
||||
@@ -116,13 +116,16 @@ public:
|
||||
/// Unwraps the whole container, returning the data in IVFC Level 4 of all partitions.
|
||||
std::vector<std::vector<u8>> GetIVFCLevel4Data() const;
|
||||
|
||||
bool IsGood() const;
|
||||
|
||||
private:
|
||||
void InitAsDISA();
|
||||
void InitAsDIFF();
|
||||
bool InitAsDISA();
|
||||
bool InitAsDIFF();
|
||||
|
||||
/// Unwraps the whole container, returning the data in IVFC Level 4 of a partition.
|
||||
std::vector<u8> GetPartitionData(u8 index) const;
|
||||
|
||||
bool is_good = false;
|
||||
std::vector<u8> data;
|
||||
u32 partition_count;
|
||||
u64_le partition_table_offset;
|
||||
|
||||
@@ -54,6 +54,10 @@ bool SDMCDecryptor::DecryptAndWriteFile(const std::string& source,
|
||||
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption aes;
|
||||
aes.SetKeyWithIV(key.data(), key.size(), ctr.data());
|
||||
|
||||
if (!FileUtil::CreateFullPath(destination)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string absolute_source = root_folder + source;
|
||||
try {
|
||||
CryptoPP::FileSource(absolute_source.c_str(), true,
|
||||
|
||||
+70
-9
@@ -6,6 +6,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "core/data_container.h"
|
||||
#include "core/decryptor.h"
|
||||
#include "core/importer.h"
|
||||
#include "core/inner_fat.h"
|
||||
@@ -89,7 +90,13 @@ bool SDMCImporter::ImportTitle(u64 id) {
|
||||
|
||||
bool SDMCImporter::ImportSavegame(u64 id) {
|
||||
const auto path = fmt::format("title/{:08x}/{:08x}/data/", (id >> 32), (id & 0xFFFFFFFF));
|
||||
SDSavegame save(decryptor->DecryptFile(fmt::format("/{}00000001.sav", path)));
|
||||
|
||||
DataContainer container(decryptor->DecryptFile(fmt::format("/{}00000001.sav", path)));
|
||||
if (!container.IsGood()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDSavegame save(std::move(container.GetIVFCLevel4Data()));
|
||||
if (!save.IsGood()) {
|
||||
return false;
|
||||
}
|
||||
@@ -116,6 +123,9 @@ bool SDMCImporter::ImportSysdata(u64 id) {
|
||||
case 0: { // boot9.bin
|
||||
const auto target_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + BOOTROM9;
|
||||
LOG_INFO(Core, "Copying {} from {} to {}", BOOTROM9, config.bootrom_path, target_path);
|
||||
if (!FileUtil::CreateFullPath(target_path)) {
|
||||
return false;
|
||||
}
|
||||
return FileUtil::Copy(config.bootrom_path, target_path);
|
||||
}
|
||||
case 1: { // safe mode firm
|
||||
@@ -129,22 +139,31 @@ bool SDMCImporter::ImportSysdata(u64 id) {
|
||||
real_path = config.safe_mode_firm_path + "old/";
|
||||
}
|
||||
return FileUtil::ForeachDirectoryEntry(
|
||||
nullptr, config.safe_mode_firm_path,
|
||||
nullptr, real_path,
|
||||
[is_new_3ds](u64* /*num_entries_out*/, const std::string& directory,
|
||||
const std::string& virtual_name) {
|
||||
if (FileUtil::IsDirectory(directory + virtual_name)) {
|
||||
return true;
|
||||
}
|
||||
return FileUtil::Copy(
|
||||
directory + virtual_name,
|
||||
|
||||
const auto target_path =
|
||||
fmt::format("{}00000000000000000000000000000000/title/00040138/{}/content/{}",
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir),
|
||||
(is_new_3ds ? "20000003" : "00000003"), virtual_name));
|
||||
(is_new_3ds ? "20000003" : "00000003"), virtual_name);
|
||||
|
||||
if (!FileUtil::CreateFullPath(target_path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return FileUtil::Copy(directory + virtual_name, target_path);
|
||||
});
|
||||
}
|
||||
case 2: { // seed db
|
||||
const auto target_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + SEED_DB;
|
||||
LOG_INFO(Core, "Copying {} from {} to {}", SEED_DB, config.seed_db_path, target_path);
|
||||
if (!FileUtil::CreateFullPath(target_path)) {
|
||||
return false;
|
||||
}
|
||||
return FileUtil::Copy(config.seed_db_path, target_path);
|
||||
}
|
||||
case 3: { // secret sector
|
||||
@@ -152,8 +171,23 @@ bool SDMCImporter::ImportSysdata(u64 id) {
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + SECRET_SECTOR;
|
||||
LOG_INFO(Core, "Copying {} from {} to {}", SECRET_SECTOR, config.secret_sector_path,
|
||||
target_path);
|
||||
if (!FileUtil::CreateFullPath(target_path)) {
|
||||
return false;
|
||||
}
|
||||
return FileUtil::Copy(config.secret_sector_path, target_path);
|
||||
}
|
||||
case 4: { // aes_keys.txt
|
||||
const auto target_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + AES_KEYS;
|
||||
if (!FileUtil::CreateFullPath(target_path)) {
|
||||
return false;
|
||||
}
|
||||
FileUtil::IOFile file(target_path, "w");
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
file.WriteString("slot0x25KeyX=" + Key::KeyToString(Key::GetKeyX(0x25)) + "\n");
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE_MSG("Unexpected sysdata id {}", id);
|
||||
}
|
||||
@@ -167,17 +201,25 @@ std::vector<ContentSpecifier> SDMCImporter::ListContent() const {
|
||||
return content_list;
|
||||
}
|
||||
|
||||
// Regex for half Title IDs
|
||||
static const std::regex title_regex{"[0-9a-f]{8}"};
|
||||
|
||||
void SDMCImporter::ListTitle(std::vector<ContentSpecifier>& out) const {
|
||||
const auto ProcessDirectory = [&out, &sdmc_path = config.sdmc_path](ContentType type,
|
||||
u64 high_id) {
|
||||
const auto ProcessDirectory = [& decryptor = this->decryptor, &out,
|
||||
&sdmc_path = config.sdmc_path](ContentType type, u64 high_id) {
|
||||
FileUtil::ForeachDirectoryEntry(
|
||||
nullptr, fmt::format("{}title/{:08x}/", sdmc_path, high_id),
|
||||
[type, high_id, &out](u64* /*num_entries_out*/, const std::string& directory,
|
||||
const std::string& virtual_name) {
|
||||
[&decryptor, type, high_id, &out](u64* /*num_entries_out*/,
|
||||
const std::string& directory,
|
||||
const std::string& virtual_name) {
|
||||
if (!FileUtil::IsDirectory(directory + virtual_name + "/")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!std::regex_match(virtual_name, title_regex)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const u64 id = (high_id << 32) + std::stoull(virtual_name, nullptr, 16);
|
||||
const auto citra_path = fmt::format(
|
||||
"{}Nintendo "
|
||||
@@ -194,6 +236,15 @@ void SDMCImporter::ListTitle(std::vector<ContentSpecifier>& out) const {
|
||||
return true;
|
||||
}
|
||||
if (FileUtil::Exists(directory + virtual_name + "/data/")) {
|
||||
// Savegames can be uninitialized.
|
||||
// TODO: Is there a better way of checking this other than performing the
|
||||
// decryption? (Very costy)
|
||||
DataContainer container(decryptor->DecryptFile(
|
||||
fmt::format("/title/{:08x}/{}/data/00000001.sav", high_id, virtual_name)));
|
||||
if (!container.IsGood()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
out.push_back(
|
||||
{ContentType::Savegame, id, FileUtil::Exists(citra_path + "data/"),
|
||||
FileUtil::GetDirectoryTreeSize(directory + virtual_name + "/data/")});
|
||||
@@ -216,6 +267,10 @@ void SDMCImporter::ListExtdata(std::vector<ContentSpecifier>& out) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!std::regex_match(virtual_name, title_regex)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const u64 id = std::stoull(virtual_name, nullptr, 16);
|
||||
const auto citra_path =
|
||||
fmt::format("{}Nintendo "
|
||||
@@ -240,6 +295,12 @@ void SDMCImporter::ListSysdata(std::vector<ContentSpecifier>& out) const {
|
||||
CHECK_CONTENT(0, config.bootrom_path, sysdata_path + BOOTROM9, BOOTROM9);
|
||||
CHECK_CONTENT(2, config.seed_db_path, sysdata_path + SEED_DB, SEED_DB);
|
||||
CHECK_CONTENT(3, config.secret_sector_path, sysdata_path + SECRET_SECTOR, SECRET_SECTOR);
|
||||
if (!config.bootrom_path.empty()) {
|
||||
// 47 bytes = "slot0x26KeyX=<32>\r\n" is only for Windows,
|
||||
// but it's maximum_size so probably okay
|
||||
out.push_back(
|
||||
{ContentType::Sysdata, 4, FileUtil::Exists(sysdata_path + AES_KEYS), 47, AES_KEYS});
|
||||
}
|
||||
}
|
||||
|
||||
#undef CHECK_CONTENT
|
||||
|
||||
@@ -55,6 +55,8 @@ struct Config {
|
||||
std::string safe_mode_firm_path; ///< Path to safe mode firm (A folder) (Sysdata 1)
|
||||
std::string seed_db_path; ///< Path to seeddb.bin (Sysdata 2)
|
||||
std::string secret_sector_path; ///< Path to secret sector (New3DS only) (Sysdata 3)
|
||||
|
||||
// Sysdata 4 is aes_keys.db (slot0x25KeyX)
|
||||
};
|
||||
|
||||
class SDMCImporter {
|
||||
|
||||
+31
-15
@@ -74,11 +74,17 @@ bool InnerFAT::WriteMetadata(const std::string& path) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
SDSavegame::SDSavegame(std::vector<u8> data_) : duplicate_data(true), data(std::move(data_)) {}
|
||||
|
||||
SDSavegame::SDSavegame(std::vector<u8> partitionA_, std::vector<u8> partitionB_)
|
||||
: duplicate_data(false), partitionA(std::move(partitionA_)),
|
||||
partitionB(std::move(partitionB_)) {
|
||||
SDSavegame::SDSavegame(std::vector<std::vector<u8>> partitions) {
|
||||
if (partitions.size() == 1) {
|
||||
duplicate_data = true;
|
||||
data = std::move(partitions[0]);
|
||||
} else if (partitions.size() == 2) {
|
||||
duplicate_data = false;
|
||||
partitionA = std::move(partitions[0]);
|
||||
partitionB = std::move(partitions[1]);
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
is_good = Init();
|
||||
}
|
||||
|
||||
@@ -151,9 +157,9 @@ bool SDSavegame::ExtractFile(const std::string& path, std::size_t index) const {
|
||||
std::array<char, 17> name_data = {}; // Append a null terminator
|
||||
std::memcpy(name_data.data(), entry.name.data(), entry.name.size());
|
||||
|
||||
std::string name{name_data.data()};
|
||||
std::string name = name_data.data();
|
||||
FileUtil::IOFile file(path + name, "wb");
|
||||
if (!file.IsOpen()) {
|
||||
if (!file) {
|
||||
LOG_ERROR(Core, "Could not open file {}", path + name);
|
||||
return false;
|
||||
}
|
||||
@@ -163,6 +169,7 @@ bool SDSavegame::ExtractFile(const std::string& path, std::size_t index) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
u64 file_size = entry.file_size;
|
||||
while (true) {
|
||||
// Entry index is block index + 1
|
||||
auto block_data = fat[block + 1];
|
||||
@@ -172,14 +179,16 @@ bool SDSavegame::ExtractFile(const std::string& path, std::size_t index) const {
|
||||
last_block = fat[block + 2].v.index - 1;
|
||||
}
|
||||
|
||||
std::size_t size = fs_info.data_region_block_size * (last_block - block + 1);
|
||||
if (file.WriteBytes(data_region.data() + fs_info.data_region_block_size * block, size) !=
|
||||
size) {
|
||||
const std::size_t size = fs_info.data_region_block_size * (last_block - block + 1);
|
||||
const std::size_t to_write = std::min(file_size, size);
|
||||
if (file.WriteBytes(data_region.data() + fs_info.data_region_block_size * block,
|
||||
to_write) != to_write) {
|
||||
LOG_ERROR(Core, "Write data failed (file: {})", path + name);
|
||||
return false;
|
||||
}
|
||||
file_size -= to_write;
|
||||
|
||||
if (block_data.v.index == 0) // last node
|
||||
if (block_data.v.index == 0 || file_size == 0) // last node
|
||||
break;
|
||||
|
||||
block = block_data.v.index - 1;
|
||||
@@ -209,7 +218,7 @@ ArchiveFormatInfo SDSavegame::GetFormatInfo() const {
|
||||
// Tests on a physical 3DS shows that the `total_size` field seems to always be 0
|
||||
// when requested with the UserSaveData archive, and 134328448 when requested with
|
||||
// the SaveData archive. More investigation is required to tell whether this is a fixed value.
|
||||
ArchiveFormatInfo format_info = {/* total_size */ 134328448,
|
||||
ArchiveFormatInfo format_info = {/* total_size */ 0x40000,
|
||||
/* number_directories */ fs_info.maximum_directory_count,
|
||||
/* number_files */ fs_info.maximum_file_count,
|
||||
/* duplicate_data */ duplicate_data};
|
||||
@@ -236,7 +245,10 @@ bool SDExtdata::Init() {
|
||||
LOG_ERROR(Core, "Failed to load or decrypt VSXE");
|
||||
return false;
|
||||
}
|
||||
DataContainer vsxe_container(vsxe_raw);
|
||||
DataContainer vsxe_container(std::move(vsxe_raw));
|
||||
if (!vsxe_container.IsGood()) {
|
||||
return false;
|
||||
}
|
||||
auto vsxe = vsxe_container.GetIVFCLevel4Data()[0];
|
||||
|
||||
// Read header
|
||||
@@ -298,7 +310,7 @@ bool SDExtdata::ExtractFile(const std::string& path, std::size_t index) const {
|
||||
std::array<char, 17> name_data = {}; // Append a null terminator
|
||||
std::memcpy(name_data.data(), entry.name.data(), entry.name.size());
|
||||
|
||||
std::string name{name_data.data()};
|
||||
std::string name = name_data.data();
|
||||
FileUtil::IOFile file(path + name, "wb");
|
||||
if (!file) {
|
||||
LOG_ERROR(Core, "Could not open file {}", path + name);
|
||||
@@ -317,7 +329,11 @@ bool SDExtdata::ExtractFile(const std::string& path, std::size_t index) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
DataContainer container(container_data);
|
||||
DataContainer container(std::move(container_data));
|
||||
if (!container.IsGood()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto data = container.GetIVFCLevel4Data()[0];
|
||||
if (file.WriteBytes(data.data(), data.size()) != data.size()) {
|
||||
LOG_ERROR(Core, "Write data failed (file: {})", path + name);
|
||||
|
||||
@@ -156,8 +156,7 @@ protected:
|
||||
|
||||
class SDSavegame : public InnerFAT {
|
||||
public:
|
||||
explicit SDSavegame(std::vector<u8> data);
|
||||
explicit SDSavegame(std::vector<u8> partitionA, std::vector<u8> partitionB);
|
||||
explicit SDSavegame(std::vector<std::vector<u8>> partitions);
|
||||
~SDSavegame() override;
|
||||
|
||||
bool Extract(std::string path) const override;
|
||||
|
||||
+22
-4
@@ -82,14 +82,15 @@ struct KeySlot {
|
||||
std::array<KeySlot, KeySlotID::MaxKeySlotID> key_slots;
|
||||
std::array<std::optional<AESKey>, 6> common_key_y_slots;
|
||||
|
||||
std::string KeyToString(AESKey& key) {
|
||||
} // namespace
|
||||
|
||||
std::string KeyToString(const AESKey& key) {
|
||||
std::string s;
|
||||
for (auto pos : key) {
|
||||
s += fmt::format("{:02X}", pos);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void LoadBootromKeys(const std::string& path) {
|
||||
constexpr std::array<KeyDesc, 80> keys = {
|
||||
@@ -160,6 +161,18 @@ void LoadBootromKeys(const std::string& path) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// HACK: "Dump" 0x25 KeyX
|
||||
// TODO: Is this legal?
|
||||
constexpr std::array<u64, 16> offsets{{0x138A, 0xCAB, 0xD07, 0x3004, 0x2C, 0x49, 0xE6, 0x146E,
|
||||
0x1126, 0xD0, 0x85C, 0x47, 0x70A, 0x112C, 0x808, 0x89}};
|
||||
|
||||
for (std::size_t i = 0; i < offsets.size(); ++i) {
|
||||
file.Seek(offsets[i], SEEK_SET);
|
||||
file.ReadBytes(&new_key[i], 1);
|
||||
}
|
||||
LOG_DEBUG(Key, "Loaded Slot0x25 KeyX: {}", KeyToString(new_key));
|
||||
SetKeyX(0x25, new_key);
|
||||
}
|
||||
|
||||
void LoadMovableSedKeys(const std::string& path) {
|
||||
@@ -174,7 +187,7 @@ void LoadMovableSedKeys(const std::string& path) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr std::size_t KEY_SECTION_START = 0x118;
|
||||
constexpr std::size_t KEY_SECTION_START = 0x110;
|
||||
file.Seek(KEY_SECTION_START, SEEK_SET); // Jump to the key section
|
||||
|
||||
AESKey key;
|
||||
@@ -184,7 +197,8 @@ void LoadMovableSedKeys(const std::string& path) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetKeyY(0x26, key);
|
||||
LOG_DEBUG(Key, "Loaded Slot0x34KeyY: {}", KeyToString(key));
|
||||
SetKeyY(0x34, key);
|
||||
}
|
||||
|
||||
void ClearKeys() {
|
||||
@@ -212,6 +226,10 @@ AESKey GetNormalKey(std::size_t slot_id) {
|
||||
return key_slots.at(slot_id).normal.value_or(AESKey{});
|
||||
}
|
||||
|
||||
AESKey GetKeyX(std::size_t slot_id) {
|
||||
return key_slots.at(slot_id).x.value_or(AESKey{});
|
||||
}
|
||||
|
||||
void SelectCommonKeyIndex(u8 index) {
|
||||
key_slots[KeySlotID::TicketCommonKey].SetKeyY(common_key_y_slots.at(index));
|
||||
}
|
||||
|
||||
@@ -56,6 +56,8 @@ constexpr std::size_t AES_BLOCK_SIZE = 16;
|
||||
|
||||
using AESKey = std::array<u8, AES_BLOCK_SIZE>;
|
||||
|
||||
std::string KeyToString(const AESKey& key);
|
||||
|
||||
void LoadBootromKeys(const std::string& path);
|
||||
void LoadMovableSedKeys(const std::string& path);
|
||||
void ClearKeys();
|
||||
@@ -67,6 +69,9 @@ void SetNormalKey(std::size_t slot_id, const AESKey& key);
|
||||
bool IsNormalKeyAvailable(std::size_t slot_id);
|
||||
AESKey GetNormalKey(std::size_t slot_id);
|
||||
|
||||
// For importing aes_keys.txt
|
||||
AESKey GetKeyX(std::size_t slot_id);
|
||||
|
||||
void SelectCommonKeyIndex(u8 index);
|
||||
|
||||
} // namespace Core::Key
|
||||
|
||||
Reference in New Issue
Block a user