Port GameCardProcess & use new ExtractJob system. User can now specify custom extract jobs with specific virtual paths.

This commit is contained in:
jakcron 2021-10-08 18:05:51 +08:00
parent 9d41a1c913
commit e7dfa8ad44
13 changed files with 502 additions and 347 deletions

View file

@ -8,15 +8,7 @@ nstool::AssetProcess::AssetProcess() :
mCliOutputMode(true, false, false, false), mCliOutputMode(true, false, false, false),
mVerify(false) mVerify(false)
{ {
} }
void nstool::AssetProcess::process()
{
importHeader();
if (mCliOutputMode.show_basic_info)
displayHeader();
processSections();
}
void nstool::AssetProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file) void nstool::AssetProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
{ {
@ -33,11 +25,6 @@ void nstool::AssetProcess::setVerifyMode(bool verify)
mVerify = verify; mVerify = verify;
} }
void nstool::AssetProcess::setListFs(bool list)
{
mRomfs.setListFs(list);
}
void nstool::AssetProcess::setIconExtractPath(const tc::io::Path& path) void nstool::AssetProcess::setIconExtractPath(const tc::io::Path& path)
{ {
mIconExtractPath = path; mIconExtractPath = path;
@ -48,11 +35,23 @@ void nstool::AssetProcess::setNacpExtractPath(const tc::io::Path& path)
mNacpExtractPath = path; mNacpExtractPath = path;
} }
void nstool::AssetProcess::setRomfsExtractPath(const tc::io::Path& path) void nstool::AssetProcess::setRomfsShowFsTree(bool show_fs_tree)
{ {
mRomfs.setExtractPath(path); mRomfs.setShowFsTree(show_fs_tree);
} }
void nstool::AssetProcess::setRomfsExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs)
{
mRomfs.setExtractJobs(extract_jobs);
}
void nstool::AssetProcess::process()
{
importHeader();
if (mCliOutputMode.show_basic_info)
displayHeader();
processSections();
}
void nstool::AssetProcess::importHeader() void nstool::AssetProcess::importHeader()
{ {

View file

@ -12,18 +12,17 @@ class AssetProcess
public: public:
AssetProcess(); AssetProcess();
void process();
void setInputFile(const std::shared_ptr<tc::io::IStream>& file); void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setCliOutputMode(CliOutputMode type); void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify); void setVerifyMode(bool verify);
void setListFs(bool list);
void setIconExtractPath(const tc::io::Path& path); void setIconExtractPath(const tc::io::Path& path);
void setNacpExtractPath(const tc::io::Path& path); void setNacpExtractPath(const tc::io::Path& path);
void setRomfsExtractPath(const tc::io::Path& path);
void setRomfsShowFsTree(bool show_fs_tree);
void setRomfsExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs);
void process();
private: private:
std::string mModuleName; std::string mModuleName;

View file

