[programs] Remove xcitool, npdmtool, pfstool. Add nstool.

This commit is contained in:
jakcron 2018-04-24 13:24:20 +08:00
parent c42b498e19
commit 8ef9f478b0
33 changed files with 2253 additions and 1376 deletions

38
.vscode/settings.json vendored
View file

@ -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"
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 T, static T (*F)(T)>
class ISerialiseablePrimative {
public:
@ -53,4 +54,65 @@ typedef ISerialiseablePrimative<uint16_t, be_hword> be_uint16_t;
typedef ISerialiseablePrimative<uint32_t, le_word> le_uint32_t;
typedef ISerialiseablePrimative<uint32_t, be_word> be_uint32_t;
typedef ISerialiseablePrimative<uint64_t, le_dword> le_uint64_t;
typedef ISerialiseablePrimative<uint64_t, be_dword> be_uint64_t;
typedef ISerialiseablePrimative<uint64_t, be_dword> 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;
};

View file

@ -1,4 +1,4 @@
PROGS = ncatool npdmtool pfstool tiktool xcitool
PROGS = nstool ncatool tiktool
BIN_DIR = "../bin"
main: build

View file

@ -5,6 +5,7 @@
#include <fnd/SimpleTextOutput.h>
#include <nx/NXCrypto.h>
#include <nx/NcaHeader.h>
#include <nx/NcaUtils.h>
#include <nx/PfsHeader.h>
#include <inttypes.h>
#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());
}

View file

@ -1,137 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{550C6AC3-EBE0-46CA-AE6C-EEEB59DDF35C}</ProjectGuid>
<RootNamespace>npdmtool</RootNamespace>
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_MBCS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_MBCS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_MBCS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_MBCS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="source\main.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\lib\libcrypto\crypto.vcxproj">
<Project>{6adbb60d-dba0-411d-bd2d-a355ef8e0fe1}</Project>
</ProjectReference>
<ProjectReference Include="..\..\lib\libfnd\fnd.vcxproj">
<Project>{4d27edb9-5110-44fe-8ce2-d46c5ad3c55b}</Project>
</ProjectReference>
<ProjectReference Include="..\..\lib\libnx\nx.vcxproj">
<Project>{91ba9e79-8242-4f7d-b997-0dfec95ea22b}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View file

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="source\main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View file

@ -1,437 +0,0 @@
#include <cstdio>
#include <crypto/aes.h>
#include <fnd/io.h>
#include <fnd/MemoryBlob.h>
#include <nx/NXCrypto.h>
#include <nx/NpdmBinary.h>
#include <inttypes.h>
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<uint8_t> 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<nx::MemoryMappingHandler::sMemoryMapping> maps = kern.getMemoryMaps().getMemoryMaps();
fnd::List<nx::MemoryMappingHandler::sMemoryMapping> 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<uint16_t> 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<nx::MiscFlagsHandler::Flags> 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 <file>\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;
}

View file

@ -0,0 +1,33 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/SimpleFile.h>
#include <nx/nca.h>
#include <nx/NcaHeader.h>
#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;
};

View file

@ -0,0 +1,683 @@
#include "NpdmProcess.h"
#include <fnd/SimpleFile.h>
#include <fnd/MemoryBlob.h>
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<uint8_t> 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<nx::MemoryMappingHandler::sMemoryMapping> maps = kern.getMemoryMaps().getMemoryMaps();
fnd::List<nx::MemoryMappingHandler::sMemoryMapping> 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<uint16_t> 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<nx::MiscFlagsHandler::Flags> 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;
}

View file

@ -0,0 +1,41 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/SimpleFile.h>
#include <nx/NpdmBinary.h>
#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);
};

View file

@ -0,0 +1,176 @@
#include "PfsProcess.h"
#include <fnd/io.h>
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<nx::PfsHeader::sFile>& 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<nx::PfsHeader::sFile>& 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;
}

View file

@ -0,0 +1,53 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/SimpleFile.h>
#include <nx/PfsHeader.h>
#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();
};

View file

View file

@ -0,0 +1,537 @@
#include "UserSettings.h"
#include "version.h"
#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <fnd/SimpleFile.h>
#include <fnd/SimpleTextOutput.h>
#include <fnd/MemoryBlob.h>
#include <fnd/ResourceFileReader.h>
#include <nx/NcaUtils.h>
#include <nx/AesKeygen.h>
#include <nx/xci.h>
#include <nx/pfs.h>
#include <nx/nca.h>
#include <nx/npdm.h>
#include <nx/romfs.h>
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... ] <file>\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 <dir> --normal <dir> --secure <dir>] <.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 <dir>] <file>\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 <key> --titlekey <key>] [--part0 <dir> ...] <.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<std::string>& UserSettings::getUpdatePath() const
{
return mUpdatePath;
}
const sOptional<std::string>& UserSettings::getNormalPath() const
{
return mNormalPath;
}
const sOptional<std::string>& UserSettings::getSecurePath() const
{
return mSecurePath;
}
const sOptional<std::string>& UserSettings::getFsPath() const
{
return mFsPath;
}
void UserSettings::populateCmdArgs(int argc, char** argv, sCmdArgs& cmd_args)
{
// create vector of args
std::vector<std::string> 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;
}

