mirror of
https://github.com/Atmosphere-NX/hac2l.git
synced 2025-08-07 16:09:28 +02:00
hactool: add print/extract for gc partitions, verif for gc header
This commit is contained in:
parent
af20a5818b
commit
952c488d28
@ -172,6 +172,102 @@ namespace ams::hactool {
|
||||
R_RETURN(res);
|
||||
}
|
||||
|
||||
Result ExtractDirectoryWithProgress(std::shared_ptr<fs::fsa::IFileSystem> &dst_fs, std::shared_ptr<fs::fsa::IFileSystem> &src_fs, const char *prefix, const char *dst_path, const char *src_path) {
|
||||
/* Allocate a work buffer. */
|
||||
void *buffer = std::malloc(WorkBufferSize);
|
||||
if (buffer == nullptr) {
|
||||
fprintf(stderr, "[Warning]: Failed to allocate work buffer to extract %s%s to %s!\n", prefix, src_path, dst_path);
|
||||
R_SUCCEED();
|
||||
}
|
||||
ON_SCOPE_EXIT { std::free(buffer); };
|
||||
|
||||
auto extract_impl = [&] () -> Result {
|
||||
/* Set up the destination work path to point at the target directory. */
|
||||
fs::Path dst_fs_path;
|
||||
R_TRY(dst_fs_path.SetShallowBuffer(dst_path));
|
||||
|
||||
/* Try to create the destination directory. */
|
||||
dst_fs->CreateDirectory(dst_fs_path);
|
||||
|
||||
/* Verify that we can open the directory on the base filesystem. */
|
||||
{
|
||||
std::unique_ptr<fs::fsa::IDirectory> sub_dir;
|
||||
R_TRY(dst_fs->OpenDirectory(std::addressof(sub_dir), dst_fs_path, fs::OpenDirectoryMode_Directory));
|
||||
}
|
||||
|
||||
/* Create/Initialize subdirectory filesystem. */
|
||||
fssystem::SubDirectoryFileSystem subdir_fs{dst_fs};
|
||||
R_TRY(subdir_fs.Initialize(dst_fs_path));
|
||||
|
||||
/* Set up the source path to point at the target directory. */
|
||||
fs::Path src_fs_path;
|
||||
R_TRY(src_fs_path.SetShallowBuffer(src_path));
|
||||
|
||||
/* Iterate, copying files. */
|
||||
R_RETURN(fssystem::IterateDirectoryRecursively(src_fs.get(), src_fs_path,
|
||||
[&](const fs::Path &path, const fs::DirectoryEntry &) -> Result { /* On Enter Directory */
|
||||
/* Create the directory. */
|
||||
R_TRY_CATCH(subdir_fs.CreateDirectory(path)) {
|
||||
R_CATCH(fs::ResultPathAlreadyExists) { /* ... */ }
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
R_SUCCEED();
|
||||
},
|
||||
[&](const fs::Path &, const fs::DirectoryEntry &) -> Result { /* On Exit Directory */
|
||||
R_SUCCEED();
|
||||
},
|
||||
[&](const fs::Path &path, const fs::DirectoryEntry &) -> Result { /* On File */
|
||||
/* Delete a file, if one already exists. */
|
||||
subdir_fs.DeleteFile(path);
|
||||
|
||||
/* Open the existing file. */
|
||||
std::shared_ptr<fs::IStorage> storage;
|
||||
R_TRY(OpenFileStorage(std::addressof(storage), src_fs, path.GetString()));
|
||||
|
||||
/* Get the file size. */
|
||||
s64 size;
|
||||
R_TRY(storage->GetSize(std::addressof(size)));
|
||||
|
||||
/* Create the file. */
|
||||
R_TRY(subdir_fs.CreateFile(path, size));
|
||||
|
||||
/* Open the file. */
|
||||
std::unique_ptr<fs::fsa::IFile> base_file;
|
||||
R_TRY(subdir_fs.OpenFile(std::addressof(base_file), path, fs::OpenMode_ReadWrite));
|
||||
|
||||
/* Set the file size. */
|
||||
R_TRY(base_file->SetSize(size));
|
||||
|
||||
/* Create a progress printer. */
|
||||
char prog_prefix[1_KB];
|
||||
util::TSNPrintf(prog_prefix, sizeof(prog_prefix), "Saving %s%s... ", prefix, path.GetString());
|
||||
ProgressPrinter<40> printer{prog_prefix, static_cast<size_t>(size)};
|
||||
|
||||
/* Write. */
|
||||
s64 offset = 0;
|
||||
const s64 end_offset = static_cast<s64>(offset + size);
|
||||
while (offset < end_offset) {
|
||||
const s64 cur_write_size = std::min<s64>(WorkBufferSize, end_offset - offset);
|
||||
|
||||
R_TRY(storage->Read(offset, buffer, cur_write_size));
|
||||
R_TRY(base_file->Write(offset, buffer, cur_write_size, fs::WriteOption::None));
|
||||
|
||||
offset += cur_write_size;
|
||||
printer.Update(static_cast<size_t>(offset));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
));
|
||||
};
|
||||
|
||||
const auto res = extract_impl();
|
||||
if (R_FAILED(res)) {
|
||||
fprintf(stderr, "[Warning]: Failed to extract %s%s to %s: 2%03d-%04d\n", prefix, src_path, dst_path, res.GetModule(), res.GetDescription());
|
||||
}
|
||||
R_RETURN(res);
|
||||
}
|
||||
|
||||
Result SaveToFile(std::shared_ptr<fs::fsa::IFileSystem> &fs, const char *path, fs::IStorage *storage, s64 offset, size_t size) {
|
||||
/* Allocate a work buffer. */
|
||||
void *buffer = std::malloc(WorkBufferSize);
|
||||
|
@ -23,6 +23,7 @@ namespace ams::hactool {
|
||||
Result PrintDirectory(std::shared_ptr<fs::fsa::IFileSystem> &fs, const char *prefix, const char *path);
|
||||
|
||||
Result ExtractDirectory(std::shared_ptr<fs::fsa::IFileSystem> &dst_fs, std::shared_ptr<fs::fsa::IFileSystem> &src_fs, const char *prefix, const char *dst_path, const char *src_path);
|
||||
Result ExtractDirectoryWithProgress(std::shared_ptr<fs::fsa::IFileSystem> &dst_fs, std::shared_ptr<fs::fsa::IFileSystem> &src_fs, const char *prefix, const char *dst_path, const char *src_path);
|
||||
|
||||
Result SaveToFile(std::shared_ptr<fs::fsa::IFileSystem> &fs, const char *path, fs::IStorage *storage, s64 offset, size_t size);
|
||||
Result SaveToFile(std::shared_ptr<fs::fsa::IFileSystem> &fs, const char *path, fs::IStorage *storage);
|
||||
|
@ -142,7 +142,13 @@ namespace ams::hactool {
|
||||
MakeOptionHandler("plaintext", [] (Options &options, const char *arg) { return CreateFilePath(std::addressof(options.plaintext_out_path), arg); }),
|
||||
MakeOptionHandler("ciphertext", [] (Options &options, const char *arg) { return CreateFilePath(std::addressof(options.ciphertext_out_path), arg); }),
|
||||
MakeOptionHandler("json", [] (Options &options, const char *arg) { return CreateFilePath(std::addressof(options.json_out_file_path), arg); }),
|
||||
MakeOptionHandler("rootdir", [] (Options &options, const char *arg) { return CreateFilePath(std::addressof(options.root_partition_out_dir), arg); }),
|
||||
MakeOptionHandler("securedir", [] (Options &options, const char *arg) { return CreateFilePath(std::addressof(options.secure_partition_out_dir), arg); }),
|
||||
MakeOptionHandler("normaldir", [] (Options &options, const char *arg) { return CreateFilePath(std::addressof(options.normal_partition_out_dir), arg); }),
|
||||
MakeOptionHandler("updatedir", [] (Options &options, const char *arg) { return CreateFilePath(std::addressof(options.update_partition_out_dir), arg); }),
|
||||
MakeOptionHandler("logodir", [] (Options &options, const char *arg) { return CreateFilePath(std::addressof(options.logo_partition_out_dir), arg); }),
|
||||
MakeOptionHandler("listromfs", [] (Options &options) { options.list_romfs = true; }),
|
||||
MakeOptionHandler("listupdate", [] (Options &options) { options.list_update = true; }),
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -58,7 +58,13 @@ namespace ams::hactool {
|
||||
const char *ciphertext_out_path = nullptr;
|
||||
const char *uncompressed_out_path = nullptr;
|
||||
const char *json_out_file_path = nullptr;
|
||||
const char *root_partition_out_dir = nullptr;
|
||||
const char *update_partition_out_dir = nullptr;
|
||||
const char *normal_partition_out_dir = nullptr;
|
||||
const char *logo_partition_out_dir = nullptr;
|
||||
const char *secure_partition_out_dir = nullptr;
|
||||
bool list_romfs = false;
|
||||
bool list_update = false;
|
||||
/* TODO: More things. */
|
||||
};
|
||||
|
||||
|
@ -249,7 +249,7 @@ namespace ams::hactool {
|
||||
}
|
||||
|
||||
/* Declare helper for printing a card header. */
|
||||
auto PrintCardHeader = [&](const char *header_name, const gc::impl::CardHeaderWithSignature &header, const void *modulus) {
|
||||
auto PrintCardHeader = [&](const char *header_name, const gc::impl::CardHeaderWithSignature &header, const gc::impl::CardHeaderWithSignature &enc_header, const void *modulus) {
|
||||
auto _ = this->PrintHeader(header_name);
|
||||
|
||||
/* Print the magic. */
|
||||
@ -257,7 +257,7 @@ namespace ams::hactool {
|
||||
|
||||
/* Print the signature. */
|
||||
if (m_options.verify) {
|
||||
const bool signature_valid = R_SUCCEEDED(gc::impl::GcCrypto::VerifyCardHeader(std::addressof(header), sizeof(header), modulus, crypto::Rsa2048Pkcs1Sha256Verifier::ModulusSize));
|
||||
const bool signature_valid = R_SUCCEEDED(gc::impl::GcCrypto::VerifyCardHeader(std::addressof(enc_header), sizeof(enc_header), modulus, crypto::Rsa2048Pkcs1Sha256Verifier::ModulusSize));
|
||||
this->PrintBytesWithVerify("Signature", signature_valid, header.signature, sizeof(header.signature));
|
||||
} else {
|
||||
this->PrintBytes("Signature", header.signature, sizeof(header.signature));
|
||||
@ -284,8 +284,33 @@ namespace ams::hactool {
|
||||
this->PrintString("Sel Sec", fs::impl::IdString().ToString(static_cast<gc::impl::SelSec>(header.data.sel_sec)));
|
||||
this->PrintInteger("Sel T1 Key", header.data.sel_t1_key);
|
||||
this->PrintInteger("Sel Key", header.data.sel_key);
|
||||
this->PrintBytes("Initial Data Hash", header.data.initial_data_hash, sizeof(header.data.initial_data_hash));
|
||||
this->PrintBytes("Partition Header Hash", header.data.partition_fs_header_hash, sizeof(header.data.partition_fs_header_hash));
|
||||
if (m_options.verify) {
|
||||
u8 hash[crypto::Sha256Generator::HashSize];
|
||||
|
||||
crypto::GenerateSha256(hash, sizeof(hash), std::addressof(ctx.card_data.initial_data), sizeof(ctx.card_data.initial_data));
|
||||
const bool initial_data_hash_good = ctx.key_area_storage != nullptr && crypto::IsSameBytes(hash, header.data.initial_data_hash, sizeof(hash));
|
||||
this->PrintBytesWithVerify("Initial Data Hash", initial_data_hash_good, header.data.initial_data_hash, sizeof(header.data.initial_data_hash));
|
||||
|
||||
{
|
||||
void *tmp = std::malloc(header.data.partition_fs_header_size + 1);
|
||||
AMS_ABORT_UNLESS(tmp != nullptr);
|
||||
ON_SCOPE_EXIT { std::free(tmp); };
|
||||
|
||||
R_ABORT_UNLESS(ctx.body_storage->Read(header.data.partition_fs_header_address, tmp, header.data.partition_fs_header_size));
|
||||
if (static_cast<fs::GameCardCompatibilityType>(header.data.encrypted_data.compatibility_type) != fs::GameCardCompatibilityType::Normal) {
|
||||
static_cast<u8 *>(tmp)[header.data.partition_fs_header_size] = header.data.encrypted_data.compatibility_type;
|
||||
crypto::GenerateSha256(hash, sizeof(hash), tmp, header.data.partition_fs_header_size + 1);
|
||||
} else {
|
||||
crypto::GenerateSha256(hash, sizeof(hash), tmp, header.data.partition_fs_header_size);
|
||||
}
|
||||
}
|
||||
|
||||
const bool partition_header_hash_good = crypto::IsSameBytes(hash, header.data.partition_fs_header_hash, sizeof(hash));
|
||||
this->PrintBytesWithVerify("Partition Header Hash", partition_header_hash_good, header.data.partition_fs_header_hash, sizeof(header.data.partition_fs_header_hash));
|
||||
} else {
|
||||
this->PrintBytes("Initial Data Hash", header.data.initial_data_hash, sizeof(header.data.initial_data_hash));
|
||||
this->PrintBytes("Partition Header Hash", header.data.partition_fs_header_hash, sizeof(header.data.partition_fs_header_hash));
|
||||
}
|
||||
this->PrintBytes("Encrypted Data Iv", header.data.iv, sizeof(header.data.iv));
|
||||
{
|
||||
auto _ = this->PrintHeader("Card Info");
|
||||
@ -307,16 +332,39 @@ namespace ams::hactool {
|
||||
};
|
||||
|
||||
/* Print the main card header. */
|
||||
PrintCardHeader("Main Header", ctx.card_data.decrypted_header, nullptr);
|
||||
PrintCardHeader("Main Header", ctx.card_data.decrypted_header, ctx.card_data.header, nullptr);
|
||||
|
||||
/* TODO: Print partitions. */
|
||||
auto PrintGamecardPartition = [&](const char *header, const char *prefix, ProcessAsXciContext::PartitionData &part) {
|
||||
if (part.fs != nullptr) {
|
||||
auto _ = this->PrintHeader(header);
|
||||
|
||||
char print_prefix[0x100];
|
||||
std::memset(print_prefix, ' ', WidthToPrintFieldValue);
|
||||
util::TSNPrintf(print_prefix + WidthToPrintFieldValue, sizeof(print_prefix) - WidthToPrintFieldValue, "%s", prefix);
|
||||
PrintDirectory(part.fs, print_prefix, "/");
|
||||
}
|
||||
};
|
||||
|
||||
PrintGamecardPartition("Root Partition", "root:", ctx.root_partition);
|
||||
PrintGamecardPartition("Logo Partition", "logo:", ctx.logo_partition);
|
||||
PrintGamecardPartition("Normal Partition", "normal:", ctx.normal_partition);
|
||||
PrintGamecardPartition("Secure Partition", "secure:", ctx.secure_partition);
|
||||
if (m_options.list_update) {
|
||||
PrintGamecardPartition("Update Partition", "update:", ctx.update_partition);
|
||||
}
|
||||
|
||||
AMS_UNUSED(ctx);
|
||||
}
|
||||
|
||||
void Processor::SaveAsXci(ProcessAsXciContext &ctx) {
|
||||
/* TODO */
|
||||
AMS_UNUSED(ctx);
|
||||
/* Extract partitions. */
|
||||
if (m_options.root_partition_out_dir != nullptr) { ExtractDirectoryWithProgress(m_local_fs, ctx.root_partition.fs, "root:", m_options.root_partition_out_dir, "/"); }
|
||||
if (m_options.logo_partition_out_dir != nullptr) { ExtractDirectoryWithProgress(m_local_fs, ctx.logo_partition.fs, "logo:", m_options.logo_partition_out_dir, "/"); }
|
||||
if (m_options.normal_partition_out_dir != nullptr) { ExtractDirectoryWithProgress(m_local_fs, ctx.normal_partition.fs, "normal:", m_options.normal_partition_out_dir, "/"); }
|
||||
if (m_options.secure_partition_out_dir != nullptr) { ExtractDirectoryWithProgress(m_local_fs, ctx.secure_partition.fs, "secure:", m_options.secure_partition_out_dir, "/"); }
|
||||
if (m_options.update_partition_out_dir != nullptr) { ExtractDirectoryWithProgress(m_local_fs, ctx.update_partition.fs, "update:", m_options.update_partition_out_dir, "/"); }
|
||||
|
||||
/* TODO: Recurse, dump NCAs? */
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user