@ -1,11 +1,20 @@
#include "FsProcess.h" #include "FsProcess.h"
#include <iostream> #include "util.h"
#include <memory>
#include <tc/io/FileNotFoundException.h>
#include <tc/io/DirectoryNotFoundException.h>
nstool::FsProcess::FsProcess() : nstool::FsProcess::FsProcess() :
mModuleLabel("nstool::FsProcess"), mModuleLabel("nstool::FsProcess"),
mInputFs(), mInputFs(),
mShowFs(false), mFsFormatName(),
mExtractPath() mShowFsInfo(false),
mProperties(),
mShowFsTree(false),
mFsRootLabel(),
mExtractJobs(),
mDataCache(0x10000)
{ {
} }
@ -15,19 +24,34 @@ void nstool::FsProcess::setInputFileSystem(const std::shared_ptr<tc::io::IStorag
mInputFs = input_fs; mInputFs = input_fs;
} }
void nstool::FsProcess::setFsLabel(const std::string& fs_label) void nstool::FsProcess::setFsFormatName(const std::string& fs_format_name)
{ {
mFsLabel = fs_label; mFsFormatName = fs_format_name;
} }
void nstool::FsProcess::setCliOutputMode(bool show_fs) void nstool::FsProcess::setShowFsInfo(bool show_fs_info)
{ {
mShowFs = show_fs; mShowFsInfo = show_fs_info;
} }
void nstool::FsProcess::setExtractPath(const tc::io::Path& extract_path) void nstool::FsProcess::setFsProperties(const std::vector<std::string>& properties)
{ {
mExtractPath = extract_path; mProperties = properties;
}
void nstool::FsProcess::setShowFsTree(bool show_fs_tree)
{
mShowFsTree = show_fs_tree;
}
void nstool::FsProcess::setFsRootLabel(const std::string& root_label)
{
mFsRootLabel = root_label;
}
void nstool::FsProcess::setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs)
{
mExtractJobs = extract_jobs;
} }
void nstool::FsProcess::process() void nstool::FsProcess::process()
@ -37,23 +61,147 @@ void nstool::FsProcess::process()
throw tc::InvalidOperationException(mModuleLabel, "No input filesystem"); throw tc::InvalidOperationException(mModuleLabel, "No input filesystem");
} }
if (mShowFs) if (mShowFsInfo)
printFs(); {
fmt::print("[{:s}]\n", mFsFormatName.isSet() ? mFsFormatName.get() : "FileSystem/Info");
for (auto itr = mProperties.begin(); itr != mProperties.end(); itr++)
{
fmt::print(" {:s}\n", *itr);
}
}
if (mExtractPath.isSet()) if (mShowFsTree)
{
printFs();
}
if (mExtractJobs.empty() == false)
{
extractFs(); extractFs();
}
} }
void nstool::FsProcess::printFs() void nstool::FsProcess::printFs()
{ {
fmt::print("[{:s}FsTree]\n", (mFsLabel.isSet() ? (mFsLabel.get() + "/") : "")); fmt::print("[{:s}/Tree]\n", (mFsFormatName.isSet() ? mFsFormatName.get() : "FileSystem"));
visitDir(tc::io::Path("/"), tc::io::Path("/"), false, true); visitDir(tc::io::Path("/"), tc::io::Path("/"), false, true);
} }
void nstool::FsProcess::extractFs() void nstool::FsProcess::extractFs()
{ {
fmt::print("[{:s}FsExtract]\n", (mFsLabel.isSet() ? (mFsLabel.get() + "/") : "")); fmt::print("[{:s}/Extract]\n", (mFsFormatName.isSet() ? mFsFormatName.get() : "FileSystem"));
visitDir(tc::io::Path("/"), mExtractPath.get(), true, false);
for (auto itr = mExtractJobs.begin(); itr != mExtractJobs.end(); itr++)
{
std::string path_str;
tc::io::PathUtil::pathToUnixUTF8(itr->virtual_path, path_str);
// check if root path (legacy case)
if (itr->virtual_path == tc::io::Path("/"))
{
visitDir(tc::io::Path("/"), itr->extract_path, true, false);
//fmt::print("Root Dir Virtual Path: \"{:s}\"\n", path_str);
// root directory extract successful, continue to next job
continue;
}
// otherwise determine if this is a file or subdirectory
try {
std::shared_ptr<tc::io::IStream> file_stream;
mInputFs->openFile(itr->virtual_path, tc::io::FileMode::Open, tc::io::FileAccess::Read, file_stream);
//fmt::print("Valid File Path: \"{:s}\"\n", path_str);
// the output path for this file will depend on the user specified extract path
std::shared_ptr<tc::io::IStorage> local_fs = std::make_shared<tc::io::LocalStorage>(tc::io::LocalStorage());
// case: the extract_path is a valid path to an existing directory
// behaviour: extract the file, preserving the original filename, to the specified directory
// method: try getDirectoryListing(itr->extract_path), if this is does not throw, then we can be sure this is a valid path to a directory, file_extract_path = itr->extract_path + itr->virtual_path.back()
try {
tc::io::sDirectoryListing dir_listing;
local_fs->getDirectoryListing(itr->extract_path, dir_listing);
tc::io::Path file_extract_path = itr->extract_path + itr->virtual_path.back();
std::string file_extract_path_str;
tc::io::PathUtil::pathToUnixUTF8(file_extract_path, file_extract_path_str);
fmt::print("Saving {:s}...\n", file_extract_path_str);
writeStreamToFile(file_stream, itr->extract_path + itr->virtual_path.back(), mDataCache);
continue;
} catch (tc::io::DirectoryNotFoundException& e) {
// acceptable exception, just means directory didn't exist
}
// case: the extract_path up until the last element is a valid path to an existing directory, but the full path specifies neither a directory or a file
// behaviour: treat extract_path as the intended location to write the extracted file (the original filename is not preserved, instead specified by the user in the final element of the extract path)
// method: since this checks n-1 elements, it implies a path with more than one element, so that must be accounted for, as relative paths are valid and single element paths aren't always root
try {
std::string test_path_str;
// get path to parent directory
tc::io::Path parent_dir_path = itr->extract_path;
// replace final path element with the current directory alias
parent_dir_path.pop_back(); // remove filename
parent_dir_path.push_back("."); // replace with the current dir name alias
tc::io::PathUtil::pathToUnixUTF8(parent_dir_path, test_path_str);
// test parent directory exists
tc::io::sDirectoryListing dir_listing;
local_fs->getDirectoryListing(parent_dir_path, dir_listing);
std::string file_extract_path_str;
tc::io::PathUtil::pathToUnixUTF8(itr->extract_path, file_extract_path_str);
fmt::print("Saving {:s} as {:s}...\n", path_str, file_extract_path_str);
writeStreamToFile(file_stream, itr->extract_path, mDataCache);
continue;
} catch (tc::io::DirectoryNotFoundException& e) {
// acceptable exception, just means the parent directory didn't exist
}
// extract path could not be determined, inform the user and skip this job
std::string literal_extract_path_str;
tc::io::PathUtil::pathToUnixUTF8(itr->extract_path, literal_extract_path_str);
fmt::print("[WARNING] Extract path was invalid, and was skipped: {:s}\n", literal_extract_path_str);
continue;
} catch (tc::io::FileNotFoundException& e) {
// acceptable exception, just means file didn't exist
}
// not a file, attempt to process this as a directory
try {
tc::io::sDirectoryListing dir_listing;
mInputFs->getDirectoryListing(itr->virtual_path, dir_listing);
visitDir(itr->virtual_path, itr->extract_path, true, false);
//fmt::print("Valid Directory Path: \"{:s}\"\n", path_str);
// directory extract successful, continue to next job
continue;
} catch (tc::io::DirectoryNotFoundException& e) {
// acceptable exception, just means directory didn't exist
}
fmt::print("[WARNING] Failed to extract virtual path: \"{:s}\"\n", path_str);
}
} }
void nstool::FsProcess::visitDir(const tc::io::Path& v_path, const tc::io::Path& l_path, bool extract_fs, bool print_fs) void nstool::FsProcess::visitDir(const tc::io::Path& v_path, const tc::io::Path& l_path, bool extract_fs, bool print_fs)
@ -67,9 +215,9 @@ void nstool::FsProcess::visitDir(const tc::io::Path& v_path, const tc::io::Path&
if (print_fs) if (print_fs)
{ {
for (size_t i = 0; i < v_path.size(); i++) for (size_t i = 0; i < v_path.size(); i++)
fmt::print(" ");; fmt::print(" ");
fmt::print("{:s}/\n", ((v_path.size() == 1) ? "Root:" : v_path.back())); fmt::print("{:s}/\n", ((v_path.size() == 1) ? (mFsRootLabel.isSet() ? (mFsRootLabel.get() + ":") : "Root:") : v_path.back()));
} }
if (extract_fs) if (extract_fs)
{ {
@ -78,7 +226,6 @@ void nstool::FsProcess::visitDir(const tc::io::Path& v_path, const tc::io::Path&
} }
// iterate thru child files // iterate thru child files
tc::ByteData cache = tc::ByteData(0x10000);
size_t cache_read_len; size_t cache_read_len;
tc::io::Path out_path; tc::io::Path out_path;
std::string out_path_str; std::string out_path_str;
@ -108,13 +255,13 @@ void nstool::FsProcess::visitDir(const tc::io::Path& v_path, const tc::io::Path&
out_stream->seek(0, tc::io::SeekOrigin::Begin); out_stream->seek(0, tc::io::SeekOrigin::Begin);
for (int64_t remaining_data = in_stream->length(); remaining_data > 0;) for (int64_t remaining_data = in_stream->length(); remaining_data > 0;)
{ {
cache_read_len = in_stream->read(cache.data(), cache.size()); cache_read_len = in_stream->read(mDataCache.data(), mDataCache.size());
if (cache_read_len == 0) if (cache_read_len == 0)
{ {
throw tc::io::IOException(mModuleLabel, fmt::format("Failed to read from {:s}file.", (mFsLabel.isSet() ? (mFsLabel.get() + " ") : ""))); throw tc::io::IOException(mModuleLabel, fmt::format("Failed to read from {:s}file.", (mFsFormatName.isSet() ? (mFsFormatName.get() + " ") : "")));
} }
out_stream->write(cache.data(), cache_read_len); out_stream->write(mDataCache.data(), cache_read_len);
remaining_data -= int64_t(cache_read_len); remaining_data -= int64_t(cache_read_len);
} }

View file

@ -13,18 +13,33 @@ public:
FsProcess(); FsProcess();
void setInputFileSystem(const std::shared_ptr<tc::io::IStorage>& input_fs); void setInputFileSystem(const std::shared_ptr<tc::io::IStorage>& input_fs);
void setFsLabel(const std::string& fs_label); void setFsFormatName(const std::string& fs_format_name);
void setCliOutputMode(bool show_fs); void setFsProperties(const std::vector<std::string>& properties);
void setExtractPath(const tc::io::Path& extract_path); void setShowFsInfo(bool show_fs_info);
void setShowFsTree(bool show_fs_tree);
void setFsRootLabel(const std::string& root_label);
void setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs);
void process(); void process();
private: private:
std::string mModuleLabel; std::string mModuleLabel;
std::shared_ptr<tc::io::IStorage> mInputFs; std::shared_ptr<tc::io::IStorage> mInputFs;
tc::Optional<std::string> mFsLabel;
bool mShowFs; // fs info
tc::Optional<tc::io::Path> mExtractPath; tc::Optional<std::string> mFsFormatName;
bool mShowFsInfo;
std::vector<std::string> mProperties;
// fs tree
bool mShowFsTree;
tc::Optional<std::string> mFsRootLabel;
// extract jobs
std::vector<nstool::ExtractJob> mExtractJobs;
// cache for file extract
tc::ByteData mDataCache;
void printFs(); void printFs();
void extractFs(); void extractFs();

View file

@ -1,20 +1,25 @@
#include <iostream> #include "GameCardProcess.h"
#include <iomanip>
#include <fnd/SimpleTextOutput.h> #include <tc/crypto.h>
#include <fnd/OffsetAdjustedIFile.h> #include <tc/io/IOUtil.h>
#include <nn/hac/GameCardUtil.h> #include <nn/hac/GameCardUtil.h>
#include <nn/hac/ContentMetaUtil.h> #include <nn/hac/ContentMetaUtil.h>
#include <nn/hac/ContentArchiveUtil.h> #include <nn/hac/ContentArchiveUtil.h>
#include "GameCardProcess.h"
#include <nn/hac/GameCardFsMetaGenerator.h>
#include "FsProcess.h"
nstool::GameCardProcess::GameCardProcess() : nstool::GameCardProcess::GameCardProcess() :
mModuleName("nstool::GameCardProcess"),
mFile(), mFile(),
mCliOutputMode(true, false, false, false), mCliOutputMode(true, false, false, false),
mVerify(false), mVerify(false),
mListFs(false), mListFs(false),
mProccessExtendedHeader(false), mProccessExtendedHeader(false),
mRootPfs(), mRootPfs(),
mExtractInfo() mExtractJobs()
{ {
} }
@ -30,11 +35,8 @@ void nstool::GameCardProcess::process()
if (mCliOutputMode.show_basic_info) if (mCliOutputMode.show_basic_info)
displayHeader(); displayHeader();
// process root partition // process nested HFS0
processRootPfs(); processRootPfs();
// process partitions
processPartitionPfs();
} }
void nstool::GameCardProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file) void nstool::GameCardProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
@ -57,60 +59,69 @@ void nstool::GameCardProcess::setVerifyMode(bool verify)
mVerify = verify; mVerify = verify;
} }
void nstool::GameCardProcess::setPartitionForExtract(const std::string& partition_name, const std::string& extract_path) void nstool::GameCardProcess::setExtractJobs(const std::vector<nstool::ExtractJob> extract_jobs)
{ {
mExtractInfo.push_back({partition_name, extract_path}); mExtractJobs = extract_jobs;
} }
void nstool::GameCardProcess::setListFs(bool list_fs) void nstool::GameCardProcess::setShowFsTree(bool show_fs_tree)
{ {
mListFs = list_fs; mListFs = show_fs_tree;
} }
void nstool::GameCardProcess::importHeader() void nstool::GameCardProcess::importHeader()
{ {
tc::ByteData scratch; if (mFile == nullptr)
if (*mFile == nullptr)
{ {
throw tc::Exception(kModuleName, "No file reader set."); throw tc::Exception(mModuleName, "No file reader set.");
}
if (mFile->canRead() == false || mFile->canSeek() == false)
{
throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions.");
} }
// check stream is large enough for header
if (mFile->length() < tc::io::IOUtil::castSizeToInt64(sizeof(nn::hac::sSdkGcHeader)))
{
throw tc::Exception(mModuleName, "Corrupt GameCard Image: File too small.");
}
// allocate memory for header // allocate memory for header
scratch.alloc(sizeof(nn::hac::sSdkGcHeader)); tc::ByteData scratch = tc::ByteData(sizeof(nn::hac::sSdkGcHeader));
// read header region // read header region
(*mFile)->read((byte_t*)scratch.data(), 0, sizeof(nn::hac::sSdkGcHeader)); mFile->seek(0, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
// determine if this is a SDK XCI or a "Community" XCI // determine if this is a SDK XCI or a "Community" XCI
if (((nn::hac::sSdkGcHeader*)scratch.data())->signed_header.header.st_magic.get() == nn::hac::gc::kGcHeaderStructMagic) if (((nn::hac::sSdkGcHeader*)scratch.data())->signed_header.header.st_magic.unwrap() == nn::hac::gc::kGcHeaderStructMagic)
{ {
mIsTrueSdkXci = true; mIsTrueSdkXci = true;
mGcHeaderOffset = sizeof(nn::hac::sGcKeyDataRegion); mGcHeaderOffset = sizeof(nn::hac::sGcKeyDataRegion);
} }
else if (((nn::hac::sGcHeader_Rsa2048Signed*)scratch.data())->header.st_magic.get() == nn::hac::gc::kGcHeaderStructMagic) else if (((nn::hac::sGcHeader_Rsa2048Signed*)scratch.data())->header.st_magic.unwrap() == nn::hac::gc::kGcHeaderStructMagic)
{ {
mIsTrueSdkXci = false; mIsTrueSdkXci = false;
mGcHeaderOffset = 0; mGcHeaderOffset = 0;
} }
else else
{ {
throw tc::Exception(kModuleName, "GameCard image did not have expected magic bytes"); throw tc::Exception(mModuleName, "Corrupt GameCard Image: Unexpected magic bytes.");
} }
nn::hac::sGcHeader_Rsa2048Signed* hdr_ptr = (nn::hac::sGcHeader_Rsa2048Signed*)(scratch.data() + mGcHeaderOffset); nn::hac::sGcHeader_Rsa2048Signed* hdr_ptr = (nn::hac::sGcHeader_Rsa2048Signed*)(scratch.data() + mGcHeaderOffset);
// generate hash of raw header // generate hash of raw header
fnd::sha::Sha256((byte_t*)&hdr_ptr->header, sizeof(nn::hac::sGcHeader), mHdrHash.bytes); tc::crypto::GenerateSha256Hash(mHdrHash.data(), (byte_t*)&hdr_ptr->header, sizeof(nn::hac::sGcHeader));
// save the signature // save the signature
memcpy(mHdrSignature, hdr_ptr->signature, fnd::rsa::kRsa2048Size); memcpy(mHdrSignature.data(), hdr_ptr->signature.data(), mHdrSignature.size());
// decrypt extended header // decrypt extended header
KeyBag::aes128_key_t header_key; byte_t xci_header_key_index = hdr_ptr->header.key_flag & 7;
if (mKeyCfg.getXciHeaderKey(header_key)) if (mKeyCfg.xci_header_key.find(xci_header_key_index) != mKeyCfg.xci_header_key.end())
{ {
nn::hac::GameCardUtil::decryptXciHeader(&hdr_ptr->header, header_key.key); nn::hac::GameCardUtil::decryptXciHeader(&hdr_ptr->header, mKeyCfg.xci_header_key[xci_header_key_index].data());
mProccessExtendedHeader = true; mProccessExtendedHeader = true;
} }
@ -120,164 +131,156 @@ void nstool::GameCardProcess::importHeader()
void nstool::GameCardProcess::displayHeader() void nstool::GameCardProcess::displayHeader()
{ {
std::cout << "[GameCard Header]" << std::endl; const nn::hac::sGcHeader* raw_hdr = (const nn::hac::sGcHeader*)mHdr.getBytes().data();
std::cout << " CardHeaderVersion: " << std::dec << (uint32_t)mHdr.getCardHeaderVersion() << std::endl;
std::cout << " RomSize: " << nn::hac::GameCardUtil::getRomSizeAsString((nn::hac::gc::RomSize)mHdr.getRomSizeType()); fmt::print("[GameCard/Header]\n");
fmt::print(" CardHeaderVersion: {:d}\n", mHdr.getCardHeaderVersion());
fmt::print(" RomSize: {:s}", nn::hac::GameCardUtil::getRomSizeAsString((nn::hac::gc::RomSize)mHdr.getRomSizeType()));
if (mCliOutputMode.show_extended_info) if (mCliOutputMode.show_extended_info)
std::cout << " (0x" << std::hex << (uint32_t)mHdr.getRomSizeType() << ")"; fmt::print(" (0x{:x})", mHdr.getRomSizeType());
std::cout << std::endl; fmt::print("\n");
std::cout << " PackageId: 0x" << std::hex << std::setw(16) << std::setfill('0') << mHdr.getPackageId() << std::endl; fmt::print(" PackageId: 0x{:016x}\n", mHdr.getPackageId());
std::cout << " Flags: 0x" << std::dec << (uint32_t)mHdr.getFlags() << std::endl; fmt::print(" Flags: 0x{:02x}\n", *((byte_t*)&raw_hdr->flags));
if (mHdr.getFlags() != 0) for (auto itr = mHdr.getFlags().begin(); itr != mHdr.getFlags().end(); itr++)
{ {
for (uint32_t i = 0; i < 8; i++) fmt::print(" {:s}\n", nn::hac::GameCardUtil::getHeaderFlagsAsString((nn::hac::gc::HeaderFlags)*itr));
{ }
if (_HAS_BIT(mHdr.getFlags(), i))
{
std::cout << " " << nn::hac::GameCardUtil::getHeaderFlagsAsString((nn::hac::gc::HeaderFlags)i) << std::endl; if (mCliOutputMode.show_extended_info)
} {
} fmt::print(" KekIndex: {:s} ({:d})\n", nn::hac::GameCardUtil::getKekIndexAsString((nn::hac::gc::KekIndex)mHdr.getKekIndex()), mHdr.getKekIndex());
fmt::print(" TitleKeyDecIndex: {:d}\n", mHdr.getTitleKeyDecIndex());
fmt::print(" InitialData:\n");
fmt::print(" Hash:\n");
fmt::print(" {:s}", tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(mHdr.getInitialDataHash().data(), mHdr.getInitialDataHash().size(), true, ":", 0x10, 6, false));
} }
if (mCliOutputMode.show_extended_info) if (mCliOutputMode.show_extended_info)
{ {
std::cout << " KekIndex: " << nn::hac::GameCardUtil::getKekIndexAsString((nn::hac::gc::KekIndex)mHdr.getKekIndex()) << " (" << std::dec << (uint32_t)mHdr.getKekIndex() << ")" << std::endl; fmt::print(" Extended Header AesCbc IV:\n");
std::cout << " TitleKeyDecIndex: " << std::dec << (uint32_t)mHdr.getTitleKeyDecIndex() << std::endl; fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mHdr.getAesCbcIv().data(), mHdr.getAesCbcIv().size(), true, ":"));
std::cout << " InitialData:" << std::endl;
std::cout << " Hash:" << std::endl;
std::cout << " " << fnd::SimpleTextOutput::arrayToString(mHdr.getInitialDataHash().bytes, 0x10, true, ":") << std::endl;
std::cout << " " << fnd::SimpleTextOutput::arrayToString(mHdr.getInitialDataHash().bytes+0x10, 0x10, true, ":") << std::endl;
} }
if (mCliOutputMode.show_extended_info) fmt::print(" SelSec: 0x{:x}\n", mHdr.getSelSec());
{ fmt::print(" SelT1Key: 0x{:x}\n", mHdr.getSelT1Key());
std::cout << " Extended Header AesCbc IV:" << std::endl; fmt::print(" SelKey: 0x{:x}\n", mHdr.getSelKey());
std::cout << " " << fnd::SimpleTextOutput::arrayToString(mHdr.getAesCbcIv().iv, sizeof(mHdr.getAesCbcIv().iv), true, ":") << std::endl;
}
std::cout << " SelSec: 0x" << std::hex << mHdr.getSelSec() << std::endl;
std::cout << " SelT1Key: 0x" << std::hex << mHdr.getSelT1Key() << std::endl;
std::cout << " SelKey: 0x" << std::hex << mHdr.getSelKey() << std::endl;
if (mCliOutputMode.show_layout) if (mCliOutputMode.show_layout)
{ {
std::cout << " RomAreaStartPage: 0x" << std::hex << mHdr.getRomAreaStartPage(); fmt::print(" RomAreaStartPage: 0x{:x}", mHdr.getRomAreaStartPage());
if (mHdr.getRomAreaStartPage() != (uint32_t)(-1)) if (mHdr.getRomAreaStartPage() != (uint32_t)(-1))
std::cout << " (0x" << std::hex << nn::hac::GameCardUtil::blockToAddr(mHdr.getRomAreaStartPage()) << ")"; fmt::print(" (0x{:x})", nn::hac::GameCardUtil::blockToAddr(mHdr.getRomAreaStartPage()));
std::cout << std::endl; fmt::print("\n");
std::cout << " BackupAreaStartPage: 0x" << std::hex << mHdr.getBackupAreaStartPage(); fmt::print(" BackupAreaStartPage: 0x{:x}", mHdr.getBackupAreaStartPage());
if (mHdr.getBackupAreaStartPage() != (uint32_t)(-1)) if (mHdr.getBackupAreaStartPage() != (uint32_t)(-1))
std::cout << " (0x" << std::hex << nn::hac::GameCardUtil::blockToAddr(mHdr.getBackupAreaStartPage()) << ")"; fmt::print(" (0x{:x})", nn::hac::GameCardUtil::blockToAddr(mHdr.getBackupAreaStartPage()));
std::cout << std::endl; fmt::print("\n");
std::cout << " ValidDataEndPage: 0x" << std::hex << mHdr.getValidDataEndPage(); fmt::print(" ValidDataEndPage: 0x{:x}", mHdr.getValidDataEndPage());
if (mHdr.getValidDataEndPage() != (uint32_t)(-1)) if (mHdr.getValidDataEndPage() != (uint32_t)(-1))
std::cout << " (0x" << std::hex << nn::hac::GameCardUtil::blockToAddr(mHdr.getValidDataEndPage()) << ")"; fmt::print(" (0x{:x})", nn::hac::GameCardUtil::blockToAddr(mHdr.getValidDataEndPage()));
std::cout << std::endl; fmt::print("\n");
std::cout << " LimArea: 0x" << std::hex << mHdr.getLimAreaPage(); fmt::print(" LimArea: 0x{:x}", mHdr.getLimAreaPage());
if (mHdr.getLimAreaPage() != (uint32_t)(-1)) if (mHdr.getLimAreaPage() != (uint32_t)(-1))
std::cout << " (0x" << std::hex << nn::hac::GameCardUtil::blockToAddr(mHdr.getLimAreaPage()) << ")"; fmt::print(" (0x{:x})", nn::hac::GameCardUtil::blockToAddr(mHdr.getLimAreaPage()));
std::cout << std::endl; fmt::print("\n");
std::cout << " PartitionFs Header:" << std::endl; fmt::print(" PartitionFs Header:\n");
std::cout << " Offset: 0x" << std::hex << mHdr.getPartitionFsAddress() << std::endl; fmt::print(" Offset: 0x{:x}\n", mHdr.getPartitionFsAddress());
std::cout << " Size: 0x" << std::hex << mHdr.getPartitionFsSize() << std::endl; fmt::print(" Size: 0x{:x}\n", mHdr.getPartitionFsSize());
if (mCliOutputMode.show_extended_info) if (mCliOutputMode.show_extended_info)
{ {
std::cout << " Hash:" << std::endl; fmt::print(" Hash:\n");
std::cout << " " << fnd::SimpleTextOutput::arrayToString(mHdr.getPartitionFsHash().bytes, 0x10, true, ":") << std::endl; fmt::print(" {:s}", tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(mHdr.getPartitionFsHash().data(), mHdr.getPartitionFsHash().size(), true, ":", 0x10, 6, false));
std::cout << " " << fnd::SimpleTextOutput::arrayToString(mHdr.getPartitionFsHash().bytes+0x10, 0x10, true, ":") << std::endl;
} }
} }
if (mProccessExtendedHeader) if (mProccessExtendedHeader)
{ {
std::cout << "[GameCard Extended Header]" << std::endl; fmt::print("[GameCard/ExtendedHeader]\n");
std::cout << " FwVersion: v" << std::dec << mHdr.getFwVersion() << " (" << nn::hac::GameCardUtil::getCardFwVersionDescriptionAsString((nn::hac::gc::FwVersion)mHdr.getFwVersion()) << ")" << std::endl; fmt::print(" FwVersion: v{:d} ({:s})\n", mHdr.getFwVersion(), nn::hac::GameCardUtil::getCardFwVersionDescriptionAsString((nn::hac::gc::FwVersion)mHdr.getFwVersion()));
std::cout << " AccCtrl1: 0x" << std::hex << mHdr.getAccCtrl1() << std::endl; fmt::print(" AccCtrl1: 0x{:x}\n", mHdr.getAccCtrl1());
std::cout << " CardClockRate: " << nn::hac::GameCardUtil::getCardClockRateAsString((nn::hac::gc::CardClockRate)mHdr.getAccCtrl1()) << std::endl; fmt::print(" CardClockRate: {:s}\n", nn::hac::GameCardUtil::getCardClockRateAsString((nn::hac::gc::CardClockRate)mHdr.getAccCtrl1()));
std::cout << " Wait1TimeRead: 0x" << std::hex << mHdr.getWait1TimeRead() << std::endl; fmt::print(" Wait1TimeRead: 0x{:x}\n", mHdr.getWait1TimeRead());
std::cout << " Wait2TimeRead: 0x" << std::hex << mHdr.getWait2TimeRead() << std::endl; fmt::print(" Wait2TimeRead: 0x{:x}\n", mHdr.getWait2TimeRead());
std::cout << " Wait1TimeWrite: 0x" << std::hex << mHdr.getWait1TimeWrite() << std::endl; fmt::print(" Wait1TimeWrite: 0x{:x}\n", mHdr.getWait1TimeWrite());
std::cout << " Wait2TimeWrite: 0x" << std::hex << mHdr.getWait2TimeWrite() << std::endl; fmt::print(" Wait2TimeWrite: 0x{:x}\n", mHdr.getWait2TimeWrite());
std::cout << " SdkAddon Version: " << nn::hac::ContentArchiveUtil::getSdkAddonVersionAsString(mHdr.getFwMode()) << " (v" << std::dec << mHdr.getFwMode() << ")" << std::endl; fmt::print(" SdkAddon Version: {:s} (v{:d})\n", nn::hac::ContentArchiveUtil::getSdkAddonVersionAsString(mHdr.getFwMode()), mHdr.getFwMode());
std::cout << " CompatibilityType: " << nn::hac::GameCardUtil::getCompatibilityTypeAsString((nn::hac::gc::CompatibilityType)mHdr.getCompatibilityType()) << " (" << std::dec << (uint32_t) mHdr.getCompatibilityType() << ")" << std::endl; fmt::print(" CompatibilityType: {:s} ({:d})\n", nn::hac::GameCardUtil::getCompatibilityTypeAsString((nn::hac::gc::CompatibilityType)mHdr.getCompatibilityType()), mHdr.getCompatibilityType());
std::cout << " Update Partition Info:" << std::endl; fmt::print(" Update Partition Info:\n");
std::cout << " CUP Version: " << nn::hac::ContentMetaUtil::getVersionAsString(mHdr.getUppVersion()) << " (v" << std::dec << mHdr.getUppVersion() << ")" << std::endl; fmt::print(" CUP Version: {:s} (v{:d})\n", nn::hac::ContentMetaUtil::getVersionAsString(mHdr.getUppVersion()), mHdr.getUppVersion());
std::cout << " CUP TitleId: 0x" << std::hex << std::setw(16) << std::setfill('0') << mHdr.getUppId() << std::endl; fmt::print(" CUP TitleId: 0x{:016x}\n", mHdr.getUppId());
std::cout << " CUP Digest: " << fnd::SimpleTextOutput::arrayToString(mHdr.getUppHash(), 8, true, ":") << std::endl; fmt::print(" CUP Digest: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mHdr.getUppHash().data(), mHdr.getUppHash().size(), true, ":"));
} }
} }
bool nstool::GameCardProcess::validateRegionOfFile(size_t offset, size_t len, const byte_t* test_hash, bool use_salt, byte_t salt) bool nstool::GameCardProcess::validateRegionOfFile(int64_t offset, int64_t len, const byte_t* test_hash, bool use_salt, byte_t salt)
{ {
tc::ByteData scratch; // read region into memory
fnd::sha::sSha256Hash calc_hash; tc::ByteData scratch = tc::ByteData(tc::io::IOUtil::castInt64ToSize(len));
mFile->seek(offset, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
// update hash
tc::crypto::Sha256Generator sha256_gen;
sha256_gen.initialize();
sha256_gen.update(scratch.data(), scratch.size());
if (use_salt) if (use_salt)
{ sha256_gen.update(&salt, sizeof(salt));
scratch.alloc(len + 1);
scratch.data()[len] = salt;
}
else
{
scratch.alloc(len);
}
(*mFile)->read(scratch.data(), offset, len); // calculate hash
fnd::sha::Sha256(scratch.data(), scratch.size(), calc_hash.bytes); nn::hac::detail::sha256_hash_t calc_hash;
sha256_gen.getHash(calc_hash.data());
return calc_hash.compare(test_hash); return memcmp(calc_hash.data(), test_hash, calc_hash.size()) == 0;
} }
bool nstool::GameCardProcess::validateRegionOfFile(size_t offset, size_t len, const byte_t* test_hash) bool nstool::GameCardProcess::validateRegionOfFile(int64_t offset, int64_t len, const byte_t* test_hash)
{ {
return validateRegionOfFile(offset, len, test_hash, false, 0); return validateRegionOfFile(offset, len, test_hash, false, 0);
} }
void nstool::GameCardProcess::validateXciSignature() void nstool::GameCardProcess::validateXciSignature()
{ {
fnd::rsa::sRsa2048Key header_sign_key; if (mKeyCfg.xci_header_sign_key.isSet())
mKeyCfg.getXciHeaderSignKey(header_sign_key);
if (fnd::rsa::pkcs::rsaVerify(header_sign_key, fnd::sha::HASH_SHA256, mHdrHash.bytes, mHdrSignature) != 0)
{ {
std::cout << "[WARNING] GameCard Header Signature: FAIL" << std::endl; if (tc::crypto::VerifyRsa2048Pkcs1Sha256(mHdrSignature.data(), mHdrHash.data(), mKeyCfg.xci_header_sign_key.get()) == false)
{
fmt::print("[WARNING] GameCard Header Signature: FAIL\n");
}
}
else
{
fmt::print("[WARNING] GameCard Header Signature: FAIL (Failed to load rsa public key.)\n");
} }
} }
void nstool::GameCardProcess::processRootPfs() void nstool::GameCardProcess::processRootPfs()
{ {
if (mVerify && validateRegionOfFile(mHdr.getPartitionFsAddress(), mHdr.getPartitionFsSize(), mHdr.getPartitionFsHash().bytes, mHdr.getCompatibilityType() != nn::hac::gc::COMPAT_GLOBAL, mHdr.getCompatibilityType()) == false) if (mVerify && validateRegionOfFile(mHdr.getPartitionFsAddress(), mHdr.getPartitionFsSize(), mHdr.getPartitionFsHash().data(), mHdr.getCompatibilityType() != nn::hac::gc::COMPAT_GLOBAL, mHdr.getCompatibilityType()) == false)
{ {
std::cout << "[WARNING] GameCard Root HFS0: FAIL (bad hash)" << std::endl; fmt::print("[WARNING] GameCard Root HFS0: FAIL (bad hash)\n");
} }
mRootPfs.setInputFile(new fnd::OffsetAdjustedIFile(mFile, mHdr.getPartitionFsAddress(), mHdr.getPartitionFsSize()));
mRootPfs.setListFs(mListFs);
mRootPfs.setVerifyMode(false);
mRootPfs.setCliOutputMode(mCliOutputMode);
mRootPfs.setMountPointName(kXciMountPointName);
mRootPfs.process();
}
void nstool::GameCardProcess::processPartitionPfs() std::shared_ptr<tc::io::IStream> gc_fs_raw = std::make_shared<tc::io::SubStream>(tc::io::SubStream(mFile, mHdr.getPartitionFsAddress(), nn::hac::GameCardUtil::blockToAddr(mHdr.getValidDataEndPage()+1) - mHdr.getPartitionFsAddress()));
{
const std::vector<nn::hac::PartitionFsHeader::sFile>& rootPartitions = mRootPfs.getPfsHeader().getFileList();
for (size_t i = 0; i < rootPartitions.size(); i++)
{
// this must be validated here because only the size of the root partiton header is known at verification time
if (mVerify && validateRegionOfFile(mHdr.getPartitionFsAddress() + rootPartitions[i].offset, rootPartitions[i].hash_protected_size, rootPartitions[i].hash.bytes) == false)
{
std::cout << "[WARNING] GameCard " << rootPartitions[i].name << " Partition HFS0: FAIL (bad hash)" << std::endl;
}
PfsProcess tmp; auto gc_vfs_meta = nn::hac::GameCardFsMetaGenerator(gc_fs_raw, mHdr.getPartitionFsSize(), mVerify ? nn::hac::GameCardFsMetaGenerator::ValidationMode_Warn : nn::hac::GameCardFsMetaGenerator::ValidationMode_None);
tmp.setInputFile(new fnd::OffsetAdjustedIFile(mFile, mHdr.getPartitionFsAddress() + rootPartitions[i].offset, rootPartitions[i].size)); std::shared_ptr<tc::io::IStorage> gc_vfs = std::make_shared<tc::io::VirtualFileSystem>(tc::io::VirtualFileSystem(gc_vfs_meta) );
tmp.setListFs(mListFs);
tmp.setVerifyMode(mVerify); FsProcess fs_proc;
tmp.setCliOutputMode(mCliOutputMode);
tmp.setMountPointName(kXciMountPointName + rootPartitions[i].name); fs_proc.setInputFileSystem(gc_vfs);
if (mExtractInfo.hasElement<std::string>(rootPartitions[i].name)) fs_proc.setFsFormatName("PartitionFS");
tmp.setExtractPath(mExtractInfo.getElement<std::string>(rootPartitions[i].name).extract_path); fs_proc.setFsProperties({
fmt::format("Type: Nested HFS0"),
tmp.process(); fmt::format("DirNum: {:d}", gc_vfs_meta.dir_entries.empty() ? 0 : gc_vfs_meta.dir_entries.size() - 1), // -1 to not include root directory
} fmt::format("FileNum: {:d}", gc_vfs_meta.file_entries.size())
});
fs_proc.setShowFsInfo(mCliOutputMode.show_basic_info);
fs_proc.setShowFsTree(mListFs);
fs_proc.setFsRootLabel(kXciMountPointName);
fs_proc.setExtractJobs(mExtractJobs);
fs_proc.process();
} }

View file

@ -12,65 +12,46 @@ class GameCardProcess
public: public:
GameCardProcess(); GameCardProcess();
void process();
// generic // generic
void setInputFile(const std::shared_ptr<tc::io::IStream>& file); void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setKeyCfg(const KeyBag& keycfg); void setKeyCfg(const KeyBag& keycfg);
void setCliOutputMode(CliOutputMode type); void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify); void setVerifyMode(bool verify);
// xci specific // fs specific
void setPartitionForExtract(const std::string& partition_name, const tc::io::Path& extract_path); void setShowFsTree(bool show_fs_tree);
void setListFs(bool list_fs); void setExtractJobs(const std::vector<nstool::ExtractJob> extract_jobs);
void process();
private: private:
const std::string kModuleName = "GameCardProcess"; const std::string kXciMountPointName = "gamecard";
const std::string kXciMountPointName = "gamecard:/";
std::string mModuleName;
std::shared_ptr<tc::io::IStream> mFile; std::shared_ptr<tc::io::IStream> mFile;
KeyBag mKeyCfg; KeyBag mKeyCfg;
CliOutputMode mCliOutputMode; CliOutputMode mCliOutputMode;
bool mVerify; bool mVerify;
bool mListFs; bool mListFs;
struct sExtractInfo
{
std::string partition_name;
tc::io::Path extract_path;
void operator=(const sExtractInfo& other)
{
partition_name = other.partition_name;
extract_path = other.extract_path;
}
bool operator==(const tc::io::Path& name) const
{
return name == partition_name;
}
};
bool mIsTrueSdkXci; bool mIsTrueSdkXci;
bool mIsSdkXciEncrypted; bool mIsSdkXciEncrypted;
size_t mGcHeaderOffset; size_t mGcHeaderOffset;
bool mProccessExtendedHeader; bool mProccessExtendedHeader;
nn::hac::detail::rsa2048_signature_t mHdrSignature; nn::hac::detail::rsa2048_signature_t mHdrSignature;
fnd::sha::sSha256Hash mHdrHash; nn::hac::detail::sha256_hash_t mHdrHash;
nn::hac::GameCardHeader mHdr; nn::hac::GameCardHeader mHdr;
PfsProcess mRootPfs; PfsProcess mRootPfs;
std::vector<sExtractInfo> mExtractInfo; std::vector<nstool::ExtractJob> mExtractJobs;
void importHeader(); void importHeader();
void displayHeader(); void displayHeader();
bool validateRegionOfFile(size_t offset, size_t len, const byte_t* test_hash, bool use_salt, byte_t salt); bool validateRegionOfFile(int64_t offset, int64_t len, const byte_t* test_hash, bool use_salt, byte_t salt);
bool validateRegionOfFile(size_t offset, size_t len, const byte_t* test_hash); bool validateRegionOfFile(int64_t offset, int64_t len, const byte_t* test_hash);
void validateXciSignature(); void validateXciSignature();
void processRootPfs(); void processRootPfs();
void processPartitionPfs();
}; };
} }

