From 8ef9f478b0fe49a65d1bf968c3223b6c88176a80 Mon Sep 17 00:00:00 2001 From: jakcron Date: Tue, 24 Apr 2018 13:24:20 +0800 Subject: [PATCH] [programs] Remove xcitool, npdmtool, pfstool. Add nstool. --- .vscode/settings.json | 38 +- NXTools.sln | 8 - README.md | 8 +- lib/libcrypto/makefile | 4 +- lib/libfnd/include/fnd/Endian.h | 64 +- programs/makefile | 2 +- programs/ncatool/source/main.cpp | 51 +- programs/npdmtool/npdmtool.vcxproj | 137 ----- programs/npdmtool/npdmtool.vcxproj.filters | 22 - programs/npdmtool/source/main.cpp | 437 ------------- programs/{npdmtool => nstool}/makefile | 0 programs/nstool/source/NcaProcess.h | 33 + programs/nstool/source/NpdmProcess.cpp | 683 +++++++++++++++++++++ programs/nstool/source/NpdmProcess.h | 41 ++ programs/nstool/source/PfsProcess.cpp | 176 ++++++ programs/nstool/source/PfsProcess.h | 53 ++ programs/nstool/source/RomfsProcess.h | 0 programs/nstool/source/UserSettings.cpp | 537 ++++++++++++++++ programs/nstool/source/UserSettings.h | 89 +++ programs/nstool/source/XciProcess.cpp | 271 ++++++++ programs/nstool/source/XciProcess.h | 60 ++ programs/nstool/source/main.cpp | 97 +++ programs/nstool/source/nstool.h | 93 +++ programs/nstool/source/version.h | 4 + programs/pfstool/makefile | 47 -- programs/pfstool/pfstool.vcxproj | 140 ----- programs/pfstool/pfstool.vcxproj.filters | 25 - programs/pfstool/source/main.cpp | 77 --- programs/xcitool/makefile | 47 -- programs/xcitool/source/main.cpp | 226 ------- programs/xcitool/xcitool.vcxproj | 130 ---- programs/xcitool/xcitool.vcxproj.filters | 25 - programs/xcitool/xcitool.vcxproj.user | 4 - 33 files changed, 2253 insertions(+), 1376 deletions(-) delete mode 100644 programs/npdmtool/npdmtool.vcxproj delete mode 100644 programs/npdmtool/npdmtool.vcxproj.filters delete mode 100644 programs/npdmtool/source/main.cpp rename programs/{npdmtool => nstool}/makefile (100%) create mode 100644 programs/nstool/source/NcaProcess.h create mode 100644 programs/nstool/source/NpdmProcess.cpp create mode 100644 programs/nstool/source/NpdmProcess.h create mode 100644 programs/nstool/source/PfsProcess.cpp create mode 100644 programs/nstool/source/PfsProcess.h create mode 100644 programs/nstool/source/RomfsProcess.h create mode 100644 programs/nstool/source/UserSettings.cpp create mode 100644 programs/nstool/source/UserSettings.h create mode 100644 programs/nstool/source/XciProcess.cpp create mode 100644 programs/nstool/source/XciProcess.h create mode 100644 programs/nstool/source/main.cpp create mode 100644 programs/nstool/source/nstool.h create mode 100644 programs/nstool/source/version.h delete mode 100644 programs/pfstool/makefile delete mode 100644 programs/pfstool/pfstool.vcxproj delete mode 100644 programs/pfstool/pfstool.vcxproj.filters delete mode 100644 programs/pfstool/source/main.cpp delete mode 100644 programs/xcitool/makefile delete mode 100644 programs/xcitool/source/main.cpp delete mode 100644 programs/xcitool/xcitool.vcxproj delete mode 100644 programs/xcitool/xcitool.vcxproj.filters delete mode 100644 programs/xcitool/xcitool.vcxproj.user diff --git a/.vscode/settings.json b/.vscode/settings.json index a9ccd01..331ed15 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,20 +1,32 @@ { "files.associations": { - "__functional_base": "cpp", - "memory": "cpp", - "tuple": "cpp", - "utility": "cpp", - "__string": "cpp", + "__config": "cpp", + "__nullptr": "cpp", + "cstddef": "cpp", + "exception": "cpp", + "initializer_list": "cpp", + "new": "cpp", + "stdexcept": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp", + "__split_buffer": "cpp", + "__tree": "cpp", + "bitset": "cpp", + "iterator": "cpp", + "map": "cpp", "string": "cpp", "string_view": "cpp", - "__split_buffer": "cpp", - "initializer_list": "cpp", - "iterator": "cpp", + "utility": "cpp", "vector": "cpp", - "__functional_base_03": "cpp", - "__tuple": "cpp", - "limits": "cpp", - "type_traits": "cpp", - "stdexcept": "cpp" + "istream": "cpp", + "atomic": "cpp", + "ios": "cpp", + "system_error": "cpp", + "__functional_base": "cpp", + "locale": "cpp", + "memory": "cpp", + "tuple": "cpp", + "__locale": "cpp", + "cinttypes": "cpp" } } \ No newline at end of file diff --git a/NXTools.sln b/NXTools.sln index 89167ac..d0b43e6 100644 --- a/NXTools.sln +++ b/NXTools.sln @@ -9,15 +9,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libcrypto", "lib\libcrypto\ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libnx", "lib\libnx\nx.vcxproj", "{91BA9E79-8242-4F7D-B997-0DFEC95EA22B}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ncatool", "programs\ncatool\ncatool.vcxproj", "{7DA88C6F-4470-495D-995A-4F633F3370C1}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{170B4A09-1B67-4A62-93AB-116EBCFF4A8C}" ProjectSection(SolutionItems) = preProject lib\makefile = lib\makefile EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "npdmtool", "programs\npdmtool\npdmtool.vcxproj", "{550C6AC3-EBE0-46CA-AE6C-EEEB59DDF35C}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Programs", "Programs", "{E0863FCC-8E72-490D-BE1B-458F12CA8298}" ProjectSection(SolutionItems) = preProject programs\makefile = programs\makefile @@ -30,14 +26,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pfstool", "programs\pfstool\pfstool.vcxproj", "{BC2F2D07-BAB3-469C-9C25-8CC54F96F7AB}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libes", "lib\libes\es.vcxproj", "{7BE99936-0D40-410D-944B-4513C2EFF8DC}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tiktool", "programs\tiktool\tiktool.vcxproj", "{2200B834-F15A-4C6E-9DDB-6012B9A5C246}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xcitool", "programs\xcitool\xcitool.vcxproj", "{007FF616-7B99-4CB3-84CD-39C47F64FC7E}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 diff --git a/README.md b/README.md index e6bc796..b49fba9 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ Tools for NX (Nintendo Switch). == Tools == * ncatool - read/extract *.nca -* npdmtool - read *.npdm -* pfstool - read/extract PartitionFS (PFS0|HFS0) blobs (including *.nsp). -* xcitool - read *.xci +* nstool - read *.npdm, read/extract PartitionFS (PFS0|HFS0) blobs (including *.nsp), read *.xci +* tiktool - read ticket fields. == Issues == -* [ncatool] nca section crypto not implemented +* [ncatool] nca section extraction not implemented +* [nstool] romfs support not implemented diff --git a/lib/libcrypto/makefile b/lib/libcrypto/makefile index 8e35f03..07909d3 100644 --- a/lib/libcrypto/makefile +++ b/lib/libcrypto/makefile @@ -22,8 +22,8 @@ else UNAME = $(shell uname -s) ifeq ($(UNAME), Darwin) # MacOS Only Flags/Libs - CFLAGS += -Wno-unused-private-field - CXXFLAGS += -Wno-unused-private-field + CFLAGS += -arch x86_64 -Wno-unused-private-field + CXXFLAGS += -arch x86_64 -Wno-unused-private-field ARFLAGS = rc else # *nix Only Flags/Libs diff --git a/lib/libfnd/include/fnd/Endian.h b/lib/libfnd/include/fnd/Endian.h index 922b48c..db793f8 100644 --- a/lib/libfnd/include/fnd/Endian.h +++ b/lib/libfnd/include/fnd/Endian.h @@ -37,6 +37,7 @@ static inline uint16_t le_hword(uint16_t a) { return __local_bswap16(a); } #error "What's the endianness of the platform you're targeting?" #endif +/* template class ISerialiseablePrimative { public: @@ -53,4 +54,65 @@ typedef ISerialiseablePrimative be_uint16_t; typedef ISerialiseablePrimative le_uint32_t; typedef ISerialiseablePrimative be_uint32_t; typedef ISerialiseablePrimative le_uint64_t; -typedef ISerialiseablePrimative be_uint64_t; \ No newline at end of file +typedef ISerialiseablePrimative be_uint64_t; +*/ + +class le_uint16_t { +public: + inline uint16_t get() const { return le_hword(mVar);} + inline void set(uint16_t var) { mVar = le_hword(var); } + inline uint16_t operator=(uint16_t var) { set(var); return get();} + inline uint16_t operator*() const { return get(); } +private: + uint16_t mVar; +}; + +class be_uint16_t { +public: + inline uint16_t get() const { return be_hword(mVar);} + inline void set(uint16_t var) { mVar = be_hword(var); } + inline uint16_t operator=(uint16_t var) { set(var); return get();} + inline uint16_t operator*() const { return get(); } +private: + uint16_t mVar; +}; + +class le_uint32_t { +public: + inline uint32_t get() const { return le_word(mVar);} + inline void set(uint32_t var) { mVar = le_word(var); } + inline uint32_t operator=(uint32_t var) { set(var); return get();} + inline uint32_t operator*() const { return get(); } +private: + uint32_t mVar; +}; + +class be_uint32_t { +public: + inline uint32_t get() const { return be_word(mVar);} + inline void set(uint32_t var) { mVar = be_word(var); } + inline uint32_t operator=(uint32_t var) { set(var); return get();} + inline uint32_t operator*() const { return get(); } +private: + uint32_t mVar; +}; + +class le_uint64_t { +public: + inline uint64_t get() const { return le_dword(mVar);} + inline void set(uint64_t var) { mVar = le_dword(var); } + inline uint64_t operator=(uint64_t var) { set(var); return get();} + inline uint64_t operator*() const { return get(); } +private: + uint64_t mVar; +}; + +class be_uint64_t { +public: + inline uint64_t get() const { return be_dword(mVar);} + inline void set(uint64_t var) { mVar = be_dword(var); } + inline uint64_t operator=(uint64_t var) { set(var); return get();} + inline uint64_t operator*() const { return get(); } +private: + uint64_t mVar; +}; \ No newline at end of file diff --git a/programs/makefile b/programs/makefile index 3691828..b670a6f 100644 --- a/programs/makefile +++ b/programs/makefile @@ -1,4 +1,4 @@ -PROGS = ncatool npdmtool pfstool tiktool xcitool +PROGS = nstool ncatool tiktool BIN_DIR = "../bin" main: build diff --git a/programs/ncatool/source/main.cpp b/programs/ncatool/source/main.cpp index 55b5feb..efdec4f 100644 --- a/programs/ncatool/source/main.cpp +++ b/programs/ncatool/source/main.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #ifdef _WIN32 @@ -77,7 +78,7 @@ static const byte_t* kNcaHeaderKey[2][2] = { crypto::aes::nx::prod::nca_header_key[0], crypto::aes::nx::prod::nca_header_key[1] } }; -inline size_t sectorToOffset(size_t sector_index) { return sector_index * nx::nca::kSectorSize; } +inline size_t sectorToOffset(size_t sector_index) { return nx::NcaUtils::sectorToOffset(sector_index); } void initNcaCtr(byte_t ctr[crypto::aes::kAesBlockSize], uint32_t generation) { @@ -90,23 +91,9 @@ void initNcaCtr(byte_t ctr[crypto::aes::kAesBlockSize], uint32_t generation) void decryptNcaHeader(byte_t header[nx::nca::kHeaderSize], const byte_t* key[2]) { - byte_t tweak[crypto::aes::kAesBlockSize]; - - // decrypt main header - byte_t raw_hdr[nx::nca::kSectorSize]; - nx::NcaHeader hdr; - crypto::aes::AesXtsMakeTweak(tweak, 1); - crypto::aes::AesXtsDecryptSector(header + sectorToOffset(1), nx::nca::kSectorSize, key[0], key[1], tweak, raw_hdr); - hdr.importBinary(raw_hdr, nx::nca::kSectorSize); - - bool useNca2SectorIndex = hdr.getFormatVersion() == nx::NcaHeader::NCA2_FORMAT; - - // decrypt whole header - for (size_t i = 0; i < nx::nca::kHeaderSectorNum; i++) - { - crypto::aes::AesXtsMakeTweak(tweak, (i > 1 && useNca2SectorIndex)? 0 : i); - crypto::aes::AesXtsDecryptSector(header + sectorToOffset(i), nx::nca::kSectorSize, key[0], key[1], tweak, header + sectorToOffset(i)); - } + crypto::aes::sAesXts128Key a; + a.set(key[0],key[1]); + nx::NcaUtils::decryptNcaHeader(header, header, a); } bool testNcaHeaderKey(const byte_t* header_src, const byte_t* key[2]) @@ -198,21 +185,25 @@ void printHeader(const byte_t* header) printf(" Format Type: %s\n", kFormatTypeStr[fsHdr->format_type].c_str()); printf(" Hash Type: %s\n", kHashTypeStr[fsHdr->hash_type].c_str()); printf(" Enc. Type: %s\n", kEncryptionTypeStr[fsHdr->encryption_type].c_str()); - if (fsHdr->format_type == nx::nca::FORMAT_ROMFS) + if (fsHdr->hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY) { - + const nx::sIvfcHeader* hash_hdr = (const nx::sIvfcHeader*)(header + sectorToOffset(sector_index) + sizeof(nx::sNcaFsHeader)); + //printf(" HashHierarchicalIntegrity Header:\n"); + //printf(" ") + } - else if (fsHdr->format_type == nx::nca::FORMAT_PFS0) + else if (fsHdr->hash_type == nx::nca::HASH_HIERARCHICAL_SHA256) { - const nx::sPfsSuperBlock* pfs0 = (const nx::sPfsSuperBlock*)(header + sectorToOffset(sector_index) + sizeof(nx::sNcaFsHeader)); - printf(" PFS0 SuperBlock:\n"); - printf(" Master Hash: \n"); - printf(" HashBlockSize: 0x%x\n", pfs0->hash_block_size.get()); - printf(" Unknown: 0x%x\n", pfs0->unk_0x02.get()); - printf(" HashDataOffset: 0x%" PRIx64 "\n", pfs0->hash_data.offset.get()); - printf(" HashDataSize: 0x%" PRIx64 "\n", pfs0->hash_data.size.get()); - printf(" HashTargetOffset: 0x%" PRIx64 "\n", pfs0->hash_target.offset.get()); - printf(" HashTargetSize: 0x%" PRIx64 "\n", pfs0->hash_target.size.get()); + const nx::sHierarchicalSha256Header* hash_hdr = (const nx::sHierarchicalSha256Header*)(header + sectorToOffset(sector_index) + sizeof(nx::sNcaFsHeader)); + printf(" HashHierarchicalSha256 Header:\n"); + printf(" Master Hash: "); + fnd::SimpleTextOutput::hexDump(hash_hdr->master_hash, 0x20); + printf(" HashBlockSize: 0x%x\n", hash_hdr->hash_block_size.get()); + printf(" Unknown: 0x%x\n", hash_hdr->unk_0x02.get()); + printf(" HashDataOffset: 0x%" PRIx64 "\n", hash_hdr->hash_data.offset.get()); + printf(" HashDataSize: 0x%" PRIx64 "\n", hash_hdr->hash_data.size.get()); + printf(" HashTargetOffset: 0x%" PRIx64 "\n", hash_hdr->hash_target.offset.get()); + printf(" HashTargetSize: 0x%" PRIx64 "\n", hash_hdr->hash_target.size.get()); } diff --git a/programs/npdmtool/npdmtool.vcxproj b/programs/npdmtool/npdmtool.vcxproj deleted file mode 100644 index 3dfdca2..0000000 --- a/programs/npdmtool/npdmtool.vcxproj +++ /dev/null @@ -1,137 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - {550C6AC3-EBE0-46CA-AE6C-EEEB59DDF35C} - npdmtool - 8.1 - - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - - - - - - - - - - - - - - - - - - - - - Level3 - Disabled - true - ..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include; - _MBCS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) - - - - - Level3 - Disabled - true - ..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include; - _MBCS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) - - - - - Level3 - MaxSpeed - true - true - true - ..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include; - _MBCS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) - - - true - true - - - - - Level3 - MaxSpeed - true - true - true - ..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include; - _MBCS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) - - - true - true - - - - - - - - {6adbb60d-dba0-411d-bd2d-a355ef8e0fe1} - - - {4d27edb9-5110-44fe-8ce2-d46c5ad3c55b} - - - {91ba9e79-8242-4f7d-b997-0dfec95ea22b} - - - - - - \ No newline at end of file diff --git a/programs/npdmtool/npdmtool.vcxproj.filters b/programs/npdmtool/npdmtool.vcxproj.filters deleted file mode 100644 index 4c0a72b..0000000 --- a/programs/npdmtool/npdmtool.vcxproj.filters +++ /dev/null @@ -1,22 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - \ No newline at end of file diff --git a/programs/npdmtool/source/main.cpp b/programs/npdmtool/source/main.cpp deleted file mode 100644 index da39a3a..0000000 --- a/programs/npdmtool/source/main.cpp +++ /dev/null @@ -1,437 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -const std::string kInstructionType[2] = { "32Bit", "64Bit" }; -const std::string kProcAddrSpace[4] = { "Unknown", "64Bit", "32Bit", "32Bit no reserved" }; -const std::string kAciType[2] = { "ACI0", "ACID" }; -const std::string kMiscFlag[15] = { "EnableDebug", "ForceDebug", "bit2", "bit3", "bit4", "bit5", "bit6", "bit7", "bit8", "bit9", "bit10", "bit11", "bit12", "bit13", "bit14"}; -const std::string kFsaFlag[64] = -{ - "ApplicationInfo", - "BootModeControl", - "Calibration", - "SystemSaveData", - "GameCard", - "SaveDataBackUp", - "SaveDataManagement", - "BisAllRaw", - "GameCardRaw", - "GameCardPrivate", - "SetTime", - "ContentManager", - "ImageManager", - "CreateSaveData", - "SystemSaveDataManagement", - "BisFileSystem", - "SystemUpdate", - "SaveDataMeta", - "DeviceSaveData", - "SettingsControl", - "Bit20", - "Bit21", - "Bit22", - "Bit23", - "Bit24", - "Bit25", - "Bit26", - "Bit27", - "Bit28", - "Bit29", - "Bit30", - "Bit31", - "Bit32", - "Bit33", - "Bit34", - "Bit35", - "Bit36", - "Bit37", - "Bit38", - "Bit39", - "Bit40", - "Bit41", - "Bit42", - "Bit43", - "Bit44", - "Bit45", - "Bit46", - "Bit47", - "Bit48", - "Bit49", - "Bit50", - "Bit51", - "Bit52", - "Bit53", - "Bit54", - "Bit55", - "Bit56", - "Bit57", - "Bit58", - "Bit59", - "Bit60", - "Bit61", - "Debug", - "FullPermission" -}; -const std::string kSysCall[0x80] = -{ - "svc00", - "SetHeapSize", - "SetMemoryPermission", - "SetMemoryAttribute", - "MapMemory", - "UnmapMemory", - "QueryMemory", - "ExitProcess", - "CreateThread", - "StartThread", - "ExitThread", - "SleepThread", - "GetThreadPriority", - "SetThreadPriority", - "GetThreadCoreMask", - "SetThreadCoreMask", - "GetCurrentProcessorNumber", - "SignalEvent", - "ClearEvent", - "MapSharedMemory", - "UnmapSharedMemory", - "CreateTransferMemory", - "CloseHandle", - "ResetSignal", - "WaitSynchronization", - "CancelSynchronization", - "ArbitrateLock", - "ArbitrateUnlock", - "WaitProcessWideKeyAtomic", - "SignalProcessWideKey", - "GetSystemTick", - "ConnectToNamedPort", - "SendSyncRequestLight", - "SendSyncRequest", - "SendSyncRequestWithUserBuffer", - "SendAsyncRequestWithUserBuffer", - "GetProcessId", - "GetThreadId", - "Break", - "OutputDebugString", - "ReturnFromException", - "GetInfo", - "FlushEntireDataCache", - "FlushDataCache", - "MapPhysicalMemory", - "UnmapPhysicalMemory", - "svc2E", - "GetLastThreadInfo", - "GetResourceLimitLimitValue", - "GetResourceLimitCurrentValue", - "SetThreadActivity", - "GetThreadContext3", - "svc34", - "svc35", - "svc36", - "svc37", - "svc38", - "svc39", - "svc3A", - "svc3B", - "DumpInfo", - "svc3D", - "svc3E", - "svc3F", - "CreateSession", - "AcceptSession", - "ReplyAndReceiveLight", - "ReplyAndReceive", - "ReplyAndReceiveWithUserBuffer", - "CreateEvent", - "svc46", - "svc47", - "svc48", - "svc49", - "svc4A", - "svc4B", - "svc4C", - "SleepSystem", - "ReadWriteRegister", - "SetProcessActivity", - "CreateSharedMemory", - "MapTransferMemory", - "UnmapTransferMemory", - "CreateInterruptEvent", - "QueryPhysicalAddress", - "QueryIoMapping", - "CreateDeviceAddressSpace", - "AttachDeviceAddressSpace", - "DetachDeviceAddressSpace", - "MapDeviceAddressSpaceByForce", - "MapDeviceAddressSpaceAligned", - "MapDeviceAddressSpace", - "UnmapDeviceAddressSpace", - "InvalidateProcessDataCache", - "StoreProcessDataCache", - "FlushProcessDataCache", - "DebugActiveProcess", - "BreakDebugProcess", - "TerminateDebugProcess", - "GetDebugEvent", - "ContinueDebugEvent", - "GetProcessList", - "GetThreadList", - "GetDebugThreadContext", - "SetDebugThreadContext", - "QueryDebugProcessMemory", - "ReadDebugProcessMemory", - "WriteDebugProcessMemory", - "SetHardwareBreakPoint", - "GetDebugThreadParam", - "svc6E", - "svc6F", - "CreatePort", - "ManageNamedPort", - "ConnectToPort", - "SetProcessMemoryPermission", - "MapProcessMemory", - "UnmapProcessMemory", - "QueryProcessMemory", - "MapProcessCodeMemory", - "UnmapProcessCodeMemory", - "CreateProcess", - "StartProcess", - "TerminateProcess", - "GetProcessInfo", - "CreateResourceLimit", - "SetResourceLimitLimitValue", - "CallSecureMonitor" -}; - -const std::string kMemMapPerm[2] = { "RW", "RO" }; -const std::string kMemMapType[2] = { "Io", "Static" }; - -const std::string kAcidTarget[2] = { "Development", "Production" }; - -void displayNpdmHeader(const nx::NpdmHeader& hdr) -{ - printf("[NPDM HEADER]\n"); - printf(" Process Architecture Params:\n"); - printf(" Ins. Type: %s\n", kInstructionType[hdr.getInstructionType()].c_str()); - printf(" Addr Space: %s\n", kProcAddrSpace[hdr.getProcAddressSpaceType()].c_str()); - printf(" Main Thread Params:\n"); - printf(" Priority: %d\n", hdr.getMainThreadPriority()); - printf(" CpuId: %d\n", hdr.getMainThreadCpuId()); - printf(" StackSize: 0x%x\n", hdr.getMainThreadStackSize()); - printf(" TitleInfo:\n"); - printf(" Version: v%" PRIu32 "\n", hdr.getVersion()); - printf(" Name: %s\n", hdr.getName().c_str()); - if (hdr.getProductCode().length()) - { - printf(" ProductCode: %s\n", hdr.getProductCode().c_str()); - } -} - -void displayAciHdr(const nx::AciHeader& aci) -{ - printf("[Access Control Info]\n"); - printf(" ACI Type: %s\n", kAciType[aci.getAciType()].c_str()); - if (aci.getAciType() == nx::AciBinary::TYPE_ACI0) - { - printf(" ProgramID: %016" PRIx64 "\n", aci.getProgramId()); - } - else if (aci.getAciType() == nx::AciBinary::TYPE_ACID) - { - - printf(" ACID Size: %" PRIx64 "\n", aci.getAcidSize()); - printf(" Target: %s\n", kAcidTarget[aci.isProduction()].c_str()); - printf(" ProgramID Restriction\n"); - printf(" Min: %016" PRIx64 "\n", aci.getProgramIdMin()); - printf(" Max: %016" PRIx64 "\n", aci.getProgramIdMax()); - - } -} - -void displayFac(const nx::FacBinary& fac) -{ - printf("[FS Access Control]\n"); - printf(" Format Version: %d\n", fac.getFormatVersion()); - - if (fac.getFsaRightsList().getSize()) - { - printf(" FS Rights:\n"); - for (size_t i = 0; i < fac.getFsaRightsList().getSize(); i++) - { - if (i % 10 == 0) - { - printf("%s ", i != 0 ? "\n" : ""); - } - printf("%s%s", kFsaFlag[fac.getFsaRightsList()[i]].c_str(), fac.getFsaRightsList()[i] != fac.getFsaRightsList().atBack() ? ", " : "\n"); - } - } - else - { - printf(" FS Rights: NONE\n"); - } - - if (fac.getContentOwnerIdList().getSize()) - { - printf(" Content Owner IDs:\n"); - for (size_t i = 0; i < fac.getContentOwnerIdList().getSize(); i++) - { - printf(" 0x%08x\n", fac.getContentOwnerIdList()[i]); - } - } - if (fac.getSaveDataOwnerIdList().getSize()) - { - printf(" Save Data Owner IDs:\n"); - for (size_t i = 0; i < fac.getSaveDataOwnerIdList().getSize(); i++) - { - printf(" 0x%08x\n", fac.getSaveDataOwnerIdList()[i]); - } - } - -} - -void displaySac(const nx::SacBinary& sac) -{ - printf("[Service Access Control]\n"); - printf(" Service List:\n"); - for (size_t i = 0; i < sac.getServiceList().getSize(); i++) - { - if (i % 10 == 0) - { - printf("%s ", i != 0 ? "\n" : ""); - } - printf("%s%s%s", sac.getServiceList()[i].getName().c_str(), sac.getServiceList()[i].isServer() ? "(isSrv)" : "", sac.getServiceList()[i] != sac.getServiceList().atBack() ? ", " : "\n"); - } -} - -void displayKernelCap(const nx::KcBinary& kern) -{ - printf("[Kernel Capabilities]\n"); - if (kern.getThreadInfo().isSet()) - { - nx::ThreadInfoHandler threadInfo = kern.getThreadInfo(); - printf(" Thread Priority:\n"); - printf(" Min: %d\n", threadInfo.getMinPriority()); - printf(" Max: %d\n", threadInfo.getMaxPriority()); - printf(" CpuId:\n"); - printf(" Min: %d\n", threadInfo.getMinCpuId()); - printf(" Max: %d\n", threadInfo.getMaxCpuId()); - } - if (kern.getSystemCalls().isSet()) - { - fnd::List syscalls = kern.getSystemCalls().getSystemCalls(); - printf(" SystemCalls:"); - printf("\n "); - size_t lineLen = 0; - for (size_t i = 0; i < syscalls.getSize(); i++) - { - if (lineLen > 60) - { - lineLen = 0; - printf("\n "); - } - printf("%s%s", kSysCall[syscalls[i]].c_str(), syscalls[i] != syscalls.atBack() ? ", " : "\n"); - lineLen += kSysCall[syscalls[i]].length(); - } - } - if (kern.getMemoryMaps().isSet()) - { - fnd::List maps = kern.getMemoryMaps().getMemoryMaps(); - fnd::List ioMaps = kern.getMemoryMaps().getIoMemoryMaps(); - - printf(" MemoryMaps:\n"); - for (size_t i = 0; i < maps.getSize(); i++) - { - printf(" 0x%016" PRIx64 " - 0x%016" PRIx64 " (perm=%s) (type=%s)\n", (uint64_t)maps[i].addr << 12, ((uint64_t)(maps[i].addr + maps[i].size) << 12) - 1, kMemMapPerm[maps[i].perm].c_str(), kMemMapType[maps[i].type].c_str()); - } - //printf(" IoMaps:\n"); - for (size_t i = 0; i < ioMaps.getSize(); i++) - { - printf(" 0x%016" PRIx64 " - 0x%016" PRIx64 " (perm=%s) (type=%s)\n", (uint64_t)ioMaps[i].addr << 12, ((uint64_t)(ioMaps[i].addr + ioMaps[i].size) << 12) - 1, kMemMapPerm[ioMaps[i].perm].c_str(), kMemMapType[ioMaps[i].type].c_str()); - } - } - if (kern.getInterupts().isSet()) - { - fnd::List interupts = kern.getInterupts().getInteruptList(); - printf(" Interupts Flags:\n"); - for (uint32_t i = 0; i < interupts.getSize(); i++) - { - if (i % 10 == 0) - { - printf("%s ", i != 0 ? "\n" : ""); - } - printf("0x%x%s", interupts[i], interupts[i] != interupts.atBack() ? ", " : "\n"); - } - } - if (kern.getMiscParams().isSet()) - { - printf(" ProgramType: %d\n", kern.getMiscParams().getProgramType()); - } - if (kern.getKernelVersion().isSet()) - { - printf(" Kernel Version: %d.%d\n", kern.getKernelVersion().getVerMajor(), kern.getKernelVersion().getVerMinor()); - } - if (kern.getHandleTableSize().isSet()) - { - printf(" Handle Table Size: 0x%x\n", kern.getHandleTableSize().getHandleTableSize()); - } - if (kern.getMiscFlags().isSet()) - { - fnd::List flagList = kern.getMiscFlags().getFlagList(); - - printf(" Misc Flags:\n"); - for (uint32_t i = 0; i < flagList.getSize(); i++) - { - if (i % 10 == 0) - { - printf("%s ", i != 0 ? "\n" : ""); - } - printf("%s%s", kMiscFlag[flagList[i]].c_str(), flagList[i] != flagList.atBack() ? ", " : "\n"); - } - } -} - - -int main(int argc, char** argv) -{ - if (argc < 2) - { - printf("usage: npdmtool \n"); - return 1; - } - - try - { - fnd::MemoryBlob file; - fnd::io::readFile(argv[1], file); - - // import - nx::NpdmBinary npdm; - npdm.importBinary(file.getBytes(), file.getSize()); - - // npdm binary - displayNpdmHeader(npdm); - - // aci binary - displayAciHdr(npdm.getAci()); - displayFac(npdm.getAci().getFac()); - displaySac(npdm.getAci().getSac()); - displayKernelCap(npdm.getAci().getKc()); - - // acid binary - displayAciHdr(npdm.getAcid()); - displayFac(npdm.getAcid().getFac()); - displaySac(npdm.getAcid().getSac()); - displayKernelCap(npdm.getAcid().getKc()); - - } catch (const fnd::Exception& e) - { - printf("%s\n", e.what()); - } - - return 0; -} \ No newline at end of file diff --git a/programs/npdmtool/makefile b/programs/nstool/makefile similarity index 100% rename from programs/npdmtool/makefile rename to programs/nstool/makefile diff --git a/programs/nstool/source/NcaProcess.h b/programs/nstool/source/NcaProcess.h new file mode 100644 index 0000000..610ad46 --- /dev/null +++ b/programs/nstool/source/NcaProcess.h @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "nstool.h" + +class NcaProcess +{ +public: + NcaProcess(); + ~NcaProcess(); + + void process(); + + // generic + void setInputFile(fnd::IFile& reader); + void setInputFileOffset(size_t offset); + void setKeyset(const sKeyset* keyset); + void setCliOutputMode(CliOutputType type); + void setVerifyMode(bool verify); + + // nca specfic + +private: + const std::string kModuleName = "NcaProcess"; + + byte_t mRawHeader[nx::nca::kHeaderSize]; + std::string mPath; + const sKeyset* mKeyset; +}; \ No newline at end of file diff --git a/programs/nstool/source/NpdmProcess.cpp b/programs/nstool/source/NpdmProcess.cpp new file mode 100644 index 0000000..5c1e846 --- /dev/null +++ b/programs/nstool/source/NpdmProcess.cpp @@ -0,0 +1,683 @@ +#include "NpdmProcess.h" +#include +#include + +const std::string kInstructionType[2] = { "32Bit", "64Bit" }; +const std::string kProcAddrSpace[4] = { "Unknown", "64Bit", "32Bit", "32Bit no reserved" }; +const std::string kAciType[2] = { "ACI0", "ACID" }; +const std::string kMiscFlag[15] = { "EnableDebug", "ForceDebug", "bit2", "bit3", "bit4", "bit5", "bit6", "bit7", "bit8", "bit9", "bit10", "bit11", "bit12", "bit13", "bit14"}; +const std::string kFsaFlag[64] = +{ + "ApplicationInfo", + "BootModeControl", + "Calibration", + "SystemSaveData", + "GameCard", + "SaveDataBackUp", + "SaveDataManagement", + "BisAllRaw", + "GameCardRaw", + "GameCardPrivate", + "SetTime", + "ContentManager", + "ImageManager", + "CreateSaveData", + "SystemSaveDataManagement", + "BisFileSystem", + "SystemUpdate", + "SaveDataMeta", + "DeviceSaveData", + "SettingsControl", + "Bit20", + "Bit21", + "Bit22", + "Bit23", + "Bit24", + "Bit25", + "Bit26", + "Bit27", + "Bit28", + "Bit29", + "Bit30", + "Bit31", + "Bit32", + "Bit33", + "Bit34", + "Bit35", + "Bit36", + "Bit37", + "Bit38", + "Bit39", + "Bit40", + "Bit41", + "Bit42", + "Bit43", + "Bit44", + "Bit45", + "Bit46", + "Bit47", + "Bit48", + "Bit49", + "Bit50", + "Bit51", + "Bit52", + "Bit53", + "Bit54", + "Bit55", + "Bit56", + "Bit57", + "Bit58", + "Bit59", + "Bit60", + "Bit61", + "Debug", + "FullPermission" +}; +const std::string kSysCall[0x80] = +{ + "svc00", + "SetHeapSize", + "SetMemoryPermission", + "SetMemoryAttribute", + "MapMemory", + "UnmapMemory", + "QueryMemory", + "ExitProcess", + "CreateThread", + "StartThread", + "ExitThread", + "SleepThread", + "GetThreadPriority", + "SetThreadPriority", + "GetThreadCoreMask", + "SetThreadCoreMask", + "GetCurrentProcessorNumber", + "SignalEvent", + "ClearEvent", + "MapSharedMemory", + "UnmapSharedMemory", + "CreateTransferMemory", + "CloseHandle", + "ResetSignal", + "WaitSynchronization", + "CancelSynchronization", + "ArbitrateLock", + "ArbitrateUnlock", + "WaitProcessWideKeyAtomic", + "SignalProcessWideKey", + "GetSystemTick", + "ConnectToNamedPort", + "SendSyncRequestLight", + "SendSyncRequest", + "SendSyncRequestWithUserBuffer", + "SendAsyncRequestWithUserBuffer", + "GetProcessId", + "GetThreadId", + "Break", + "OutputDebugString", + "ReturnFromException", + "GetInfo", + "FlushEntireDataCache", + "FlushDataCache", + "MapPhysicalMemory", + "UnmapPhysicalMemory", + "svc2E", + "GetLastThreadInfo", + "GetResourceLimitLimitValue", + "GetResourceLimitCurrentValue", + "SetThreadActivity", + "GetThreadContext3", + "svc34", + "svc35", + "svc36", + "svc37", + "svc38", + "svc39", + "svc3A", + "svc3B", + "DumpInfo", + "svc3D", + "svc3E", + "svc3F", + "CreateSession", + "AcceptSession", + "ReplyAndReceiveLight", + "ReplyAndReceive", + "ReplyAndReceiveWithUserBuffer", + "CreateEvent", + "svc46", + "svc47", + "svc48", + "svc49", + "svc4A", + "svc4B", + "svc4C", + "SleepSystem", + "ReadWriteRegister", + "SetProcessActivity", + "CreateSharedMemory", + "MapTransferMemory", + "UnmapTransferMemory", + "CreateInterruptEvent", + "QueryPhysicalAddress", + "QueryIoMapping", + "CreateDeviceAddressSpace", + "AttachDeviceAddressSpace", + "DetachDeviceAddressSpace", + "MapDeviceAddressSpaceByForce", + "MapDeviceAddressSpaceAligned", + "MapDeviceAddressSpace", + "UnmapDeviceAddressSpace", + "InvalidateProcessDataCache", + "StoreProcessDataCache", + "FlushProcessDataCache", + "DebugActiveProcess", + "BreakDebugProcess", + "TerminateDebugProcess", + "GetDebugEvent", + "ContinueDebugEvent", + "GetProcessList", + "GetThreadList", + "GetDebugThreadContext", + "SetDebugThreadContext", + "QueryDebugProcessMemory", + "ReadDebugProcessMemory", + "WriteDebugProcessMemory", + "SetHardwareBreakPoint", + "GetDebugThreadParam", + "svc6E", + "svc6F", + "CreatePort", + "ManageNamedPort", + "ConnectToPort", + "SetProcessMemoryPermission", + "MapProcessMemory", + "UnmapProcessMemory", + "QueryProcessMemory", + "MapProcessCodeMemory", + "UnmapProcessCodeMemory", + "CreateProcess", + "StartProcess", + "TerminateProcess", + "GetProcessInfo", + "CreateResourceLimit", + "SetResourceLimitLimitValue", + "CallSecureMonitor" +}; + +const std::string kMemMapPerm[2] = { "RW", "RO" }; +const std::string kMemMapType[2] = { "Io", "Static" }; + +const std::string kAcidTarget[2] = { "Development", "Production" }; + +void NpdmProcess::validateAcidSignature(const nx::AcidBinary& acid) +{ + try { + acid.verifyBinary(mKeyset->acid_sign_key); + } + catch (...) { + // this is minimal even though it's a warning because it's a validation method + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACID Signature: FAIL\n"); + } + +} + +void NpdmProcess::validateAciFromAcid(const nx::AciBinary& aci, const nx::AcidBinary& acid) +{ + // check Program ID + if (acid.getProgramIdMin() > 0 && aci.getProgramId() < acid.getProgramIdMin()) + { + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACI ProgramId: FAIL (Outside Legal Range)\n"); + } + else if (acid.getProgramIdMax() > 0 && aci.getProgramId() > acid.getProgramIdMax()) + { + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACI ProgramId: FAIL (Outside Legal Range)\n"); + } + + // Check FAC + if (aci.getFac().getFormatVersion() != acid.getFac().getFormatVersion()) + { + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACI/FAC FormatVersion: FAIL (%d != %d (expected))\n", aci.getFac().getFormatVersion(),acid.getFac().getFormatVersion()); + } + + for (size_t i = 0; i < aci.getFac().getFsaRightsList().getSize(); i++) + { + bool fsaRightFound = false; + for (size_t j = 0; j < acid.getFac().getFsaRightsList().getSize() && fsaRightFound == false; j++) + { + if (aci.getFac().getFsaRightsList()[i] == acid.getFac().getFsaRightsList()[j]) + fsaRightFound = true; + } + + if (fsaRightFound == false) + { + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACI/FAC FsaRights: FAIL (%s not permitted)\n", kFsaFlag[aci.getFac().getFsaRightsList()[i]].c_str()); + } + } + + for (size_t i = 0; i < aci.getFac().getContentOwnerIdList().getSize(); i++) + { + bool rightFound = false; + for (size_t j = 0; j < acid.getFac().getContentOwnerIdList().getSize() && rightFound == false; j++) + { + if (aci.getFac().getContentOwnerIdList()[i] == acid.getFac().getContentOwnerIdList()[j]) + rightFound = true; + } + + if (rightFound == false) + { + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACI/FAC ContentOwnerId: FAIL (%08x not permitted)\n", aci.getFac().getContentOwnerIdList()[i]); + } + } + + for (size_t i = 0; i < aci.getFac().getSaveDataOwnerIdList().getSize(); i++) + { + bool rightFound = false; + for (size_t j = 0; j < acid.getFac().getSaveDataOwnerIdList().getSize() && rightFound == false; j++) + { + if (aci.getFac().getSaveDataOwnerIdList()[i] == acid.getFac().getSaveDataOwnerIdList()[j]) + rightFound = true; + } + + if (rightFound == false) + { + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACI/FAC ContentOwnerId: FAIL (%08x not permitted)\n", aci.getFac().getSaveDataOwnerIdList()[i]); + } + } + + // check SAC + for (size_t i = 0; i < aci.getSac().getServiceList().getSize(); i++) + { + bool rightFound = false; + for (size_t j = 0; j < acid.getSac().getServiceList().getSize() && rightFound == false; j++) + { + if (aci.getSac().getServiceList()[i] == acid.getSac().getServiceList()[j]) + rightFound = true; + } + + if (rightFound == false) + { + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACI/SAC ServiceList: FAIL (%s%s not permitted)\n", aci.getSac().getServiceList()[i].getName().c_str(), aci.getSac().getServiceList()[i].isServer()? " (Server)" : ""); + } + } + + // check KC + // check thread info + if (aci.getKc().getThreadInfo().getMaxCpuId() != acid.getKc().getThreadInfo().getMaxCpuId()) + { + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACI/KC ThreadInfo/MaxCpuId: FAIL (%d not permitted)\n", aci.getKc().getThreadInfo().getMaxCpuId()); + } + if (aci.getKc().getThreadInfo().getMinCpuId() != acid.getKc().getThreadInfo().getMinCpuId()) + { + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACI/KC ThreadInfo/MinCpuId: FAIL (%d not permitted)\n", aci.getKc().getThreadInfo().getMinCpuId()); + } + if (aci.getKc().getThreadInfo().getMaxPriority() != acid.getKc().getThreadInfo().getMaxPriority()) + { + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACI/KC ThreadInfo/MaxPriority: FAIL (%d not permitted)\n", aci.getKc().getThreadInfo().getMaxPriority()); + } + if (aci.getKc().getThreadInfo().getMinPriority() != acid.getKc().getThreadInfo().getMinPriority()) + { + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACI/KC ThreadInfo/MinPriority: FAIL (%d not permitted)\n", aci.getKc().getThreadInfo().getMinPriority()); + } + // check system calls + for (size_t i = 0; i < aci.getKc().getSystemCalls().getSystemCalls().getSize(); i++) + { + bool rightFound = false; + for (size_t j = 0; j < acid.getKc().getSystemCalls().getSystemCalls().getSize() && rightFound == false; j++) + { + if (aci.getKc().getSystemCalls().getSystemCalls()[i] == acid.getKc().getSystemCalls().getSystemCalls()[j]) + rightFound = true; + } + + if (rightFound == false) + { + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACI/KC SystemCallList: FAIL (%s not permitted)\n", kSysCall[aci.getKc().getSystemCalls().getSystemCalls()[i]].c_str()); + } + } + // check memory maps + for (size_t i = 0; i < aci.getKc().getMemoryMaps().getMemoryMaps().getSize(); i++) + { + bool rightFound = false; + for (size_t j = 0; j < acid.getKc().getMemoryMaps().getMemoryMaps().getSize() && rightFound == false; j++) + { + if (aci.getKc().getMemoryMaps().getMemoryMaps()[i] == acid.getKc().getMemoryMaps().getMemoryMaps()[j]) + rightFound = true; + } + + if (rightFound == false) + { + const nx::MemoryMappingHandler::sMemoryMapping& map = aci.getKc().getMemoryMaps().getMemoryMaps()[i]; + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACI/KC MemoryMap: FAIL (0x%016" PRIx64 " - 0x%016" PRIx64 " (perm=%s) (type=%s) not permitted)\n", (uint64_t)map.addr << 12, ((uint64_t)(map.addr + map.size) << 12) - 1, kMemMapPerm[map.perm].c_str(), kMemMapType[map.type].c_str()); + } + } + for (size_t i = 0; i < aci.getKc().getMemoryMaps().getIoMemoryMaps().getSize(); i++) + { + bool rightFound = false; + for (size_t j = 0; j < acid.getKc().getMemoryMaps().getIoMemoryMaps().getSize() && rightFound == false; j++) + { + if (aci.getKc().getMemoryMaps().getIoMemoryMaps()[i] == acid.getKc().getMemoryMaps().getIoMemoryMaps()[j]) + rightFound = true; + } + + if (rightFound == false) + { + const nx::MemoryMappingHandler::sMemoryMapping& map = aci.getKc().getMemoryMaps().getIoMemoryMaps()[i]; + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACI/KC IoMemoryMap: FAIL (0x%016" PRIx64 " - 0x%016" PRIx64 " (perm=%s) (type=%s) not permitted)\n", (uint64_t)map.addr << 12, ((uint64_t)(map.addr + map.size) << 12) - 1, kMemMapPerm[map.perm].c_str(), kMemMapType[map.type].c_str()); + } + } + // check interupts + for (size_t i = 0; i < aci.getKc().getInterupts().getInteruptList().getSize(); i++) + { + bool rightFound = false; + for (size_t j = 0; j < acid.getKc().getInterupts().getInteruptList().getSize() && rightFound == false; j++) + { + if (aci.getKc().getInterupts().getInteruptList()[i] == acid.getKc().getInterupts().getInteruptList()[j]) + rightFound = true; + } + + if (rightFound == false) + { + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACI/KC InteruptsList: FAIL (0x%0x not permitted)\n", aci.getKc().getInterupts().getInteruptList()[i]); + } + } + // check misc params + if (aci.getKc().getMiscParams().getProgramType() != acid.getKc().getMiscParams().getProgramType()) + { + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACI/KC ProgramType: FAIL (%d not permitted)\n", aci.getKc().getMiscParams().getProgramType()); + } + // check kernel version + uint32_t aciKernelVersion = (uint32_t)aci.getKc().getKernelVersion().getVerMajor() << 16 | (uint32_t)aci.getKc().getKernelVersion().getVerMinor(); + uint32_t acidKernelVersion = (uint32_t)acid.getKc().getKernelVersion().getVerMajor() << 16 | (uint32_t)acid.getKc().getKernelVersion().getVerMinor(); + if (aciKernelVersion < acidKernelVersion) + { + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACI/KC RequiredKernelVersion: FAIL (%d.%d not permitted)\n", aci.getKc().getKernelVersion().getVerMajor(), aci.getKc().getKernelVersion().getVerMinor()); + } + // check handle table size + if (aci.getKc().getHandleTableSize().getHandleTableSize() > acid.getKc().getHandleTableSize().getHandleTableSize()) + { + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACI/KC HandleTableSize: FAIL (0x%x too large)\n", aci.getKc().getHandleTableSize().getHandleTableSize()); + } + // check misc flags + for (size_t i = 0; i < aci.getKc().getMiscFlags().getFlagList().getSize(); i++) + { + bool rightFound = false; + for (size_t j = 0; j < acid.getKc().getMiscFlags().getFlagList().getSize() && rightFound == false; j++) + { + if (aci.getKc().getMiscFlags().getFlagList()[i] == acid.getKc().getMiscFlags().getFlagList()[j]) + rightFound = true; + } + + if (rightFound == false) + { + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] ACI/KC MiscFlag: FAIL (%s not permitted)\n", kMiscFlag[aci.getKc().getMiscFlags().getFlagList()[i]].c_str()); + } + } +} + +void NpdmProcess::displayNpdmHeader(const nx::NpdmHeader& hdr) +{ + printf("[NPDM HEADER]\n"); + printf(" Process Architecture Params:\n"); + printf(" Ins. Type: %s\n", kInstructionType[hdr.getInstructionType()].c_str()); + printf(" Addr Space: %s\n", kProcAddrSpace[hdr.getProcAddressSpaceType()].c_str()); + printf(" Main Thread Params:\n"); + printf(" Priority: %d\n", hdr.getMainThreadPriority()); + printf(" CpuId: %d\n", hdr.getMainThreadCpuId()); + printf(" StackSize: 0x%x\n", hdr.getMainThreadStackSize()); + printf(" TitleInfo:\n"); + printf(" Version: v%" PRIu32 "\n", hdr.getVersion()); + printf(" Name: %s\n", hdr.getName().c_str()); + if (hdr.getProductCode().length()) + { + printf(" ProductCode: %s\n", hdr.getProductCode().c_str()); + } +} + +void NpdmProcess::displayAciHdr(const nx::AciHeader& aci) +{ + printf("[Access Control Info]\n"); + printf(" ACI Type: %s\n", kAciType[aci.getAciType()].c_str()); + if (aci.getAciType() == nx::AciBinary::TYPE_ACI0) + { + printf(" ProgramID: %016" PRIx64 "\n", aci.getProgramId()); + } + else if (aci.getAciType() == nx::AciBinary::TYPE_ACID) + { + + printf(" ACID Size: %" PRIx64 "\n", aci.getAcidSize()); + printf(" Target: %s\n", kAcidTarget[aci.isProduction()].c_str()); + printf(" ProgramID Restriction\n"); + printf(" Min: %016" PRIx64 "\n", aci.getProgramIdMin()); + printf(" Max: %016" PRIx64 "\n", aci.getProgramIdMax()); + + } +} + +void NpdmProcess::displayFac(const nx::FacBinary& fac) +{ + printf("[FS Access Control]\n"); + printf(" Format Version: %d\n", fac.getFormatVersion()); + + if (fac.getFsaRightsList().getSize()) + { + printf(" FS Rights:\n"); + for (size_t i = 0; i < fac.getFsaRightsList().getSize(); i++) + { + if (i % 10 == 0) + { + printf("%s ", i != 0 ? "\n" : ""); + } + printf("%s%s", kFsaFlag[fac.getFsaRightsList()[i]].c_str(), fac.getFsaRightsList()[i] != fac.getFsaRightsList().atBack() ? ", " : "\n"); + } + } + else + { + printf(" FS Rights: NONE\n"); + } + + if (fac.getContentOwnerIdList().getSize()) + { + printf(" Content Owner IDs:\n"); + for (size_t i = 0; i < fac.getContentOwnerIdList().getSize(); i++) + { + printf(" 0x%08x\n", fac.getContentOwnerIdList()[i]); + } + } + if (fac.getSaveDataOwnerIdList().getSize()) + { + printf(" Save Data Owner IDs:\n"); + for (size_t i = 0; i < fac.getSaveDataOwnerIdList().getSize(); i++) + { + printf(" 0x%08x\n", fac.getSaveDataOwnerIdList()[i]); + } + } + +} + +void NpdmProcess::displaySac(const nx::SacBinary& sac) +{ + printf("[Service Access Control]\n"); + printf(" Service List:\n"); + for (size_t i = 0; i < sac.getServiceList().getSize(); i++) + { + if (i % 10 == 0) + { + printf("%s ", i != 0 ? "\n" : ""); + } + printf("%s%s%s", sac.getServiceList()[i].getName().c_str(), sac.getServiceList()[i].isServer() ? "(isSrv)" : "", sac.getServiceList()[i] != sac.getServiceList().atBack() ? ", " : "\n"); + } +} + +void NpdmProcess::displayKernelCap(const nx::KcBinary& kern) +{ + printf("[Kernel Capabilities]\n"); + if (kern.getThreadInfo().isSet()) + { + nx::ThreadInfoHandler threadInfo = kern.getThreadInfo(); + printf(" Thread Priority:\n"); + printf(" Min: %d\n", threadInfo.getMinPriority()); + printf(" Max: %d\n", threadInfo.getMaxPriority()); + printf(" CpuId:\n"); + printf(" Min: %d\n", threadInfo.getMinCpuId()); + printf(" Max: %d\n", threadInfo.getMaxCpuId()); + } + if (kern.getSystemCalls().isSet()) + { + fnd::List syscalls = kern.getSystemCalls().getSystemCalls(); + printf(" SystemCalls:"); + printf("\n "); + size_t lineLen = 0; + for (size_t i = 0; i < syscalls.getSize(); i++) + { + if (lineLen > 60) + { + lineLen = 0; + printf("\n "); + } + printf("%s%s", kSysCall[syscalls[i]].c_str(), syscalls[i] != syscalls.atBack() ? ", " : "\n"); + lineLen += kSysCall[syscalls[i]].length(); + } + } + if (kern.getMemoryMaps().isSet()) + { + fnd::List maps = kern.getMemoryMaps().getMemoryMaps(); + fnd::List ioMaps = kern.getMemoryMaps().getIoMemoryMaps(); + + printf(" MemoryMaps:\n"); + for (size_t i = 0; i < maps.getSize(); i++) + { + printf(" 0x%016" PRIx64 " - 0x%016" PRIx64 " (perm=%s) (type=%s)\n", (uint64_t)maps[i].addr << 12, ((uint64_t)(maps[i].addr + maps[i].size) << 12) - 1, kMemMapPerm[maps[i].perm].c_str(), kMemMapType[maps[i].type].c_str()); + } + //printf(" IoMaps:\n"); + for (size_t i = 0; i < ioMaps.getSize(); i++) + { + printf(" 0x%016" PRIx64 " - 0x%016" PRIx64 " (perm=%s) (type=%s)\n", (uint64_t)ioMaps[i].addr << 12, ((uint64_t)(ioMaps[i].addr + ioMaps[i].size) << 12) - 1, kMemMapPerm[ioMaps[i].perm].c_str(), kMemMapType[ioMaps[i].type].c_str()); + } + } + if (kern.getInterupts().isSet()) + { + fnd::List interupts = kern.getInterupts().getInteruptList(); + printf(" Interupts Flags:\n"); + for (uint32_t i = 0; i < interupts.getSize(); i++) + { + if (i % 10 == 0) + { + printf("%s ", i != 0 ? "\n" : ""); + } + printf("0x%x%s", interupts[i], interupts[i] != interupts.atBack() ? ", " : "\n"); + } + } + if (kern.getMiscParams().isSet()) + { + printf(" ProgramType: %d\n", kern.getMiscParams().getProgramType()); + } + if (kern.getKernelVersion().isSet()) + { + printf(" Kernel Version: %d.%d\n", kern.getKernelVersion().getVerMajor(), kern.getKernelVersion().getVerMinor()); + } + if (kern.getHandleTableSize().isSet()) + { + printf(" Handle Table Size: 0x%x\n", kern.getHandleTableSize().getHandleTableSize()); + } + if (kern.getMiscFlags().isSet()) + { + fnd::List flagList = kern.getMiscFlags().getFlagList(); + + printf(" Misc Flags:\n"); + for (uint32_t i = 0; i < flagList.getSize(); i++) + { + if (i % 10 == 0) + { + printf("%s ", i != 0 ? "\n" : ""); + } + printf("%s%s", kMiscFlag[flagList[i]].c_str(), flagList[i] != flagList.atBack() ? ", " : "\n"); + } + } +} + +NpdmProcess::NpdmProcess() : + mReader(nullptr), + mOffset(0), + mKeyset(nullptr), + mCliOutputType(OUTPUT_NORMAL), + mVerify(false) +{ +} + +void NpdmProcess::process() +{ + fnd::MemoryBlob scratch; + scratch.alloc(mReader->size()); + mReader->read(scratch.getBytes(), 0, scratch.getSize()); + + mNpdm.importBinary(scratch.getBytes(), scratch.getSize()); + + if (mVerify) + { + validateAcidSignature(mNpdm.getAcid()); + validateAciFromAcid(mNpdm.getAci(), mNpdm.getAcid()); + } + + if (mCliOutputType >= OUTPUT_NORMAL) + { + // npdm binary + displayNpdmHeader(mNpdm); + + // aci binary + displayAciHdr(mNpdm.getAci()); + displayFac(mNpdm.getAci().getFac()); + displaySac(mNpdm.getAci().getSac()); + displayKernelCap(mNpdm.getAci().getKc()); + + // acid binary + displayAciHdr(mNpdm.getAcid()); + displayFac(mNpdm.getAcid().getFac()); + displaySac(mNpdm.getAcid().getSac()); + displayKernelCap(mNpdm.getAcid().getKc()); + } +} + +void NpdmProcess::setInputFile(fnd::IFile& reader) +{ + mReader = &reader; +} + +void NpdmProcess::setInputFileOffset(size_t offset) +{ + mOffset = offset; +} + +void NpdmProcess::setKeyset(const sKeyset* keyset) +{ + mKeyset = keyset; +} + +void NpdmProcess::setCliOutputMode(CliOutputType type) +{ + mCliOutputType = type; +} + +void NpdmProcess::setVerifyMode(bool verify) +{ + mVerify = verify; +} \ No newline at end of file diff --git a/programs/nstool/source/NpdmProcess.h b/programs/nstool/source/NpdmProcess.h new file mode 100644 index 0000000..d2538a9 --- /dev/null +++ b/programs/nstool/source/NpdmProcess.h @@ -0,0 +1,41 @@ +#pragma once +#include +#include +#include +#include + +#include "nstool.h" + +class NpdmProcess +{ +public: + NpdmProcess(); + + void process(); + + void setInputFile(fnd::IFile& reader); + void setInputFileOffset(size_t offset); + void setKeyset(const sKeyset* keyset); + void setCliOutputMode(CliOutputType type); + void setVerifyMode(bool verify); + +private: + const std::string kModuleName = "NpdmProcess"; + + fnd::IFile* mReader; + size_t mOffset; + const sKeyset* mKeyset; + CliOutputType mCliOutputType; + bool mVerify; + + nx::NpdmBinary mNpdm; + + void validateAcidSignature(const nx::AcidBinary& acid); + void validateAciFromAcid(const nx::AciBinary& aci, const nx::AcidBinary& acid); + + void displayNpdmHeader(const nx::NpdmHeader& hdr); + void displayAciHdr(const nx::AciHeader& aci); + void displayFac(const nx::FacBinary& fac); + void displaySac(const nx::SacBinary& sac); + void displayKernelCap(const nx::KcBinary& kern); +}; \ No newline at end of file diff --git a/programs/nstool/source/PfsProcess.cpp b/programs/nstool/source/PfsProcess.cpp new file mode 100644 index 0000000..037c6b6 --- /dev/null +++ b/programs/nstool/source/PfsProcess.cpp @@ -0,0 +1,176 @@ +#include "PfsProcess.h" +#include + +void PfsProcess::displayHeader() +{ + printf("[PartitionFS]\n"); + printf(" Type: %s\n", mPfs.getFsType() == mPfs.TYPE_PFS0? "PFS0" : "HFS0"); + printf(" FileNum: %u\n", mPfs.getFileList().getSize()); + 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) + printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ")\n", mPfs.getFileList()[i].offset, mPfs.getFileList()[i].size); + else + printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ", hash_protected_size=0x%" PRIx64 ")\n", mPfs.getFileList()[i].offset, mPfs.getFileList()[i].size, mPfs.getFileList()[i].hash_protected_size); + } + else + { + printf("\n"); + } + + } +} + +size_t PfsProcess::determineHeaderSize(const nx::sPfsHeader* hdr) +{ + size_t fileEntrySize = 0; + if (std::string(hdr->signature, 4) == nx::pfs::kPfsSig) + fileEntrySize = sizeof(nx::sPfsFile); + else + fileEntrySize = sizeof(nx::sHashedPfsFile); + + return sizeof(nx::sPfsHeader) + hdr->file_num.get() * fileEntrySize + hdr->name_table_size.get(); +} + +void PfsProcess::validateHfs() +{ + fnd::MemoryBlob scratch; + crypto::sha::sSha256Hash hash; + const fnd::List& file = mPfs.getFileList(); + for (size_t i = 0; i < file.getSize(); i++) + { + scratch.alloc(file[i].hash_protected_size); + mReader->read(scratch.getBytes(), mOffset + file[i].offset, file[i].hash_protected_size); + crypto::sha::Sha256(scratch.getBytes(), scratch.getSize(), hash.bytes); + if (hash != file[i].hash) + { + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] HFS0 %s%s%s: FAIL (bad hash)\n", !mMountName.empty()? mMountName.c_str() : "", !mMountName.empty()? "/" : "", file[i].name.c_str()); + + } + } +} + +void PfsProcess::extractFs() +{ + // allocate scratch memory + fnd::MemoryBlob scratch; + scratch.alloc(kFileExportBlockSize); + + // make extract dir + fnd::io::makeDirectory(mExtractPath); + + fnd::SimpleFile outFile; + const fnd::List& file = mPfs.getFileList(); + + for (size_t i = 0; i < file.getSize(); i++) + { + outFile.open(mExtractPath + kPathSeparator + file[i].name, outFile.Create); + mReader->seek(mOffset + file[i].offset); + for (size_t j = 0; j < (file[i].size / kFileExportBlockSize); j++) + { + mReader->read(scratch.getBytes(), kFileExportBlockSize); + outFile.write(scratch.getBytes(), kFileExportBlockSize); + } + if (file[i].size % kFileExportBlockSize) + { + mReader->read(scratch.getBytes(), file[i].size % kFileExportBlockSize); + outFile.write(scratch.getBytes(), file[i].size % kFileExportBlockSize); + } + outFile.close(); + } +} + +PfsProcess::PfsProcess() : + mReader(nullptr), + mOffset(0), + mKeyset(nullptr), + mCliOutputType(OUTPUT_NORMAL), + mVerify(false), + mExtractPath(), + mExtract(false), + mMountName(), + mListFs(false), + mPfs() +{ + +} + +void PfsProcess::process() +{ + fnd::MemoryBlob scratch; + + // open minimum header to get full header size + scratch.alloc(sizeof(nx::sPfsHeader)); + mReader->read(scratch.getBytes(), mOffset, scratch.getSize()); + size_t pfsHeaderSize = determineHeaderSize(((nx::sPfsHeader*)scratch.getBytes())); + + // open minimum header to get full header size + scratch.alloc(pfsHeaderSize); + mReader->read(scratch.getBytes(), mOffset, scratch.getSize()); + 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(); +} + +void PfsProcess::setInputFile(fnd::IFile& reader) +{ + mReader = &reader; +} + +void PfsProcess::setInputFileOffset(size_t offset) +{ + mOffset = offset; +} + +void PfsProcess::setKeyset(const sKeyset* keyset) +{ + mKeyset = keyset; +} + +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; +} diff --git a/programs/nstool/source/PfsProcess.h b/programs/nstool/source/PfsProcess.h new file mode 100644 index 0000000..50145e0 --- /dev/null +++ b/programs/nstool/source/PfsProcess.h @@ -0,0 +1,53 @@ +#pragma once +#include +#include +#include +#include + +#include "nstool.h" + +class PfsProcess +{ +public: + PfsProcess(); + + void process(); + + // generic + void setInputFile(fnd::IFile& reader); + void setInputFileOffset(size_t offset); + void setKeyset(const sKeyset* keyset); + void setCliOutputMode(CliOutputType type); + void setVerifyMode(bool verify); + + // pfs specific + void setMountPointName(const std::string& mount_name); + void setExtractPath(const std::string& path); + void setListFs(bool list_fs); + + const nx::PfsHeader& getPfsHeader() const; + +private: + const std::string kModuleName = "PfsProcess"; + static const size_t kFileExportBlockSize = 0x1000000; + + fnd::IFile* mReader; + size_t mOffset; + const sKeyset* mKeyset; + CliOutputType mCliOutputType; + bool mVerify; + + + std::string mExtractPath; + bool mExtract; + std::string mMountName; + bool mListFs; + + nx::PfsHeader mPfs; + + void displayHeader(); + void displayFs(); + size_t determineHeaderSize(const nx::sPfsHeader* hdr); + void validateHfs(); + void extractFs(); +}; \ No newline at end of file diff --git a/programs/nstool/source/RomfsProcess.h b/programs/nstool/source/RomfsProcess.h new file mode 100644 index 0000000..e69de29 diff --git a/programs/nstool/source/UserSettings.cpp b/programs/nstool/source/UserSettings.cpp new file mode 100644 index 0000000..ccb6f03 --- /dev/null +++ b/programs/nstool/source/UserSettings.cpp @@ -0,0 +1,537 @@ +#include "UserSettings.h" +#include "version.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +UserSettings::UserSettings() +{} + +void UserSettings::parseCmdArgs(int argc, char** argv) +{ + sCmdArgs args; + populateCmdArgs(argc, argv, args); + populateKeyset(args); + populateUserSettings(args); +} + +void UserSettings::showHelp() +{ + printf("NSTool v%d.%d (C) %s\n", VER_MAJOR, VER_MINOR, AUTHORS); + printf("Built: %s %s\n\n", __TIME__, __DATE__); + + printf("Usage: nstool [options... ] \n"); + printf("\n General Options:\n"); + printf(" -d, --dev Use devkit keyset\n"); + printf(" -k, --keyset Specify keyset file\n"); + printf(" -t, --type Specify input file type [xci, pfs, romfs, nca, npdm]\n"); + printf(" -y, --verify Verify file\n"); + printf(" -v, --verbose Verbose output\n"); + printf(" -q, --quiet Minimal output\n"); + printf("\n XCI (GameCard Image)\n"); + printf(" nstool [--listfs] [--update --normal --secure ] <.xci file>\n"); + printf(" --listfs Print file system in embedded partitions\n"); + printf(" --update Extract \"update\" partition to directory\n"); + printf(" --normal Extract \"normal\" partition to directory\n"); + printf(" --secure Extract \"secure\" partition to directory\n"); + printf("\n PFS0/HFS0 (PartitionFs), RomFs, NSP (Ninendo Submission Package)\n"); + printf(" nstool [--listfs] [--fsdir ] \n"); + printf(" --listfs Print file system\n"); + printf(" --fsdir Extract file system to directory\n"); + /* + printf("\n NCA (Nintendo Content Archive)\n"); + printf(" nstool [--listfs] [--bodykey --titlekey ] [--part0 ...] <.nca file>\n"); + printf(" --listfs Print file system in embedded partitions\n"); + printf(" --titlekey Specify title key extracted from ticket\n"); + printf(" --bodykey Specify body encryption key\n"); + printf(" --part0 Extract \"partition 0\" to directory \n"); + printf(" --part1 Extract \"partition 1\" to directory \n"); + printf(" --part2 Extract \"partition 2\" to directory \n"); + printf(" --part3 Extract \"partition 3\" to directory \n"); + */ +} + +const std::string UserSettings::getInputPath() const +{ + return mInputPath; +} + +const sKeyset& UserSettings::getKeyset() const +{ + return mKeyset; +} + +FileType UserSettings::getFileType() const +{ + return mFileType; +} + +bool UserSettings::isVerifyFile() const +{ + return mVerifyFile; +} + +CliOutputType UserSettings::getCliOutputType() const +{ + return mOutputType; +} + +bool UserSettings::isListFs() const +{ + return mListFs; +} + +const sOptional& UserSettings::getUpdatePath() const +{ + return mUpdatePath; +} + +const sOptional& UserSettings::getNormalPath() const +{ + return mNormalPath; +} + +const sOptional& UserSettings::getSecurePath() const +{ + return mSecurePath; +} + +const sOptional& UserSettings::getFsPath() const +{ + return mFsPath; +} + + +void UserSettings::populateCmdArgs(int argc, char** argv, sCmdArgs& cmd_args) +{ + // create vector of args + std::vector args; + for (size_t i = 0; i < (size_t)argc; i++) + { + args.push_back(argv[i]); + } + + // show help text + if (args.size() < 2) + { + showHelp(); + throw fnd::Exception(kModuleName, "Not enough arguments."); + } + + cmd_args.clear(); + cmd_args.input_path = args.back(); + + for (size_t i = 1; i < args.size(); i++) + { + if (args[i] == "-h" || args[i] == "--help") + { + showHelp(); + throw fnd::Exception(kModuleName, "Nothing to do."); + } + } + + for (size_t i = 1; i+1 < args.size(); i++) + { + bool hasParamter = args[i+1][0] != '-' && i+2 < args.size(); + + if (args[i] == "-d" || args[i] == "--dev") + { + if (hasParamter) throw fnd::Exception(kModuleName, args[i] + " does not take a parameter."); + cmd_args.devkit_keys = true; + } + + else if (args[i] == "-y" || args[i] == "--verify") + { + if (hasParamter) throw fnd::Exception(kModuleName, args[i] + " does not take a parameter."); + cmd_args.verify_file = true; + } + + else if (args[i] == "-v" || args[i] == "--verbose") + { + if (hasParamter) throw fnd::Exception(kModuleName, args[i] + " does not take a parameter."); + cmd_args.verbose_output = true; + } + + else if (args[i] == "-q" || args[i] == "--quiet") + { + if (hasParamter) throw fnd::Exception(kModuleName, args[i] + " does not take a parameter."); + cmd_args.minimal_output = true; + } + + else if (args[i] == "-k" || args[i] == "--keyset") + { + if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter."); + cmd_args.keyset_path = args[i+1]; + } + + else if (args[i] == "-t" || args[i] == "--type") + { + if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter."); + cmd_args.file_type = args[i+1]; + } + + else if (args[i] == "--listfs") + { + if (hasParamter) throw fnd::Exception(kModuleName, args[i] + " does not take a parameter."); + cmd_args.list_fs = true; + } + + else if (args[i] == "--update") + { + if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter."); + cmd_args.update_path = args[i+1]; + } + + else if (args[i] == "--normal") + { + if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter."); + cmd_args.normal_path = args[i+1]; + } + + else if (args[i] == "--secure") + { + if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter."); + cmd_args.secure_path = args[i+1]; + } + + else if (args[i] == "--fsdir") + { + if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter."); + cmd_args.fs_path = args[i+1]; + } + + else if (args[i] == "--titlekey") + { + if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter."); + cmd_args.nca_titlekey = args[i+1]; + } + + else if (args[i] == "--bodykey") + { + if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter."); + cmd_args.nca_bodykey = args[i+1]; + } + + else if (args[i] == "-o") + { + if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter."); + cmd_args.output_path = args[i+1]; + } + + else + { + throw fnd::Exception(kModuleName, args[i] + " is not recognised."); + } + + i += hasParamter; + } +} + +void UserSettings::populateKeyset(sCmdArgs& args) +{ + crypto::aes::sAes128Key zeros_aes_key; + crypto::aes::sAesXts128Key zeros_aes_xts_key; + memset((void*)&zeros_aes_key, 0, sizeof(crypto::aes::sAes128Key)); + memset((void*)&zeros_aes_xts_key, 0, sizeof(crypto::aes::sAesXts128Key)); + memset((void*)&mKeyset, 0, sizeof(sKeyset)); + + fnd::ResourceFileReader res; + if (args.keyset_path.isSet) + { + res.processFile(*args.keyset_path); + } + else + { + // open other resource files in $HOME/.switch/prod.keys (or $HOME/.switch/dev.keys if -d/--dev is set). + char* home = nullptr; + if (home == nullptr) home = getenv("HOME"); + if (home == nullptr) home = getenv("USERPROFILE"); + if (home == nullptr) return; + + const std::string kKeysetNameStr[2] = {"prod.keys", "dev.keys"}; + + std::string keyset_path = std::string(home) + std::string("/") + ".switch" + std::string("/") + kKeysetNameStr[args.devkit_keys.isSet ? *args.devkit_keys : 0]; + //std::cout << keyset_path << std::endl; + res.processFile(keyset_path); + } + + const std::string kMasterKeyBase = "master_key_"; + const std::string kPackage1KeyBase = "package1_key_"; + const std::string kPackage2KeyBase = "package2_key_"; + const std::string kTicketCommonKeyBase[2] = { "titlekek_", "ticket_commonkey_" }; + const std::string kNcaKeyAreaKeyBase[3] = {"key_area_key_application_", "key_area_key_ocean_", "key_area_key_system_"}; + const std::string kKeySource = "source"; + const std::string kKeyIndex[kMasterKeyNum] = {"00","01","02","03","04","05","06","07","08","09","0a","0b","0c","0d","0e","0f","10","11","12","13","14","15","16","17","18","19","1a","1b","1c","1d","1e","1f"}; + + const std::string kNcaHeaderKey[2] = {"header_key", "nca_header_key"}; + const std::string kXciHeaderKey = "xci_header_key"; + const std::string kKekGenSource = "aes_kek_generation_"; + const std::string kKeyGenSource = "aes_key_generation_"; + + const std::string kNcaHeaderSignKeyBase = "nca_header_sign_key_"; + const std::string kXciHeaderSignKeyBase = "xci_header_sign_key_"; + const std::string kAcidSignKeyBase = "acid_sign_key_"; + const std::string kPackage2SignKeyBase = "package2_sign_key_"; + const std::string kRsaKeyComponent[2] = {"private", "modulus"}; + + // sources + crypto::aes::sAes128Key master_key[kMasterKeyNum] = { zeros_aes_key }; + crypto::aes::sAes128Key package2_key_source = zeros_aes_key; + crypto::aes::sAes128Key ticket_titlekek_source = zeros_aes_key; + crypto::aes::sAes128Key key_area_key_source[3] = { zeros_aes_key, zeros_aes_key, zeros_aes_key }; + crypto::aes::sAes128Key aes_kek_generation_source = zeros_aes_key; + crypto::aes::sAes128Key aes_key_generation_source = zeros_aes_key; + + std::string key; + +#define _SAVE_KEYDATA(key_name, array, len) \ + key = res[(key_name)]; \ + if (key.empty() == false) { \ + decodeHexStringToBytes((key_name), key, (byte_t*)array, len); \ + } + + _SAVE_KEYDATA(kPackage2KeyBase + kKeySource, package2_key_source.key, 0x10); + _SAVE_KEYDATA(kTicketCommonKeyBase[0] + kKeySource, ticket_titlekek_source.key, 0x10); + _SAVE_KEYDATA(kTicketCommonKeyBase[1] + kKeySource, ticket_titlekek_source.key, 0x10); + _SAVE_KEYDATA(kNcaKeyAreaKeyBase[0] + kKeySource, key_area_key_source[0].key, 0x10); + _SAVE_KEYDATA(kNcaKeyAreaKeyBase[1] + kKeySource, key_area_key_source[1].key, 0x10); + _SAVE_KEYDATA(kNcaKeyAreaKeyBase[2] + kKeySource, key_area_key_source[2].key, 0x10); + _SAVE_KEYDATA(kKekGenSource + kKeySource, aes_kek_generation_source.key, 0x10); + _SAVE_KEYDATA(kKeyGenSource + kKeySource, aes_key_generation_source.key, 0x10); + + // Store Key Variants/Derivatives + for (size_t i = 0; i < kMasterKeyNum; i++) + { + _SAVE_KEYDATA(kMasterKeyBase + kKeyIndex[i], master_key[i].key, 0x10); + _SAVE_KEYDATA(kPackage1KeyBase + kKeyIndex[i], mKeyset.package1_key[i].key, 0x10); + _SAVE_KEYDATA(kPackage2KeyBase + kKeyIndex[i], mKeyset.package2_key[i].key, 0x10); + _SAVE_KEYDATA(kTicketCommonKeyBase[0] + kKeyIndex[i], mKeyset.ticket.titlekey_kek[i].key, 0x10); + _SAVE_KEYDATA(kTicketCommonKeyBase[1] + kKeyIndex[i], mKeyset.ticket.titlekey_kek[i].key, 0x10); + _SAVE_KEYDATA(kNcaKeyAreaKeyBase[0] + kKeyIndex[i], mKeyset.nca.key_area_key[0][i].key, 0x10); + _SAVE_KEYDATA(kNcaKeyAreaKeyBase[1] + kKeyIndex[i], mKeyset.nca.key_area_key[1][i].key, 0x10); + _SAVE_KEYDATA(kNcaKeyAreaKeyBase[2] + kKeyIndex[i], mKeyset.nca.key_area_key[2][i].key, 0x10); + } + + // store nca header key + _SAVE_KEYDATA(kNcaHeaderKey[0], mKeyset.nca.header_key.key[0], 0x20); + _SAVE_KEYDATA(kNcaHeaderKey[1], mKeyset.nca.header_key.key[0], 0x20); + // store xci header key + _SAVE_KEYDATA(kXciHeaderKey, mKeyset.xci.header_key.key, 0x10); + + // store rsa keys + _SAVE_KEYDATA(kNcaHeaderSignKeyBase + kRsaKeyComponent[0], mKeyset.nca.header_sign_key.priv_exponent, 0x100); + _SAVE_KEYDATA(kNcaHeaderSignKeyBase + kRsaKeyComponent[1], mKeyset.nca.header_sign_key.modulus, 0x100); + + _SAVE_KEYDATA(kXciHeaderSignKeyBase + kRsaKeyComponent[0], mKeyset.xci.header_sign_key.priv_exponent, 0x100); + _SAVE_KEYDATA(kXciHeaderSignKeyBase + kRsaKeyComponent[1], mKeyset.xci.header_sign_key.modulus, 0x100); + + _SAVE_KEYDATA(kAcidSignKeyBase + kRsaKeyComponent[0], mKeyset.acid_sign_key.priv_exponent, 0x100); + _SAVE_KEYDATA(kAcidSignKeyBase + kRsaKeyComponent[1], mKeyset.acid_sign_key.modulus, 0x100); + + _SAVE_KEYDATA(kPackage2SignKeyBase + kRsaKeyComponent[0], mKeyset.package2_sign_key.priv_exponent, 0x100); + _SAVE_KEYDATA(kPackage2SignKeyBase + kRsaKeyComponent[1], mKeyset.package2_sign_key.modulus, 0x100); + + // save keydata from input args + if (args.nca_bodykey.isSet) + { + if (args.nca_bodykey.var.length() == (sizeof(crypto::aes::sAes128Key)*2)) + { + decodeHexStringToBytes("--bodykey", args.nca_bodykey.var, mKeyset.nca.manual_body_key_aesctr.key, sizeof(crypto::aes::sAes128Key)); + } + else + { + decodeHexStringToBytes("--bodykey", args.nca_bodykey.var, mKeyset.nca.manual_body_key_aesxts.key[0], sizeof(crypto::aes::sAesXts128Key)); + } + } + + if (args.nca_titlekey.isSet) + { + decodeHexStringToBytes("--titlekey", args.nca_titlekey.var, mKeyset.nca.manual_title_key.key, sizeof(crypto::aes::sAes128Key)); + } + +#undef _SAVE_KEYDATA + + // Derive keys + for (size_t i = 0; i < kMasterKeyNum; i++) + { + if (master_key[i] != zeros_aes_key) + { + if (aes_kek_generation_source != zeros_aes_key && aes_key_generation_source != zeros_aes_key) + { + for (size_t j = 0; j < nx::nca::kKeyAreaEncryptionKeyNum; j++) + { + if (key_area_key_source[j] != zeros_aes_key && mKeyset.nca.key_area_key[j][i] == zeros_aes_key) + { + nx::AesKeygen::generateKey(mKeyset.nca.key_area_key[j][i].key, aes_kek_generation_source.key, key_area_key_source[j].key, aes_key_generation_source.key, master_key[i].key); + //printf("nca keak %d/%02d ", j, i); + //fnd::SimpleTextOutput::hexDump(mKeyset.nca.key_area_key[j][i].key, 0x10); + } + } + } + + if (ticket_titlekek_source != zeros_aes_key && mKeyset.ticket.titlekey_kek[i] == zeros_aes_key) + { + nx::AesKeygen::generateKey(mKeyset.ticket.titlekey_kek[i].key, ticket_titlekek_source.key, master_key[i].key); + //printf("ticket titlekek %02d ", i); + //fnd::SimpleTextOutput::hexDump(mKeyset.ticket.titlekey_kek[i].key, 0x10); + } + if (package2_key_source != zeros_aes_key && mKeyset.package2_key[i] == zeros_aes_key) + { + nx::AesKeygen::generateKey(mKeyset.package2_key[i].key, package2_key_source.key, master_key[i].key); + //printf("package2 key %02d ", i); + //fnd::SimpleTextOutput::hexDump(mKeyset.package2_key[i].key, 0x10); + } + } + } + + +} + +void UserSettings::populateUserSettings(sCmdArgs& args) +{ + // check invalid input + if (args.input_path.isSet == false) + throw fnd::Exception(kModuleName, "No input file specified"); + if (args.verbose_output.isSet && args.minimal_output.isSet) + throw fnd::Exception(kModuleName, "Options --verbose and --quiet cannot be used together."); + + // save arguments + mInputPath = *args.input_path; + mVerifyFile = args.verify_file.isSet; + mListFs = args.list_fs.isSet; + mUpdatePath = args.update_path; + mNormalPath = args.normal_path; + mSecurePath = args.secure_path; + mFsPath = args.fs_path; + + // determine output path + if (args.verbose_output.isSet) + mOutputType = OUTPUT_VERBOSE; + else if (args.minimal_output.isSet) + mOutputType = OUTPUT_MINIMAL; + else + mOutputType = OUTPUT_NORMAL; + + // determine input file type + if (args.file_type.isSet) + mFileType = getFileTypeFromString(*args.file_type); + else + mFileType = determineFileTypeFromFile(mInputPath); + + // check is the input file could be identified + if (mFileType == FILE_INVALID) + throw fnd::Exception(kModuleName, "Unknown file type."); +} + + +void UserSettings::decodeHexStringToBytes(const std::string& name, const std::string& str, byte_t* out, size_t out_len) +{ + size_t size = str.size(); + if ((size % 2) || ((size / 2) != out_len)) + { + throw fnd::Exception(kModuleName, "Key: \"" + name + "\" has incorrect length"); + } + + for (size_t i = 0; i < out_len; i++) + { + out[i] = (charToByte(str[i * 2]) << 4) | charToByte(str[(i * 2) + 1]); + } +} + +FileType UserSettings::getFileTypeFromString(const std::string& type_str) +{ + std::string str = type_str; + std::transform(str.begin(), str.end(), str.begin(), ::tolower); + + FileType type; + if (str == "xci") + type = FILE_XCI; + else if ( str == "partitionfs" \ + || str == "pfs" || str == "pfs0" \ + || str == "hfs" || str == "hfs0" \ + || str == "nsp") + type = FILE_PARTITIONFS; + else if (str == "romfs") + type = FILE_ROMFS; + else if (str == "nca") + type = FILE_NCA; + else if (str == "npdm") + type = FILE_NPDM; + else + type = FILE_INVALID; + + return type; +} + +FileType UserSettings::determineFileTypeFromFile(const std::string& path) +{ + static const size_t kMaxReadSize = 0x1000; + FileType file_type = FILE_INVALID; + fnd::SimpleFile file; + fnd::MemoryBlob blob; + + // open file + file.open(path, file.Read); + + // read file + blob.alloc(MIN(kMaxReadSize, file.size())); + file.read(blob.getBytes(), 0, blob.getSize()); + // close file + file.close(); + + // prepare decrypted NCA data + byte_t nca_raw[nx::nca::kHeaderSize]; + nx::sNcaHeader* nca_header = (nx::sNcaHeader*)(nca_raw + nx::NcaUtils::sectorToOffset(1)); + + if (blob.getSize() >= nx::nca::kHeaderSize) + { + nx::NcaUtils::decryptNcaHeader(blob.getBytes(), nca_raw, mKeyset.nca.header_key); + } + + // _QUICK_CAST resolves to a pointer of type 'st' located at blob.getBytes() + 'oft' +#define _QUICK_CAST(st, oft) ((st*)(blob.getBytes() + (oft))) +#define _ASSERT_SIZE(size) (blob.getSize() >= (size)) + + // test npdm + if (_ASSERT_SIZE(0x100 + sizeof(nx::sXciHeader)) && memcmp(_QUICK_CAST(nx::sXciHeader, 0x100)->signature, nx::xci::kXciSig.c_str(), 4) == 0) + file_type = FILE_XCI; + // test pfs0 + else if (_ASSERT_SIZE(sizeof(nx::sPfsHeader)) && memcmp(_QUICK_CAST(nx::sPfsHeader, 0)->signature, nx::pfs::kPfsSig.c_str(), 4) == 0) + file_type = FILE_PARTITIONFS; + // test hfs0 + else if (_ASSERT_SIZE(sizeof(nx::sPfsHeader)) && memcmp(_QUICK_CAST(nx::sPfsHeader, 0)->signature, nx::pfs::kHashedPfsSig.c_str(), 4) == 0) + file_type = FILE_PARTITIONFS; + // test romfs + else if (_ASSERT_SIZE(sizeof(nx::sRomfsHeader)) && _QUICK_CAST(nx::sRomfsHeader, 0)->header_size.get() == sizeof(nx::sRomfsHeader) && _QUICK_CAST(nx::sRomfsHeader, 0)->header_size.get() == _QUICK_CAST(nx::sRomfsHeader, 0)->sections[0].offset.get()) + file_type = FILE_ROMFS; + // test nca2 + else if (_ASSERT_SIZE(nx::nca::kHeaderSize) && memcmp(nca_header->signature, nx::nca::kNca2Sig.c_str(), 4) == 0) + file_type = FILE_NCA; + // test nca3 + else if (_ASSERT_SIZE(nx::nca::kHeaderSize) && memcmp(nca_header->signature, nx::nca::kNca3Sig.c_str(), 4) == 0) + file_type = FILE_NCA; + // test npdm + else if (_ASSERT_SIZE(sizeof(nx::sNpdmHeader)) && memcmp(_QUICK_CAST(nx::sNpdmHeader, 0)->signature(), nx::npdm::kNpdmStructSig.c_str(), 4) == 0) + file_type = FILE_NPDM; + // else unrecognised + else + file_type = FILE_INVALID; + +#undef _ASSERT_SIZE +#undef _QUICK_CAST + + return file_type; +} \ No newline at end of file diff --git a/programs/nstool/source/UserSettings.h b/programs/nstool/source/UserSettings.h new file mode 100644 index 0000000..fbd1afe --- /dev/null +++ b/programs/nstool/source/UserSettings.h @@ -0,0 +1,89 @@ +#pragma once +#include +#include +#include "nstool.h" + +class UserSettings +{ +public: + UserSettings(); + + void parseCmdArgs(int argc, char** argv); + void showHelp(); + + // generic options + const std::string getInputPath() const; + const sKeyset& getKeyset() const; + FileType getFileType() const; + bool isVerifyFile() const; + CliOutputType getCliOutputType() const; + + // specialised toggles + bool isListFs() const; + + // specialised paths + const sOptional& getUpdatePath() const; + const sOptional& getNormalPath() const; + const sOptional& getSecurePath() const; + const sOptional& getFsPath() const; + +private: + const std::string kModuleName = "UserSettings"; + + struct sCmdArgs + { + sOptional input_path; + sOptional output_path; + sOptional devkit_keys; + sOptional keyset_path; + sOptional file_type; + sOptional verify_file; + sOptional verbose_output; + sOptional minimal_output; + sOptional list_fs; + sOptional update_path; + sOptional normal_path; + sOptional secure_path; + sOptional fs_path; + sOptional nca_titlekey; + sOptional nca_bodykey; + + void clear() + { + input_path.isSet = false; + output_path.isSet = false; + devkit_keys.isSet = false; + keyset_path.isSet = false; + file_type.isSet = false; + verify_file.isSet = false; + verbose_output.isSet = false; + minimal_output.isSet = false; + list_fs.isSet = false; + update_path.isSet = false; + normal_path.isSet = false; + secure_path.isSet = false; + fs_path.isSet = false; + nca_titlekey.isSet = false; + nca_bodykey.isSet = false; + } + }; + + std::string mInputPath; + FileType mFileType; + sKeyset mKeyset; + bool mVerifyFile; + CliOutputType mOutputType; + + bool mListFs; + sOptional mUpdatePath; + sOptional mNormalPath; + sOptional mSecurePath; + sOptional mFsPath; + + void populateCmdArgs(int argc, char** argv, sCmdArgs& cmd_args); + void populateKeyset(sCmdArgs& args); + void populateUserSettings(sCmdArgs& args); + void decodeHexStringToBytes(const std::string& name, const std::string& str, byte_t* out, size_t out_len); + FileType getFileTypeFromString(const std::string& type_str); + FileType determineFileTypeFromFile(const std::string& path); +}; \ No newline at end of file diff --git a/programs/nstool/source/XciProcess.cpp b/programs/nstool/source/XciProcess.cpp new file mode 100644 index 0000000..d66038b --- /dev/null +++ b/programs/nstool/source/XciProcess.cpp @@ -0,0 +1,271 @@ +#include "XciProcess.h" +#include +#include + +inline const char* getBoolStr(bool isTrue) +{ + return isTrue? "TRUE" : "FALSE"; +} + +inline const char* getRomSizeStr(byte_t rom_size) +{ + const char* str = "unknown"; + switch (rom_size) + { + case (nx::xci::ROM_SIZE_1GB) : + str = "1GB"; + break; + case (nx::xci::ROM_SIZE_2GB) : + str = "2GB"; + break; + case (nx::xci::ROM_SIZE_4GB) : + str = "4GB"; + break; + case (nx::xci::ROM_SIZE_8GB) : + str = "8GB"; + break; + case (nx::xci::ROM_SIZE_16GB) : + str = "16GB"; + break; + case (nx::xci::ROM_SIZE_32GB) : + str = "32GB"; + break; + } + return str; +} + +inline const char* getCardClockRate(uint32_t acc_ctrl_1) +{ + const char* str = "unknown"; + switch (acc_ctrl_1) + { + case (nx::xci::CLOCK_RATE_25) : + str = "20 MHz"; + break; + case (nx::xci::CLOCK_RATE_50) : + str = "50 MHz"; + break; + + } + return str; +} + + +void XciProcess::displayHeader() +{ + printf("[XCI HEADER]\n"); + printf(" Magic: HEAD\n"); + printf(" RomAreaStartPage: 0x%0x", mHdr.getRomAreaStartPage()); + if (mHdr.getRomAreaStartPage() != -1) + printf(" (0x%" PRIx64 ")", nx::XciUtils::blockToAddr(mHdr.getRomAreaStartPage())); + printf("\n"); + printf(" BackupAreaStartPage: 0x%0x", mHdr.getBackupAreaStartPage()); + if (mHdr.getBackupAreaStartPage() != -1) + printf(" (0x%" PRIx64 ")", nx::XciUtils::blockToAddr(mHdr.getBackupAreaStartPage())); + printf("\n"); + printf(" KekIndex: %d\n", mHdr.getKekIndex()); + printf(" TitleKeyDecIndex: %d\n", mHdr.getTitleKeyDecIndex()); + printf(" RomSize: 0x%x (%s)\n", mHdr.getRomSizeType(), getRomSizeStr(mHdr.getRomSizeType())); + printf(" CardHeaderVersion: %d\n", mHdr.getCardHeaderVersion()); + printf(" Flags: 0x%x\n", mHdr.getFlags()); + printf(" AutoBoot: %s\n", getBoolStr(_HAS_BIT(mHdr.getFlags(), nx::xci::FLAG_AUTOBOOT))); + printf(" HistoryErase: %s\n", getBoolStr(_HAS_BIT(mHdr.getFlags(), nx::xci::FLAG_HISTORY_ERASE))); + printf(" RepairTool: %s\n", getBoolStr(_HAS_BIT(mHdr.getFlags(), nx::xci::FLAG_REPAIR_TOOL))); + printf(" PackageId: 0x%" PRIx64 "\n", mHdr.getPackageId()); + printf(" ValidDataEndPage: 0x%x", mHdr.getValidDataEndPage()); + if (mHdr.getValidDataEndPage() != -1) + printf(" (0x%" PRIx64 ")", nx::XciUtils::blockToAddr(mHdr.getValidDataEndPage())); + printf("\n"); + printf(" AesIv: "); + fnd::SimpleTextOutput::hexDump(mHdr.getAesCbcIv().iv, sizeof(mHdr.getAesCbcIv().iv)); + printf(" PartitionFs:\n"); + printf(" Offset: 0x%" PRIx64 "\n", mHdr.getPartitionFsAddress()); + printf(" Size: 0x%" PRIx64 "\n", mHdr.getPartitionFsSize()); + printf(" Hash: "); + fnd::SimpleTextOutput::hexDump(mHdr.getPartitionFsHash().bytes, sizeof(mHdr.getPartitionFsHash().bytes)); + printf(" InitialData:\n"); + printf(" Hash: "); + fnd::SimpleTextOutput::hexDump(mHdr.getInitialDataHash().bytes, sizeof(mHdr.getInitialDataHash().bytes)); + printf(" SelSec: 0x%x\n", mHdr.getSelSec()); + printf(" SelT1Key: 0x%x\n", mHdr.getSelT1Key()); + printf(" SelKey: 0x%x\n", mHdr.getSelKey()); + printf(" LimArea: 0x%x", mHdr.getLimAreaPage()); + if (mHdr.getLimAreaPage() != -1) + printf(" (0x%" PRIx64 ")", nx::XciUtils::blockToAddr(mHdr.getLimAreaPage())); + printf("\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(" UppVersion: %d\n", mHdr.getUppVersion()); + printf(" UppHash: "); + fnd::SimpleTextOutput::hexDump(mHdr.getUppHash(), 8); + printf(" UppId: %016" PRIx64 "\n", mHdr.getUppId()); + +} + +bool XciProcess::validateRegionOfFile(size_t offset, size_t len, const byte_t* test_hash) +{ + fnd::MemoryBlob scratch; + crypto::sha::sSha256Hash calc_hash; + scratch.alloc(len); + mReader->read(scratch.getBytes(), offset, len); + crypto::sha::Sha256(scratch.getBytes(), scratch.getSize(), calc_hash.bytes); + return calc_hash.compare(test_hash); +} + +void XciProcess::validateXciSignature() +{ + crypto::sha::sSha256Hash calc_hash; + crypto::sha::Sha256((byte_t*)&mHdrPage.header, sizeof(nx::sXciHeader), calc_hash.bytes); + if (crypto::rsa::pkcs::rsaVerify(mKeyset->xci.header_sign_key, crypto::sha::HASH_SHA256, calc_hash.bytes, mHdrPage.signature) != 0) + { + // this is minimal even though it's a warning because it's a validation method + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] XCI Header Signature: FAIL \n"); + } +} + +void XciProcess::processRootPfs() +{ + if (mVerify) + { + if (validateRegionOfFile(mOffset + mHdr.getPartitionFsAddress(), mHdr.getPartitionFsSize(), mHdr.getPartitionFsHash().bytes) == false) + { + printf("[WARNING] XCI Root HFS0: FAIL (bad hash)\n"); + } + } + mRootPfs.setInputFile(*mReader); + mRootPfs.setInputFileOffset(mOffset + mHdr.getPartitionFsAddress()); + mRootPfs.setListFs(mListFs); + mRootPfs.setVerifyMode(mVerify); + mRootPfs.setCliOutputMode(mCliOutputType); + mRootPfs.setMountPointName(kXciMountPointName); + mRootPfs.process(); +} + +void XciProcess::processPartitionPfs() +{ + const fnd::List& rootPartitions = mRootPfs.getPfsHeader().getFileList(); + for (size_t i = 0; i < rootPartitions.getSize(); i++) + { + PfsProcess tmp; + tmp.setInputFile(*mReader); + tmp.setInputFileOffset(mOffset + mHdr.getPartitionFsAddress() + rootPartitions[i].offset); + tmp.setListFs(mListFs); + tmp.setVerifyMode(mVerify); + tmp.setCliOutputMode(mCliOutputType); + tmp.setMountPointName(kXciMountPointName + rootPartitions[i].name); + if (mUpdatePath.doExtract && rootPartitions[i].name == "update") + tmp.setExtractPath(mUpdatePath.path); + if (mNormalPath.doExtract && rootPartitions[i].name == "normal") + tmp.setExtractPath(mNormalPath.path); + if (mSecurePath.doExtract && rootPartitions[i].name == "secure") + tmp.setExtractPath(mSecurePath.path); + tmp.process(); + } +} + +XciProcess::XciProcess() : + mReader(nullptr), + mOffset(0), + mKeyset(nullptr), + mCliOutputType(OUTPUT_NORMAL), + mVerify(false), + mListFs(false), + mRootPfs(), + mUpdatePfs(), + mNormalPfs(), + mSecurePfs() +{ + mUpdatePath.doExtract = false; + mNormalPath.doExtract = false; + mSecurePath.doExtract = false; +} + +void XciProcess::process() +{ + fnd::MemoryBlob scratch; + + // read header page + mReader->read((byte_t*)&mHdrPage, mOffset, sizeof(nx::sXciHeaderPage)); + + // allocate memory for and decrypt sXciHeader + scratch.alloc(sizeof(nx::sXciHeader)); + nx::XciUtils::decryptXciHeader((const byte_t*)&mHdrPage.header, scratch.getBytes(), mKeyset->xci.header_key.key); + + // validate header signature + if (mVerify) + { + validateXciSignature(); + } + + // deserialise header + mHdr.importBinary(scratch.getBytes(), scratch.getSize()); + + // display header + if (mCliOutputType >= OUTPUT_NORMAL) + { + displayHeader(); + } + + // process root partition + processRootPfs(); + + // process partitions + processPartitionPfs(); +} + +void XciProcess::setInputFile(fnd::IFile& reader) +{ + mReader = &reader; +} + +void XciProcess::setInputFileOffset(size_t offset) +{ + mOffset = offset; +} + +void XciProcess::setKeyset(const sKeyset* keyset) +{ + mKeyset = keyset; +} + +void XciProcess::setCliOutputMode(CliOutputType type) +{ + mCliOutputType = type; +} + +void XciProcess::setVerifyMode(bool verify) +{ + mVerify = verify; +} + +void XciProcess::setUpdateExtractPath(const std::string& path) +{ + mUpdatePath.path = path; + mUpdatePath.doExtract = true; +} + +void XciProcess::setNormalExtractPath(const std::string& path) +{ + mNormalPath.path = path; + mNormalPath.doExtract = true; +} + +void XciProcess::setSecureExtractPath(const std::string& path) +{ + mSecurePath.path = path; + mSecurePath.doExtract = true; +} + +void XciProcess::setListFs(bool list_fs) +{ + mListFs = list_fs; +} diff --git a/programs/nstool/source/XciProcess.h b/programs/nstool/source/XciProcess.h new file mode 100644 index 0000000..2036f88 --- /dev/null +++ b/programs/nstool/source/XciProcess.h @@ -0,0 +1,60 @@ +#pragma once +#include +#include +#include +#include + +#include "nstool.h" + +#include "PfsProcess.h" + + +class XciProcess +{ +public: + XciProcess(); + + void process(); + + // generic + void setInputFile(fnd::IFile& reader); + void setInputFileOffset(size_t offset); + void setKeyset(const sKeyset* keyset); + void setCliOutputMode(CliOutputType type); + void setVerifyMode(bool verify); + + // xci specific + void setUpdateExtractPath(const std::string& path); + void setNormalExtractPath(const std::string& path); + void setSecureExtractPath(const std::string& path); + void setListFs(bool list_fs); + +private: + const std::string kModuleName = "XciProcess"; + const std::string kXciMountPointName = "gamecard:/"; + static const size_t kFileExportBlockSize = 0x1000000; + + fnd::IFile* mReader; + size_t mOffset; + const sKeyset* mKeyset; + CliOutputType mCliOutputType; + bool mVerify; + + struct sExtract + { + std::string path; + bool doExtract; + } mUpdatePath, mNormalPath, mSecurePath; + + bool mListFs; + + nx::sXciHeaderPage mHdrPage; + nx::XciHeader mHdr; + PfsProcess mRootPfs, mUpdatePfs, mNormalPfs, mSecurePfs; + + void displayHeader(); + bool validateRegionOfFile(size_t offset, size_t len, const byte_t* test_hash); + void validateXciSignature(); + void processRootPfs(); + void processPartitionPfs(); +}; \ No newline at end of file diff --git a/programs/nstool/source/main.cpp b/programs/nstool/source/main.cpp new file mode 100644 index 0000000..ce6e9c8 --- /dev/null +++ b/programs/nstool/source/main.cpp @@ -0,0 +1,97 @@ +#include +#include "UserSettings.h" +#include "XciProcess.h" +#include "PfsProcess.h" +//#include "RomfsProcess.h" +//#include "NcaProcess.h" +#include "NpdmProcess.h" + + +int main(int argc, char** argv) +{ + UserSettings user_set; + try { + user_set.parseCmdArgs(argc, argv); + + fnd::SimpleFile inputFile; + inputFile.open(user_set.getInputPath(), inputFile.Read); + + if (user_set.getFileType() == FILE_XCI) + { + XciProcess xci; + + xci.setInputFile(inputFile); + + xci.setKeyset(&user_set.getKeyset()); + xci.setCliOutputMode(user_set.getCliOutputType()); + xci.setVerifyMode(user_set.isVerifyFile()); + + if (user_set.getUpdatePath().isSet) + xci.setUpdateExtractPath(user_set.getUpdatePath().var); + if (user_set.getNormalPath().isSet) + xci.setNormalExtractPath(user_set.getNormalPath().var); + if (user_set.getSecurePath().isSet) + xci.setSecureExtractPath(user_set.getSecurePath().var); + xci.setListFs(user_set.isListFs()); + + xci.process(); + } + else if (user_set.getFileType() == FILE_PARTITIONFS) + { + PfsProcess pfs; + + pfs.setInputFile(inputFile); + pfs.setKeyset(&user_set.getKeyset()); + pfs.setCliOutputMode(user_set.getCliOutputType()); + pfs.setVerifyMode(user_set.isVerifyFile()); + + if (user_set.getFsPath().isSet) + pfs.setExtractPath(user_set.getFsPath().var); + pfs.setListFs(user_set.isListFs()); + + pfs.process(); + } + else if (user_set.getFileType() == FILE_ROMFS) + { + /* + RomfsProcess romfs; + + romfs.setRomfsPath(user_set.getInputPath()); + romfs.setExtractPath(user_set.getFsPath()); + romfs.setKeyset(user_set.getKeyset()); + romfs.setCliOutputMode(user_set.getCliOutputType()); + romfs.setVerifyMode(user_set.isVerifyFile()); + + romfs.process(); + */ + } + else if (user_set.getFileType() == FILE_NCA) + { + /* + NcaProcess nca; + + nca.setNcaPath(user_set.getInputPath()); + nca.setKeyset(user_set.getKeyset()); + nca.setCliOutputMode(user_set.getCliOutputType()); + nca.setVerifyMode(user_set.isVerifyFile()); + + nca.process(); + */ + } + else if (user_set.getFileType() == FILE_NPDM) + { + NpdmProcess npdm; + + npdm.setInputFile(inputFile); + npdm.setKeyset(&user_set.getKeyset()); + npdm.setCliOutputMode(user_set.getCliOutputType()); + npdm.setVerifyMode(user_set.isVerifyFile()); + + npdm.process(); + } + } + catch (const fnd::Exception& e) { + printf("\n\n%s\n", e.what()); + } + return 0; +} \ No newline at end of file diff --git a/programs/nstool/source/nstool.h b/programs/nstool/source/nstool.h new file mode 100644 index 0000000..a411172 --- /dev/null +++ b/programs/nstool/source/nstool.h @@ -0,0 +1,93 @@ +#pragma once +#pragma once +#include +#include +#include +#include +#include + +static const size_t kMasterKeyNum = 0x20; +static const size_t kNcaKeakNum = nx::nca::kKeyAreaEncryptionKeyNum; + +#ifdef _WIN32 +const std::string kPathSeparator = "\\"; +#else +const std::string kPathSeparator = "/"; +#endif + +enum FileType +{ + FILE_XCI, + FILE_PARTITIONFS, + FILE_ROMFS, + FILE_NCA, + FILE_NPDM, + FILE_INVALID = -1, +}; + +enum CliOutputType +{ + OUTPUT_MINIMAL, + OUTPUT_NORMAL, + OUTPUT_VERBOSE +}; + +template +struct sOptional +{ + bool isSet; + T var; + inline const T& operator=(const T& other) { isSet = true; var = other; return var; } + inline const sOptional& operator=(const sOptional& other) + { + isSet = other.isSet; + if (isSet) { + var = other.var; + } + return *this; + } + inline T& operator*() { return var; } +}; + +struct sKeyset +{ + crypto::rsa::sRsa2048Key acid_sign_key; + + crypto::aes::sAes128Key package1_key[kMasterKeyNum]; + crypto::rsa::sRsa2048Key package2_sign_key; + crypto::aes::sAes128Key package2_key[kMasterKeyNum]; + + struct sNcaData + { + crypto::rsa::sRsa2048Key header_sign_key; + crypto::aes::sAesXts128Key header_key; + crypto::aes::sAes128Key key_area_key[kNcaKeakNum][kMasterKeyNum]; + + crypto::aes::sAes128Key manual_title_key; + crypto::aes::sAes128Key manual_body_key_aesctr; + crypto::aes::sAesXts128Key manual_body_key_aesxts; + } nca; + + struct sXciData + { + crypto::rsa::sRsa2048Key header_sign_key; + crypto::aes::sAes128Key header_key; + } xci; + + struct sTicketData + { + crypto::rsa::sRsa2048Key sign_key; + crypto::aes::sAes128Key titlekey_kek[kMasterKeyNum]; + } ticket; +}; + +inline byte_t charToByte(char chr) +{ + if (chr >= 'a' && chr <= 'f') + return (chr - 'a') + 0xa; + else if (chr >= 'A' && chr <= 'F') + return (chr - 'A') + 0xa; + else if (chr >= '0' && chr <= '9') + return chr - '0'; + return 0; +} diff --git a/programs/nstool/source/version.h b/programs/nstool/source/version.h new file mode 100644 index 0000000..c590661 --- /dev/null +++ b/programs/nstool/source/version.h @@ -0,0 +1,4 @@ +#pragma once +#define VER_MAJOR 0 +#define VER_MINOR 1 +#define AUTHORS "jakcron" \ No newline at end of file diff --git a/programs/pfstool/makefile b/programs/pfstool/makefile deleted file mode 100644 index da01e4a..0000000 --- a/programs/pfstool/makefile +++ /dev/null @@ -1,47 +0,0 @@ -# Sources -SRC_DIR = source -OBJS = $(foreach dir,$(SRC_DIR),$(subst .cpp,.o,$(wildcard $(dir)/*.cpp))) $(foreach dir,$(SRC_DIR),$(subst .c,.o,$(wildcard $(dir)/*.c))) - -# External dependencies -DEPENDS = nx crypto fnd -LIB_DIR = ../../lib -LIBS = $(foreach dep,$(DEPENDS), -L"$(LIB_DIR)/lib$(dep)" -l$(dep)) -INCS = $(foreach dep,$(DEPENDS), -I"$(LIB_DIR)/lib$(dep)/include") - -BIN_DIR = bin -OUTPUT = $(BIN_DIR)/$(shell basename $(CURDIR)) - -# Compiler Settings -CXXFLAGS = -std=c++11 $(INCS) -D__STDC_FORMAT_MACROS -Wall -Wno-unused-value -ifeq ($(OS),Windows_NT) - # Windows Only Flags/Libs - CC = x86_64-w64-mingw32-gcc - CXX = x86_64-w64-mingw32-g++ - CFLAGS += -Wno-unused-but-set-variable - CXXFLAGS += -Wno-unused-but-set-variable - LIBS += -static -else - UNAME = $(shell uname -s) - ifeq ($(UNAME), Darwin) - # MacOS Only Flags/Libs - CFLAGS += -Wno-unused-private-field - CXXFLAGS += -Wno-unused-private-field - LIBS += - else - # *nix Only Flags/Libs - CFLAGS += -Wno-unused-but-set-variable - CXXFLAGS += -Wno-unused-but-set-variable - LIBS += - endif -endif - -all: build - -rebuild: clean build - -build: $(OBJS) - mkdir -p $(BIN_DIR) - $(CXX) $(OBJS) $(LIBS) -o $(OUTPUT) - -clean: - rm -rf $(OBJS) $(OUTPUT) $(BIN_DIR) \ No newline at end of file diff --git a/programs/pfstool/pfstool.vcxproj b/programs/pfstool/pfstool.vcxproj deleted file mode 100644 index f9d21c8..0000000 --- a/programs/pfstool/pfstool.vcxproj +++ /dev/null @@ -1,140 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - {BC2F2D07-BAB3-469C-9C25-8CC54F96F7AB} - pfstool - 8.1 - - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - - - - - - - - - - - - - - - - - - - - - Level3 - Disabled - true - ..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include; - _MBCS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) - - - - - Level3 - Disabled - true - ..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include; - _MBCS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) - - - - - Level3 - MaxSpeed - true - true - true - ..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include; - _MBCS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) - - - true - true - - - - - Level3 - MaxSpeed - true - true - true - ..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include; - _MBCS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) - - - true - true - - - - - - - - - - - {6adbb60d-dba0-411d-bd2d-a355ef8e0fe1} - - - {4d27edb9-5110-44fe-8ce2-d46c5ad3c55b} - - - {91ba9e79-8242-4f7d-b997-0dfec95ea22b} - - - - - - \ No newline at end of file diff --git a/programs/pfstool/pfstool.vcxproj.filters b/programs/pfstool/pfstool.vcxproj.filters deleted file mode 100644 index f033c14..0000000 --- a/programs/pfstool/pfstool.vcxproj.filters +++ /dev/null @@ -1,25 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - - - - \ No newline at end of file diff --git a/programs/pfstool/source/main.cpp b/programs/pfstool/source/main.cpp deleted file mode 100644 index 8e0e4a8..0000000 --- a/programs/pfstool/source/main.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#ifdef _WIN32 -#include -#else -#include -#endif - - -std::string kFsTypeStr[] -{ - "PFS0", - "HFS0" -}; - -int main(int argc, char** argv) -{ - if (argc < 2) - { - printf("usage: pfstool []\n"); - return 1; - } - - try - { - fnd::MemoryBlob file; - fnd::io::readFile(argv[1], file); - - // import - nx::PfsHeader pfs; - pfs.importBinary(file.getBytes(), file.getSize()); - - if (argc == 3) - { -#ifdef _WIN32 - _mkdir(argv[2]); -#else - mkdir(argv[2], S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); -#endif - } - - printf("[PartitionFS]\n"); - printf(" Type: %s\n", kFsTypeStr[pfs.getFsType()].c_str()); - printf(" FileSystem: (%d files)\n", pfs.getFileList().getSize()); - for (size_t i = 0; i < pfs.getFileList().getSize(); i++) - { - - printf(" %s", pfs.getFileList()[i].name.c_str()); - if (pfs.getFsType() == pfs.TYPE_PFS0) - printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ")\n", pfs.getFileList()[i].offset, pfs.getFileList()[i].size); - else - printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ", hash_protected_size=0x%" PRIx64 ")\n", pfs.getFileList()[i].offset, pfs.getFileList()[i].size, pfs.getFileList()[i].hash_protected_size); - - if (argc == 3) - { -#ifdef _WIN32 - fnd::io::writeFile(std::string(argv[2]) + "\\" + pfs.getFileList()[i].name, file.getBytes() + pfs.getFileList()[i].offset, pfs.getFileList()[i].size); -#else - fnd::io::writeFile(std::string(argv[2]) + "/" + pfs.getFileList()[i].name, file.getBytes() + pfs.getFileList()[i].offset, pfs.getFileList()[i].size); -#endif - } - - } - - - } catch (const fnd::Exception& e) - { - printf("%s\n", e.what()); - } - - return 0; -} \ No newline at end of file diff --git a/programs/xcitool/makefile b/programs/xcitool/makefile deleted file mode 100644 index da01e4a..0000000 --- a/programs/xcitool/makefile +++ /dev/null @@ -1,47 +0,0 @@ -# Sources -SRC_DIR = source -OBJS = $(foreach dir,$(SRC_DIR),$(subst .cpp,.o,$(wildcard $(dir)/*.cpp))) $(foreach dir,$(SRC_DIR),$(subst .c,.o,$(wildcard $(dir)/*.c))) - -# External dependencies -DEPENDS = nx crypto fnd -LIB_DIR = ../../lib -LIBS = $(foreach dep,$(DEPENDS), -L"$(LIB_DIR)/lib$(dep)" -l$(dep)) -INCS = $(foreach dep,$(DEPENDS), -I"$(LIB_DIR)/lib$(dep)/include") - -BIN_DIR = bin -OUTPUT = $(BIN_DIR)/$(shell basename $(CURDIR)) - -# Compiler Settings -CXXFLAGS = -std=c++11 $(INCS) -D__STDC_FORMAT_MACROS -Wall -Wno-unused-value -ifeq ($(OS),Windows_NT) - # Windows Only Flags/Libs - CC = x86_64-w64-mingw32-gcc - CXX = x86_64-w64-mingw32-g++ - CFLAGS += -Wno-unused-but-set-variable - CXXFLAGS += -Wno-unused-but-set-variable - LIBS += -static -else - UNAME = $(shell uname -s) - ifeq ($(UNAME), Darwin) - # MacOS Only Flags/Libs - CFLAGS += -Wno-unused-private-field - CXXFLAGS += -Wno-unused-private-field - LIBS += - else - # *nix Only Flags/Libs - CFLAGS += -Wno-unused-but-set-variable - CXXFLAGS += -Wno-unused-but-set-variable - LIBS += - endif -endif - -all: build - -rebuild: clean build - -build: $(OBJS) - mkdir -p $(BIN_DIR) - $(CXX) $(OBJS) $(LIBS) -o $(OUTPUT) - -clean: - rm -rf $(OBJS) $(OUTPUT) $(BIN_DIR) \ No newline at end of file diff --git a/programs/xcitool/source/main.cpp b/programs/xcitool/source/main.cpp deleted file mode 100644 index 4ade0a4..0000000 --- a/programs/xcitool/source/main.cpp +++ /dev/null @@ -1,226 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* -struct sXciKeyData -{ - crypto::aes::sAes128Key xci_header_encryption_key; - crypto::aes::sAes128Key initial_data_key; - crypto::rsa::sRsa2048Key xci_header_signer_key; - crypto::rsa::sRsa2048Key card_key_area_oeap_key; -}; - -void getTitleKeyFromInitialData(const byte_t* initialData, crypto::aes::sAes128Key& titleKey) -{ - const sInitialData* data = (const sInitialData*)initialData; - crypto::aes::sAes128Key ccmKey; - crypto::aes::AesEcbDecrypt(data->key_source, 16, key_data.initial_data_key.key, ccmKey.key); - crypto::aes::AesCcmDecrypt(data->title_key_enc, 16, ccmKey.key, data->ccm_nonce, data->ccm_mac, titleKey.key); -} -*/ - -inline uint64_t blockToAddr(uint32_t block) -{ - return ((uint64_t)block) << 9; -} - -inline const char* getBoolStr(bool isTrue) -{ - return isTrue? "TRUE" : "FALSE"; -} - -inline const char* getRomSizeStr(byte_t rom_size) -{ - const char* str = "unknown"; - switch (rom_size) - { - case (nx::xci::ROM_SIZE_1GB) : - str = "1GB"; - break; - case (nx::xci::ROM_SIZE_2GB) : - str = "2GB"; - break; - case (nx::xci::ROM_SIZE_4GB) : - str = "4GB"; - break; - case (nx::xci::ROM_SIZE_8GB) : - str = "8GB"; - break; - case (nx::xci::ROM_SIZE_16GB) : - str = "16GB"; - break; - case (nx::xci::ROM_SIZE_32GB) : - str = "32GB"; - break; - } - return str; -} - -inline const char* getCardClockRate(uint32_t acc_ctrl_1) -{ - const char* str = "unknown"; - switch (acc_ctrl_1) - { - case (nx::xci::CLOCK_RATE_25) : - str = "20 MHz"; - break; - case (nx::xci::CLOCK_RATE_50) : - str = "50 MHz"; - break; - - } - return str; -} - -void printXciHeader(const nx::sXciHeader& hdr, bool is_decrypted) -{ - crypto::aes::sAesIvCtr iv; - for (size_t i = 0; i < sizeof(iv); i++) - { - iv.iv[15-i] = hdr.encryption_iv[i]; - } - - printf("[XCI HEADER]\n"); - printf(" Magic: HEAD\n"); - printf(" RomAreaStartPage: 0x%0x (0x%" PRIx64 ")\n", hdr.rom_area_start_page.get(), blockToAddr(hdr.rom_area_start_page.get())); - printf(" BackupAreaStartPage: 0x%0x\n", hdr.backup_area_start_page.get()); - printf(" KeyFlag: 0x%x\n", hdr.key_flag); - printf(" KekIndex: %d\n", hdr.key_flag & 7); - printf(" TitleKeyDecIndex: %d\n", (hdr.key_flag >> 4) & 7); - printf(" RomSize: 0x%x (%s)\n", hdr.rom_size, getRomSizeStr(hdr.rom_size)); - printf(" CardHeaderVersion: %d\n", hdr.card_header_version); - printf(" Flags: 0x%x\n", hdr.flags); - printf(" AutoBoot: %s\n", getBoolStr(_HAS_BIT(hdr.flags, nx::xci::FLAG_AUTOBOOT))); - printf(" HistoryErase: %s\n", getBoolStr(_HAS_BIT(hdr.flags, nx::xci::FLAG_HISTORY_ERASE))); - printf(" RepairTool: %s\n", getBoolStr(_HAS_BIT(hdr.flags, nx::xci::FLAG_REPAIR_TOOL))); - printf(" PackageId: 0x%" PRIx64 "\n", hdr.package_id.get()); - printf(" ValidDataEndPage: 0x%x (0x%" PRIx64 ")\n", hdr.valid_data_end_page.get(), blockToAddr(hdr.valid_data_end_page.get())); - printf(" AesIv: "); - fnd::SimpleTextOutput::hexDump(iv.iv, sizeof(iv)); - printf(" PartitionFs:\n"); - printf(" Offset: 0x%" PRIx64 "\n", hdr.partition_fs_header_address.get()); - printf(" Size: 0x%" PRIx64 "\n", hdr.partition_fs_header_size.get()); - printf(" Hash: "); - fnd::SimpleTextOutput::hexDump(hdr.partition_fs_header_hash, 0x20); - printf(" InitialData:\n"); - printf(" Hash: "); - fnd::SimpleTextOutput::hexDump(hdr.initial_data_hash, 0x20); - printf(" SelSec: 0x%x\n", hdr.sel_sec.get()); - printf(" SelT1Key: 0x%x\n", hdr.sel_t1_key.get()); - printf(" SelKey: 0x%x\n", hdr.sel_key.get()); - printf(" LimArea: 0x%x (0x%" PRIx64 ")\n", hdr.lim_area.get(), blockToAddr(hdr.lim_area.get())); - - if (is_decrypted == true) - { - printf(" FwVersion: v%d.%d\n", hdr.fw_version[nx::xci::FWVER_MAJOR].get(), hdr.fw_version[nx::xci::FWVER_MINOR].get()); - printf(" AccCtrl1: 0x%x\n", hdr.acc_ctrl_1.get()); - printf(" CardClockRate: %s\n", getCardClockRate(hdr.acc_ctrl_1.get())); - printf(" Wait1TimeRead: 0x%x\n", hdr.wait_1_time_read.get()); - printf(" Wait2TimeRead: 0x%x\n", hdr.wait_2_time_read.get()); - printf(" Wait1TimeWrite: 0x%x\n", hdr.wait_1_time_write.get()); - printf(" Wait2TimeWrite: 0x%x\n", hdr.wait_2_time_write.get()); - printf(" FwMode: 0x%x\n", hdr.fw_mode.get()); - printf(" UppVersion: %d\n", hdr.upp_version.get()); - printf(" UppHash: "); - fnd::SimpleTextOutput::hexDump(hdr.upp_hash, 8); - printf(" UppId: %016" PRIx64 "\n", hdr.upp_id.get()); - - } -} - -void printXciPartitionFs(const nx::PfsHeader& pfs, size_t partition_base_offset, const std::string& partition_name) -{ - printf("[PartitionFS]\n"); - printf(" Type: %s\n", pfs.getFsType() == pfs.TYPE_PFS0 ? "PFS0" : "HFS0"); - - if (partition_name.empty()) - { - printf(" FileSystem: (%d files)\n", pfs.getFileList().getSize()); - } - else - { - printf(" %s/\n", partition_name.c_str()); - } - for (size_t i = 0; i < pfs.getFileList().getSize(); i++) - { - printf(" %s", pfs.getFileList()[i].name.c_str()); - if (pfs.getFsType() == pfs.TYPE_PFS0) - printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ")\n", (partition_base_offset + pfs.getFileList()[i].offset), pfs.getFileList()[i].size); - else - printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ", hash_protected_size=0x%" PRIx64 ")\n", (partition_base_offset + pfs.getFileList()[i].offset), pfs.getFileList()[i].size, pfs.getFileList()[i].hash_protected_size); - - } -} - -void decryptXciHeader(const byte_t* src, byte_t* dst) -{ - const byte_t* src_iv = ((const nx::sXciHeader*)src)->encryption_iv; - byte_t iv[crypto::aes::kAesBlockSize]; - - for (size_t i = 0; i < crypto::aes::kAesBlockSize; i++) - { - iv[i] = src_iv[15 - i]; - } - - // copy plain - memcpy(dst, src, nx::xci::kHeaderEncOffset); - - // decrypt encrypted data - crypto::aes::AesCbcDecrypt(src + nx::xci::kHeaderEncOffset, nx::xci::kHeaderEncSize, crypto::aes::nx::prod::xci_header_key, iv, dst + nx::xci::kHeaderEncOffset); -} - -int main(int argc, char** argv) -{ - if (argc < 2) - { - printf("usage: %s \n", argv[0]); - return 1; - } - - - fnd::MemoryBlob tmp; - fnd::io::readFile(argv[1], 0x100, sizeof(nx::sXciHeader), tmp); - decryptXciHeader(tmp.getBytes(), tmp.getBytes()); - - nx::sXciHeader hdr; - memcpy((void*)&hdr, tmp.getBytes(), sizeof(nx::sXciHeader)); - printXciHeader(hdr, true); - - crypto::sha::sSha256Hash testHash; - - // read root PFS - fnd::io::readFile(argv[1], hdr.partition_fs_header_address.get(), hdr.partition_fs_header_size.get(), tmp); - crypto::sha::Sha256(tmp.getBytes(), tmp.getSize(), testHash.bytes); - if (testHash.compare(hdr.partition_fs_header_hash) == false) - { - throw fnd::Exception("xcitool", "Bad root partition hash"); - } - nx::PfsHeader rootPfs; - rootPfs.importBinary(tmp.getBytes(), tmp.getSize()); - printXciPartitionFs(rootPfs, hdr.partition_fs_header_address.get(), "xci:"); - - // read sub PFS - for (size_t i = 0; i < rootPfs.getFileList().getSize(); i++) - { - fnd::io::readFile(argv[1], hdr.partition_fs_header_address.get() + rootPfs.getFileList()[i].offset, rootPfs.getFileList()[i].hash_protected_size, tmp); - crypto::sha::Sha256(tmp.getBytes(), tmp.getSize(), testHash.bytes); - if (testHash.compare(rootPfs.getFileList()[i].hash) == false) - { - throw fnd::Exception("xcitool", "Bad partition hash"); - } - nx::PfsHeader pfs; - pfs.importBinary(tmp.getBytes(), tmp.getSize()); - printXciPartitionFs(pfs, hdr.partition_fs_header_address.get() + rootPfs.getFileList()[i].offset, "xci:/" + rootPfs.getFileList()[i].name); - } - - return 0; -} \ No newline at end of file diff --git a/programs/xcitool/xcitool.vcxproj b/programs/xcitool/xcitool.vcxproj deleted file mode 100644 index affaa78..0000000 --- a/programs/xcitool/xcitool.vcxproj +++ /dev/null @@ -1,130 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {007FF616-7B99-4CB3-84CD-39C47F64FC7E} - xcitool - 10.0.16299.0 - - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - - - - - - - - - - - - - - - - - - - - - Level3 - Disabled - true - true - ..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include; - - - - - Level3 - Disabled - true - true - ..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include; - - - - - Level3 - MaxSpeed - true - true - true - true - ..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include; - - - true - true - - - - - Level3 - MaxSpeed - true - true - true - true - ..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include; - - - true - true - - - - - - - - - - - - \ No newline at end of file diff --git a/programs/xcitool/xcitool.vcxproj.filters b/programs/xcitool/xcitool.vcxproj.filters deleted file mode 100644 index bd54fe4..0000000 --- a/programs/xcitool/xcitool.vcxproj.filters +++ /dev/null @@ -1,25 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - - - - Source Files - - - \ No newline at end of file diff --git a/programs/xcitool/xcitool.vcxproj.user b/programs/xcitool/xcitool.vcxproj.user deleted file mode 100644 index be25078..0000000 --- a/programs/xcitool/xcitool.vcxproj.user +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file