diff --git a/source/hactool_fs_utils.cpp b/source/hactool_fs_utils.cpp index 71c4c86..f212d4c 100644 --- a/source/hactool_fs_utils.cpp +++ b/source/hactool_fs_utils.cpp @@ -172,6 +172,102 @@ namespace ams::hactool { R_RETURN(res); } + Result ExtractDirectoryWithProgress(std::shared_ptr &dst_fs, std::shared_ptr &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 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 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 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)}; + + /* Write. */ + s64 offset = 0; + const s64 end_offset = static_cast(offset + size); + while (offset < end_offset) { + const s64 cur_write_size = std::min(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(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, const char *path, fs::IStorage *storage, s64 offset, size_t size) { /* Allocate a work buffer. */ void *buffer = std::malloc(WorkBufferSize); diff --git a/source/hactool_fs_utils.hpp b/source/hactool_fs_utils.hpp index 30e8890..2eeeb54 100644 --- a/source/hactool_fs_utils.hpp +++ b/source/hactool_fs_utils.hpp @@ -23,6 +23,7 @@ namespace ams::hactool { Result PrintDirectory(std::shared_ptr &fs, const char *prefix, const char *path); Result ExtractDirectory(std::shared_ptr &dst_fs, std::shared_ptr &src_fs, const char *prefix, const char *dst_path, const char *src_path); + Result ExtractDirectoryWithProgress(std::shared_ptr &dst_fs, std::shared_ptr &src_fs, const char *prefix, const char *dst_path, const char *src_path); Result SaveToFile(std::shared_ptr &fs, const char *path, fs::IStorage *storage, s64 offset, size_t size); Result SaveToFile(std::shared_ptr &fs, const char *path, fs::IStorage *storage); diff --git a/source/hactool_options.cpp b/source/hactool_options.cpp index 10980b7..1eb246b 100644 --- a/source/hactool_options.cpp +++ b/source/hactool_options.cpp @@ -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; }), }; } diff --git a/source/hactool_options.hpp b/source/hactool_options.hpp index 2aa0c67..590c199 100644 --- a/source/hactool_options.hpp +++ b/source/hactool_options.hpp @@ -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. */ }; diff --git a/source/hactool_processor.xci.cpp b/source/hactool_processor.xci.cpp index 463cb3e..e6dfa00 100644 --- a/source/hactool_processor.xci.cpp +++ b/source/hactool_processor.xci.cpp @@ -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(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(header.data.encrypted_data.compatibility_type) != fs::GameCardCompatibilityType::Normal) { + static_cast(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? */ } } \ No newline at end of file