View file

@ -17,19 +17,7 @@ nstool::PfsProcess::PfsProcess() :
mFileSystem(), mFileSystem(),
mFsProcess() mFsProcess()
{ {
mFsProcess.setFsLabel("PartitionFS"); mFsProcess.setFsFormatName("PartitionFS");
}
void nstool::PfsProcess::process()
{
importHeader();
if (mCliOutputMode.show_basic_info)
{
displayHeader();
}
mFsProcess.process();
} }
void nstool::PfsProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file) void nstool::PfsProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
@ -40,6 +28,7 @@ void nstool::PfsProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& fi
void nstool::PfsProcess::setCliOutputMode(CliOutputMode type) void nstool::PfsProcess::setCliOutputMode(CliOutputMode type)
{ {
mCliOutputMode = type; mCliOutputMode = type;
mFsProcess.setShowFsInfo(mCliOutputMode.show_basic_info);
} }
void nstool::PfsProcess::setVerifyMode(bool verify) void nstool::PfsProcess::setVerifyMode(bool verify)
@ -47,32 +36,22 @@ void nstool::PfsProcess::setVerifyMode(bool verify)
mVerify = verify; mVerify = verify;
} }
void nstool::PfsProcess::setMountPointName(const std::string& mount_name) void nstool::PfsProcess::setShowFsTree(bool show_fs_tree)
{ {
mFsProcess.setFsLabel(mount_name); mFsProcess.setShowFsTree(show_fs_tree);
} }
void nstool::PfsProcess::setExtractPath(const tc::io::Path& path) void nstool::PfsProcess::setFsRootLabel(const std::string& root_label)
{ {
mFsProcess.setExtractPath(path); mFsProcess.setFsRootLabel(root_label);
} }
void nstool::PfsProcess::setListFs(bool list_fs) void nstool::PfsProcess::setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs)
{ {
mFsProcess.setCliOutputMode(list_fs); mFsProcess.setExtractJobs(extract_jobs);
} }
const nn::hac::PartitionFsHeader& nstool::PfsProcess::getPfsHeader() const void nstool::PfsProcess::process()
{
return mPfs;
}
const std::shared_ptr<tc::io::IStorage>& nstool::PfsProcess::getFileSystem() const
{
return mFileSystem;
}
void nstool::PfsProcess::importHeader()
{ {
if (mFile == nullptr) if (mFile == nullptr)
{ {
@ -116,13 +95,24 @@ void nstool::PfsProcess::importHeader()
// create virtual filesystem // create virtual filesystem
mFileSystem = std::make_shared<tc::io::VirtualFileSystem>(tc::io::VirtualFileSystem(nn::hac::PartitionFsMetaGenerator(mFile, mVerify ? nn::hac::PartitionFsMetaGenerator::ValidationMode_Warn : nn::hac::PartitionFsMetaGenerator::ValidationMode_None))); mFileSystem = std::make_shared<tc::io::VirtualFileSystem>(tc::io::VirtualFileSystem(nn::hac::PartitionFsMetaGenerator(mFile, mVerify ? nn::hac::PartitionFsMetaGenerator::ValidationMode_Warn : nn::hac::PartitionFsMetaGenerator::ValidationMode_None)));
mFsProcess.setInputFileSystem(mFileSystem); mFsProcess.setInputFileSystem(mFileSystem);
// set properties for FsProcess
mFsProcess.setFsProperties({
fmt::format("Type: {:s}", nn::hac::PartitionFsUtil::getFsTypeAsString(mPfs.getFsType())),
fmt::format("FileNum: {:d}", mPfs.getFileList().size())
});
mFsProcess.process();
} }
void nstool::PfsProcess::displayHeader() const nn::hac::PartitionFsHeader& nstool::PfsProcess::getPfsHeader() const
{ {
fmt::print("[PartitionFS]\n"); return mPfs;
fmt::print(" Type: {:s}\n", nn::hac::PartitionFsUtil::getFsTypeAsString(mPfs.getFsType())); }
fmt::print(" FileNum: {:d}\n", mPfs.getFileList().size());
const std::shared_ptr<tc::io::IStorage>& nstool::PfsProcess::getFileSystem() const
{
return mFileSystem;
} }
size_t nstool::PfsProcess::determineHeaderSize(const nn::hac::sPfsHeader* hdr) size_t nstool::PfsProcess::determineHeaderSize(const nn::hac::sPfsHeader* hdr)

View file

@ -11,18 +11,19 @@ class PfsProcess
public: public:
PfsProcess(); PfsProcess();
void process();
// generic // generic
void setInputFile(const std::shared_ptr<tc::io::IStream>& file); void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setCliOutputMode(CliOutputMode type); void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify); void setVerifyMode(bool verify);
// pfs specific // fs specific
void setMountPointName(const std::string& mount_name); void setShowFsTree(bool show_fs_tree);
void setExtractPath(const tc::io::Path& path); void setFsRootLabel(const std::string& root_label);
void setListFs(bool list_fs); void setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs);
void process();
// post process() get PFS/FS out
const nn::hac::PartitionFsHeader& getPfsHeader() const; const nn::hac::PartitionFsHeader& getPfsHeader() const;
const std::shared_ptr<tc::io::IStorage>& getFileSystem() const; const std::shared_ptr<tc::io::IStorage>& getFileSystem() const;
@ -39,9 +40,7 @@ private:
std::shared_ptr<tc::io::IStorage> mFileSystem; std::shared_ptr<tc::io::IStorage> mFileSystem;
FsProcess mFsProcess; FsProcess mFsProcess;
void importHeader();
void displayHeader();
size_t determineHeaderSize(const nn::hac::sPfsHeader* hdr); size_t determineHeaderSize(const nn::hac::sPfsHeader* hdr);
bool validateHeaderMagic(const nn::hac::sPfsHeader* hdr); bool validateHeaderMagic(const nn::hac::sPfsHeader* hdr);
}; };

