From 3d0810de6dd26e6fc45a2f873045a7c009e16d01 Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 17 Nov 2019 14:26:23 +0800 Subject: [PATCH] Start development for implemented SDK XCI support --- deps/libnintendo-hac | 2 +- src/GameCardProcess.cpp | 132 ++++++++++++++++++++++++++++++++++------ src/GameCardProcess.h | 16 ++++- src/UserSettings.cpp | 10 +-- src/common.h | 2 +- src/main.cpp | 2 +- 6 files changed, 137 insertions(+), 27 deletions(-) diff --git a/deps/libnintendo-hac b/deps/libnintendo-hac index 07ad9cb..1c8cba8 160000 --- a/deps/libnintendo-hac +++ b/deps/libnintendo-hac @@ -1 +1 @@ -Subproject commit 07ad9cb0c08e12795337e16fbc45b5adf9e5e3f1 +Subproject commit 1c8cba84c14f5353bdb94d6e14285dbb0e5fc99d diff --git a/src/GameCardProcess.cpp b/src/GameCardProcess.cpp index d4c52d0..51feab5 100644 --- a/src/GameCardProcess.cpp +++ b/src/GameCardProcess.cpp @@ -10,6 +10,7 @@ GameCardProcess::GameCardProcess() : mCliOutputMode(_BIT(OUTPUT_BASIC)), mVerify(false), mListFs(false), + mProccessExtendedHeader(false), mRootPfs(), mExtractInfo() { @@ -73,18 +74,46 @@ void GameCardProcess::importHeader() throw fnd::Exception(kModuleName, "No file reader set."); } - // read header page - (*mFile)->read((byte_t*)&mHdrPage, 0, sizeof(nn::hac::sGcHeaderPage)); + // allocate memory for header + scratch.alloc(sizeof(nn::hac::sSdkGcHeader)); - // allocate memory for and decrypt sXciHeader - scratch.alloc(sizeof(nn::hac::sGcHeader)); + // read header region + (*mFile)->read((byte_t*)scratch.data(), 0, sizeof(nn::hac::sSdkGcHeader)); + // 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) + { + mIsTrueSdkXci = true; + mGcHeaderOffset = sizeof(nn::hac::sGcKeyDataRegion); + } + else if (((nn::hac::sGcHeader_Rsa2048Signed*)scratch.data())->header.st_magic.get() == nn::hac::gc::kGcHeaderStructMagic) + { + mIsTrueSdkXci = false; + mGcHeaderOffset = 0; + } + else + { + throw fnd::Exception(kModuleName, "GameCard image did not have expected 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); + + // save the signature + memcpy(mHdrSignature, hdr_ptr->signature, fnd::rsa::kRsa2048Size); + + // decrypt extended header fnd::aes::sAes128Key header_key; - mKeyCfg.getXciHeaderKey(header_key); - nn::hac::GameCardUtils::decryptXciHeader((const byte_t*)&mHdrPage.header, scratch.data(), header_key.key); - + if (mKeyCfg.getXciHeaderKey(header_key)) + { + nn::hac::GameCardUtils::decryptXciHeader(&hdr_ptr->header, header_key.key); + mProccessExtendedHeader = true; + } + // deserialise header - mHdr.fromBytes(scratch.data(), scratch.size()); + mHdr.fromBytes((byte_t*)&hdr_ptr->header, sizeof(nn::hac::sGcHeader)); } void GameCardProcess::displayHeader() @@ -110,7 +139,7 @@ void GameCardProcess::displayHeader() if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED)) { std::cout << " InitialData:" << std::endl; - std::cout << " KekIndex: " << std::dec << (uint32_t)mHdr.getKekIndex() << std::endl; + std::cout << " KekIndex: " << getKekIndexStr(mHdr.getKekIndex()) << "(" << std::dec << (uint32_t)mHdr.getKekIndex() << ")" << std::endl; std::cout << " TitleKeyDecIndex: " << std::dec << (uint32_t)mHdr.getTitleKeyDecIndex() << std::endl; std::cout << " Hash:" << std::endl; std::cout << " " << fnd::SimpleTextOutput::arrayToString(mHdr.getInitialDataHash().bytes, 0x10, true, ":") << std::endl; @@ -158,10 +187,10 @@ void GameCardProcess::displayHeader() } - if (mHdr.getFwVerMinor() != 0) + if (mProccessExtendedHeader) { std::cout << "[GameCard Extended Header]" << std::endl; - std::cout << " FwVersion: v" << std::dec << mHdr.getFwVerMajor() << "." << mHdr.getFwVerMinor() << std::endl; + std::cout << " FwVersion: v" << std::dec << mHdr.getFwVersion() << "(" << getCardFwVersionDescriptionStr(mHdr.getFwVersion()) << ")" << std::endl; std::cout << " AccCtrl1: 0x" << std::hex << mHdr.getAccCtrl1() << std::endl; std::cout << " CardClockRate: " << getCardClockRate(mHdr.getAccCtrl1()) << std::endl; std::cout << " Wait1TimeRead: 0x" << std::hex << mHdr.getWait1TimeRead() << std::endl; @@ -169,12 +198,13 @@ void GameCardProcess::displayHeader() std::cout << " Wait1TimeWrite: 0x" << std::hex << mHdr.getWait1TimeWrite() << std::endl; std::cout << " Wait2TimeWrite: 0x" << std::hex << mHdr.getWait2TimeWrite() << std::endl; std::cout << " FwMode: 0x" << std::hex << mHdr.getFwMode() << std::endl; + std::cout << " CompatibilityType: " << getCardCompatibiltyType(mHdr.getCompatibilityType()) << "(" << std::dec << mHdr.getCompatibilityType() << ")" << std::endl; std::cout << " Update Partition Info:" << std::endl; #define _SPLIT_VER(ver) std::dec << ((ver>>26) & 0x3f) << "." << ((ver>>20) & 0x3f) << "." << ((ver>>16) & 0xf) << "." << (ver & 0xffff) std::cout << " CUP Version: v" << std::dec << mHdr.getUppVersion() << " (" << _SPLIT_VER(mHdr.getUppVersion()) << ")" << std::endl; #undef _SPLIT_VER std::cout << " CUP TitleId: 0x" << std::hex << std::setw(16) << std::setfill('0') << mHdr.getUppId() << std::endl; - std::cout << " Partition Hash: " << fnd::SimpleTextOutput::arrayToString(mHdr.getUppHash(), 8, true, ":") << std::endl; + std::cout << " CUP Digest: " << fnd::SimpleTextOutput::arrayToString(mHdr.getUppHash(), 8, true, ":") << std::endl; } } @@ -191,10 +221,9 @@ bool GameCardProcess::validateRegionOfFile(size_t offset, size_t len, const byte void GameCardProcess::validateXciSignature() { fnd::rsa::sRsa2048Key header_sign_key; - fnd::sha::sSha256Hash calc_hash; - fnd::sha::Sha256((byte_t*)&mHdrPage.header, sizeof(nn::hac::sGcHeader), calc_hash.bytes); + mKeyCfg.getXciHeaderSignKey(header_sign_key); - if (fnd::rsa::pkcs::rsaVerify(header_sign_key, fnd::sha::HASH_SHA256, calc_hash.bytes, mHdrPage.signature) != 0) + if (fnd::rsa::pkcs::rsaVerify(header_sign_key, fnd::sha::HASH_SHA256, mHdrHash.bytes, mHdrSignature) != 0) { std::cout << "[WARNING] GameCard Header Signature: FAIL" << std::endl; } @@ -238,6 +267,26 @@ void GameCardProcess::processPartitionPfs() } } +const char* GameCardProcess::getKekIndexStr(byte_t kek_index) const +{ + const char* str = nullptr; + + switch (kek_index) + { + case (nn::hac::gc::KEK_PROD): + str = "Production"; + break; + case (nn::hac::gc::KEK_DEV): + str = "Development"; + break; + default: + str = "Unknown"; + break; + } + + return str; +} + const char* GameCardProcess::getRomSizeStr(byte_t rom_size) const { const char* str = nullptr; @@ -282,8 +331,37 @@ const char* GameCardProcess::getHeaderFlagStr(byte_t flag) const case (nn::hac::gc::FLAG_HISTORY_ERASE): str = "HistoryErase"; break; - case (nn::hac::gc::FLAG_REPAIR_TOOL): - str = "RepairTool"; + case (nn::hac::gc::FLAG_REPAIR_TIME_REVISOR_TOOL): + str = "RepairTimeRevisorTool"; + break; + case (nn::hac::gc::FLAG_ALLOW_CUP_TO_CHINA): + str = "AllowCupToChina"; + break; + case (nn::hac::gc::FLAG_ALLOW_CUP_TO_GLOBAL): + str = "AllowCupToGlobal"; + break; + default: + str = "Unknown"; + break; + } + + return str; +} + +const char* GameCardProcess::getCardFwVersionDescriptionStr(uint64_t version) const +{ + const char* str = nullptr; + + switch (version) + { + case (nn::hac::gc::FWVER_DEV): + str = "ForDevelopment"; + break; + case (nn::hac::gc::FWVER_PROD): + str = "1.0.0+"; + break; + case (nn::hac::gc::FWVER_PROD_SINCE_4_0_0NUP): + str = "4.0.0+"; break; default: str = "Unknown"; @@ -313,3 +391,23 @@ const char* GameCardProcess::getCardClockRate(uint32_t acc_ctrl_1) const return str; } + +const char* GameCardProcess::getCardCompatibiltyType(byte_t flag) const +{ + const char* str = nullptr; + + switch (flag) + { + case (nn::hac::gc::COMPAT_GLOBAL): + str = "Global"; + break; + case (nn::hac::gc::COMPAT_CHINA): + str = "China"; + break; + default: + str = "Unknown"; + break; + } + + return str; +} diff --git a/src/GameCardProcess.h b/src/GameCardProcess.h index d1cba42..5351603 100644 --- a/src/GameCardProcess.h +++ b/src/GameCardProcess.h @@ -35,6 +35,7 @@ private: KeyConfiguration mKeyCfg; CliOutputMode mCliOutputMode; bool mVerify; + bool mListFs; struct sExtractInfo { @@ -53,10 +54,16 @@ private: } }; - bool mListFs; - - nn::hac::sGcHeaderPage mHdrPage; + + + bool mIsTrueSdkXci; + bool mIsSdkXciEncrypted; + size_t mGcHeaderOffset; + bool mProccessExtendedHeader; + byte_t mHdrSignature[fnd::rsa::kRsa2048Size]; + fnd::sha::sSha256Hash mHdrHash; nn::hac::GameCardHeader mHdr; + PfsProcess mRootPfs; fnd::List mExtractInfo; @@ -68,7 +75,10 @@ private: void processPartitionPfs(); // strings + const char* getKekIndexStr(byte_t kek_index) const; const char* getRomSizeStr(byte_t rom_size) const; const char* getHeaderFlagStr(byte_t flag) const; + const char* getCardFwVersionDescriptionStr(uint64_t version) const; const char* getCardClockRate(uint32_t acc_ctrl_1) const; + const char* getCardCompatibiltyType(byte_t flag) const; }; \ No newline at end of file diff --git a/src/UserSettings.cpp b/src/UserSettings.cpp index 46ce86e..0f82524 100644 --- a/src/UserSettings.cpp +++ b/src/UserSettings.cpp @@ -609,7 +609,7 @@ FileType UserSettings::getFileTypeFromString(const std::string& type_str) FileType type; if (str == "gc" || str == "gamecard" || str == "xci") - type = FILE_GC; + type = FILE_GAMECARD; else if (str == "nsp") type = FILE_NSP; else if (str == "partitionfs" || str == "hashedpartitionfs" \ @@ -666,9 +666,11 @@ FileType UserSettings::determineFileTypeFromFile(const std::string& path) #define _TYPE_PTR(st) ((st*)(scratch.data())) #define _ASSERT_SIZE(sz) (scratch.size() >= (sz)) - // test npdm - if (_ASSERT_SIZE(sizeof(nn::hac::sGcHeaderPage)) && _TYPE_PTR(nn::hac::sGcHeaderPage)->header.st_magic.get() == nn::hac::gc::kGcHeaderStructMagic) - file_type = FILE_GC; + // test gamecard + if (_ASSERT_SIZE(sizeof(nn::hac::sGcHeader_Rsa2048Signed)) && _TYPE_PTR(nn::hac::sGcHeader_Rsa2048Signed)->header.st_magic.get() == nn::hac::gc::kGcHeaderStructMagic) + file_type = FILE_GAMECARD; + else if (_ASSERT_SIZE(sizeof(nn::hac::sSdkGcHeader)) && _TYPE_PTR(nn::hac::sSdkGcHeader)->signed_header.header.st_magic.get() == nn::hac::gc::kGcHeaderStructMagic) + file_type = FILE_GAMECARD; // test pfs0 else if (_ASSERT_SIZE(sizeof(nn::hac::sPfsHeader)) && _TYPE_PTR(nn::hac::sPfsHeader)->st_magic.get() == nn::hac::pfs::kPfsStructMagic) file_type = FILE_PARTITIONFS; diff --git a/src/common.h b/src/common.h index df33d69..8625a8f 100644 --- a/src/common.h +++ b/src/common.h @@ -10,7 +10,7 @@ static const size_t kNcaKeakNum = nn::hac::nca::kKeyAreaEncryptionKeyNum; enum FileType { - FILE_GC, + FILE_GAMECARD, FILE_NSP, FILE_PARTITIONFS, FILE_ROMFS, diff --git a/src/main.cpp b/src/main.cpp index 2ed927b..5e0e45b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -40,7 +40,7 @@ int main(int argc, char** argv) fnd::SharedPtr inputFile(new fnd::SimpleFile(user_set.getInputPath(), fnd::SimpleFile::Read)); - if (user_set.getFileType() == FILE_GC) + if (user_set.getFileType() == FILE_GAMECARD) { GameCardProcess obj;