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

@ -10,14 +10,6 @@ nstool::AssetProcess::AssetProcess() :
{
}
void nstool::AssetProcess::process()
{
importHeader();
if (mCliOutputMode.show_basic_info)
displayHeader();
processSections();
}
void nstool::AssetProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
{
mFile = file;
@ -33,11 +25,6 @@ void nstool::AssetProcess::setVerifyMode(bool verify)
mVerify = verify;
}
void nstool::AssetProcess::setListFs(bool list)
{
mRomfs.setListFs(list);
}
void nstool::AssetProcess::setIconExtractPath(const tc::io::Path& path)
{
mIconExtractPath = path;
@ -48,11 +35,23 @@ void nstool::AssetProcess::setNacpExtractPath(const tc::io::Path& 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()
{

View file

@ -12,18 +12,17 @@ class AssetProcess
public:
AssetProcess();
void process();
void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify);
void setListFs(bool list);
void setIconExtractPath(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:
std::string mModuleName;

View file

@ -1,11 +1,20 @@
#include "FsProcess.h"
#include <iostream>
#include "util.h"
#include <memory>
#include <tc/io/FileNotFoundException.h>
#include <tc/io/DirectoryNotFoundException.h>
nstool::FsProcess::FsProcess() :
mModuleLabel("nstool::FsProcess"),
mInputFs(),
mShowFs(false),
mExtractPath()
mFsFormatName(),
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;
}
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()
@ -37,23 +61,147 @@ void nstool::FsProcess::process()
throw tc::InvalidOperationException(mModuleLabel, "No input filesystem");
}
if (mShowFs)
printFs();
if (mShowFsInfo)
{
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();
}
}
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);
}
void nstool::FsProcess::extractFs()
{
fmt::print("[{:s}FsExtract]\n", (mFsLabel.isSet() ? (mFsLabel.get() + "/") : ""));
visitDir(tc::io::Path("/"), mExtractPath.get(), true, false);
fmt::print("[{:s}/Extract]\n", (mFsFormatName.isSet() ? mFsFormatName.get() : "FileSystem"));
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)
@ -67,9 +215,9 @@ void nstool::FsProcess::visitDir(const tc::io::Path& v_path, const tc::io::Path&
if (print_fs)
{
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)
{
@ -78,7 +226,6 @@ void nstool::FsProcess::visitDir(const tc::io::Path& v_path, const tc::io::Path&
}
// iterate thru child files
tc::ByteData cache = tc::ByteData(0x10000);
size_t cache_read_len;
tc::io::Path out_path;
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);
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)
{
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);
}

View file

@ -13,18 +13,33 @@ public:
FsProcess();
void setInputFileSystem(const std::shared_ptr<tc::io::IStorage>& input_fs);
void setFsLabel(const std::string& fs_label);
void setCliOutputMode(bool show_fs);
void setExtractPath(const tc::io::Path& extract_path);
void setFsFormatName(const std::string& fs_format_name);
void setFsProperties(const std::vector<std::string>& properties);
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();
private:
std::string mModuleLabel;
std::shared_ptr<tc::io::IStorage> mInputFs;
tc::Optional<std::string> mFsLabel;
bool mShowFs;
tc::Optional<tc::io::Path> mExtractPath;
// fs info
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 extractFs();

View file

