2018-04-24 05:24:20 +00:00
|
|
|
#include <fnd/SimpleTextOutput.h>
|
2018-08-07 07:17:51 +00:00
|
|
|
#include <nn/hac/XciUtils.h>
|
2018-05-12 15:02:53 +00:00
|
|
|
#include "OffsetAdjustedIFile.h"
|
|
|
|
#include "XciProcess.h"
|
2018-04-24 05:24:20 +00:00
|
|
|
|
2018-06-03 08:48:12 +00:00
|
|
|
XciProcess::XciProcess() :
|
|
|
|
mFile(nullptr),
|
|
|
|
mOwnIFile(false),
|
|
|
|
mKeyset(nullptr),
|
2018-06-18 15:30:19 +00:00
|
|
|
mCliOutputMode(_BIT(OUTPUT_BASIC)),
|
2018-06-03 08:48:12 +00:00
|
|
|
mVerify(false),
|
|
|
|
mListFs(false),
|
|
|
|
mRootPfs(),
|
|
|
|
mExtractInfo()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
XciProcess::~XciProcess()
|
|
|
|
{
|
|
|
|
if (mOwnIFile)
|
|
|
|
{
|
|
|
|
delete mFile;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void XciProcess::process()
|
|
|
|
{
|
2018-06-24 15:01:16 +00:00
|
|
|
fnd::Vec<byte_t> scratch;
|
2018-06-03 08:48:12 +00:00
|
|
|
|
|
|
|
if (mFile == nullptr)
|
|
|
|
{
|
|
|
|
throw fnd::Exception(kModuleName, "No file reader set.");
|
|
|
|
}
|
|
|
|
|
|
|
|
// read header page
|
2018-08-07 07:17:51 +00:00
|
|
|
mFile->read((byte_t*)&mHdrPage, 0, sizeof(nn::hac::sXciHeaderPage));
|
2018-06-03 08:48:12 +00:00
|
|
|
|
|
|
|
// allocate memory for and decrypt sXciHeader
|
2018-08-07 07:17:51 +00:00
|
|
|
scratch.alloc(sizeof(nn::hac::sXciHeader));
|
|
|
|
nn::hac::XciUtils::decryptXciHeader((const byte_t*)&mHdrPage.header, scratch.data(), mKeyset->xci.header_key.key);
|
2018-06-03 08:48:12 +00:00
|
|
|
|
|
|
|
// validate header signature
|
|
|
|
if (mVerify)
|
|
|
|
{
|
|
|
|
validateXciSignature();
|
|
|
|
}
|
|
|
|
|
|
|
|
// deserialise header
|
2018-06-24 15:01:16 +00:00
|
|
|
mHdr.fromBytes(scratch.data(), scratch.size());
|
2018-06-03 08:48:12 +00:00
|
|
|
|
|
|
|
// display header
|
2018-06-18 15:30:19 +00:00
|
|
|
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
|
2018-06-03 08:48:12 +00:00
|
|
|
displayHeader();
|
|
|
|
|
|
|
|
// process root partition
|
|
|
|
processRootPfs();
|
|
|
|
|
|
|
|
// process partitions
|
|
|
|
processPartitionPfs();
|
|
|
|
}
|
|
|
|
|
|
|
|
void XciProcess::setInputFile(fnd::IFile* file, bool ownIFile)
|
|
|
|
{
|
|
|
|
mFile = file;
|
|
|
|
mOwnIFile = ownIFile;
|
|
|
|
}
|
|
|
|
|
|
|
|
void XciProcess::setKeyset(const sKeyset* keyset)
|
|
|
|
{
|
|
|
|
mKeyset = keyset;
|
|
|
|
}
|
|
|
|
|
2018-06-18 15:30:19 +00:00
|
|
|
void XciProcess::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
|
|
|
}
|
|
|
|
|
|
|
|
void XciProcess::setVerifyMode(bool verify)
|
|
|
|
{
|
|
|
|
mVerify = verify;
|
|
|
|
}
|
|
|
|
|
|
|
|
void XciProcess::setPartitionForExtract(const std::string& partition_name, const std::string& extract_path)
|
|
|
|
{
|
2018-06-24 15:01:16 +00:00
|
|
|
mExtractInfo.addElement({partition_name, extract_path});
|
2018-06-03 08:48:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void XciProcess::setListFs(bool list_fs)
|
|
|
|
{
|
|
|
|
mListFs = list_fs;
|
|
|
|
}
|
|
|
|
|
2018-04-24 05:24:20 +00:00
|
|
|
void XciProcess::displayHeader()
|
|
|
|
{
|
2018-06-18 15:30:19 +00:00
|
|
|
printf("[XCI Header]\n");
|
|
|
|
printf(" CardHeaderVersion: %d\n", mHdr.getCardHeaderVersion());
|
2018-07-01 11:18:38 +00:00
|
|
|
printf(" RomSize: %s", getRomSizeStr(mHdr.getRomSizeType()));
|
|
|
|
if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
|
|
|
|
printf(" (0x%x)", mHdr.getRomSizeType());
|
|
|
|
printf("\n");
|
2018-06-18 15:30:19 +00:00
|
|
|
printf(" PackageId: 0x%" PRIx64 "\n", mHdr.getPackageId());
|
2018-07-01 11:18:38 +00:00
|
|
|
printf(" Flags: 0x%x\n", mHdr.getFlags());
|
|
|
|
if (mHdr.getFlags() != 0)
|
|
|
|
{
|
|
|
|
for (uint32_t i = 0; i < 8; i++)
|
|
|
|
{
|
|
|
|
if (_HAS_BIT(mHdr.getFlags(), i))
|
|
|
|
{
|
|
|
|
printf(" %s\n", getHeaderFlagStr(i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-06-18 15:30:19 +00:00
|
|
|
if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
|
|
|
|
{
|
2018-07-01 11:18:38 +00:00
|
|
|
printf(" InitialData:\n");
|
|
|
|
printf(" KekIndex: %d\n", mHdr.getKekIndex());
|
|
|
|
printf(" TitleKeyDecIndex: %d\n", mHdr.getTitleKeyDecIndex());
|
|
|
|
printf(" Hash:\n");
|
|
|
|
fnd::SimpleTextOutput::hexDump(mHdr.getInitialDataHash().bytes, sizeof(mHdr.getInitialDataHash().bytes), 0x10, 6);
|
2018-06-18 15:30:19 +00:00
|
|
|
}
|
|
|
|
if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
|
|
|
|
{
|
2018-07-01 11:18:38 +00:00
|
|
|
printf(" Enc Header AES-IV:\n");
|
|
|
|
fnd::SimpleTextOutput::hexDump(mHdr.getAesCbcIv().iv, sizeof(mHdr.getAesCbcIv().iv), 0x10, 4);
|
2018-06-18 15:30:19 +00:00
|
|
|
}
|
|
|
|
printf(" SelSec: 0x%x\n", mHdr.getSelSec());
|
|
|
|
printf(" SelT1Key: 0x%x\n", mHdr.getSelT1Key());
|
|
|
|
printf(" SelKey: 0x%x\n", mHdr.getSelKey());
|
|
|
|
if (_HAS_BIT(mCliOutputMode, OUTPUT_LAYOUT))
|
|
|
|
{
|
|
|
|
printf(" RomAreaStartPage: 0x%0x", mHdr.getRomAreaStartPage());
|
|
|
|
if (mHdr.getRomAreaStartPage() != (uint32_t)(-1))
|
2018-08-07 07:17:51 +00:00
|
|
|
printf(" (0x%" PRIx64 ")", nn::hac::XciUtils::blockToAddr(mHdr.getRomAreaStartPage()));
|
2018-06-18 15:30:19 +00:00
|
|
|
printf("\n");
|
|
|
|
|
|
|
|
printf(" BackupAreaStartPage: 0x%0x", mHdr.getBackupAreaStartPage());
|
|
|
|
if (mHdr.getBackupAreaStartPage() != (uint32_t)(-1))
|
2018-08-07 07:17:51 +00:00
|
|
|
printf(" (0x%" PRIx64 ")", nn::hac::XciUtils::blockToAddr(mHdr.getBackupAreaStartPage()));
|
2018-06-18 15:30:19 +00:00
|
|
|
printf("\n");
|
2018-04-24 05:24:20 +00:00
|
|
|
|
2018-06-18 15:30:19 +00:00
|
|
|
printf(" ValidDataEndPage: 0x%x", mHdr.getValidDataEndPage());
|
|
|
|
if (mHdr.getValidDataEndPage() != (uint32_t)(-1))
|
2018-08-07 07:17:51 +00:00
|
|
|
printf(" (0x%" PRIx64 ")", nn::hac::XciUtils::blockToAddr(mHdr.getValidDataEndPage()));
|
2018-06-18 15:30:19 +00:00
|
|
|
printf("\n");
|
2018-04-24 05:24:20 +00:00
|
|
|
|
2018-06-18 15:30:19 +00:00
|
|
|
printf(" LimArea: 0x%x", mHdr.getLimAreaPage());
|
|
|
|
if (mHdr.getLimAreaPage() != (uint32_t)(-1))
|
2018-08-07 07:17:51 +00:00
|
|
|
printf(" (0x%" PRIx64 ")", nn::hac::XciUtils::blockToAddr(mHdr.getLimAreaPage()));
|
2018-06-18 15:30:19 +00:00
|
|
|
printf("\n");
|
|
|
|
|
|
|
|
printf(" PartitionFs Header:\n");
|
2018-07-01 11:18:38 +00:00
|
|
|
printf(" Offset: 0x%" PRIx64 "\n", mHdr.getPartitionFsAddress());
|
|
|
|
printf(" Size: 0x%" PRIx64 "\n", mHdr.getPartitionFsSize());
|
2018-06-18 15:30:19 +00:00
|
|
|
if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
|
|
|
|
{
|
2018-07-01 11:18:38 +00:00
|
|
|
printf(" Hash:\n");
|
|
|
|
fnd::SimpleTextOutput::hexDump(mHdr.getPartitionFsHash().bytes, sizeof(mHdr.getPartitionFsHash().bytes), 0x10, 6);
|
2018-06-18 15:30:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-01 11:18:38 +00:00
|
|
|
|
|
|
|
if (mHdr.getFwVerMinor() != 0)
|
|
|
|
{
|
|
|
|
printf("[XCI Extended Header]\n");
|
|
|
|
printf(" FwVersion: v%d.%d\n", mHdr.getFwVerMajor(), mHdr.getFwVerMinor());
|
|
|
|
printf(" AccCtrl1: 0x%x\n", mHdr.getAccCtrl1());
|
|
|
|
printf(" CardClockRate: %s\n", getCardClockRate(mHdr.getAccCtrl1()));
|
|
|
|
printf(" Wait1TimeRead: 0x%x\n", mHdr.getWait1TimeRead());
|
|
|
|
printf(" Wait2TimeRead: 0x%x\n", mHdr.getWait2TimeRead());
|
|
|
|
printf(" Wait1TimeWrite: 0x%x\n", mHdr.getWait1TimeWrite());
|
|
|
|
printf(" Wait2TimeWrite: 0x%x\n", mHdr.getWait2TimeWrite());
|
|
|
|
printf(" FwMode: 0x%x\n", mHdr.getFwMode());
|
|
|
|
printf(" Update Partition Info:\n");
|
2018-05-12 16:04:07 +00:00
|
|
|
#define _SPLIT_VER(ver) ( (ver>>26) & 0x3f), ( (ver>>20) & 0x3f), ( (ver>>16) & 0xf), (ver & 0xffff)
|
2018-07-01 11:18:38 +00:00
|
|
|
printf(" CUP Version: v%" PRId32 " (%d.%d.%d.%d)\n", mHdr.getUppVersion(), _SPLIT_VER(mHdr.getUppVersion()));
|
2018-05-12 16:04:07 +00:00
|
|
|
#undef _SPLIT_VER
|
2018-07-01 11:18:38 +00:00
|
|
|
printf(" CUP TitleId: %016" PRIx64 "\n", mHdr.getUppId());
|
|
|
|
printf(" Partition Hash: ");
|
|
|
|
fnd::SimpleTextOutput::hexDump(mHdr.getUppHash(), 8);
|
|
|
|
}
|
2018-04-24 05:24:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool XciProcess::validateRegionOfFile(size_t offset, size_t len, const byte_t* test_hash)
|
|
|
|
{
|
2018-06-24 15:01:16 +00:00
|
|
|
fnd::Vec<byte_t> scratch;
|
2018-04-24 05:24:20 +00:00
|
|
|
crypto::sha::sSha256Hash calc_hash;
|
|
|
|
scratch.alloc(len);
|
2018-06-24 15:01:16 +00:00
|
|
|
mFile->read(scratch.data(), offset, scratch.size());
|
|
|
|
crypto::sha::Sha256(scratch.data(), scratch.size(), calc_hash.bytes);
|
2018-04-24 05:24:20 +00:00
|
|
|
return calc_hash.compare(test_hash);
|
|
|
|
}
|
|
|
|
|
|
|
|
void XciProcess::validateXciSignature()
|
|
|
|
{
|
|
|
|
crypto::sha::sSha256Hash calc_hash;
|
2018-08-07 07:17:51 +00:00
|
|
|
crypto::sha::Sha256((byte_t*)&mHdrPage.header, sizeof(nn::hac::sXciHeader), calc_hash.bytes);
|
2018-04-24 05:24:20 +00:00
|
|
|
if (crypto::rsa::pkcs::rsaVerify(mKeyset->xci.header_sign_key, crypto::sha::HASH_SHA256, calc_hash.bytes, mHdrPage.signature) != 0)
|
|
|
|
{
|
2018-06-18 15:30:19 +00:00
|
|
|
printf("[WARNING] XCI Header Signature: FAIL \n");
|
2018-04-24 05:24:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void XciProcess::processRootPfs()
|
|
|
|
{
|
2018-06-06 13:27:46 +00:00
|
|
|
if (mVerify && validateRegionOfFile(mHdr.getPartitionFsAddress(), mHdr.getPartitionFsSize(), mHdr.getPartitionFsHash().bytes) == false)
|
2018-04-24 05:24:20 +00:00
|
|
|
{
|
2018-06-06 13:27:46 +00:00
|
|
|
printf("[WARNING] XCI Root HFS0: FAIL (bad hash)\n");
|
2018-04-24 05:24:20 +00:00
|
|
|
}
|
2018-06-03 08:48:12 +00:00
|
|
|
mRootPfs.setInputFile(new OffsetAdjustedIFile(mFile, SHARED_IFILE, mHdr.getPartitionFsAddress(), mHdr.getPartitionFsSize()), OWN_IFILE);
|
2018-04-24 05:24:20 +00:00
|
|
|
mRootPfs.setListFs(mListFs);
|
2018-06-06 13:27:46 +00:00
|
|
|
mRootPfs.setVerifyMode(false);
|
2018-06-18 15:30:19 +00:00
|
|
|
mRootPfs.setCliOutputMode(mCliOutputMode);
|
2018-04-24 05:24:20 +00:00
|
|
|
mRootPfs.setMountPointName(kXciMountPointName);
|
|
|
|
mRootPfs.process();
|
|
|
|
}
|
|
|
|
|
|
|
|
void XciProcess::processPartitionPfs()
|
|
|
|
{
|
2018-08-07 07:17:51 +00:00
|
|
|
const fnd::List<nn::hac::PfsHeader::sFile>& rootPartitions = mRootPfs.getPfsHeader().getFileList();
|
2018-06-24 15:01:16 +00:00
|
|
|
for (size_t i = 0; i < rootPartitions.size(); i++)
|
2018-06-06 13:27:46 +00:00
|
|
|
{
|
|
|
|
// 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)
|
|
|
|
{
|
|
|
|
printf("[WARNING] XCI %s Partition HFS0: FAIL (bad hash)\n", rootPartitions[i].name.c_str());
|
|
|
|
}
|
|
|
|
|
2018-04-24 05:24:20 +00:00
|
|
|
PfsProcess tmp;
|
2018-06-03 08:48:12 +00:00
|
|
|
tmp.setInputFile(new OffsetAdjustedIFile(mFile, SHARED_IFILE, mHdr.getPartitionFsAddress() + rootPartitions[i].offset, rootPartitions[i].size), OWN_IFILE);
|
2018-04-24 05:24:20 +00:00
|
|
|
tmp.setListFs(mListFs);
|
|
|
|
tmp.setVerifyMode(mVerify);
|
2018-06-18 15:30:19 +00:00
|
|
|
tmp.setCliOutputMode(mCliOutputMode);
|
2018-04-24 05:24:20 +00:00
|
|
|
tmp.setMountPointName(kXciMountPointName + rootPartitions[i].name);
|
2018-06-24 15:01:16 +00:00
|
|
|
if (mExtractInfo.hasElement<std::string>(rootPartitions[i].name))
|
|
|
|
tmp.setExtractPath(mExtractInfo.getElement<std::string>(rootPartitions[i].name).extract_path);
|
|
|
|
|
2018-04-24 05:24:20 +00:00
|
|
|
tmp.process();
|
|
|
|
}
|
2018-07-01 11:18:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const char* XciProcess::getRomSizeStr(byte_t rom_size) const
|
|
|
|
{
|
|
|
|
const char* str = "unknown";
|
|
|
|
switch (rom_size)
|
|
|
|
{
|
2018-08-07 07:17:51 +00:00
|
|
|
case (nn::hac::xci::ROM_SIZE_1GB):
|
2018-07-01 11:18:38 +00:00
|
|
|
str = "1GB";
|
|
|
|
break;
|
2018-08-07 07:17:51 +00:00
|
|
|
case (nn::hac::xci::ROM_SIZE_2GB):
|
2018-07-01 11:18:38 +00:00
|
|
|
str = "2GB";
|
|
|
|
break;
|
2018-08-07 07:17:51 +00:00
|
|
|
case (nn::hac::xci::ROM_SIZE_4GB):
|
2018-07-01 11:18:38 +00:00
|
|
|
str = "4GB";
|
|
|
|
break;
|
2018-08-07 07:17:51 +00:00
|
|
|
case (nn::hac::xci::ROM_SIZE_8GB):
|
2018-07-01 11:18:38 +00:00
|
|
|
str = "8GB";
|
|
|
|
break;
|
2018-08-07 07:17:51 +00:00
|
|
|
case (nn::hac::xci::ROM_SIZE_16GB):
|
2018-07-01 11:18:38 +00:00
|
|
|
str = "16GB";
|
|
|
|
break;
|
2018-08-07 07:17:51 +00:00
|
|
|
case (nn::hac::xci::ROM_SIZE_32GB):
|
2018-07-01 11:18:38 +00:00
|
|
|
str = "32GB";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* XciProcess::getHeaderFlagStr(byte_t flag) const
|
|
|
|
{
|
|
|
|
const char* str = "unknown";
|
|
|
|
switch (flag)
|
|
|
|
{
|
2018-08-07 07:17:51 +00:00
|
|
|
case (nn::hac::xci::FLAG_AUTOBOOT):
|
2018-07-01 11:18:38 +00:00
|
|
|
str = "AutoBoot";
|
|
|
|
break;
|
2018-08-07 07:17:51 +00:00
|
|
|
case (nn::hac::xci::FLAG_HISTORY_ERASE):
|
2018-07-01 11:18:38 +00:00
|
|
|
str = "HistoryErase";
|
|
|
|
break;
|
2018-08-07 07:17:51 +00:00
|
|
|
case (nn::hac::xci::FLAG_REPAIR_TOOL):
|
2018-07-01 11:18:38 +00:00
|
|
|
str = "RepairTool";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const char* XciProcess::getCardClockRate(uint32_t acc_ctrl_1) const
|
|
|
|
{
|
|
|
|
const char* str = "unknown";
|
|
|
|
switch (acc_ctrl_1)
|
|
|
|
{
|
2018-08-07 07:17:51 +00:00
|
|
|
case (nn::hac::xci::CLOCK_RATE_25):
|
2018-07-01 11:18:38 +00:00
|
|
|
str = "20 MHz";
|
|
|
|
break;
|
2018-08-07 07:17:51 +00:00
|
|
|
case (nn::hac::xci::CLOCK_RATE_50):
|
2018-07-01 11:18:38 +00:00
|
|
|
str = "50 MHz";
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
return str;
|
|
|
|
}
|