View file

@ -15,19 +15,7 @@ nstool::RomfsProcess::RomfsProcess() :
mFileSystem(), mFileSystem(),
mFsProcess() mFsProcess()
{ {
mFsProcess.setFsLabel("RomFS"); mFsProcess.setFsFormatName("RomFS");
}
void nstool::RomfsProcess::process()
{
importHeader();
if (mCliOutputMode.show_basic_info)
{
displayHeader();
}
mFsProcess.process();
} }
void nstool::RomfsProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file) void nstool::RomfsProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
@ -38,6 +26,7 @@ void nstool::RomfsProcess::setInputFile(const std::shared_ptr<tc::io::IStream>&
void nstool::RomfsProcess::setCliOutputMode(CliOutputMode type) void nstool::RomfsProcess::setCliOutputMode(CliOutputMode type)
{ {
mCliOutputMode = type; mCliOutputMode = type;
mFsProcess.setShowFsInfo(mCliOutputMode.show_basic_info);
} }
void nstool::RomfsProcess::setVerifyMode(bool verify) void nstool::RomfsProcess::setVerifyMode(bool verify)
@ -45,27 +34,22 @@ void nstool::RomfsProcess::setVerifyMode(bool verify)
mVerify = verify; mVerify = verify;
} }
void nstool::RomfsProcess::setMountPointName(const std::string& mount_name) void nstool::RomfsProcess::setFsRootLabel(const std::string& root_label)
{ {
mFsProcess.setFsLabel(mount_name); mFsProcess.setFsRootLabel(root_label);
} }
void nstool::RomfsProcess::setExtractPath(const tc::io::Path& path) void nstool::RomfsProcess::setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs)
{ {
mFsProcess.setExtractPath(path); mFsProcess.setExtractJobs(extract_jobs);
} }
void nstool::RomfsProcess::setListFs(bool list_fs) void nstool::RomfsProcess::setShowFsTree(bool list_fs)
{ {
mFsProcess.setCliOutputMode(list_fs); mFsProcess.setShowFsTree(list_fs);
} }
const std::shared_ptr<tc::io::IStorage>& nstool::RomfsProcess::getFileSystem() const void nstool::RomfsProcess::process()
{
return mFileSystem;
}
void nstool::RomfsProcess::importHeader()
{ {
if (mFile == nullptr) if (mFile == nullptr)
{ {
@ -132,11 +116,13 @@ void nstool::RomfsProcess::importHeader()
// create virtual filesystem // create virtual filesystem
mFileSystem = std::make_shared<tc::io::VirtualFileSystem>(tc::io::VirtualFileSystem(nn::hac::RomFsMetaGenerator(mFile))); mFileSystem = std::make_shared<tc::io::VirtualFileSystem>(tc::io::VirtualFileSystem(nn::hac::RomFsMetaGenerator(mFile)));
mFsProcess.setInputFileSystem(mFileSystem); mFsProcess.setInputFileSystem(mFileSystem);
}
void nstool::RomfsProcess::displayHeader() // set properties for FsProcess
{ mFsProcess.setFsProperties({
fmt::print("[RomFS]\n"); fmt::format("DirNum: {:d}", mDirNum),
fmt::print(" DirNum: {:d}\n", mDirNum); fmt::format("FileNum: {:d}", mFileNum)
fmt::print(" FileNum: {:d}\n", mFileNum); });
// process filesystem
mFsProcess.process();
} }

View file

@ -11,20 +11,17 @@ class RomfsProcess
public: public:
RomfsProcess(); RomfsProcess();
void process();
// generic // generic
void setInputFile(const std::shared_ptr<tc::io::IStream>& file); void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setCliOutputMode(CliOutputMode type); void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify); void setVerifyMode(bool verify);
// pfs specific // fs specific
void setMountPointName(const std::string& mount_name); void setFsRootLabel(const std::string& root_label);
void setExtractPath(const tc::io::Path& path); void setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs);
void setListFs(bool list_fs); void setShowFsTree(bool show_fs_tree);
const std::shared_ptr<tc::io::IStorage>& getFileSystem() const;
void process();
private: private:
static const size_t kCacheSize = 0x10000; static const size_t kCacheSize = 0x10000;
@ -40,9 +37,6 @@ private:
std::shared_ptr<tc::io::IStorage> mFileSystem; std::shared_ptr<tc::io::IStorage> mFileSystem;
FsProcess mFsProcess; FsProcess mFsProcess;
void importHeader();
void displayHeader();
}; };
} }

