hactool: add print/extract for gc partitions, verif for gc header

This commit is contained in:
Michael Scire 2022-03-14 17:30:38 -07:00
parent af20a5818b
commit 952c488d28
5 changed files with 165 additions and 8 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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; }),
};
}

View File

@ -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. */
};

View File

@ -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? */
}
}