2020-02-26 09:05:51 +00:00
|
|
|
#include "PfsProcess.h"
|
2021-10-02 14:53:21 +00:00
|
|
|
#include "util.h"
|
2020-02-26 09:05:51 +00:00
|
|
|
|
|
|
|
#include <nn/hac/PartitionFsUtil.h>
|
2021-10-02 14:53:21 +00:00
|
|
|
#include <tc/io/LocalStorage.h>
|
2020-02-26 09:05:51 +00:00
|
|
|
|
2018-04-24 05:24:20 +00:00
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
nstool::PfsProcess::PfsProcess() :
|
2021-10-02 14:53:21 +00:00
|
|
|
mModuleName("nstool::PfsProcess"),
|
2018-09-23 03:29:22 +00:00
|
|
|
mFile(),
|
2021-09-28 11:15:54 +00:00
|
|
|
mCliOutputMode(true, false, false, false),
|
2018-04-24 05:24:20 +00:00
|
|
|
mVerify(false),
|
|
|
|
mExtractPath(),
|
|
|
|
mMountName(),
|
|
|
|
mListFs(false),
|
|
|
|
mPfs()
|
|
|
|
{
|
2018-05-12 15:02:53 +00:00
|
|
|
}
|
2018-04-24 05:24:20 +00:00
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::PfsProcess::process()
|
2018-04-24 05:24:20 +00:00
|
|
|
{
|
2018-08-13 17:14:21 +00:00
|
|
|
importHeader();
|
2018-04-24 05:24:20 +00:00
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
if (mCliOutputMode.show_basic_info)
|
2018-06-18 15:30:19 +00:00
|
|
|
{
|
2018-04-24 05:24:20 +00:00
|
|
|
displayHeader();
|
2021-09-28 11:15:54 +00:00
|
|
|
if (mListFs || mCliOutputMode.show_extended_info)
|
2018-06-18 15:30:19 +00:00
|
|
|
displayFs();
|
|
|
|
}
|
2018-04-24 05:24:20 +00:00
|
|
|
if (mPfs.getFsType() == mPfs.TYPE_HFS0 && mVerify)
|
|
|
|
validateHfs();
|
2021-10-02 14:53:21 +00:00
|
|
|
if (mExtractPath.isSet())
|
2018-04-24 05:24:20 +00:00
|
|
|
extractFs();
|
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::PfsProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
|
2018-04-24 05:24:20 +00:00
|
|
|
{
|
2018-06-03 08:48:12 +00:00
|
|
|
mFile = file;
|
2018-04-24 05:24:20 +00:00
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::PfsProcess::setCliOutputMode(CliOutputMode type)
|
2018-04-24 05:24:20 +00:00
|
|
|
{
|
2018-06-18 15:30:19 +00:00
|
|
|
mCliOutputMode = type;
|
2018-04-24 05:24:20 +00:00
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::PfsProcess::setVerifyMode(bool verify)
|
2018-04-24 05:24:20 +00:00
|
|
|
{
|
|
|
|
mVerify = verify;
|
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::PfsProcess::setMountPointName(const std::string& mount_name)
|
2018-04-24 05:24:20 +00:00
|
|
|
{
|
|
|
|
mMountName = mount_name;
|
|
|
|
}
|
|
|
|
|
2021-10-02 14:53:21 +00:00
|
|
|
void nstool::PfsProcess::setExtractPath(const tc::io::Path& path)
|
2018-04-24 05:24:20 +00:00
|
|
|
{
|
|
|
|
mExtractPath = path;
|
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::PfsProcess::setListFs(bool list_fs)
|
2018-04-24 05:24:20 +00:00
|
|
|
{
|
|
|
|
mListFs = list_fs;
|
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
const nn::hac::PartitionFsHeader& nstool::PfsProcess::getPfsHeader() const
|
2018-04-24 05:24:20 +00:00
|
|
|
{
|
|
|
|
return mPfs;
|
|
|
|
}
|
2018-05-26 13:13:21 +00:00
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::PfsProcess::importHeader()
|
2018-08-13 17:14:21 +00:00
|
|
|
{
|
2021-10-02 14:53:21 +00:00
|
|
|
if (mFile == nullptr)
|
|
|
|
{
|
|
|
|
throw tc::Exception(mModuleName, "No file reader set.");
|
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
tc::ByteData scratch;
|
2018-08-13 17:14:21 +00:00
|
|
|
|
2021-10-02 14:53:21 +00:00
|
|
|
// read base header to determine complete header size
|
|
|
|
if (mFile->length() < tc::io::IOUtil::castSizeToInt64(sizeof(nn::hac::sPfsHeader)))
|
2018-08-13 17:14:21 +00:00
|
|
|
{
|
2021-10-02 14:53:21 +00:00
|
|
|
throw tc::Exception(mModuleName, "Corrupt PartitionFs: File too small");
|
2018-08-13 17:14:21 +00:00
|
|
|
}
|
2021-10-02 14:53:21 +00:00
|
|
|
|
|
|
|
scratch = tc::ByteData(sizeof(nn::hac::sPfsHeader));
|
|
|
|
mFile->seek(0, tc::io::SeekOrigin::Begin);
|
|
|
|
mFile->read(scratch.data(), scratch.size());
|
2018-08-13 17:14:21 +00:00
|
|
|
if (validateHeaderMagic(((nn::hac::sPfsHeader*)scratch.data())) == false)
|
|
|
|
{
|
2021-10-02 14:53:21 +00:00
|
|
|
throw tc::Exception(mModuleName, "Corrupt PartitionFs: Header had incorrect struct magic.");
|
2018-08-13 17:14:21 +00:00
|
|
|
}
|
2021-10-02 14:53:21 +00:00
|
|
|
|
|
|
|
// read complete size header
|
2018-08-13 17:14:21 +00:00
|
|
|
size_t pfsHeaderSize = determineHeaderSize(((nn::hac::sPfsHeader*)scratch.data()));
|
2021-10-02 14:53:21 +00:00
|
|
|
if (mFile->length() < tc::io::IOUtil::castSizeToInt64(pfsHeaderSize))
|
|
|
|
{
|
|
|
|
throw tc::Exception(mModuleName, "Corrupt PartitionFs: File too small");
|
|
|
|
}
|
|
|
|
|
|
|
|
scratch = tc::ByteData(pfsHeaderSize);
|
|
|
|
mFile->seek(0, tc::io::SeekOrigin::Begin);
|
|
|
|
mFile->read(scratch.data(), scratch.size());
|
|
|
|
|
|
|
|
// process PFS
|
2018-08-13 17:14:21 +00:00
|
|
|
mPfs.fromBytes(scratch.data(), scratch.size());
|
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::PfsProcess::displayHeader()
|
2018-05-26 13:13:21 +00:00
|
|
|
{
|
2021-10-02 14:53:21 +00:00
|
|
|
fmt::print("[PartitionFS]\n");
|
|
|
|
fmt::print(" Type: {:s}\n", nn::hac::PartitionFsUtil::getFsTypeAsString(mPfs.getFsType()));
|
|
|
|
fmt::print(" FileNum: {:d}\n", mPfs.getFileList().size());
|
2018-08-13 17:14:21 +00:00
|
|
|
if (mMountName.empty() == false)
|
|
|
|
{
|
2021-10-02 14:53:21 +00:00
|
|
|
fmt::print(" MountPoint: {:s}");
|
2018-08-13 17:14:21 +00:00
|
|
|
if (mMountName.at(mMountName.length()-1) != '/')
|
2021-10-02 14:53:21 +00:00
|
|
|
fmt::print("/");
|
|
|
|
fmt::print("\n");
|
2018-08-13 17:14:21 +00:00
|
|
|
}
|
2018-05-26 13:13:21 +00:00
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::PfsProcess::displayFs()
|
2018-05-26 13:13:21 +00:00
|
|
|
{
|
2021-10-02 14:53:21 +00:00
|
|
|
auto file_list = mPfs.getFileList();
|
|
|
|
for (auto itr = file_list.begin(); itr != file_list.end(); itr++)
|
2018-05-26 13:13:21 +00:00
|
|
|
{
|
2021-10-02 14:53:21 +00:00
|
|
|
fmt::print(" {:s}", itr->name);
|
2021-09-28 11:15:54 +00:00
|
|
|
if (mCliOutputMode.show_layout)
|
2018-05-26 13:13:21 +00:00
|
|
|
{
|
2018-08-13 17:14:21 +00:00
|
|
|
switch (mPfs.getFsType())
|
|
|
|
{
|
2018-10-27 05:43:57 +00:00
|
|
|
case (nn::hac::PartitionFsHeader::TYPE_PFS0):
|
2021-10-02 14:53:21 +00:00
|
|
|
fmt::print(" (offset=0x{:x}, size=0x{:x})", itr->offset, itr->size);
|
2018-08-13 17:14:21 +00:00
|
|
|
break;
|
2018-10-27 05:43:57 +00:00
|
|
|
case (nn::hac::PartitionFsHeader::TYPE_HFS0):
|
2021-10-02 14:53:21 +00:00
|
|
|
fmt::print(" (offset=0x{:x}, size=0x{:x}, hash_protected_size=0x{:x})", itr->offset, itr->size, itr->hash_protected_size);
|
2018-08-13 17:14:21 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-05-26 13:13:21 +00:00
|
|
|
}
|
2021-10-02 14:53:21 +00:00
|
|
|
fmt::print("\n");
|
2018-05-26 13:13:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
size_t nstool::PfsProcess::determineHeaderSize(const nn::hac::sPfsHeader* hdr)
|
2018-05-26 13:13:21 +00:00
|
|
|
{
|
|
|
|
size_t fileEntrySize = 0;
|
2021-10-02 14:53:21 +00:00
|
|
|
if (hdr->st_magic.unwrap() == nn::hac::pfs::kPfsStructMagic)
|
2018-08-07 07:17:51 +00:00
|
|
|
fileEntrySize = sizeof(nn::hac::sPfsFile);
|
2018-05-26 13:13:21 +00:00
|
|
|
else
|
2018-08-07 07:17:51 +00:00
|
|
|
fileEntrySize = sizeof(nn::hac::sHashedPfsFile);
|
2018-05-26 13:13:21 +00:00
|
|
|
|
2021-10-02 14:53:21 +00:00
|
|
|
return sizeof(nn::hac::sPfsHeader) + hdr->file_num.unwrap() * fileEntrySize + hdr->name_table_size.unwrap();
|
2018-05-26 13:13:21 +00:00
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
bool nstool::PfsProcess::validateHeaderMagic(const nn::hac::sPfsHeader* hdr)
|
2018-05-26 13:13:21 +00:00
|
|
|
{
|
2021-10-02 14:53:21 +00:00
|
|
|
return hdr->st_magic.unwrap() == nn::hac::pfs::kPfsStructMagic || hdr->st_magic.unwrap() == nn::hac::pfs::kHashedPfsStructMagic;
|
2018-05-26 13:13:21 +00:00
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::PfsProcess::validateHfs()
|
2018-05-26 13:13:21 +00:00
|
|
|
{
|
2021-10-02 14:53:21 +00:00
|
|
|
nn::hac::detail::sha256_hash_t hash;
|
|
|
|
auto file_list = mPfs.getFileList();
|
|
|
|
for (auto itr = file_list.begin(); itr != file_list.end(); itr++)
|
2018-05-26 13:13:21 +00:00
|
|
|
{
|
2021-10-02 14:53:21 +00:00
|
|
|
tc::ByteData cache = tc::ByteData(tc::io::IOUtil::castInt64ToSize(itr->hash_protected_size));
|
|
|
|
mFile->seek(itr->offset, tc::io::SeekOrigin::Begin);
|
|
|
|
mFile->read(cache.data(), cache.size());
|
|
|
|
tc::crypto::GenerateSha256Hash(hash.data(), cache.data(), cache.size());
|
|
|
|
if (hash != itr->hash)
|
2018-05-26 13:13:21 +00:00
|
|
|
{
|
2021-10-02 14:53:21 +00:00
|
|
|
fmt::print("[WARNING] HFS0 {:s}{:s}{:s}: FAIL (bad hash)\n", !mMountName.empty()? mMountName.c_str() : "", (!mMountName.empty() && mMountName.at(mMountName.length()-1) != '/' )? "/" : "", itr->name);
|
2018-05-26 13:13:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-28 11:15:54 +00:00
|
|
|
void nstool::PfsProcess::extractFs()
|
2018-05-26 13:13:21 +00:00
|
|
|
{
|
2021-10-02 14:53:21 +00:00
|
|
|
// create extract directory
|
|
|
|
tc::io::LocalStorage fs;
|
|
|
|
fs.createDirectory(mExtractPath.get());
|
2018-05-26 13:13:21 +00:00
|
|
|
|
2021-10-02 14:53:21 +00:00
|
|
|
// extract files
|
|
|
|
tc::ByteData cache_for_extract = tc::ByteData(kCacheSize);
|
2018-05-26 13:13:21 +00:00
|
|
|
|
2021-10-02 14:53:21 +00:00
|
|
|
auto file_list = mPfs.getFileList();
|
|
|
|
for (auto itr = file_list.begin(); itr != file_list.end(); itr++)
|
2018-05-26 13:13:21 +00:00
|
|
|
{
|
2021-10-02 14:53:21 +00:00
|
|
|
tc::io::Path extract_path = mExtractPath.get() + itr->name;
|
2018-05-26 13:13:21 +00:00
|
|
|
|
2021-10-02 14:53:21 +00:00
|
|
|
writeSubStreamToFile(mFile, itr->offset, itr->size, extract_path, cache_for_extract);
|
2018-05-26 13:13:21 +00:00
|
|
|
}
|
2018-08-13 17:14:21 +00:00
|
|
|
}
|