View file

@ -350,6 +350,68 @@ private:
std::vector<std::string> mOptStrings; std::vector<std::string> mOptStrings;
}; };
class ExtractDataPathOptionHandler : public tc::cli::OptionParser::IOptionHandler
{
public:
ExtractDataPathOptionHandler(std::vector<nstool::ExtractJob>& jobs, const std::vector<std::string>& opts) :
mJobs(jobs),
mOptStrings(opts)
{}
const std::vector<std::string>& getOptionStrings() const
{
return mOptStrings;
}
void processOption(const std::string& option, const std::vector<std::string>& params)
{
if (params.size() == 1)
{
mJobs.push_back({tc::io::Path("/"), tc::io::Path(params[0])});
}
else if (params.size() == 2)
{
mJobs.push_back({tc::io::Path(params[0]), tc::io::Path(params[1])});
}
else
{
throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires parameters in the format \"[<internal path>] <extract path>\".", option));
}
}
private:
std::vector<nstool::ExtractJob>& mJobs;
std::vector<std::string> mOptStrings;
};
class CustomExtractDataPathOptionHandler : public tc::cli::OptionParser::IOptionHandler
{
public:
CustomExtractDataPathOptionHandler(std::vector<nstool::ExtractJob>& jobs, const std::vector<std::string>& opts, const tc::io::Path& custom_path) :
mJobs(jobs),
mOptStrings(opts),
mCustomPath(custom_path)
{}
const std::vector<std::string>& getOptionStrings() const
{
return mOptStrings;
}
void processOption(const std::string& option, const std::vector<std::string>& params)
{
if (params.size() != 1)
{
throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option));
}
mJobs.push_back({mCustomPath, tc::io::Path(params[0])});
}
private:
std::vector<nstool::ExtractJob>& mJobs;
std::vector<std::string> mOptStrings;
tc::io::Path mCustomPath;
};
nstool::SettingsInitializer::SettingsInitializer(const std::vector<std::string>& args) : nstool::SettingsInitializer::SettingsInitializer(const std::vector<std::string>& args) :
Settings(), Settings(),
mModuleLabel("nstool::SettingsInitializer"), mModuleLabel("nstool::SettingsInitializer"),
@ -484,19 +546,19 @@ void nstool::SettingsInitializer::parse_args(const std::vector<std::string>& arg
// fs options // fs options
opts.registerOptionHandler(std::shared_ptr<FlagOptionHandler>(new FlagOptionHandler(fs.show_fs_tree, { "--listfs" }))); opts.registerOptionHandler(std::shared_ptr<FlagOptionHandler>(new FlagOptionHandler(fs.show_fs_tree, { "--listfs" })));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(fs.extract_path, { "--fsdir" }))); opts.registerOptionHandler(std::shared_ptr<ExtractDataPathOptionHandler>(new ExtractDataPathOptionHandler(fs.extract_jobs, { "--fsdir", "-x", "--extract" })));
// xci options // xci options
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(xci.update_extract_path, { "--update" }))); opts.registerOptionHandler(std::shared_ptr<CustomExtractDataPathOptionHandler>(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--update" }, tc::io::Path("/update/"))));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(xci.normal_extract_path, { "--normal" }))); opts.registerOptionHandler(std::shared_ptr<CustomExtractDataPathOptionHandler>(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--normal" }, tc::io::Path("/normal/"))));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(xci.secure_extract_path, { "--secure" }))); opts.registerOptionHandler(std::shared_ptr<CustomExtractDataPathOptionHandler>(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--secure" }, tc::io::Path("/secure/"))));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(xci.logo_extract_path, { "--logo" }))); opts.registerOptionHandler(std::shared_ptr<CustomExtractDataPathOptionHandler>(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--logo" }, tc::io::Path("/logo/"))));
// nca options // nca options
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(nca.part0_extract_path, { "--part0" }))); opts.registerOptionHandler(std::shared_ptr<CustomExtractDataPathOptionHandler>(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--part0" }, tc::io::Path("/0/"))));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(nca.part1_extract_path, { "--part1" }))); opts.registerOptionHandler(std::shared_ptr<CustomExtractDataPathOptionHandler>(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--part1" }, tc::io::Path("/1/"))));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(nca.part2_extract_path, { "--part2" }))); opts.registerOptionHandler(std::shared_ptr<CustomExtractDataPathOptionHandler>(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--part2" }, tc::io::Path("/2/"))));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(nca.part3_extract_path, { "--part3" }))); opts.registerOptionHandler(std::shared_ptr<CustomExtractDataPathOptionHandler>(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--part3" }, tc::io::Path("/3/"))));
// kip options // kip options
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(kip.extract_path, { "--kipdir" }))); opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(kip.extract_path, { "--kipdir" })));
@ -552,7 +614,7 @@ void nstool::SettingsInitializer::determine_filetype()
// detect ROMFS // detect ROMFS
else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sRomfsHeader)) else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sRomfsHeader))
&& _TYPE_PTR(nn::hac::sRomfsHeader)->header_size.unwrap() == sizeof(nn::hac::sRomfsHeader) && _TYPE_PTR(nn::hac::sRomfsHeader)->header_size.unwrap() == sizeof(nn::hac::sRomfsHeader)
&& _TYPE_PTR(nn::hac::sRomfsHeader)->sections[1].offset.unwrap() == (_TYPE_PTR(nn::hac::sRomfsHeader)->sections[0].offset.unwrap() + _TYPE_PTR(nn::hac::sRomfsHeader)->sections[0].size.unwrap())) && _TYPE_PTR(nn::hac::sRomfsHeader)->dir_entry.offset.unwrap() == (_TYPE_PTR(nn::hac::sRomfsHeader)->dir_hash_bucket.offset.unwrap() + _TYPE_PTR(nn::hac::sRomfsHeader)->dir_hash_bucket.size.unwrap()))
{ {
infile.filetype = FILE_TYPE_ROMFS; infile.filetype = FILE_TYPE_ROMFS;
} }