@ -1,20 +1,25 @@
#include <iostream>
#include <iomanip>
#include <fnd/SimpleTextOutput.h>
#include <fnd/OffsetAdjustedIFile.h>
#include "GameCardProcess.h"
#include <tc/crypto.h>
#include <tc/io/IOUtil.h>
#include <nn/hac/GameCardUtil.h>
#include <nn/hac/ContentMetaUtil.h>
#include <nn/hac/ContentArchiveUtil.h>
#include "GameCardProcess.h"
#include <nn/hac/GameCardFsMetaGenerator.h>
#include "FsProcess.h"
nstool::GameCardProcess::GameCardProcess() :
mModuleName("nstool::GameCardProcess"),
mFile(),
mCliOutputMode(true, false, false, false),
mVerify(false),
mListFs(false),
mProccessExtendedHeader(false),
mRootPfs(),
mExtractInfo()
mExtractJobs()
{
}
@ -30,11 +35,8 @@ void nstool::GameCardProcess::process()
if (mCliOutputMode.show_basic_info)
displayHeader();
// process root partition
// process nested HFS0
processRootPfs();
// process partitions
processPartitionPfs();
}
void nstool::GameCardProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
@ -57,60 +59,69 @@ void nstool::GameCardProcess::setVerifyMode(bool 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()
{
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
scratch.alloc(sizeof(nn::hac::sSdkGcHeader));
tc::ByteData scratch = tc::ByteData(sizeof(nn::hac::sSdkGcHeader));
// 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
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;
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;
mGcHeaderOffset = 0;
}
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);
// 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
memcpy(mHdrSignature, hdr_ptr->signature, fnd::rsa::kRsa2048Size);
memcpy(mHdrSignature.data(), hdr_ptr->signature.data(), mHdrSignature.size());
// decrypt extended header
KeyBag::aes128_key_t header_key;
if (mKeyCfg.getXciHeaderKey(header_key))
byte_t xci_header_key_index = hdr_ptr->header.key_flag & 7;
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;
}
@ -120,164 +131,156 @@ void nstool::GameCardProcess::importHeader()
void nstool::GameCardProcess::displayHeader()
{
std::cout << "[GameCard Header]" << std::endl;
std::cout << " CardHeaderVersion: " << std::dec << (uint32_t)mHdr.getCardHeaderVersion() << std::endl;
std::cout << " RomSize: " << nn::hac::GameCardUtil::getRomSizeAsString((nn::hac::gc::RomSize)mHdr.getRomSizeType());
const nn::hac::sGcHeader* raw_hdr = (const nn::hac::sGcHeader*)mHdr.getBytes().data();
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)
std::cout << " (0x" << std::hex << (uint32_t)mHdr.getRomSizeType() << ")";
std::cout << std::endl;
std::cout << " PackageId: 0x" << std::hex << std::setw(16) << std::setfill('0') << mHdr.getPackageId() << std::endl;
std::cout << " Flags: 0x" << std::dec << (uint32_t)mHdr.getFlags() << std::endl;
if (mHdr.getFlags() != 0)
fmt::print(" (0x{:x})", mHdr.getRomSizeType());
fmt::print("\n");
fmt::print(" PackageId: 0x{:016x}\n", mHdr.getPackageId());
fmt::print(" Flags: 0x{:02x}\n", *((byte_t*)&raw_hdr->flags));
for (auto itr = mHdr.getFlags().begin(); itr != mHdr.getFlags().end(); itr++)
{
for (uint32_t i = 0; i < 8; i++)
{
if (_HAS_BIT(mHdr.getFlags(), i))
{
std::cout << " " << nn::hac::GameCardUtil::getHeaderFlagsAsString((nn::hac::gc::HeaderFlags)i) << std::endl;
}
fmt::print(" {:s}\n", nn::hac::GameCardUtil::getHeaderFlagsAsString((nn::hac::gc::HeaderFlags)*itr));
}
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)
{
std::cout << " KekIndex: " << nn::hac::GameCardUtil::getKekIndexAsString((nn::hac::gc::KekIndex)mHdr.getKekIndex()) << " (" << std::dec << (uint32_t)mHdr.getKekIndex() << ")" << std::endl;
std::cout << " TitleKeyDecIndex: " << std::dec << (uint32_t)mHdr.getTitleKeyDecIndex() << std::endl;
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;
fmt::print(" Extended Header AesCbc IV:\n");
fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mHdr.getAesCbcIv().data(), mHdr.getAesCbcIv().size(), true, ":"));
}
if (mCliOutputMode.show_extended_info)
{
std::cout << " Extended Header AesCbc IV:" << std::endl;
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;
fmt::print(" SelSec: 0x{:x}\n", mHdr.getSelSec());
fmt::print(" SelT1Key: 0x{:x}\n", mHdr.getSelT1Key());
fmt::print(" SelKey: 0x{:x}\n", mHdr.getSelKey());
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))
std::cout << " (0x" << std::hex << nn::hac::GameCardUtil::blockToAddr(mHdr.getRomAreaStartPage()) << ")";
std::cout << std::endl;
fmt::print(" (0x{:x})", nn::hac::GameCardUtil::blockToAddr(mHdr.getRomAreaStartPage()));
fmt::print("\n");
std::cout << " BackupAreaStartPage: 0x" << std::hex << mHdr.getBackupAreaStartPage();
fmt::print(" BackupAreaStartPage: 0x{:x}", mHdr.getBackupAreaStartPage());
if (mHdr.getBackupAreaStartPage() != (uint32_t)(-1))
std::cout << " (0x" << std::hex << nn::hac::GameCardUtil::blockToAddr(mHdr.getBackupAreaStartPage()) << ")";
std::cout << std::endl;
fmt::print(" (0x{:x})", nn::hac::GameCardUtil::blockToAddr(mHdr.getBackupAreaStartPage()));
fmt::print("\n");
std::cout << " ValidDataEndPage: 0x" << std::hex << mHdr.getValidDataEndPage();
fmt::print(" ValidDataEndPage: 0x{:x}", mHdr.getValidDataEndPage());
if (mHdr.getValidDataEndPage() != (uint32_t)(-1))
std::cout << " (0x" << std::hex << nn::hac::GameCardUtil::blockToAddr(mHdr.getValidDataEndPage()) << ")";
std::cout << std::endl;
fmt::print(" (0x{:x})", nn::hac::GameCardUtil::blockToAddr(mHdr.getValidDataEndPage()));
fmt::print("\n");
std::cout << " LimArea: 0x" << std::hex << mHdr.getLimAreaPage();
fmt::print(" LimArea: 0x{:x}", mHdr.getLimAreaPage());
if (mHdr.getLimAreaPage() != (uint32_t)(-1))
std::cout << " (0x" << std::hex << nn::hac::GameCardUtil::blockToAddr(mHdr.getLimAreaPage()) << ")";
std::cout << std::endl;
fmt::print(" (0x{:x})", nn::hac::GameCardUtil::blockToAddr(mHdr.getLimAreaPage()));
fmt::print("\n");
std::cout << " PartitionFs Header:" << std::endl;
std::cout << " Offset: 0x" << std::hex << mHdr.getPartitionFsAddress() << std::endl;
std::cout << " Size: 0x" << std::hex << mHdr.getPartitionFsSize() << std::endl;
fmt::print(" PartitionFs Header:\n");
fmt::print(" Offset: 0x{:x}\n", mHdr.getPartitionFsAddress());
fmt::print(" Size: 0x{:x}\n", mHdr.getPartitionFsSize());
if (mCliOutputMode.show_extended_info)
{
std::cout << " Hash:" << std::endl;
std::cout << " " << fnd::SimpleTextOutput::arrayToString(mHdr.getPartitionFsHash().bytes, 0x10, true, ":") << std::endl;
std::cout << " " << fnd::SimpleTextOutput::arrayToString(mHdr.getPartitionFsHash().bytes+0x10, 0x10, true, ":") << std::endl;
fmt::print(" Hash:\n");
fmt::print(" {:s}", tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(mHdr.getPartitionFsHash().data(), mHdr.getPartitionFsHash().size(), true, ":", 0x10, 6, false));
}
}
if (mProccessExtendedHeader)
{
std::cout << "[GameCard Extended Header]" << std::endl;
std::cout << " FwVersion: v" << std::dec << mHdr.getFwVersion() << " (" << nn::hac::GameCardUtil::getCardFwVersionDescriptionAsString((nn::hac::gc::FwVersion)mHdr.getFwVersion()) << ")" << std::endl;
std::cout << " AccCtrl1: 0x" << std::hex << mHdr.getAccCtrl1() << std::endl;
std::cout << " CardClockRate: " << nn::hac::GameCardUtil::getCardClockRateAsString((nn::hac::gc::CardClockRate)mHdr.getAccCtrl1()) << std::endl;
std::cout << " Wait1TimeRead: 0x" << std::hex << mHdr.getWait1TimeRead() << std::endl;
std::cout << " Wait2TimeRead: 0x" << std::hex << mHdr.getWait2TimeRead() << std::endl;
std::cout << " Wait1TimeWrite: 0x" << std::hex << mHdr.getWait1TimeWrite() << std::endl;
std::cout << " Wait2TimeWrite: 0x" << std::hex << mHdr.getWait2TimeWrite() << std::endl;
std::cout << " SdkAddon Version: " << nn::hac::ContentArchiveUtil::getSdkAddonVersionAsString(mHdr.getFwMode()) << " (v" << std::dec << mHdr.getFwMode() << ")" << std::endl;
std::cout << " CompatibilityType: " << nn::hac::GameCardUtil::getCompatibilityTypeAsString((nn::hac::gc::CompatibilityType)mHdr.getCompatibilityType()) << " (" << std::dec << (uint32_t) mHdr.getCompatibilityType() << ")" << std::endl;
std::cout << " Update Partition Info:" << std::endl;
std::cout << " CUP Version: " << nn::hac::ContentMetaUtil::getVersionAsString(mHdr.getUppVersion()) << " (v" << std::dec << mHdr.getUppVersion() << ")" << std::endl;
std::cout << " CUP TitleId: 0x" << std::hex << std::setw(16) << std::setfill('0') << mHdr.getUppId() << std::endl;
std::cout << " CUP Digest: " << fnd::SimpleTextOutput::arrayToString(mHdr.getUppHash(), 8, true, ":") << std::endl;
fmt::print("[GameCard/ExtendedHeader]\n");
fmt::print(" FwVersion: v{:d} ({:s})\n", mHdr.getFwVersion(), nn::hac::GameCardUtil::getCardFwVersionDescriptionAsString((nn::hac::gc::FwVersion)mHdr.getFwVersion()));
fmt::print(" AccCtrl1: 0x{:x}\n", mHdr.getAccCtrl1());
fmt::print(" CardClockRate: {:s}\n", nn::hac::GameCardUtil::getCardClockRateAsString((nn::hac::gc::CardClockRate)mHdr.getAccCtrl1()));
fmt::print(" Wait1TimeRead: 0x{:x}\n", mHdr.getWait1TimeRead());
fmt::print(" Wait2TimeRead: 0x{:x}\n", mHdr.getWait2TimeRead());
fmt::print(" Wait1TimeWrite: 0x{:x}\n", mHdr.getWait1TimeWrite());
fmt::print(" Wait2TimeWrite: 0x{:x}\n", mHdr.getWait2TimeWrite());
fmt::print(" SdkAddon Version: {:s} (v{:d})\n", nn::hac::ContentArchiveUtil::getSdkAddonVersionAsString(mHdr.getFwMode()), mHdr.getFwMode());
fmt::print(" CompatibilityType: {:s} ({:d})\n", nn::hac::GameCardUtil::getCompatibilityTypeAsString((nn::hac::gc::CompatibilityType)mHdr.getCompatibilityType()), mHdr.getCompatibilityType());
fmt::print(" Update Partition Info:\n");
fmt::print(" CUP Version: {:s} (v{:d})\n", nn::hac::ContentMetaUtil::getVersionAsString(mHdr.getUppVersion()), mHdr.getUppVersion());
fmt::print(" CUP TitleId: 0x{:016x}\n", mHdr.getUppId());
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;
fnd::sha::sSha256Hash calc_hash;
// read region into memory
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)
{
scratch.alloc(len + 1);
scratch.data()[len] = salt;
}
else
{
scratch.alloc(len);
}
sha256_gen.update(&salt, sizeof(salt));
(*mFile)->read(scratch.data(), offset, len);
fnd::sha::Sha256(scratch.data(), scratch.size(), calc_hash.bytes);
// calculate hash
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);
}
void nstool::GameCardProcess::validateXciSignature()
{
fnd::rsa::sRsa2048Key header_sign_key;
mKeyCfg.getXciHeaderSignKey(header_sign_key);
if (fnd::rsa::pkcs::rsaVerify(header_sign_key, fnd::sha::HASH_SHA256, mHdrHash.bytes, mHdrSignature) != 0)
if (mKeyCfg.xci_header_sign_key.isSet())
{
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()
{
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;
}
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()
{
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;
tmp.setInputFile(new fnd::OffsetAdjustedIFile(mFile, mHdr.getPartitionFsAddress() + rootPartitions[i].offset, rootPartitions[i].size));
tmp.setListFs(mListFs);
tmp.setVerifyMode(mVerify);
tmp.setCliOutputMode(mCliOutputMode);
tmp.setMountPointName(kXciMountPointName + rootPartitions[i].name);
if (mExtractInfo.hasElement<std::string>(rootPartitions[i].name))
tmp.setExtractPath(mExtractInfo.getElement<std::string>(rootPartitions[i].name).extract_path);
tmp.process();
fmt::print("[WARNING] GameCard Root HFS0: FAIL (bad hash)\n");
}
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()));
auto gc_vfs_meta = nn::hac::GameCardFsMetaGenerator(gc_fs_raw, mHdr.getPartitionFsSize(), mVerify ? nn::hac::GameCardFsMetaGenerator::ValidationMode_Warn : nn::hac::GameCardFsMetaGenerator::ValidationMode_None);
std::shared_ptr<tc::io::IStorage> gc_vfs = std::make_shared<tc::io::VirtualFileSystem>(tc::io::VirtualFileSystem(gc_vfs_meta) );
FsProcess fs_proc;
fs_proc.setInputFileSystem(gc_vfs);
fs_proc.setFsFormatName("PartitionFS");
fs_proc.setFsProperties({
fmt::format("Type: Nested HFS0"),
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,21 +12,22 @@ class GameCardProcess
public:
GameCardProcess();
void process();
// generic
void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setKeyCfg(const KeyBag& keycfg);
void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify);
// xci specific
void setPartitionForExtract(const std::string& partition_name, const tc::io::Path& extract_path);
void setListFs(bool list_fs);
// fs specific
void setShowFsTree(bool show_fs_tree);
void setExtractJobs(const std::vector<nstool::ExtractJob> extract_jobs);
void process();
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;
KeyBag mKeyCfg;
@ -34,43 +35,23 @@ private:
bool mVerify;
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 mIsSdkXciEncrypted;
size_t mGcHeaderOffset;
bool mProccessExtendedHeader;
nn::hac::detail::rsa2048_signature_t mHdrSignature;
fnd::sha::sSha256Hash mHdrHash;
nn::hac::detail::sha256_hash_t mHdrHash;
nn::hac::GameCardHeader mHdr;
PfsProcess mRootPfs;
std::vector<sExtractInfo> mExtractInfo;
std::vector<nstool::ExtractJob> mExtractJobs;
void importHeader();
void displayHeader();
bool validateRegionOfFile(size_t offset, size_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, bool use_salt, byte_t salt);
bool validateRegionOfFile(int64_t offset, int64_t len, const byte_t* test_hash);
void validateXciSignature();
void processRootPfs();
void processPartitionPfs();
};
}

