2018-04-24 07:51:29 +00:00
|
|
|
#include <fnd/SimpleFile.h>
|
2018-04-24 05:24:20 +00:00
|
|
|
#include <fnd/io.h>
|
2018-05-12 15:02:53 +00:00
|
|
|
#include "PfsProcess.h"
|
2018-04-24 05:24:20 +00:00
|
|
|
|
|
|
|
PfsProcess::PfsProcess() :
|
2018-06-03 08:48:12 +00:00
|
|
|
mFile(nullptr),
|
|
|
|
mOwnIFile(false),
|
2018-04-24 05:24:20 +00:00
|
|
|
mCliOutputType(OUTPUT_NORMAL),
|
|
|
|
mVerify(false),
|
|
|
|
mExtractPath(),
|
|
|
|
mExtract(false),
|
|
|
|
mMountName(),
|
|
|
|
mListFs(false),
|
|
|
|
mPfs()
|
|
|
|
{
|
2018-05-12 15:02:53 +00:00
|
|
|
}
|
2018-04-24 05:24:20 +00:00
|
|
|
|
2018-05-12 15:02:53 +00:00
|
|
|
PfsProcess::~PfsProcess()
|
|
|
|
{
|
2018-06-03 08:48:12 +00:00
|
|
|
if (mOwnIFile)
|
2018-05-12 15:02:53 +00:00
|
|
|
{
|
2018-06-03 08:48:12 +00:00
|
|
|
delete mFile;
|
2018-05-12 15:02:53 +00:00
|
|
|
}
|
2018-04-24 05:24:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void PfsProcess::process()
|
|
|
|
{
|
|
|
|
fnd::MemoryBlob scratch;
|
2018-04-25 11:36:58 +00:00
|
|
|
|
2018-06-03 08:48:12 +00:00
|
|
|
if (mFile == nullptr)
|
2018-04-25 11:36:58 +00:00
|
|
|
{
|
|
|
|
throw fnd::Exception(kModuleName, "No file reader set.");
|
|
|
|
}
|
2018-04-24 05:24:20 +00:00
|
|
|
|
|
|
|
// open minimum header to get full header size
|
|
|
|
scratch.alloc(sizeof(nx::sPfsHeader));
|
2018-06-03 08:48:12 +00:00
|
|
|
mFile->read(scratch.getBytes(), 0, scratch.getSize());
|
2018-05-11 15:47:31 +00:00
|
|
|
if (validateHeaderMagic(((nx::sPfsHeader*)scratch.getBytes())) == false)
|
|
|
|
{
|
|
|
|
throw fnd::Exception(kModuleName, "Corrupt Header");
|
|
|
|
}
|
2018-04-24 05:24:20 +00:00
|
|
|
size_t pfsHeaderSize = determineHeaderSize(((nx::sPfsHeader*)scratch.getBytes()));
|
|
|
|
|
|
|
|
// open minimum header to get full header size
|
|
|
|
scratch.alloc(pfsHeaderSize);
|
2018-06-03 08:48:12 +00:00
|
|
|
mFile->read(scratch.getBytes(), 0, scratch.getSize());
|
2018-04-24 05:24:20 +00:00
|
|
|
mPfs.importBinary(scratch.getBytes(), scratch.getSize());
|
|
|
|
|
|
|
|
if (mCliOutputType >= OUTPUT_NORMAL)
|
|
|
|
displayHeader();
|
|
|
|
if (mListFs || mCliOutputType >= OUTPUT_VERBOSE)
|
|
|
|
displayFs();
|
|
|
|
if (mPfs.getFsType() == mPfs.TYPE_HFS0 && mVerify)
|
|
|
|
validateHfs();
|
|
|
|
if (mExtract)
|
|
|
|
extractFs();
|
|
|
|
}
|
|
|
|
|
2018-06-03 08:48:12 +00:00
|
|
|
void PfsProcess::setInputFile(fnd::IFile* file, bool ownIFile)
|
2018-04-24 05:24:20 +00:00
|
|
|
{
|
2018-06-03 08:48:12 +00:00
|
|
|
mFile = file;
|
|
|
|
mOwnIFile = ownIFile;
|
2018-04-24 05:24:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void PfsProcess::setCliOutputMode(CliOutputType type)
|
|
|
|
{
|
|
|
|
mCliOutputType = type;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PfsProcess::setVerifyMode(bool verify)
|
|
|
|
{
|
|
|
|
mVerify = verify;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PfsProcess::setMountPointName(const std::string& mount_name)
|
|
|
|
{
|
|
|
|
mMountName = mount_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PfsProcess::setExtractPath(const std::string& path)
|
|
|
|
{
|
|
|
|
mExtract = true;
|
|
|
|
mExtractPath = path;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PfsProcess::setListFs(bool list_fs)
|
|
|
|
{
|
|
|
|
mListFs = list_fs;
|
|
|
|
}
|
|
|
|
|
|
|
|
const nx::PfsHeader& PfsProcess::getPfsHeader() const
|
|
|
|
{
|
|
|
|
return mPfs;
|
|
|
|
}
|
2018-05-26 13:13:21 +00:00
|
|
|
|
|
|
|
void PfsProcess::displayHeader()
|
|
|
|
{
|
|
|
|
printf("[PartitionFS]\n");
|
|
|
|
printf(" Type: %s\n", mPfs.getFsType() == mPfs.TYPE_PFS0? "PFS0" : "HFS0");
|
2018-05-27 10:17:34 +00:00
|
|
|
printf(" FileNum: %" PRId64 "\n", (uint64_t)mPfs.getFileList().getSize());
|
2018-05-26 13:13:21 +00:00
|
|
|
if (mMountName.empty() == false)
|
|
|
|
printf(" MountPoint: %s%s\n", mMountName.c_str(), mMountName.at(mMountName.length()-1) != '/' ? "/" : "");
|
|
|
|
}
|
|
|
|
|
|
|
|
void PfsProcess::displayFs()
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < mPfs.getFileList().getSize(); i++)
|
|
|
|
{
|
|
|
|
printf(" %s", mPfs.getFileList()[i].name.c_str());
|
|
|
|
if (mCliOutputType >= OUTPUT_VERBOSE)
|
|
|
|
{
|
|
|
|
if (mPfs.getFsType() == mPfs.TYPE_PFS0)
|
2018-05-27 10:17:34 +00:00
|
|
|
printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ")\n", (uint64_t)mPfs.getFileList()[i].offset, (uint64_t)mPfs.getFileList()[i].size);
|
2018-05-26 13:13:21 +00:00
|
|
|
else
|
2018-05-27 10:17:34 +00:00
|
|
|
printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ", hash_protected_size=0x%" PRIx64 ")\n", (uint64_t)mPfs.getFileList()[i].offset, (uint64_t)mPfs.getFileList()[i].size, (uint64_t)mPfs.getFileList()[i].hash_protected_size);
|
2018-05-26 13:13:21 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t PfsProcess::determineHeaderSize(const nx::sPfsHeader* hdr)
|
|
|
|
{
|
|
|
|
size_t fileEntrySize = 0;
|
2018-06-03 07:41:56 +00:00
|
|
|
if (hdr->signature.get() == nx::pfs::kPfsSig)
|
2018-05-26 13:13:21 +00:00
|
|
|
fileEntrySize = sizeof(nx::sPfsFile);
|
|
|
|
else
|
|
|
|
fileEntrySize = sizeof(nx::sHashedPfsFile);
|
|
|
|
|
|
|
|
return sizeof(nx::sPfsHeader) + hdr->file_num.get() * fileEntrySize + hdr->name_table_size.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PfsProcess::validateHeaderMagic(const nx::sPfsHeader* hdr)
|
|
|
|
{
|
2018-06-03 07:41:56 +00:00
|
|
|
return hdr->signature.get() == nx::pfs::kPfsSig || hdr->signature.get() == nx::pfs::kHashedPfsSig;
|
2018-05-26 13:13:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void PfsProcess::validateHfs()
|
|
|
|
{
|
|
|
|
crypto::sha::sSha256Hash hash;
|
|
|
|
const fnd::List<nx::PfsHeader::sFile>& file = mPfs.getFileList();
|
|
|
|
for (size_t i = 0; i < file.getSize(); i++)
|
|
|
|
{
|
2018-05-27 10:17:34 +00:00
|
|
|
mCache.alloc(file[i].hash_protected_size);
|
2018-06-03 08:48:12 +00:00
|
|
|
mFile->read(mCache.getBytes(), file[i].offset, file[i].hash_protected_size);
|
2018-06-06 13:27:46 +00:00
|
|
|
crypto::sha::Sha256(mCache.getBytes(), file[i].hash_protected_size, hash.bytes);
|
2018-05-26 13:13:21 +00:00
|
|
|
if (hash != file[i].hash)
|
|
|
|
{
|
|
|
|
if (mCliOutputType >= OUTPUT_MINIMAL)
|
2018-06-06 13:27:46 +00:00
|
|
|
printf("[WARNING] HFS0 %s%s%s: FAIL (bad hash)\n", !mMountName.empty()? mMountName.c_str() : "", (!mMountName.empty() && mMountName.at(mMountName.length()-1) != '/' )? "/" : "", file[i].name.c_str());
|
2018-05-26 13:13:21 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PfsProcess::extractFs()
|
|
|
|
{
|
|
|
|
// allocate only when extractDir is invoked
|
2018-05-27 10:17:34 +00:00
|
|
|
mCache.alloc(kCacheSize);
|
2018-05-26 13:13:21 +00:00
|
|
|
|
|
|
|
// make extract dir
|
|
|
|
fnd::io::makeDirectory(mExtractPath);
|
|
|
|
|
|
|
|
fnd::SimpleFile outFile;
|
|
|
|
const fnd::List<nx::PfsHeader::sFile>& file = mPfs.getFileList();
|
|
|
|
|
|
|
|
std::string file_path;
|
|
|
|
for (size_t i = 0; i < file.getSize(); i++)
|
|
|
|
{
|
|
|
|
file_path.clear();
|
|
|
|
fnd::io::appendToPath(file_path, mExtractPath);
|
|
|
|
fnd::io::appendToPath(file_path, file[i].name);
|
|
|
|
|
|
|
|
if (mCliOutputType >= OUTPUT_VERBOSE)
|
|
|
|
printf("extract=[%s]\n", file_path.c_str());
|
|
|
|
|
|
|
|
outFile.open(file_path, outFile.Create);
|
2018-06-03 08:48:12 +00:00
|
|
|
mFile->seek(file[i].offset);
|
2018-05-27 10:17:34 +00:00
|
|
|
for (size_t j = 0; j < ((file[i].size / kCacheSize) + ((file[i].size % kCacheSize) != 0)); j++)
|
2018-05-26 13:13:21 +00:00
|
|
|
{
|
2018-06-03 08:48:12 +00:00
|
|
|
mFile->read(mCache.getBytes(), MIN(file[i].size - (kCacheSize * j),kCacheSize));
|
2018-05-27 10:17:34 +00:00
|
|
|
outFile.write(mCache.getBytes(), MIN(file[i].size - (kCacheSize * j),kCacheSize));
|
2018-05-26 13:13:21 +00:00
|
|
|
}
|
|
|
|
outFile.close();
|
|
|
|
}
|
|
|
|
}
|