View file

@ -57,7 +57,7 @@ struct Settings
struct FsOptions struct FsOptions
{ {
bool show_fs_tree; bool show_fs_tree;
tc::Optional<tc::io::Path> extract_path; std::vector<ExtractJob> extract_jobs;
} fs; } fs;
// XCI options // XCI options
@ -105,18 +105,8 @@ struct Settings
code.list_symbols = false; code.list_symbols = false;
code.is_64bit_instruction = true; code.is_64bit_instruction = true;
xci.update_extract_path = tc::Optional<tc::io::Path>();
xci.logo_extract_path = tc::Optional<tc::io::Path>();
xci.normal_extract_path = tc::Optional<tc::io::Path>();
xci.secure_extract_path = tc::Optional<tc::io::Path>();
fs.show_fs_tree = false; fs.show_fs_tree = false;
fs.extract_path = tc::Optional<tc::io::Path>(); fs.extract_jobs = std::vector<ExtractJob>();
nca.part0_extract_path = tc::Optional<tc::io::Path>();
nca.part1_extract_path = tc::Optional<tc::io::Path>();
nca.part2_extract_path = tc::Optional<tc::io::Path>();
nca.part3_extract_path = tc::Optional<tc::io::Path>();
kip.extract_path = tc::Optional<tc::io::Path>(); kip.extract_path = tc::Optional<tc::io::Path>();