View file

@ -17,19 +17,7 @@ nstool::PfsProcess::PfsProcess() :
mFileSystem(),
mFsProcess()
{
mFsProcess.setFsLabel("PartitionFS");
}
void nstool::PfsProcess::process()
{
importHeader();
if (mCliOutputMode.show_basic_info)
{
displayHeader();
}
mFsProcess.process();
mFsProcess.setFsFormatName("PartitionFS");
}
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)
{
mCliOutputMode = type;
mFsProcess.setShowFsInfo(mCliOutputMode.show_basic_info);
}
void nstool::PfsProcess::setVerifyMode(bool verify)
@ -47,32 +36,22 @@ void nstool::PfsProcess::setVerifyMode(bool 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
{
return mPfs;
}
const std::shared_ptr<tc::io::IStorage>& nstool::PfsProcess::getFileSystem() const
{
return mFileSystem;
}
void nstool::PfsProcess::importHeader()
void nstool::PfsProcess::process()
{
if (mFile == nullptr)
{
@ -116,13 +95,24 @@ void nstool::PfsProcess::importHeader()
// 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)));
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");
fmt::print(" Type: {:s}\n", nn::hac::PartitionFsUtil::getFsTypeAsString(mPfs.getFsType()));
fmt::print(" FileNum: {:d}\n", mPfs.getFileList().size());
return mPfs;
}
const std::shared_ptr<tc::io::IStorage>& nstool::PfsProcess::getFileSystem() const
{
return mFileSystem;
}
size_t nstool::PfsProcess::determineHeaderSize(const nn::hac::sPfsHeader* hdr)

View file

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

View file

@ -15,19 +15,7 @@ nstool::RomfsProcess::RomfsProcess() :
mFileSystem(),
mFsProcess()
{
mFsProcess.setFsLabel("RomFS");
}
void nstool::RomfsProcess::process()
{
importHeader();
if (mCliOutputMode.show_basic_info)
{
displayHeader();
}
mFsProcess.process();
mFsProcess.setFsFormatName("RomFS");
}
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)
{
mCliOutputMode = type;
mFsProcess.setShowFsInfo(mCliOutputMode.show_basic_info);
}
void nstool::RomfsProcess::setVerifyMode(bool verify)
@ -45,27 +34,22 @@ void nstool::RomfsProcess::setVerifyMode(bool 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
{
return mFileSystem;
}
void nstool::RomfsProcess::importHeader()
void nstool::RomfsProcess::process()
{
if (mFile == nullptr)
{
@ -132,11 +116,13 @@ void nstool::RomfsProcess::importHeader()
// create virtual filesystem
mFileSystem = std::make_shared<tc::io::VirtualFileSystem>(tc::io::VirtualFileSystem(nn::hac::RomFsMetaGenerator(mFile)));
mFsProcess.setInputFileSystem(mFileSystem);
}
void nstool::RomfsProcess::displayHeader()
{
fmt::print("[RomFS]\n");
fmt::print(" DirNum: {:d}\n", mDirNum);
fmt::print(" FileNum: {:d}\n", mFileNum);
// set properties for FsProcess
mFsProcess.setFsProperties({
fmt::format("DirNum: {:d}", mDirNum),
fmt::format("FileNum: {:d}", mFileNum)
});
// process filesystem
mFsProcess.process();
}

View file

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

View file

@ -350,6 +350,68 @@ private:
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) :
Settings(),
mModuleLabel("nstool::SettingsInitializer"),
@ -484,19 +546,19 @@ void nstool::SettingsInitializer::parse_args(const std::vector<std::string>& arg
// fs options
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
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(xci.update_extract_path, { "--update" })));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(xci.normal_extract_path, { "--normal" })));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(xci.secure_extract_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, { "--update" }, tc::io::Path("/update/"))));
opts.registerOptionHandler(std::shared_ptr<CustomExtractDataPathOptionHandler>(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--normal" }, tc::io::Path("/normal/"))));
opts.registerOptionHandler(std::shared_ptr<CustomExtractDataPathOptionHandler>(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--secure" }, tc::io::Path("/secure/"))));
opts.registerOptionHandler(std::shared_ptr<CustomExtractDataPathOptionHandler>(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--logo" }, tc::io::Path("/logo/"))));
// nca options
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(nca.part0_extract_path, { "--part0" })));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(nca.part1_extract_path, { "--part1" })));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(nca.part2_extract_path, { "--part2" })));
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(nca.part3_extract_path, { "--part3" })));
opts.registerOptionHandler(std::shared_ptr<CustomExtractDataPathOptionHandler>(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--part0" }, tc::io::Path("/0/"))));
opts.registerOptionHandler(std::shared_ptr<CustomExtractDataPathOptionHandler>(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--part1" }, tc::io::Path("/1/"))));
opts.registerOptionHandler(std::shared_ptr<CustomExtractDataPathOptionHandler>(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--part2" }, tc::io::Path("/2/"))));
opts.registerOptionHandler(std::shared_ptr<CustomExtractDataPathOptionHandler>(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--part3" }, tc::io::Path("/3/"))));
// kip options
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(kip.extract_path, { "--kipdir" })));
@ -552,7 +614,7 @@ void nstool::SettingsInitializer::determine_filetype()
// detect ROMFS
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)->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;
}

View file

@ -57,7 +57,7 @@ struct Settings
struct FsOptions
{
bool show_fs_tree;
tc::Optional<tc::io::Path> extract_path;
std::vector<ExtractJob> extract_jobs;
} fs;
// XCI options
@ -105,18 +105,8 @@ struct Settings
code.list_symbols = false;
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.extract_path = tc::Optional<tc::io::Path>();
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>();
fs.extract_jobs = std::vector<ExtractJob>();
kip.extract_path = tc::Optional<tc::io::Path>();

View file

@ -3,7 +3,7 @@
#include "Settings.h"
//#include "GameCardProcess.h"
#include "GameCardProcess.h"
#include "PfsProcess.h"
#include "RomfsProcess.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));
/*
if (set.infile.filetype == nstool::Settings::FILE_TYPE_GAMECARD)
{
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.setVerifyMode(set.opt.verify);
if (set.xci.update_extract_path.isSet())
obj.setPartitionForExtract(nn::hac::gc::kUpdatePartitionStr, set.xci.update_extract_path.get());
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.setShowFsTree(set.fs.show_fs_tree);
obj.setExtractJobs(set.fs.extract_jobs);
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;
obj.setInputFile(infile_stream);
obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify);
if (set.fs.extract_path.isSet())
obj.setExtractPath(set.fs.extract_path.get());
obj.setListFs(set.fs.show_fs_tree);
obj.setShowFsTree(set.fs.show_fs_tree);
obj.setExtractJobs(set.fs.extract_jobs);
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.setVerifyMode(set.opt.verify);
if (set.fs.extract_path.isSet())
obj.setExtractPath(set.fs.extract_path.get());
obj.setListFs(set.fs.show_fs_tree);
obj.setShowFsTree(set.fs.show_fs_tree);
obj.setExtractJobs(set.fs.extract_jobs);
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())
obj.setNacpExtractPath(set.aset.nacp_extract_path.get());
if (set.fs.extract_path.isSet())
obj.setRomfsExtractPath(set.fs.extract_path.get());
obj.setListFs(set.fs.show_fs_tree);
obj.setRomfsShowFsTree(set.fs.show_fs_tree);
obj.setRomfsExtractJobs(set.fs.extract_jobs);
obj.process();
}