View file

@ -0,0 +1,89 @@
#pragma once
#include <string>
#include <fnd/types.h>
#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<std::string>& getUpdatePath() const;
const sOptional<std::string>& getNormalPath() const;
const sOptional<std::string>& getSecurePath() const;
const sOptional<std::string>& getFsPath() const;
private:
const std::string kModuleName = "UserSettings";
struct sCmdArgs
{
sOptional<std::string> input_path;
sOptional<std::string> output_path;
sOptional<bool> devkit_keys;
sOptional<std::string> keyset_path;
sOptional<std::string> file_type;
sOptional<bool> verify_file;
sOptional<bool> verbose_output;
sOptional<bool> minimal_output;
sOptional<bool> list_fs;
sOptional<std::string> update_path;
sOptional<std::string> normal_path;
sOptional<std::string> secure_path;
sOptional<std::string> fs_path;
sOptional<std::string> nca_titlekey;
sOptional<std::string> 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<std::string> mUpdatePath;
sOptional<std::string> mNormalPath;
sOptional<std::string> mSecurePath;
sOptional<std::string> 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);
};

View file

@ -0,0 +1,271 @@
#include "XciProcess.h"
#include <fnd/SimpleTextOutput.h>
#include <nx/XciUtils.h>
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<nx::PfsHeader::sFile>& 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;
}

View file

@ -0,0 +1,60 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/SimpleFile.h>
#include <nx/XciHeader.h>
#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();
};

View file

@ -0,0 +1,97 @@
#include <cstdio>
#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;
}

View file

@ -0,0 +1,93 @@
#pragma once
#pragma once
#include <string>
#include <fnd/types.h>
#include <crypto/aes.h>
#include <crypto/rsa.h>
#include <nx/nca.h>
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 <typename T>
struct sOptional
{
bool isSet;
T var;
inline const T& operator=(const T& other) { isSet = true; var = other; return var; }
inline const sOptional<T>& operator=(const sOptional<T>& 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;
}

View file

@ -0,0 +1,4 @@
#pragma once
#define VER_MAJOR 0
#define VER_MINOR 1
#define AUTHORS "jakcron"

View file

@ -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)

View file

@ -1,140 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{BC2F2D07-BAB3-469C-9C25-8CC54F96F7AB}</ProjectGuid>
<RootNamespace>pfstool</RootNamespace>
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_MBCS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_MBCS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_MBCS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_MBCS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="source\main.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="makefile" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\lib\libcrypto\crypto.vcxproj">
<Project>{6adbb60d-dba0-411d-bd2d-a355ef8e0fe1}</Project>
</ProjectReference>
<ProjectReference Include="..\..\lib\libfnd\fnd.vcxproj">
<Project>{4d27edb9-5110-44fe-8ce2-d46c5ad3c55b}</Project>
</ProjectReference>
<ProjectReference Include="..\..\lib\libnx\nx.vcxproj">
<Project>{91ba9e79-8242-4f7d-b997-0dfec95ea22b}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View file

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="source\main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="makefile" />
</ItemGroup>
</Project>

View file

@ -1,77 +0,0 @@
#include <cstdio>
#include <crypto/aes.h>
#include <fnd/io.h>
#include <fnd/MemoryBlob.h>
#include <nx/NXCrypto.h>
#include <nx/PfsHeader.h>
#include <inttypes.h>
#ifdef _WIN32
#include <direct.h>
#else
#include <sys/stat.h>
#endif
std::string kFsTypeStr[]
{
"PFS0",
"HFS0"
};
int main(int argc, char** argv)
{
if (argc < 2)
{
printf("usage: pfstool <file> [<output dir>]\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;
}

View file

@ -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)

View file

@ -1,226 +0,0 @@
#include <cstdio>
#include <vector>
#include <string>
#include <crypto/aes.h>
#include <crypto/rsa.h>
#include <fnd/io.h>
#include <fnd/MemoryBlob.h>
#include <fnd/SimpleTextOutput.h>
#include <nx/NXCrypto.h>
#include <nx/xci.h>
#include <nx/PfsHeader.h>
/*
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 <xci file>\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;
}

View file

@ -1,130 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{007FF616-7B99-4CB3-84CD-39C47F64FC7E}</ProjectGuid>
<RootNamespace>xcitool</RootNamespace>
<WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>..\..\lib\libfnd\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<None Include="makefile" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="source\main.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View file

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<None Include="makefile" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="source\main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup />
</Project>