View file

@ -3,7 +3,7 @@
#include "Settings.h" #include "Settings.h"
//#include "GameCardProcess.h" #include "GameCardProcess.h"
#include "PfsProcess.h" #include "PfsProcess.h"
#include "RomfsProcess.h" #include "RomfsProcess.h"
//#include "NcaProcess.h" //#include "NcaProcess.h"
@ -27,7 +27,6 @@ int umain(const std::vector<std::string>& args, const std::vector<std::string>&
std::shared_ptr<tc::io::IStream> infile_stream = std::make_shared<tc::io::FileStream>(tc::io::FileStream(set.infile.path.get(), tc::io::FileMode::Open, tc::io::FileAccess::Read)); std::shared_ptr<tc::io::IStream> infile_stream = std::make_shared<tc::io::FileStream>(tc::io::FileStream(set.infile.path.get(), tc::io::FileMode::Open, tc::io::FileAccess::Read));
/*
if (set.infile.filetype == nstool::Settings::FILE_TYPE_GAMECARD) if (set.infile.filetype == nstool::Settings::FILE_TYPE_GAMECARD)
{ {
nstool::GameCardProcess obj; nstool::GameCardProcess obj;
@ -38,29 +37,22 @@ int umain(const std::vector<std::string>& args, const std::vector<std::string>&
obj.setCliOutputMode(set.opt.cli_output_mode); obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify); obj.setVerifyMode(set.opt.verify);
if (set.xci.update_extract_path.isSet()) obj.setShowFsTree(set.fs.show_fs_tree);
obj.setPartitionForExtract(nn::hac::gc::kUpdatePartitionStr, set.xci.update_extract_path.get()); obj.setExtractJobs(set.fs.extract_jobs);
if (set.xci.logo_extract_path.isSet())
obj.setPartitionForExtract(nn::hac::gc::kLogoPartitionStr, set.xci.logo_extract_path.get());
if (set.xci.normal_extract_path.isSet())
obj.setPartitionForExtract(nn::hac::gc::kNormalPartitionStr, set.xci.normal_extract_path.get());
if (set.xci.secure_extract_path.isSet())
obj.setPartitionForExtract(nn::hac::gc::kSecurePartitionStr, set.xci.secure_extract_path.get());
obj.setListFs(set.fs.show_fs_tree);
obj.process(); obj.process();
} }
else*/ if (set.infile.filetype == nstool::Settings::FILE_TYPE_PARTITIONFS || set.infile.filetype == nstool::Settings::FILE_TYPE_NSP) else if (set.infile.filetype == nstool::Settings::FILE_TYPE_PARTITIONFS || set.infile.filetype == nstool::Settings::FILE_TYPE_NSP)
{ {
nstool::PfsProcess obj; nstool::PfsProcess obj;
obj.setInputFile(infile_stream); obj.setInputFile(infile_stream);
obj.setCliOutputMode(set.opt.cli_output_mode); obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify); obj.setVerifyMode(set.opt.verify);
if (set.fs.extract_path.isSet()) obj.setShowFsTree(set.fs.show_fs_tree);
obj.setExtractPath(set.fs.extract_path.get()); obj.setExtractJobs(set.fs.extract_jobs);
obj.setListFs(set.fs.show_fs_tree);
obj.process(); obj.process();
} }
@ -73,9 +65,8 @@ int umain(const std::vector<std::string>& args, const std::vector<std::string>&
obj.setCliOutputMode(set.opt.cli_output_mode); obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify); obj.setVerifyMode(set.opt.verify);
if (set.fs.extract_path.isSet()) obj.setShowFsTree(set.fs.show_fs_tree);
obj.setExtractPath(set.fs.extract_path.get()); obj.setExtractJobs(set.fs.extract_jobs);
obj.setListFs(set.fs.show_fs_tree);
obj.process(); obj.process();
} }
@ -234,9 +225,8 @@ int umain(const std::vector<std::string>& args, const std::vector<std::string>&
if (set.aset.nacp_extract_path.isSet()) if (set.aset.nacp_extract_path.isSet())
obj.setNacpExtractPath(set.aset.nacp_extract_path.get()); obj.setNacpExtractPath(set.aset.nacp_extract_path.get());
if (set.fs.extract_path.isSet()) obj.setRomfsShowFsTree(set.fs.show_fs_tree);
obj.setRomfsExtractPath(set.fs.extract_path.get()); obj.setRomfsExtractJobs(set.fs.extract_jobs);
obj.setListFs(set.fs.show_fs_tree);
obj.process(); obj.process();
} }