2021-10-08 10:05:51 +00:00
|
|
|
#include "GameCardProcess.h"
|
|
|
|
|
|
|
|
#include <tc/crypto.h>
|
|
|
|
#include <tc/io/IOUtil.h>
|
|
|
|
|
2020-02-29 01:47:01 +00:00
|
|
|
#include <nn/hac/GameCardUtil.h>
|
2020-05-23 08:04:05 +00:00
|
|
|
#include <nn/hac/ContentMetaUtil.h>
|
|
|
|
#include <nn/hac/ContentArchiveUtil.h>
|
2021-10-08 10:05:51 +00:00
|
|
|
|
|
|
|
#include <nn/hac/GameCardFsMetaGenerator.h>
|
|
|
|
#include "FsProcess.h"
|
|
|
|
|
2018-04-24 05:24:20 +00:00
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
nstool::GameCardProcess::GameCardProcess() :
|
2021-10-08 10:05:51 +00:00
|
|
|
mModuleName("nstool::GameCardProcess"),
|
2018-09-23 03:29:22 +00:00
|
|
|
mFile(),
|
2021-09-28 11:15:54 +00:00
|
|
|
mCliOutputMode(true, false, false, false),
|
2018-06-03 08:48:12 +00:00
|
|
|
mVerify(false),
|
|
|
|
mListFs(false),
|
2019-11-17 06:26:23 +00:00
|
|
|
mProccessExtendedHeader(false),
|
2018-06-03 08:48:12 +00:00
|
|
|
mRootPfs(),
|
2021-10-08 10:05:51 +00:00
|
|
|
mExtractJobs()
|
2018-06-03 08:48:12 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::GameCardProcess::process()
|
2018-06-03 08:48:12 +00:00
|
|
|
{
|
2018-08-13 17:14:21 +00:00
|
|
|
importHeader();
|
2018-06-03 08:48:12 +00:00
|
|
|
|
|
|
|
// validate header signature
|
|
|
|
if (mVerify)
|
|
|
|
validateXciSignature();
|
|
|
|
|
|
|
|
// display header
|
2021-09-28 11:15:54 +00:00
|
|
|
if (mCliOutputMode.show_basic_info)
|
2018-06-03 08:48:12 +00:00
|
|
|
displayHeader();
|
|
|
|
|
2021-10-08 10:05:51 +00:00
|
|
|
// process nested HFS0
|
2018-06-03 08:48:12 +00:00
|
|
|
processRootPfs();
|
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::GameCardProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
|
2018-06-03 08:48:12 +00:00
|
|
|
{
|
|
|
|
mFile = file;
|
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::GameCardProcess::setKeyCfg(const KeyBag& keycfg)
|
2018-06-03 08:48:12 +00:00
|
|
|
{
|
2018-08-21 12:03:19 +00:00
|
|
|
mKeyCfg = keycfg;
|
2018-06-03 08:48:12 +00:00
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::GameCardProcess::setCliOutputMode(CliOutputMode type)
|
2018-06-03 08:48:12 +00:00
|
|
|
{
|
2018-06-18 15:30:19 +00:00
|
|
|
mCliOutputMode = type;
|
2018-06-03 08:48:12 +00:00
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::GameCardProcess::setVerifyMode(bool verify)
|
2018-06-03 08:48:12 +00:00
|
|
|
{
|
|
|
|
mVerify = verify;
|
|
|
|
}
|
|
|
|
|
2021-10-08 10:05:51 +00:00
|
|
|
void nstool::GameCardProcess::setExtractJobs(const std::vector<nstool::ExtractJob> extract_jobs)
|
2018-06-03 08:48:12 +00:00
|
|
|
{
|
2021-10-08 10:05:51 +00:00
|
|
|
mExtractJobs = extract_jobs;
|
2018-06-03 08:48:12 +00:00
|
|
|
}
|
|
|
|
|
2021-10-08 10:05:51 +00:00
|
|
|
void nstool::GameCardProcess::setShowFsTree(bool show_fs_tree)
|
2018-06-03 08:48:12 +00:00
|
|
|
{
|
2021-10-08 10:05:51 +00:00
|
|
|
mListFs = show_fs_tree;
|
2018-06-03 08:48:12 +00:00
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::GameCardProcess::importHeader()
|
2018-08-13 17:14:21 +00:00
|
|
|
{
|
2021-10-08 10:05:51 +00:00
|
|
|
if (mFile == nullptr)
|
|
|
|
{
|
|
|
|
throw tc::Exception(mModuleName, "No file reader set.");
|
|
|
|
}
|
|
|
|
if (mFile->canRead() == false || mFile->canSeek() == false)
|
2018-08-13 17:14:21 +00:00
|
|
|
{
|
2021-10-08 10:05:51 +00:00
|
|
|
throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions.");
|
2018-08-13 17:14:21 +00:00
|
|
|
}
|
|
|
|
|
2021-10-08 10:05:51 +00:00
|
|
|
// 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.");
|
|
|
|
}
|
|
|
|
|
2019-11-17 06:26:23 +00:00
|
|
|
// allocate memory for header
|
2021-10-08 10:05:51 +00:00
|
|
|
tc::ByteData scratch = tc::ByteData(sizeof(nn::hac::sSdkGcHeader));
|
2018-08-13 17:14:21 +00:00
|
|
|
|
2019-11-17 06:26:23 +00:00
|
|
|
// read header region
|
2021-10-08 10:05:51 +00:00
|
|
|
mFile->seek(0, tc::io::SeekOrigin::Begin);
|
|
|
|
mFile->read(scratch.data(), scratch.size());
|
2018-08-21 12:03:19 +00:00
|
|
|
|
2019-11-17 06:26:23 +00:00
|
|
|
// determine if this is a SDK XCI or a "Community" XCI
|
2021-10-08 10:05:51 +00:00
|
|
|
if (((nn::hac::sSdkGcHeader*)scratch.data())->signed_header.header.st_magic.unwrap() == nn::hac::gc::kGcHeaderStructMagic)
|
2019-11-17 06:26:23 +00:00
|
|
|
{
|
|
|
|
mIsTrueSdkXci = true;
|
|
|
|
mGcHeaderOffset = sizeof(nn::hac::sGcKeyDataRegion);
|
|
|
|
}
|
2021-10-08 10:05:51 +00:00
|
|
|
else if (((nn::hac::sGcHeader_Rsa2048Signed*)scratch.data())->header.st_magic.unwrap() == nn::hac::gc::kGcHeaderStructMagic)
|
2019-11-17 06:26:23 +00:00
|
|
|
{
|
|
|
|
mIsTrueSdkXci = false;
|
|
|
|
mGcHeaderOffset = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-10-08 10:05:51 +00:00
|
|
|
throw tc::Exception(mModuleName, "Corrupt GameCard Image: Unexpected magic bytes.");
|
2019-11-17 06:26:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
nn::hac::sGcHeader_Rsa2048Signed* hdr_ptr = (nn::hac::sGcHeader_Rsa2048Signed*)(scratch.data() + mGcHeaderOffset);
|
2018-08-13 17:14:21 +00:00
|
|
|
|
2019-11-17 06:26:23 +00:00
|
|
|
// generate hash of raw header
|
2021-10-08 10:05:51 +00:00
|
|
|
tc::crypto::GenerateSha256Hash(mHdrHash.data(), (byte_t*)&hdr_ptr->header, sizeof(nn::hac::sGcHeader));
|
2019-11-17 06:26:23 +00:00
|
|
|
|
|
|
|
// save the signature
|
2021-10-08 10:05:51 +00:00
|
|
|
memcpy(mHdrSignature.data(), hdr_ptr->signature.data(), mHdrSignature.size());
|
2019-11-17 06:26:23 +00:00
|
|
|
|
|
|
|
// decrypt extended header
|
2021-10-08 10:05:51 +00:00
|
|
|
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())
|
2019-11-17 06:26:23 +00:00
|
|
|
{
|
2021-10-08 10:05:51 +00:00
|
|
|
nn::hac::GameCardUtil::decryptXciHeader(&hdr_ptr->header, mKeyCfg.xci_header_key[xci_header_key_index].data());
|
2019-11-17 06:26:23 +00:00
|
|
|
mProccessExtendedHeader = true;
|
|
|
|
}
|
|
|
|
|
2018-08-13 17:14:21 +00:00
|
|
|
// deserialise header
|
2019-11-17 06:26:23 +00:00
|
|
|
mHdr.fromBytes((byte_t*)&hdr_ptr->header, sizeof(nn::hac::sGcHeader));
|
2018-08-13 17:14:21 +00:00
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::GameCardProcess::displayHeader()
|
2018-04-24 05:24:20 +00:00
|
|
|
{
|
2021-10-08 10:05:51 +00:00
|
|
|
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()));
|
2021-09-28 11:15:54 +00:00
|
|
|
if (mCliOutputMode.show_extended_info)
|
2021-10-08 10:05:51 +00:00
|
|
|
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++)
|
2018-07-01 11:18:38 +00:00
|
|
|
{
|
2021-10-08 10:05:51 +00:00
|
|
|
fmt::print(" {:s}\n", nn::hac::GameCardUtil::getHeaderFlagsAsString((nn::hac::gc::HeaderFlags)*itr));
|
2018-07-01 11:18:38 +00:00
|
|
|
}
|
2021-10-08 10:05:51 +00:00
|
|
|
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
if (mCliOutputMode.show_extended_info)
|
2018-06-18 15:30:19 +00:00
|
|
|
{
|
2021-10-08 10:05:51 +00:00
|
|
|
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));
|
2018-06-18 15:30:19 +00:00
|
|
|
}
|
2021-09-28 11:15:54 +00:00
|
|
|
if (mCliOutputMode.show_extended_info)
|
2018-06-18 15:30:19 +00:00
|
|
|
{
|
2021-10-08 10:05:51 +00:00
|
|
|
fmt::print(" Extended Header AesCbc IV:\n");
|
|
|
|
fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mHdr.getAesCbcIv().data(), mHdr.getAesCbcIv().size(), true, ":"));
|
2018-06-18 15:30:19 +00:00
|
|
|
}
|
2021-10-08 10:05:51 +00:00
|
|
|
fmt::print(" SelSec: 0x{:x}\n", mHdr.getSelSec());
|
|
|
|
fmt::print(" SelT1Key: 0x{:x}\n", mHdr.getSelT1Key());
|
|
|
|
fmt::print(" SelKey: 0x{:x}\n", mHdr.getSelKey());
|
2021-09-28 11:15:54 +00:00
|
|
|
if (mCliOutputMode.show_layout)
|
2018-06-18 15:30:19 +00:00
|
|
|
{
|
2021-10-08 10:05:51 +00:00
|
|
|
fmt::print(" RomAreaStartPage: 0x{:x}", mHdr.getRomAreaStartPage());
|
2018-06-18 15:30:19 +00:00
|
|
|
if (mHdr.getRomAreaStartPage() != (uint32_t)(-1))
|
2021-10-08 10:05:51 +00:00
|
|
|
fmt::print(" (0x{:x})", nn::hac::GameCardUtil::blockToAddr(mHdr.getRomAreaStartPage()));
|
|
|
|
fmt::print("\n");
|
2018-06-18 15:30:19 +00:00
|
|
|
|
2021-10-08 10:05:51 +00:00
|
|
|
fmt::print(" BackupAreaStartPage: 0x{:x}", mHdr.getBackupAreaStartPage());
|
2018-06-18 15:30:19 +00:00
|
|
|
if (mHdr.getBackupAreaStartPage() != (uint32_t)(-1))
|
2021-10-08 10:05:51 +00:00
|
|
|
fmt::print(" (0x{:x})", nn::hac::GameCardUtil::blockToAddr(mHdr.getBackupAreaStartPage()));
|
|
|
|
fmt::print("\n");
|
2018-04-24 05:24:20 +00:00
|
|
|
|
2021-10-08 10:05:51 +00:00
|
|
|
fmt::print(" ValidDataEndPage: 0x{:x}", mHdr.getValidDataEndPage());
|
2018-06-18 15:30:19 +00:00
|
|
|
if (mHdr.getValidDataEndPage() != (uint32_t)(-1))
|
2021-10-08 10:05:51 +00:00
|
|
|
fmt::print(" (0x{:x})", nn::hac::GameCardUtil::blockToAddr(mHdr.getValidDataEndPage()));
|
|
|
|
fmt::print("\n");
|
2018-04-24 05:24:20 +00:00
|
|
|
|
2021-10-08 10:05:51 +00:00
|
|
|
fmt::print(" LimArea: 0x{:x}", mHdr.getLimAreaPage());
|
2018-06-18 15:30:19 +00:00
|
|
|
if (mHdr.getLimAreaPage() != (uint32_t)(-1))
|
2021-10-08 10:05:51 +00:00
|
|
|
fmt::print(" (0x{:x})", nn::hac::GameCardUtil::blockToAddr(mHdr.getLimAreaPage()));
|
|
|
|
fmt::print("\n");
|
2018-06-18 15:30:19 +00:00
|
|
|
|
2021-10-08 10:05:51 +00:00
|
|
|
fmt::print(" PartitionFs Header:\n");
|
|
|
|
fmt::print(" Offset: 0x{:x}\n", mHdr.getPartitionFsAddress());
|
|
|
|
fmt::print(" Size: 0x{:x}\n", mHdr.getPartitionFsSize());
|
2021-09-28 11:15:54 +00:00
|
|
|
if (mCliOutputMode.show_extended_info)
|
2018-06-18 15:30:19 +00:00
|
|
|
{
|
2021-10-08 10:05:51 +00:00
|
|
|
fmt::print(" Hash:\n");
|
|
|
|
fmt::print(" {:s}", tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(mHdr.getPartitionFsHash().data(), mHdr.getPartitionFsHash().size(), true, ":", 0x10, 6, false));
|
2018-06-18 15:30:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-01 11:18:38 +00:00
|
|
|
|
2019-11-17 06:26:23 +00:00
|
|
|
if (mProccessExtendedHeader)
|
2018-07-01 11:18:38 +00:00
|
|
|
{
|
2021-10-08 10:05:51 +00:00
|
|
|
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, ":"));
|
2018-08-13 17:14:21 +00:00
|
|
|
}
|
2018-04-24 05:24:20 +00:00
|
|
|
}
|
|
|
|
|
2021-10-08 10:05:51 +00:00
|
|
|
bool nstool::GameCardProcess::validateRegionOfFile(int64_t offset, int64_t len, const byte_t* test_hash, bool use_salt, byte_t salt)
|
2018-04-24 05:24:20 +00:00
|
|
|
{
|
2021-10-08 10:05:51 +00:00
|
|
|
// 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());
|
2020-05-17 04:30:14 +00:00
|
|
|
if (use_salt)
|
2021-10-08 10:05:51 +00:00
|
|
|
sha256_gen.update(&salt, sizeof(salt));
|
2020-05-17 04:30:14 +00:00
|
|
|
|
2021-10-08 10:05:51 +00:00
|
|
|
// calculate hash
|
|
|
|
nn::hac::detail::sha256_hash_t calc_hash;
|
|
|
|
sha256_gen.getHash(calc_hash.data());
|
2020-05-17 04:30:14 +00:00
|
|
|
|
2021-10-08 10:05:51 +00:00
|
|
|
return memcmp(calc_hash.data(), test_hash, calc_hash.size()) == 0;
|
2018-04-24 05:24:20 +00:00
|
|
|
}
|
|
|
|
|
2021-10-08 10:05:51 +00:00
|
|
|
bool nstool::GameCardProcess::validateRegionOfFile(int64_t offset, int64_t len, const byte_t* test_hash)
|
2020-05-17 04:30:14 +00:00
|
|
|
{
|
|
|
|
return validateRegionOfFile(offset, len, test_hash, false, 0);
|
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::GameCardProcess::validateXciSignature()
|
2018-04-24 05:24:20 +00:00
|
|
|
{
|
2021-10-08 10:05:51 +00:00
|
|
|
if (mKeyCfg.xci_header_sign_key.isSet())
|
|
|
|
{
|
|
|
|
if (tc::crypto::VerifyRsa2048Pkcs1Sha256(mHdrSignature.data(), mHdrHash.data(), mKeyCfg.xci_header_sign_key.get()) == false)
|
|
|
|
{
|
|
|
|
fmt::print("[WARNING] GameCard Header Signature: FAIL\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
2018-04-24 05:24:20 +00:00
|
|
|
{
|
2021-10-08 10:05:51 +00:00
|
|
|
fmt::print("[WARNING] GameCard Header Signature: FAIL (Failed to load rsa public key.)\n");
|
2018-04-24 05:24:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::GameCardProcess::processRootPfs()
|
2018-04-24 05:24:20 +00:00
|
|
|
{
|
2021-10-08 10:05:51 +00:00
|
|
|
if (mVerify && validateRegionOfFile(mHdr.getPartitionFsAddress(), mHdr.getPartitionFsSize(), mHdr.getPartitionFsHash().data(), mHdr.getCompatibilityType() != nn::hac::gc::COMPAT_GLOBAL, mHdr.getCompatibilityType()) == false)
|
2018-04-24 05:24:20 +00:00
|
|
|
{
|
2021-10-08 10:05:51 +00:00
|
|
|
fmt::print("[WARNING] GameCard Root HFS0: FAIL (bad hash)\n");
|
2018-04-24 05:24:20 +00:00
|
|
|
}
|
|
|
|
|
2021-10-08 10:05:51 +00:00
|
|
|
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()));
|
2018-06-06 13:27:46 +00:00
|
|
|
|
2021-10-08 10:05:51 +00:00
|
|
|
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();
|
2020-02-29 01:47:01 +00:00
|
|
|
}
|