Merge pull request #12 from jakcron/refactor

Refactor
This commit is contained in:
Jack 2018-05-27 18:57:36 +08:00 committed by GitHub
commit 578e3ac54d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
64 changed files with 3529 additions and 1118 deletions

View file

@ -46,7 +46,7 @@ acid_sign_key_private : RSA2048 Private Exponent (0x100 bytes)
# Compatibility with hactool keyset files
NXTools keyset files share the same keyset file format as [hactool](https://github.com/SciresM/hactool/blob/master/KEYS.md), but names of keys may differ. For compatibility, hactool names for equivalent keys are accepted.
```
titlekey_source : hactool alias for ticket_commonkey_source
titlekek_source : hactool alias for ticket_commonkey_source
header_key_source : hactool alias for nca_header_key_source
header_kek_source : hactool alias for nca_header_kek_source
key_area_key_application_source : hactool alias for nca_body_keak_application_source

View file

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2017 Jack
Copyright (c) 2017-2018 Jack
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -27,6 +27,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
KEYS.md = KEYS.md
LICENSE = LICENSE
makefile = makefile
README.md = README.md
EndProjectSection

View file

@ -6,14 +6,14 @@ Tools & Libraries for NX (Nintendo Switch).
# Tools
* __nstool__ - read *.npdm, read/extract PartitionFS (PFS0|HFS0) blobs (including *.nsp), read *.xci
* __nstool__ - read *.npdm, read/extract PartitionFS (PFS0|HFS0) blobs (including *.nsp), read/extract *.xci, read/extract *.nca, read *.cnmt
# Libraries
* __libfnd__ - Foundation library.
* __libcrypto__ - Cryptographic functions (AES,SHA,RSA). Wrapper for [mbedTLS](https://github.com/ARMmbed/mbedtls)
* __libes__ - Handling of (NS relevant) eShop file type processing. (eTickets, etc)
* __libnx__ - Handling of NS file types
* __libes__ - Handling of (NX relevant) eShop file type processing. (eTickets, etc)
* __libnx__ - Handling of NX file types
# Building

View file

@ -121,7 +121,6 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="include\crypto\aes.h" />
<ClInclude Include="include\crypto\AesCtrStream.h" />
<ClInclude Include="include\crypto\rsa.h" />
<ClInclude Include="include\crypto\sha.h" />
<ClInclude Include="source\libpolarssl\include\polarssl\aes.h" />
@ -137,7 +136,6 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="source\aes.cpp" />
<ClCompile Include="source\AesCtrStream.cpp" />
<ClCompile Include="source\libpolarssl\source\polar_aes.c" />
<ClCompile Include="source\libpolarssl\source\polar_base64.c" />
<ClCompile Include="source\libpolarssl\source\polar_bignum.c" />

View file

@ -27,9 +27,6 @@
<ClInclude Include="include\crypto\aes.h">
<Filter>Header Files\crypto</Filter>
</ClInclude>
<ClInclude Include="include\crypto\AesCtrStream.h">
<Filter>Header Files\crypto</Filter>
</ClInclude>
<ClInclude Include="include\crypto\rsa.h">
<Filter>Header Files\crypto</Filter>
</ClInclude>
@ -71,9 +68,6 @@
<ClCompile Include="source\aes.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="source\AesCtrStream.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="source\rsa.cpp">
<Filter>Source Files</Filter>
</ClCompile>

View file

@ -1,165 +0,0 @@
#pragma once
#include <string>
#include <vector>
#include <fnd/Exception.h>
#include <crypto/aes.h>
namespace crypto
{
namespace aes
{
class AesCtrStream
{
public:
AesCtrStream();
~AesCtrStream();
void seek(size_t offset);
void read(size_t size, uint8_t* out);
void read(size_t offset, size_t size, uint8_t* out);
void write(size_t size, const uint8_t* in);
void write(size_t offset, size_t size, const uint8_t* in);
void AddRegion(size_t start, size_t end, const uint8_t aes_key[kAes128KeySize], const uint8_t aes_ctr[kAesBlockSize]);
protected:
// Virtual methods for implementation of seek/read/write
virtual void seek_internal(size_t offset) = 0;
virtual void read_internal(size_t size, size_t& read_len, uint8_t* out) = 0;
virtual void write_internal(size_t size, size_t& write_len, const uint8_t* in) = 0;
private:
const std::string kModuleName = "AES_CTR_STREAM";
static const size_t kIoBufferLen = 0x10000;
// private implementation of crypto region
class CryptRegion
{
public:
// stubbed constructor
CryptRegion() :
start_(0),
end_(0),
is_plaintext_(true)
{
CleanUp();
}
// plaintext constructor
CryptRegion(size_t start, size_t end) :
start_(start),
end_(end),
is_plaintext_(true)
{
CleanUp();
}
// encrypted constructor
CryptRegion(size_t start, size_t end, const uint8_t aes_key[kAes128KeySize], const uint8_t aes_ctr[kAesBlockSize]) :
start_(start),
end_(end),
is_plaintext_(false)
{
CleanUp();
memcpy(aes_key_, aes_key, kAes128KeySize);
memcpy(ctr_init_, aes_ctr, kAesBlockSize);
memcpy(ctr_, ctr_init_, kAesBlockSize);
}
// destructor
~CryptRegion()
{
CleanUp();
}
size_t start() const { return start_; }
size_t end() const { return end_; }
size_t size() const { return end_ - start_; }
size_t remaining_size(size_t start) const { return end_ - start; }
const uint8_t* aes_key() const { return aes_key_; }
uint8_t* aes_ctr() { return ctr_; }
bool is_in_region(size_t start) const { return start >= start_ && start < end_; }
bool is_in_region(size_t start, size_t end) const { return is_in_region(start) && end > start_ && end <= end_; }
void UpdateAesCtr(size_t start)
{
if (is_in_region(start))
AesIncrementCounter(ctr_init_, ((start - start_) >> 4), ctr_);
}
void GenerateXorpad(size_t start, size_t size, uint8_t* out)
{
// don't operate if requested size exceeds region size
if (is_in_region(start, start + size) == false)
{
return;
}
if (is_plaintext_ == true)
{
memset(out, 0, size);
return;
}
// parameters
size_t block_offset = (start - start_) & 0xf;
size_t block_num = size >> 4;
for (size_t pos = 0; pos < block_num; pos += (kPadBufferLen >> 4))
{
// clear pad buffer
memset(pad_buffer_, 0, kPadBufferCapacity);
// encrypt pad buffer to create xorpad
UpdateAesCtr(start + (pos << 4));
AesCtr(pad_buffer_, kPadBufferCapacity, aes_key(), aes_ctr(), pad_buffer_);
// determine the number of blocks to copy to xorpad
size_t copy_size = kPadBufferLen < ((block_num - pos) << 4) ? kPadBufferLen : ((block_num - pos) << 4);
// copy
memcpy(out + (pos << 4), pad_buffer_ + block_offset, copy_size);
}
}
private:
static const size_t kPadBufferLen = 0x10000;
static const size_t kPadBufferCapacity = kPadBufferLen + kAesBlockSize; // has an extra block to accomodate non block aligned starts
size_t start_;
size_t end_;
bool is_plaintext_;
uint8_t aes_key_[kAes128KeySize];
uint8_t ctr_init_[kAesBlockSize];
uint8_t ctr_[kAesBlockSize];
uint8_t pad_buffer_[kPadBufferCapacity];
void CleanUp()
{
memset(aes_key_, 0, kAes128KeySize);
memset(ctr_init_, 0, kAesBlockSize);
memset(ctr_, 0, kAesBlockSize);
}
};
inline void xor_data(size_t size, const uint8_t* data1, const uint8_t* data2, uint8_t* out)
{
for (size_t idx = 0; idx < size; idx++)
{
out[idx] = data1[idx] ^ data2[idx];
}
}
// Crypto Regions
size_t offset_;
std::vector<CryptRegion> regions_;
// IO Buffer
uint8_t io_buffer_[kIoBufferLen];
uint8_t pad_buffer_[kIoBufferLen];
void GenerateXorPad(size_t start);
};
}
}

View file

@ -1,138 +0,0 @@
#include <crypto/AesCtrStream.h>
using namespace crypto::aes;
AesCtrStream::AesCtrStream()
{
}
AesCtrStream::~AesCtrStream()
{
}
void AesCtrStream::seek(size_t offset)
{
offset_ = offset;
seek_internal(offset_);
}
void AesCtrStream::read(size_t size, uint8_t * out)
{
size_t read_len = 0;
size_t read_size = 0;
for (size_t pos = 0; pos < size; pos += read_size, offset_ += read_size)
{
// calculate read size
read_size = (size - pos) < kIoBufferLen ? (size - pos) : kIoBufferLen;
// read data
read_internal(read_size, read_len, io_buffer_);
if (read_size != read_len)
{
throw fnd::Exception(kModuleName, "Stream read length unexpected");
}
// crypt data
GenerateXorPad(offset_);
xor_data(read_size, pad_buffer_, io_buffer_, out + pos);
}
}
void AesCtrStream::read(size_t offset, size_t size, uint8_t * out)
{
seek(offset);
read(size, out);
}
void AesCtrStream::write(size_t size, const uint8_t * in)
{
size_t write_len = 0;
size_t write_size = 0;
for (size_t pos = 0; pos < size; pos += write_size, offset_ += write_size)
{
// calculate write size
write_size = (size - pos) < kIoBufferLen ? (size - pos) : kIoBufferLen;
// crypt data
GenerateXorPad(offset_);
xor_data(write_size, pad_buffer_, in + pos, io_buffer_);
// write data
write_internal(write_size, write_len, io_buffer_);
if (write_size != write_len)
{
throw fnd::Exception(kModuleName, "Stream write length unexpected");
}
}
}
void AesCtrStream::write(size_t offset, size_t size, const uint8_t * in)
{
seek(offset);
write(size, in);
}
void AesCtrStream::AddRegion(size_t start, size_t end, const uint8_t aes_key[kAes128KeySize], const uint8_t aes_ctr[kAesBlockSize])
{
if (start >= end)
{
throw fnd::Exception(kModuleName, "Illegal start/end position");
}
if (aes_key == nullptr || aes_ctr == nullptr)
{
throw fnd::Exception(kModuleName, "Illegal aes configuration (nullptr)");
}
regions_.push_back(CryptRegion(start, end, aes_key, aes_ctr));
}
void AesCtrStream::GenerateXorPad(size_t start)
{
size_t pad_size = 0;
for (size_t pos = 0; pos < kIoBufferLen; pos += pad_size)
{
CryptRegion* cur_region = nullptr;
CryptRegion* next_region = nullptr;
for (size_t idx = 0; idx < regions_.size(); idx++)
{
if (regions_[idx].is_in_region(start + pos))
{
cur_region = &regions_[idx];
}
else if (regions_[idx].start() > (start + pos) && (next_region == nullptr || next_region->start() > regions_[idx].start()))
{
next_region = &regions_[idx];
}
}
// if this exists in the a crypto region
if (cur_region != nullptr)
{
pad_size = cur_region->remaining_size(start + pos);
if (pad_size > kIoBufferLen - pos)
{
pad_size = kIoBufferLen - pos;
}
cur_region->GenerateXorpad(start + pos, pad_size, pad_buffer_ + pos);
}
// there is a crypto region ahead, bridge the gap
else if (next_region != nullptr)
{
pad_size = next_region->start() - (start + pos);
if (pad_size > kIoBufferLen - pos)
{
pad_size = kIoBufferLen - pos;
}
memset(pad_buffer_ + pos, 0, pad_size);
}
// there are no more crypto regions
else
{
pad_size = kIoBufferLen - pos;
memset(pad_buffer_ + pos, 0, pad_size);
}
}
}

View file

@ -62,7 +62,7 @@ void crypto::aes::AesIncrementCounter(const uint8_t in[kAesBlockSize], size_t bl
uint64_t total = ctr[i] + block_num;
// if there wasn't a wrap around, add the two together and exit
if (total <= 0xffffffff) {
ctr[i] += block_num;
ctr[i] += (uint32_t)block_num;
break;
}

View file

@ -6,6 +6,8 @@ namespace fnd
class IFile
{
public:
inline virtual ~IFile() {}
virtual size_t size() = 0;
virtual void seek(size_t offset) = 0;
virtual void read(byte_t* out, size_t len) = 0;

View file

@ -1,7 +1,11 @@
#pragma once
#include <fnd/IFile.h>
#include <string>
#ifdef _WIN32
#include <Windows.h>
#else
#include <cstdio>
#endif
namespace fnd
{
@ -34,9 +38,16 @@ namespace fnd
bool mOpen;
OpenMode mMode;
FILE* mFp;
const char* getOpenModeStr(OpenMode mMode);
#ifdef _WIN32
HANDLE mFileHandle;
DWORD getOpenModeFlag(OpenMode mode) const;
DWORD getShareModeFlag(OpenMode mode) const;
DWORD getCreationModeFlag(OpenMode mode) const;
#else
FILE* mFp;
const char* getOpenModeStr(OpenMode mode);
#endif
};
}

View file

@ -44,5 +44,5 @@ const char* Exception::module() const noexcept
const char * fnd::Exception::error() const noexcept
{
return nullptr;
return error_.c_str();
}

View file

@ -1,11 +1,16 @@
#include <fnd/SimpleFile.h>
#include <fnd/StringConv.h>
using namespace fnd;
SimpleFile::SimpleFile() :
mOpen(false),
mMode(Read),
#ifdef _WIN32
mFileHandle()
#else
mFp(nullptr)
#endif
{
}
@ -16,6 +21,29 @@ SimpleFile::~SimpleFile()
void SimpleFile::open(const std::string& path, OpenMode mode)
{
#ifdef _WIN32
// convert string to unicode
std::u16string unicodePath = fnd::StringConv::ConvertChar8ToChar16(path);
// save mode
mMode = mode;
// open file
mFileHandle = CreateFileW((LPCWSTR)unicodePath.c_str(),
getOpenModeFlag(mMode),
getShareModeFlag(mMode),
0,
getCreationModeFlag(mMode),
FILE_ATTRIBUTE_NORMAL,
NULL);
// check file handle
if (mFileHandle == INVALID_HANDLE_VALUE)
{
throw fnd::Exception(kModuleName, "Failed to open file.");
}
#else
//close();
mMode = mode;
//printf("fopen(%s,%s);\n", path.c_str(), getOpenModeStr(mMode));
@ -23,46 +51,140 @@ void SimpleFile::open(const std::string& path, OpenMode mode)
if (mFp == nullptr)
throw fnd::Exception(kModuleName, "Failed to open file.");
mOpen = true;
#endif
seek(0);
}
bool SimpleFile::isOpen() const
{
return mOpen == true && mFp != nullptr;
return mOpen == true;
}
void SimpleFile::close()
{
if (isOpen())
{
#ifdef _WIN32
CloseHandle(mFileHandle);
#else
fclose(mFp);
mFp = nullptr;
#endif
}
mFp = nullptr;
mOpen = false;
}
size_t SimpleFile::size()
{
size_t fsize = 0;
#ifdef _WIN32
if (mMode != Create)
{
LARGE_INTEGER win_fsize;
if (GetFileSizeEx(mFileHandle, &win_fsize) == false)
{
throw fnd::Exception(kModuleName, "Failed to check filesize");
}
fsize = win_fsize.QuadPart;
}
else
{
fsize = 0;
}
#else
size_t cur_pos = pos();
fseek(mFp, 0, SEEK_END);
size_t fsize = pos();
fsize = pos();
seek(cur_pos);
#endif
return fsize;
}
void SimpleFile::seek(size_t offset)
{
#ifdef _WIN32
LARGE_INTEGER win_pos, out;
win_pos.QuadPart = offset;
if (SetFilePointerEx(
mFileHandle,
win_pos,
&out,
FILE_BEGIN
) == false || out.QuadPart != win_pos.QuadPart)
{
throw fnd::Exception(kModuleName, "Failed to change file offset");
}
#else
fseek(mFp, offset, SEEK_SET);
#endif
}
size_t SimpleFile::pos()
{
#ifdef _WIN32
LARGE_INTEGER win_pos, out;
win_pos.QuadPart = 0;
if (SetFilePointerEx(
mFileHandle,
win_pos,
&out,
FILE_CURRENT
) == false)
{
throw fnd::Exception(kModuleName, "Failed to check file offset");
}
return out.QuadPart;
#else
return ftell(mFp);
#endif
}
void SimpleFile::read(byte_t* out, size_t len)
{
#ifdef _WIN32
LARGE_INTEGER win_len;
win_len.QuadPart = len;
static const DWORD kDwordHalf = (MAXDWORD / (DWORD)2) + 1; // 0x80000000
static const size_t kDwordFull = (size_t)kDwordHalf * (size_t)2; // 0x100000000
// if the size is greater than a DWORD, read it in parts,
for (LONG i = 0; i < win_len.HighPart; i++)
{
// since kDwordFull isn't a valid DWORD value, read in two parts
ReadFile(
mFileHandle,
out + i * kDwordFull,
kDwordHalf,
NULL,
NULL
);
ReadFile(
mFileHandle,
out + i * kDwordFull + kDwordHalf,
kDwordHalf,
NULL,
NULL
);
}
// read remainding low part
if (win_len.LowPart > 0)
{
ReadFile(
mFileHandle,
out + win_len.HighPart * kDwordFull,
win_len.LowPart,
NULL,
NULL
);
}
#else
fread(out, len, 1, mFp);
#endif
}
void SimpleFile::read(byte_t* out, size_t offset, size_t len)
@ -73,7 +195,47 @@ void SimpleFile::read(byte_t* out, size_t offset, size_t len)
void SimpleFile::write(const byte_t* out, size_t len)
{
#ifdef _WIN32
LARGE_INTEGER win_len;
win_len.QuadPart = len;
static const DWORD kDwordHalf = ((DWORD)MAXDWORD / (DWORD)2) + 1; // 0x80000000
static const size_t kDwordFull = (size_t)kDwordHalf * (size_t)2; // 0x100000000
// if the size is greater than a DWORD, read it in parts,
for (LONG i = 0; i < win_len.HighPart; i++)
{
// since kDwordFull isn't a valid DWORD value, read in two parts
WriteFile(
mFileHandle,
out + i * kDwordFull,
kDwordHalf,
NULL,
NULL
);
WriteFile(
mFileHandle,
out + i * kDwordFull + kDwordHalf,
kDwordHalf,
NULL,
NULL
);
}
// read remainding low part
if (win_len.LowPart > 0)
{
WriteFile(
mFileHandle,
out + win_len.HighPart * kDwordFull,
win_len.LowPart,
NULL,
NULL
);
}
#else
fwrite(out, len, 1, mFp);
#endif
}
void SimpleFile::write(const byte_t* out, size_t offset, size_t len)
@ -82,6 +244,65 @@ void SimpleFile::write(const byte_t* out, size_t offset, size_t len)
write(out, len);
}
#ifdef _WIN32
DWORD SimpleFile::getOpenModeFlag(OpenMode mode) const
{
DWORD flag = 0;
switch (mode)
{
case (Read):
flag = GENERIC_READ;
break;
case (Edit):
flag = GENERIC_READ | GENERIC_WRITE;
break;
case (Create):
flag = GENERIC_WRITE;
break;
default:
throw fnd::Exception(kModuleName, "Unknown open mode");
}
return flag;
}
DWORD fnd::SimpleFile::getShareModeFlag(OpenMode mode) const
{
DWORD flag = 0;
switch (mode)
{
case (Read):
flag = FILE_SHARE_READ;
break;
case (Edit):
flag = FILE_SHARE_READ;
break;
case (Create):
flag = 0;
break;
default:
throw fnd::Exception(kModuleName, "Unknown open mode");
}
return flag;
}
DWORD fnd::SimpleFile::getCreationModeFlag(OpenMode mode) const
{
DWORD flag = 0;
switch (mode)
{
case (Read):
flag = OPEN_EXISTING;
break;
case (Edit):
flag = OPEN_EXISTING;
break;
case (Create):
flag = CREATE_ALWAYS;
break;
default:
throw fnd::Exception(kModuleName, "Unknown open mode");
}
return flag;
}
#else
const char* SimpleFile::getOpenModeStr(OpenMode mode)
{
const char* str = "";
@ -101,3 +322,4 @@ const char* SimpleFile::getOpenModeStr(OpenMode mode)
}
return str;
}
#endif

View file

@ -41,7 +41,7 @@ std::u16string StringConv::ConvertChar8ToChar16(const std::string & in)
throw std::logic_error("not a UTF-8 string");
}
uni <= 6;
uni <<= 6;
uni |= get_utf8_data(1, in[i + j]);
}
@ -67,7 +67,7 @@ std::u16string StringConv::ConvertChar8ToChar16(const std::string & in)
char32_t uni = unicode[i];
if (uni < kUtf16NonNativeStart)
{
utf16.push_back(uni);
utf16.push_back((char16_t)uni);
}
else
{
@ -117,25 +117,25 @@ std::string StringConv::ConvertChar16ToChar8(const std::u16string & in)
{
if (unicode[i] <= kUtf8AsciiEnd)
{
utf8.push_back(unicode[i]);
utf8.push_back((char)unicode[i]);
}
else if (unicode[i] <= kUtf82ByteEnd)
{
utf8.push_back(make_utf8(2, (unicode[i] >> 6)));
utf8.push_back(make_utf8(1, (unicode[i] >> 0)));
utf8.push_back(make_utf8(2, (uint8_t)(unicode[i] >> 6)));
utf8.push_back(make_utf8(1, (uint8_t)(unicode[i] >> 0)));
}
else if (unicode[i] <= kUtf83ByteEnd)
{
utf8.push_back(make_utf8(3, (unicode[i] >> 12)));
utf8.push_back(make_utf8(1, (unicode[i] >> 6)));
utf8.push_back(make_utf8(1, (unicode[i] >> 0)));
utf8.push_back(make_utf8(3, (uint8_t)(unicode[i] >> 12)));
utf8.push_back(make_utf8(1, (uint8_t)(unicode[i] >> 6)));
utf8.push_back(make_utf8(1, (uint8_t)(unicode[i] >> 0)));
}
else if (unicode[i] <= kUtf84ByteEnd)
{
utf8.push_back(make_utf8(4, (unicode[i] >> 18)));
utf8.push_back(make_utf8(1, (unicode[i] >> 12)));
utf8.push_back(make_utf8(1, (unicode[i] >> 6)));
utf8.push_back(make_utf8(1, (unicode[i] >> 0)));
utf8.push_back(make_utf8(4, (uint8_t)(unicode[i] >> 18)));
utf8.push_back(make_utf8(1, (uint8_t)(unicode[i] >> 12)));
utf8.push_back(make_utf8(1, (uint8_t)(unicode[i] >> 6)));
utf8.push_back(make_utf8(1, (uint8_t)(unicode[i] >> 0)));
}
else
{

View file

@ -0,0 +1,260 @@
#pragma once
#include <string>
#include <fnd/MemoryBlob.h>
#include <fnd/List.h>
#include <nx/cnmt.h>
namespace nx
{
class ContentMetaBinary :
public fnd::ISerialiseableBinary
{
public:
struct ContentInfo
{
crypto::sha::sSha256Hash hash;
byte_t nca_id[cnmt::kContentIdLen];
size_t size;
cnmt::ContentType type;
ContentInfo& operator=(const ContentInfo& other)
{
hash = other.hash;
memcpy(nca_id, other.nca_id, cnmt::kContentIdLen);
size = other.size;
type = other.type;
return *this;
}
bool operator==(const ContentInfo& other) const
{
return (hash == other.hash) \
&& (memcmp(nca_id, other.nca_id, cnmt::kContentIdLen) == 0) \
&& (size == other.size) \
&& (type == other.type);
}
bool operator!=(const ContentInfo& other) const
{
return !operator==(other);
}
};
struct ContentMetaInfo
{
uint64_t id;
uint32_t version;
cnmt::ContentMetaType type;
byte_t attributes;
ContentMetaInfo& operator=(const ContentMetaInfo& other)
{
id = other.id;
version = other.version;
type = other.type;
attributes = other.attributes;
return *this;
}
bool operator==(const ContentMetaInfo& other) const
{
return (id == other.id) \
&& (version == other.version) \
&& (type == other.type) \
&& (attributes == other.attributes);
}
bool operator!=(const ContentMetaInfo& other) const
{
return !operator==(other);
}
};
struct ApplicationMetaExtendedHeader
{
uint64_t patch_id;
uint32_t required_system_version;
ApplicationMetaExtendedHeader& operator=(const ApplicationMetaExtendedHeader& other)
{
patch_id = other.patch_id;
required_system_version = other.required_system_version;
return *this;
}
bool operator==(const ApplicationMetaExtendedHeader& other) const
{
return (patch_id == other.patch_id) \
&& (required_system_version == other.required_system_version);
}
bool operator!=(const ApplicationMetaExtendedHeader& other) const
{
return !operator==(other);
}
};
struct PatchMetaExtendedHeader
{
uint64_t application_id;
uint32_t required_system_version;
PatchMetaExtendedHeader& operator=(const PatchMetaExtendedHeader& other)
{
application_id = other.application_id;
required_system_version = other.required_system_version;
return *this;
}
bool operator==(const PatchMetaExtendedHeader& other) const
{
return (application_id == other.application_id) \
&& (required_system_version == other.required_system_version);
}
bool operator!=(const PatchMetaExtendedHeader& other) const
{
return !operator==(other);
}
};
struct AddOnContentMetaExtendedHeader
{
uint64_t application_id;
uint32_t required_system_version;
AddOnContentMetaExtendedHeader& operator=(const AddOnContentMetaExtendedHeader& other)
{
application_id = other.application_id;
required_system_version = other.required_system_version;
return *this;
}
bool operator==(const AddOnContentMetaExtendedHeader& other) const
{
return (application_id == other.application_id) \
&& (required_system_version == other.required_system_version);
}
bool operator!=(const AddOnContentMetaExtendedHeader& other) const
{
return !operator==(other);
}
};
struct DeltaMetaExtendedHeader
{
uint64_t application_id;
DeltaMetaExtendedHeader& operator=(const DeltaMetaExtendedHeader& other)
{
application_id = other.application_id;
return *this;
}
bool operator==(const DeltaMetaExtendedHeader& other) const
{
return (application_id == other.application_id);
}
bool operator!=(const DeltaMetaExtendedHeader& other) const
{
return !operator==(other);
}
};
ContentMetaBinary();
ContentMetaBinary(const ContentMetaBinary& other);
ContentMetaBinary(const byte_t* bytes, size_t len);
// to be used after export
const byte_t* getBytes() const;
size_t getSize() const;
// export/import binary
void exportBinary();
void importBinary(const byte_t* bytes, size_t len);
// variables
void clear();
uint64_t getTitleId() const;
void setTitleId(uint64_t title_id);
uint32_t getTitleVersion() const;
void setTitleVersion(uint32_t version);
cnmt::ContentMetaType getType() const;
void setType(cnmt::ContentMetaType type);
byte_t getAttributes() const;
void setAttributes(byte_t attributes);
uint32_t getRequiredDownloadSystemVersion() const;
void setRequiredDownloadSystemVersion(uint32_t version);
const ApplicationMetaExtendedHeader& getApplicationMetaExtendedHeader() const;
void setApplicationMetaExtendedHeader(const ApplicationMetaExtendedHeader& exhdr);
const PatchMetaExtendedHeader& getPatchMetaExtendedHeader() const;
void setPatchMetaExtendedHeader(const PatchMetaExtendedHeader& exhdr);
const AddOnContentMetaExtendedHeader& getAddOnContentMetaExtendedHeader() const;
void setAddOnContentMetaExtendedHeader(const AddOnContentMetaExtendedHeader& exhdr);
const DeltaMetaExtendedHeader& getDeltaMetaExtendedHeader() const;
void setDeltaMetaExtendedHeader(const DeltaMetaExtendedHeader& exhdr);
const fnd::List<nx::ContentMetaBinary::ContentInfo>& getContentInfo() const;
void setContentInfo(const fnd::List<nx::ContentMetaBinary::ContentInfo>& info);
const fnd::List<nx::ContentMetaBinary::ContentMetaInfo>& getContentMetaInfo() const;
void setContentMetaInfo(const fnd::List<nx::ContentMetaBinary::ContentMetaInfo>& info);
const fnd::MemoryBlob& getExtendedData() const;
void setExtendedData(const fnd::MemoryBlob& data);
const nx::sDigest& getDigest() const;
void setDigest(const nx::sDigest& digest);
private:
const std::string kModuleName = "CONTENT_META_BINARY";
// binary blob
fnd::MemoryBlob mBinaryBlob;
// variables
uint64_t mTitleId;
uint32_t mTitleVersion;
cnmt::ContentMetaType mType;
byte_t mAttributes;
uint32_t mRequiredDownloadSystemVersion;
fnd::MemoryBlob mExtendedHeader;
ApplicationMetaExtendedHeader mApplicationMetaExtendedHeader;
PatchMetaExtendedHeader mPatchMetaExtendedHeader;
AddOnContentMetaExtendedHeader mAddOnContentMetaExtendedHeader;
DeltaMetaExtendedHeader mDeltaMetaExtendedHeader;
fnd::List<nx::ContentMetaBinary::ContentInfo> mContentInfo;
fnd::List<nx::ContentMetaBinary::ContentMetaInfo> mContentMetaInfo;
fnd::MemoryBlob mExtendedData;
nx::sDigest mDigest;
inline size_t getExtendedHeaderOffset() const { return sizeof(sContentMetaHeader); }
inline size_t getContentInfoOffset(size_t exhdrSize) const { return getExtendedHeaderOffset() + exhdrSize; }
inline size_t getContentMetaInfoOffset(size_t exhdrSize, size_t contentInfoNum) const { return getContentInfoOffset(exhdrSize) + contentInfoNum * sizeof(sContentInfo); }
inline size_t getExtendedDataOffset(size_t exhdrSize, size_t contentInfoNum, size_t contentMetaNum) const { return getContentMetaInfoOffset(exhdrSize, contentInfoNum) + contentMetaNum * sizeof(sContentMetaInfo); }
inline size_t getDigestOffset(size_t exhdrSize, size_t contentInfoNum, size_t contentMetaNum, size_t exdataSize) const { return getExtendedDataOffset(exhdrSize, contentInfoNum, contentMetaNum) + exdataSize; }
inline size_t getTotalSize(size_t exhdrSize, size_t contentInfoNum, size_t contentMetaNum, size_t exdataSize) const { return getDigestOffset(exhdrSize, contentInfoNum, contentMetaNum, exdataSize) + cnmt::kDigestLen; }
bool validateExtendedHeaderSize(cnmt::ContentMetaType type, size_t exhdrSize) const;
size_t getExtendedDataSize(cnmt::ContentMetaType type, const byte_t* data) const;
void validateBinary(const byte_t* bytes, size_t len) const;
bool isEqual(const ContentMetaBinary& other) const;
void copyFrom(const ContentMetaBinary& other);
};
}

View file

@ -0,0 +1,75 @@
#pragma once
#include <nx/hierarchicalintegrity.h>
#include <fnd/MemoryBlob.h>
#include <fnd/List.h>
#include <fnd/ISerialiseableBinary.h>
namespace nx
{
class HierarchicalIntegrityHeader :
public fnd::ISerialiseableBinary
{
public:
struct sLayer
{
size_t offset;
size_t size;
size_t block_size;
void operator=(const sLayer& other)
{
offset = other.offset;
size = other.size;
block_size = other.block_size;
}
bool operator==(const sLayer& other) const
{
return (offset == other.offset && size == other.size && block_size == other.block_size);
}
bool operator!=(const sLayer& other) const
{
return !(*this == other);
}
};
HierarchicalIntegrityHeader();
HierarchicalIntegrityHeader(const HierarchicalIntegrityHeader& other);
HierarchicalIntegrityHeader(const byte_t* bytes, size_t len);
bool operator==(const HierarchicalIntegrityHeader& other) const;
bool operator!=(const HierarchicalIntegrityHeader& other) const;
void operator=(const HierarchicalIntegrityHeader& other);
// to be used after export
const byte_t* getBytes() const;
size_t getSize() const;
// export/import binary
void exportBinary();
void importBinary(const byte_t* bytes, size_t len);
// variables
void clear();
const fnd::List<sLayer>& getLayerInfo() const;
void setLayerInfo(const fnd::List<sLayer>& layer_info);
const fnd::List<crypto::sha::sSha256Hash>& getMasterHashList() const;
void setMasterHashList(const fnd::List<crypto::sha::sSha256Hash>& master_hash_list);
private:
const std::string kModuleName = "HIERARCHICAL_INTEGRITY_HEADER";
// binary
fnd::MemoryBlob mBinaryBlob;
// data
fnd::List<sLayer> mLayerInfo;
fnd::List<crypto::sha::sSha256Hash> mMasterHashList;
bool isEqual(const HierarchicalIntegrityHeader& other) const;
void copyFrom(const HierarchicalIntegrityHeader& other);
};
}

View file

@ -0,0 +1,77 @@
#pragma once
#include <nx/hierarchicalsha256.h>
#include <fnd/MemoryBlob.h>
#include <fnd/List.h>
#include <fnd/ISerialiseableBinary.h>
namespace nx
{
class HierarchicalSha256Header :
public fnd::ISerialiseableBinary
{
public:
struct sLayer
{
size_t offset;
size_t size;
void operator=(const sLayer& other)
{
offset = other.offset;
size = other.size;
}
bool operator==(const sLayer& other) const
{
return (offset == other.offset && size == other.size);
}
bool operator!=(const sLayer& other) const
{
return !(*this == other);
}
};
HierarchicalSha256Header();
HierarchicalSha256Header(const HierarchicalSha256Header& other);
HierarchicalSha256Header(const byte_t* bytes, size_t len);
bool operator==(const HierarchicalSha256Header& other) const;
bool operator!=(const HierarchicalSha256Header& other) const;
void operator=(const HierarchicalSha256Header& other);
// to be used after export
const byte_t* getBytes() const;
size_t getSize() const;
// export/import binary
void exportBinary();
void importBinary(const byte_t* bytes, size_t len);
// variables
void clear();
const crypto::sha::sSha256Hash& getMasterHash() const;
void setMasterHash(const crypto::sha::sSha256Hash& master_hash);
size_t getHashBlockSize() const;
void setHashBlockSize(size_t hash_block_size);
const fnd::List<sLayer>& getLayerInfo() const;
void setLayerInfo(const fnd::List<sLayer>& layer_info);
private:
const std::string kModuleName = "HIERARCHICAL_SHA256_HEADER";
// binary
fnd::MemoryBlob mBinaryBlob;
// data
crypto::sha::sSha256Hash mMasterHash;
size_t mHashBlockSize;
fnd::List<sLayer> mLayerInfo;
bool isEqual(const HierarchicalSha256Header& other) const;
void copyFrom(const HierarchicalSha256Header& other);
};
}

138
lib/libnx/include/nx/cnmt.h Normal file
View file

@ -0,0 +1,138 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <crypto/aes.h>
#include <crypto/sha.h>
#include <fnd/ISerialiseableBinary.h>
namespace nx
{
namespace cnmt
{
enum ContentType
{
TYPE_META = 0,
TYPE_PROGRAM,
TYPE_DATA,
TYPE_CONTROL,
TYPE_HTML_DOCUMENT,
TYPE_LEGAL_INFORMATION,
TYPE_DELTA_FRAGMENT
};
enum ContentMetaType
{
METATYPE_SYSTEM_PROGRAM = 1,
METATYPE_SYSTEM_DATA,
METATYPE_SYSTEM_UPDATE,
METATYPE_BOOT_IMAGE_PACKAGE,
METATYPE_BOOT_IMAGE_PACKAGE_SAFE,
METATYPE_APPLICATION = 0x80,
METATYPE_PATCH, // can have extended data
METATYPE_ADD_ON_CONTENT,
METATYPE_DELTA // can have extended data
};
enum UpdateType
{
UPDATETYPE_APPLY_AS_DELTA,
UPDATETYPE_OVERWRITE,
UPDATETYPE_CREATE
};
enum ContentMetaAttribute
{
ATTRIBUTE_INCLUDES_EX_FAT_DRIVER,
ATTRIBUTE_REBOOTLESS
};
static const uint32_t kRequiredSystemVersion = 335544320;
static const uint32_t kDefaultVersion = 335545344;
static const size_t kContentIdLen = 0x10;
static const size_t kDigestLen = 0x20;
}
#pragma pack(push,1)
/*
struct sContentMeta
{
sContentMetaHeader hdr;
byte_t exhdr[]; // optional
sContentInfo info[];
sContentMetaInfo meta[];
byte_t extdata[];
byte_t digest[32]
};
*/
struct sContentMetaHeader
{
le_uint64_t id;
le_uint32_t version;
byte_t type;
byte_t reserved_0;
le_uint16_t exhdr_size;
le_uint16_t content_count;
le_uint16_t content_meta_count;
byte_t attributes;
byte_t reserved_1[3];
le_uint32_t required_download_system_version;
byte_t reserved_2[4];
};
struct sContentInfo
{
crypto::sha::sSha256Hash content_hash;
byte_t content_id[cnmt::kContentIdLen];
le_uint32_t size_lower;
le_uint16_t size_higher;
byte_t content_type;
byte_t id_offset;
};
struct sContentMetaInfo
{
le_uint64_t id;
le_uint32_t version;
byte_t type;
byte_t attributes;
byte_t reserved[2];
};
struct sApplicationMetaExtendedHeader
{
le_uint64_t patch_id;
le_uint32_t required_system_version;
byte_t reserved[4];
};
struct sPatchMetaExtendedHeader
{
le_uint64_t application_id;
le_uint32_t required_system_version;
le_uint32_t extended_data_size;
byte_t reserved[8];
};
struct sAddOnContentMetaExtendedHeader
{
le_uint64_t application_id;
le_uint32_t required_system_version;
byte_t reserved[4];
};
struct sDeltaMetaExtendedHeader
{
le_uint64_t application_id;
le_uint32_t extended_data_size;
byte_t reserved[4];
};
struct sDigest
{
byte_t data[cnmt::kDigestLen];
};
#pragma pack(pop)
}

View file

@ -0,0 +1,35 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <crypto/sha.h>
#include <fnd/ISerialiseableBinary.h>
namespace nx
{
// Also known to the public as IVFC
namespace hierarchicalintegrity
{
const std::string kStructSig = "IVFC";
static const uint32_t kRomfsTypeId = 0x20000;
static const size_t kDefaultLayerNum = 6;
static const size_t kHeaderAlignLen = 0x20;
}
#pragma pack(push,1)
struct sHierarchicalIntegrityHeader
{
char signature[4];
le_uint32_t type_id;
le_uint32_t master_hash_size;
le_uint32_t layer_num;
};
struct sHierarchicalIntegrityLayerInfo // sizeof(0x18)
{
le_uint64_t offset;
le_uint64_t size;
le_uint32_t block_size;
byte_t reserved[4];
};
#pragma pack(pop)
}

View file

@ -8,7 +8,8 @@ namespace nx
{
namespace hierarchicalsha256
{
static const size_t kDefaultLevelNum = 2;
static const size_t kDefaultLayerNum = 2;
static const size_t kMaxLayerNum = 2;
}
#pragma pack(push,1)
@ -16,12 +17,12 @@ namespace nx
{
crypto::sha::sSha256Hash master_hash;
le_uint32_t hash_block_size;
le_uint32_t hash_level_num;
struct sLayout
le_uint32_t layer_num;
struct sLayer
{
le_uint64_t offset;
le_uint64_t size;
} hash_data, hash_target;
} layer[hierarchicalsha256::kMaxLayerNum];
};
#pragma pack(pop)
}

View file

@ -1,36 +0,0 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <crypto/aes.h>
#include <crypto/sha.h>
#include <fnd/ISerialiseableBinary.h>
namespace nx
{
// Also known as HierarchicalIntegrity
namespace ivfc
{
const std::string kIvfcSig = "IVFC";
static const size_t kMaxIvfcLevel = 7;
static const uint32_t kIvfcId = 0x20000;
}
#pragma pack(push,1)
struct sIvfcHeader
{
char signature[4];
le_uint32_t id;
le_uint32_t master_hash_size;
le_uint32_t level_num;
struct sIvfcLevelHeader
{
le_uint64_t logical_offset;
le_uint64_t hash_data_size;
le_uint32_t block_size;
byte_t reserved[4];
} level_header[ivfc::kMaxIvfcLevel];
byte_t reserved_00[0x8];
crypto::sha::sSha256Hash master_hash;
};
#pragma pack(pop)
}

View file

@ -5,8 +5,6 @@
#include <crypto/sha.h>
#include <crypto/rsa.h>
#include <fnd/ISerialiseableBinary.h>
#include <nx/ivfc.h>
#include <nx/hierarchicalsha256.h>
namespace nx
{
@ -21,7 +19,7 @@ namespace nx
static const size_t kAesKeyNum = 16;
static const size_t kRightsIdLen = 0x10;
static const size_t kKeyAreaEncryptionKeyNum = 3;
static const size_t kFsHeaderHashSuperblockLen = 0x130;
static const size_t kFsHeaderHashSuperblockLen = 0x138;
static const uint16_t kDefaultFsHeaderVersion = 2;
enum ProgramPartitionId
@ -120,12 +118,8 @@ namespace nx
byte_t hash_type;
byte_t encryption_type;
byte_t reserved_0[3];
union {
byte_t hash_superblock[nca::kFsHeaderHashSuperblockLen];
nx::sHierarchicalSha256Header hierarchicalsha256_header;
nx::sIvfcHeader ivfc_header;
};
crypto::aes::sAesIvCtr base_ctr;
byte_t hash_superblock[nca::kFsHeaderHashSuperblockLen];
byte_t aes_ctr_upper[8];
byte_t reserved_1[0xB8];
};

View file

@ -42,7 +42,8 @@ namespace nx
le_uint32_t file;
le_uint32_t hash;
le_uint32_t name_size;
char name[];
char* name() { return ((char*)(this)) + sizeof(sRomfsDirEntry); }
const char* name() const { return ((char*)(this)) + sizeof(sRomfsDirEntry); }
};
struct sRomfsFileEntry
@ -53,7 +54,8 @@ namespace nx
le_uint64_t size;
le_uint32_t hash;
le_uint32_t name_size;
char name[];
char* name() { return ((char*)(this)) + sizeof(sRomfsFileEntry); }
const char* name() const { return ((char*)(this)) + sizeof(sRomfsFileEntry); }
};
#pragma pack(pop)
}

View file

@ -115,9 +115,11 @@ namespace nx
struct sKeyDataArea
{
sInitialData initial_data; // AES128-CCM encrypted {titlekey[16]}
byte_t encrypted_00[0x200*6]; // AES128-CTR encrypted {titlekey[16]}
byte_t encrypted_00_aesctr_data[0x100]; // RSA2048-OAEP-SHA256 encrypted AES-CTR data used for encrypted_00 {key[16],iv[16]}
byte_t reserved_01[0x100];
byte_t reserved_00[xci::kPageSize - sizeof(sInitialData)];
byte_t encrypted_00[xci::kPageSize * 6]; // AES128-CTR encrypted {titlekey[16]}
byte_t encrypted_00_aesctr_data[crypto::rsa::kRsa2048Size]; // RSA2048-OAEP-SHA256 encrypted AES-CTR data used for encrypted_00 {key[16],iv[16]}
byte_t reserved_01[xci::kPageSize - crypto::rsa::kRsa2048Size];
}; // sizeof() = 512*8 (8 pages)
#pragma pack(pop)
}

View file

@ -24,14 +24,19 @@
<ClInclude Include="include\nx\AcidBinary.h" />
<ClInclude Include="include\nx\AciHeader.h" />
<ClInclude Include="include\nx\AesKeygen.h" />
<ClInclude Include="include\nx\cnmt.h" />
<ClInclude Include="include\nx\ContentMetaBinary.h" />
<ClInclude Include="include\nx\FacBinary.h" />
<ClInclude Include="include\nx\FacHeader.h" />
<ClInclude Include="include\nx\HandleTableSizeEntry.h" />
<ClInclude Include="include\nx\HandleTableSizeHandler.h" />
<ClInclude Include="include\nx\HierarchicalIntegrityHeader.h" />
<ClInclude Include="include\nx\hierarchicalsha256.h" />
<ClInclude Include="include\nx\HierarchicalSha256Header.h" />
<ClInclude Include="include\nx\IKernelCapabilityHandler.h" />
<ClInclude Include="include\nx\InteruptEntry.h" />
<ClInclude Include="include\nx\InteruptHandler.h" />
<ClInclude Include="include\nx\ivfc.h" />
<ClInclude Include="include\nx\hierarchicalintegrity.h" />
<ClInclude Include="include\nx\KcBinary.h" />
<ClInclude Include="include\nx\KernelCapability.h" />
<ClInclude Include="include\nx\KernelVersionEntry.h" />
@ -67,10 +72,13 @@
<ClCompile Include="source\AcidBinary.cpp" />
<ClCompile Include="source\AciHeader.cpp" />
<ClCompile Include="source\AesKeygen.cpp" />
<ClCompile Include="source\ContentMetaBinary.cpp" />
<ClCompile Include="source\FacBinary.cpp" />
<ClCompile Include="source\FacHeader.cpp" />
<ClCompile Include="source\HandleTableSizeEntry.cpp" />
<ClCompile Include="source\HandleTableSizeHandler.cpp" />
<ClCompile Include="source\HierarchicalIntegrityHeader.cpp" />
<ClCompile Include="source\HierarchicalSha256Header.cpp" />
<ClCompile Include="source\InteruptEntry.cpp" />
<ClCompile Include="source\InteruptHandler.cpp" />
<ClCompile Include="source\KcBinary.cpp" />

View file

@ -123,9 +123,6 @@
<ClInclude Include="include\nx\AesKeygen.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\nx\ivfc.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\nx\NcaUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -141,6 +138,24 @@
<ClInclude Include="include\nx\XciUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\nx\hierarchicalsha256.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\nx\ContentMetaBinary.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\nx\cnmt.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\nx\hierarchicalintegrity.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\nx\HierarchicalSha256Header.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\nx\HierarchicalIntegrityHeader.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="source\AciBinary.cpp">
@ -242,6 +257,15 @@
<ClCompile Include="source\XciUtils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="source\ContentMetaBinary.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="source\HierarchicalIntegrityHeader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="source\HierarchicalSha256Header.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="makefile" />

View file

@ -106,12 +106,12 @@ void AciHeader::exportBinary()
// set offset/size
calculateSectionOffsets();
hdr->fac.offset = mFac.offset;
hdr->fac.size = mFac.size;
hdr->sac.offset = mSac.offset;
hdr->sac.size = mSac.size;
hdr->kc.offset = mKc.offset;
hdr->kc.size = mKc.size;
hdr->fac.offset = (uint32_t)mFac.offset;
hdr->fac.size = (uint32_t)mFac.size;
hdr->sac.offset = (uint32_t)mSac.offset;
hdr->sac.size = (uint32_t)mSac.size;
hdr->kc.offset = (uint32_t)mKc.offset;
hdr->kc.size = (uint32_t)mKc.size;
uint32_t flags = 0;
if (mIsProduction)
@ -129,7 +129,7 @@ void AciHeader::exportBinary()
else if (mType == TYPE_ACID)
{
mAcidSize = getAciSize();
hdr->size = mAcidSize;
hdr->size = (uint32_t)mAcidSize;
hdr->program_id_info.program_id_restrict.min = mProgramIdMin;
hdr->program_id_info.program_id_restrict.max = mProgramIdMax;
}

View file

@ -0,0 +1,382 @@
#include <nx/ContentMetaBinary.h>
nx::ContentMetaBinary::ContentMetaBinary()
{
clear();
}
nx::ContentMetaBinary::ContentMetaBinary(const ContentMetaBinary & other)
{
copyFrom(other);
}
nx::ContentMetaBinary::ContentMetaBinary(const byte_t * bytes, size_t len)
{
importBinary(bytes, len);
}
const byte_t * nx::ContentMetaBinary::getBytes() const
{
return mBinaryBlob.getBytes();
}
size_t nx::ContentMetaBinary::getSize() const
{
return mBinaryBlob.getSize();
}
void nx::ContentMetaBinary::exportBinary()
{
throw fnd::Exception(kModuleName, "exportBinary() not implemented");
}
void nx::ContentMetaBinary::importBinary(const byte_t * bytes, size_t len)
{
// clear member variables
clear();
// validate layout
validateBinary(bytes, len);
// get pointer to header structure
const sContentMetaHeader* hdr = (const sContentMetaHeader*)bytes;
mTitleId = hdr->id.get();
mTitleVersion = hdr->version.get();
mType = (cnmt::ContentMetaType)hdr->type;
mAttributes = hdr->attributes;
mRequiredDownloadSystemVersion = hdr->required_download_system_version.get();
size_t exdata_size = 0;
// save exheader
if (hdr->exhdr_size.get() > 0)
{
mExtendedHeader.alloc(hdr->exhdr_size.get());
memcpy(mExtendedHeader.getBytes(), bytes + getExtendedHeaderOffset(), hdr->exhdr_size.get());
switch (mType)
{
case (cnmt::METATYPE_APPLICATION):
mApplicationMetaExtendedHeader.patch_id = ((sApplicationMetaExtendedHeader*)mExtendedHeader.getBytes())->patch_id.get();
mApplicationMetaExtendedHeader.required_system_version = ((sApplicationMetaExtendedHeader*)mExtendedHeader.getBytes())->required_system_version.get();
break;
case (cnmt::METATYPE_PATCH):
mPatchMetaExtendedHeader.application_id = ((sPatchMetaExtendedHeader*)mExtendedHeader.getBytes())->application_id.get();
mPatchMetaExtendedHeader.required_system_version = ((sPatchMetaExtendedHeader*)mExtendedHeader.getBytes())->required_system_version.get();
break;
case (cnmt::METATYPE_ADD_ON_CONTENT):
mAddOnContentMetaExtendedHeader.application_id = ((sAddOnContentMetaExtendedHeader*)mExtendedHeader.getBytes())->application_id.get();
mAddOnContentMetaExtendedHeader.required_system_version = ((sAddOnContentMetaExtendedHeader*)mExtendedHeader.getBytes())->required_system_version.get();
break;
case (cnmt::METATYPE_DELTA):
mDeltaMetaExtendedHeader.application_id = ((sDeltaMetaExtendedHeader*)mExtendedHeader.getBytes())->application_id.get();
break;
default:
break;
}
exdata_size = getExtendedDataSize(mType, mExtendedHeader.getBytes());
}
// save content info
if (hdr->content_count.get() > 0)
{
const sContentInfo* info = (const sContentInfo*)(bytes + getContentInfoOffset(hdr->exhdr_size.get()));
for (size_t i = 0; i < hdr->content_count.get(); i++)
{
mContentInfo[i].hash = info[i].content_hash;
memcpy(mContentInfo[i].nca_id, info[i].content_id, cnmt::kContentIdLen);
mContentInfo[i].size = (uint64_t)(info[i].size_lower.get()) | (uint64_t)(info[i].size_higher.get()) << 32;
mContentInfo[i].type = (cnmt::ContentType)info[i].content_type;
}
}
// save content meta info
if (hdr->content_meta_count.get() > 0)
{
const sContentMetaInfo* info = (const sContentMetaInfo*)(bytes + getContentMetaInfoOffset(hdr->exhdr_size.get(), hdr->content_count.get()));
for (size_t i = 0; i < hdr->content_meta_count.get(); i++)
{
mContentMetaInfo[i].id = info[i].id.get();
mContentMetaInfo[i].version = info[i].version.get();
mContentMetaInfo[i].type = (cnmt::ContentMetaType)info[i].type;
mContentMetaInfo[i].attributes = info[i].attributes;
}
}
// save exdata
if (exdata_size > 0)
{
mExtendedData.alloc(exdata_size);
memcpy(mExtendedData.getBytes(), bytes + getExtendedDataOffset(hdr->exhdr_size.get(), hdr->content_count.get(), hdr->content_meta_count.get()), exdata_size);
}
// save digest
memcpy(mDigest.data, bytes + getDigestOffset(hdr->exhdr_size.get(), hdr->content_count.get(), hdr->content_meta_count.get(), exdata_size), cnmt::kDigestLen);
}
void nx::ContentMetaBinary::clear()
{
mBinaryBlob.clear();
mTitleId = 0;
mTitleVersion = 0;
mType = cnmt::METATYPE_SYSTEM_PROGRAM;
mAttributes = 0;
mRequiredDownloadSystemVersion = 0;
mExtendedHeader.clear();
memset(&mApplicationMetaExtendedHeader, 0, sizeof(mApplicationMetaExtendedHeader));
memset(&mPatchMetaExtendedHeader, 0, sizeof(mPatchMetaExtendedHeader));
memset(&mAddOnContentMetaExtendedHeader, 0, sizeof(mAddOnContentMetaExtendedHeader));
memset(&mDeltaMetaExtendedHeader, 0, sizeof(mDeltaMetaExtendedHeader));
mContentInfo.clear();
mContentMetaInfo.clear();
mExtendedData.clear();
memset(mDigest.data, 0, cnmt::kDigestLen);
}
uint64_t nx::ContentMetaBinary::getTitleId() const
{
return mTitleId;
}
void nx::ContentMetaBinary::setTitleId(uint64_t title_id)
{
mTitleId = title_id;
}
uint32_t nx::ContentMetaBinary::getTitleVersion() const
{
return mTitleVersion;
}
void nx::ContentMetaBinary::setTitleVersion(uint32_t version)
{
mTitleVersion = version;
}
nx::cnmt::ContentMetaType nx::ContentMetaBinary::getType() const
{
return mType;
}
void nx::ContentMetaBinary::setType(cnmt::ContentMetaType type)
{
mType = type;
}
byte_t nx::ContentMetaBinary::getAttributes() const
{
return mAttributes;
}
void nx::ContentMetaBinary::setAttributes(byte_t attributes)
{
mAttributes = attributes;
}
uint32_t nx::ContentMetaBinary::getRequiredDownloadSystemVersion() const
{
return mRequiredDownloadSystemVersion;
}
void nx::ContentMetaBinary::setRequiredDownloadSystemVersion(uint32_t version)
{
mRequiredDownloadSystemVersion = version;
}
const nx::ContentMetaBinary::ApplicationMetaExtendedHeader& nx::ContentMetaBinary::getApplicationMetaExtendedHeader() const
{
return mApplicationMetaExtendedHeader;
}
void nx::ContentMetaBinary::setApplicationMetaExtendedHeader(const ApplicationMetaExtendedHeader& exhdr)
{
mApplicationMetaExtendedHeader = exhdr;
}
const nx::ContentMetaBinary::PatchMetaExtendedHeader& nx::ContentMetaBinary::getPatchMetaExtendedHeader() const
{
return mPatchMetaExtendedHeader;
}
void nx::ContentMetaBinary::setPatchMetaExtendedHeader(const PatchMetaExtendedHeader& exhdr)
{
mPatchMetaExtendedHeader = exhdr;
}
const nx::ContentMetaBinary::AddOnContentMetaExtendedHeader& nx::ContentMetaBinary::getAddOnContentMetaExtendedHeader() const
{
return mAddOnContentMetaExtendedHeader;
}
void nx::ContentMetaBinary::setAddOnContentMetaExtendedHeader(const AddOnContentMetaExtendedHeader& exhdr)
{
mAddOnContentMetaExtendedHeader = exhdr;
}
const nx::ContentMetaBinary::DeltaMetaExtendedHeader& nx::ContentMetaBinary::getDeltaMetaExtendedHeader() const
{
return mDeltaMetaExtendedHeader;
}
void nx::ContentMetaBinary::setDeltaMetaExtendedHeader(const DeltaMetaExtendedHeader& exhdr)
{
mDeltaMetaExtendedHeader = exhdr;
}
const fnd::List<nx::ContentMetaBinary::ContentInfo>& nx::ContentMetaBinary::getContentInfo() const
{
return mContentInfo;
}
void nx::ContentMetaBinary::setContentInfo(const fnd::List<nx::ContentMetaBinary::ContentInfo>& info)
{
mContentInfo = info;
}
const fnd::List<nx::ContentMetaBinary::ContentMetaInfo>& nx::ContentMetaBinary::getContentMetaInfo() const
{
return mContentMetaInfo;
}
void nx::ContentMetaBinary::setContentMetaInfo(const fnd::List<nx::ContentMetaBinary::ContentMetaInfo>& info)
{
mContentMetaInfo = info;
}
const fnd::MemoryBlob & nx::ContentMetaBinary::getExtendedData() const
{
return mExtendedData;
}
void nx::ContentMetaBinary::setExtendedData(const fnd::MemoryBlob & data)
{
mExtendedData = data;
}
const nx::sDigest & nx::ContentMetaBinary::getDigest() const
{
return mDigest;
}
void nx::ContentMetaBinary::setDigest(const nx::sDigest & digest)
{
memcpy(mDigest.data, digest.data, cnmt::kDigestLen);
}
bool nx::ContentMetaBinary::validateExtendedHeaderSize(cnmt::ContentMetaType type, size_t exhdrSize) const
{
bool validSize = false;
switch (type)
{
case (cnmt::METATYPE_APPLICATION):
validSize = (exhdrSize == sizeof(sApplicationMetaExtendedHeader));
break;
case (cnmt::METATYPE_PATCH):
validSize = (exhdrSize == sizeof(sPatchMetaExtendedHeader));
break;
case (cnmt::METATYPE_ADD_ON_CONTENT):
validSize = (exhdrSize == sizeof(sAddOnContentMetaExtendedHeader));
break;
case (cnmt::METATYPE_DELTA):
validSize = (exhdrSize == sizeof(sDeltaMetaExtendedHeader));
break;
default:
validSize = (exhdrSize == 0);
}
return validSize;
}
size_t nx::ContentMetaBinary::getExtendedDataSize(cnmt::ContentMetaType type, const byte_t * data) const
{
size_t exdata_len = 0;
if (type == cnmt::METATYPE_PATCH)
{
const sPatchMetaExtendedHeader* exhdr = (const sPatchMetaExtendedHeader*)(data);
exdata_len = exhdr->extended_data_size.get();
}
else if (type == cnmt::METATYPE_DELTA)
{
const sDeltaMetaExtendedHeader* exhdr = (const sDeltaMetaExtendedHeader*)(data);
exdata_len = exhdr->extended_data_size.get();
}
return exdata_len;
}
void nx::ContentMetaBinary::validateBinary(const byte_t * bytes, size_t len) const
{
// check if it is large enough to read the header
if (len < sizeof(sContentMetaHeader))
{
throw fnd::Exception(kModuleName, "Binary too small");
}
// get pointer to header structure
const sContentMetaHeader* hdr = (const sContentMetaHeader*)bytes;
// validate extended header size
if (validateExtendedHeaderSize((cnmt::ContentMetaType)hdr->type, hdr->exhdr_size.get()) == false)
{
throw fnd::Exception(kModuleName, "Invalid extended header size");
}
// check binary size again for new minimum size
if (len < getTotalSize(hdr->exhdr_size.get(), hdr->content_count.get(), hdr->content_meta_count.get(), 0))
{
throw fnd::Exception(kModuleName, "Binary too small");
}
// check binary size again with extended data size
if (len < getTotalSize(hdr->exhdr_size.get(), hdr->content_count.get(), hdr->content_meta_count.get(), getExtendedDataSize((cnmt::ContentMetaType)hdr->type, bytes + getExtendedHeaderOffset())))
{
throw fnd::Exception(kModuleName, "Binary too small");
}
}
bool nx::ContentMetaBinary::isEqual(const ContentMetaBinary & other) const
{
return (mTitleId == other.mTitleId) \
&& (mTitleVersion == other.mTitleVersion) \
&& (mType == other.mType) \
&& (mAttributes == other.mAttributes) \
&& (mRequiredDownloadSystemVersion == other.mRequiredDownloadSystemVersion) \
&& (mExtendedHeader == other.mExtendedHeader) \
&& (mApplicationMetaExtendedHeader == other.mApplicationMetaExtendedHeader) \
&& (mPatchMetaExtendedHeader == other.mPatchMetaExtendedHeader) \
&& (mAddOnContentMetaExtendedHeader == other.mAddOnContentMetaExtendedHeader) \
&& (mDeltaMetaExtendedHeader == other.mDeltaMetaExtendedHeader) \
&& (mContentInfo == other.mContentInfo) \
&& (mContentMetaInfo == other.mContentMetaInfo) \
&& (mExtendedData == other.mExtendedData) \
&& (memcmp(mDigest.data, other.mDigest.data, cnmt::kDigestLen) == 0);
}
void nx::ContentMetaBinary::copyFrom(const ContentMetaBinary & other)
{
if (other.getSize() > 0)
{
importBinary(other.getBytes(), other.getSize());
}
else
{
clear();
mTitleId = other.mTitleId;
mTitleVersion = other.mTitleVersion;
mType = other.mType;
mAttributes = other.mAttributes;
mRequiredDownloadSystemVersion = other.mRequiredDownloadSystemVersion;
mExtendedHeader = other.mExtendedHeader;
mApplicationMetaExtendedHeader = other.mApplicationMetaExtendedHeader;
mPatchMetaExtendedHeader = other.mPatchMetaExtendedHeader;
mAddOnContentMetaExtendedHeader = other.mAddOnContentMetaExtendedHeader;
mDeltaMetaExtendedHeader = other.mDeltaMetaExtendedHeader;
mContentInfo = other.mContentInfo;
mContentMetaInfo = other.mContentMetaInfo;
mExtendedData = other.mExtendedData;
memcpy(mDigest.data, other.mDigest.data, cnmt::kDigestLen);
}
}

View file

@ -64,10 +64,10 @@ void nx::FacHeader::exportBinary()
hdr->fac_flags = (flag);
calculateOffsets();
hdr->content_owner_ids.start = (mContentOwnerIdPos.offset);
hdr->content_owner_ids.end = (mContentOwnerIdPos.offset + mContentOwnerIdPos.size);
hdr->save_data_owner_ids.start = (mSaveDataOwnerIdPos.offset);
hdr->save_data_owner_ids.end = (mSaveDataOwnerIdPos.offset + mSaveDataOwnerIdPos.size);
hdr->content_owner_ids.start = (uint32_t)(mContentOwnerIdPos.offset);
hdr->content_owner_ids.end = (uint32_t)(mContentOwnerIdPos.offset + mContentOwnerIdPos.size);
hdr->save_data_owner_ids.start = (uint32_t)(mSaveDataOwnerIdPos.offset);
hdr->save_data_owner_ids.end = (uint32_t)(mSaveDataOwnerIdPos.offset + mSaveDataOwnerIdPos.size);
}
void nx::FacHeader::importBinary(const byte_t * bytes, size_t len)

View file

@ -0,0 +1,156 @@
#include <sstream>
#include <nx/HierarchicalIntegrityHeader.h>
nx::HierarchicalIntegrityHeader::HierarchicalIntegrityHeader()
{
clear();
}
nx::HierarchicalIntegrityHeader::HierarchicalIntegrityHeader(const HierarchicalIntegrityHeader & other)
{
copyFrom(other);
}
nx::HierarchicalIntegrityHeader::HierarchicalIntegrityHeader(const byte_t * bytes, size_t len)
{
importBinary(bytes, len);
}
bool nx::HierarchicalIntegrityHeader::operator==(const HierarchicalIntegrityHeader & other) const
{
return isEqual(other);
}
bool nx::HierarchicalIntegrityHeader::operator!=(const HierarchicalIntegrityHeader & other) const
{
return !isEqual(other);
}
void nx::HierarchicalIntegrityHeader::operator=(const HierarchicalIntegrityHeader & other)
{
copyFrom(other);
}
const byte_t * nx::HierarchicalIntegrityHeader::getBytes() const
{
return mBinaryBlob.getBytes();
}
size_t nx::HierarchicalIntegrityHeader::getSize() const
{
return mBinaryBlob.getSize();
}
void nx::HierarchicalIntegrityHeader::exportBinary()
{
throw fnd::Exception(kModuleName, "exportBinary() not implemented");
}
void nx::HierarchicalIntegrityHeader::importBinary(const byte_t * bytes, size_t len)
{
std::stringstream error_str;
// validate size for at least header
if (len < sizeof(nx::sHierarchicalIntegrityHeader))
{
throw fnd::Exception(kModuleName, "Header too small");
}
const nx::sHierarchicalIntegrityHeader* hdr = (const nx::sHierarchicalIntegrityHeader*)bytes;
// Validate Header Sig "IVFC"
if (std::string(hdr->signature, 4) != hierarchicalintegrity::kStructSig)
{
throw fnd::Exception(kModuleName, "Invalid struct magic");
}
// Validate TypeId
if (hdr->type_id.get() != nx::hierarchicalintegrity::kRomfsTypeId)
{
error_str.clear();
error_str << "Unsupported type id (" << std::hex << hdr->type_id.get() << ")";
throw fnd::Exception(kModuleName, error_str.str());
}
// Validate Layer Num
if (hdr->layer_num.get() != hierarchicalintegrity::kDefaultLayerNum+1)
{
error_str.clear();
error_str << "Invalid layer count. ";
error_str << "(actual=" << std::dec << hdr->layer_num.get() << ", expected=" << nx::hierarchicalintegrity::kDefaultLayerNum+1 << ")";
throw fnd::Exception(kModuleName, error_str.str());
}
// Get Sizes/Offsets
size_t master_hash_offset = align((sizeof(nx::sHierarchicalIntegrityHeader) + sizeof(nx::sHierarchicalIntegrityLayerInfo) * hdr->layer_num.get()), nx::hierarchicalintegrity::kHeaderAlignLen);
size_t total_size = master_hash_offset + hdr->master_hash_size.get();
// Validate total size
if (len < total_size)
{
throw fnd::Exception(kModuleName, "Header too small");
}
// copy to internal storage
mBinaryBlob.alloc(total_size);
memcpy(mBinaryBlob.getBytes(), bytes, mBinaryBlob.getSize());
// save layer info
const nx::sHierarchicalIntegrityLayerInfo* layer_info = (const nx::sHierarchicalIntegrityLayerInfo*)(mBinaryBlob.getBytes() + sizeof(nx::sHierarchicalIntegrityHeader));
for (size_t i = 0; i < hierarchicalintegrity::kDefaultLayerNum; i++)
{
mLayerInfo.addElement({layer_info[i].offset.get(), layer_info[i].size.get(), layer_info[i].block_size.get()});
}
// save hash list
const crypto::sha::sSha256Hash* hash_list = (const crypto::sha::sSha256Hash*)(mBinaryBlob.getBytes() + master_hash_offset);
for (size_t i = 0; i < hdr->master_hash_size.get()/sizeof(crypto::sha::sSha256Hash); i++)
{
mMasterHashList.addElement(hash_list[i]);
}
}
void nx::HierarchicalIntegrityHeader::clear()
{
mLayerInfo.clear();
mMasterHashList.clear();
}
const fnd::List<nx::HierarchicalIntegrityHeader::sLayer>& nx::HierarchicalIntegrityHeader::getLayerInfo() const
{
return mLayerInfo;
}
void nx::HierarchicalIntegrityHeader::setLayerInfo(const fnd::List<sLayer>& layer_info)
{
mLayerInfo = layer_info;
}
const fnd::List<crypto::sha::sSha256Hash>& nx::HierarchicalIntegrityHeader::getMasterHashList() const
{
return mMasterHashList;
}
void nx::HierarchicalIntegrityHeader::setMasterHashList(const fnd::List<crypto::sha::sSha256Hash>& master_hash_list)
{
mMasterHashList = master_hash_list;
}
bool nx::HierarchicalIntegrityHeader::isEqual(const HierarchicalIntegrityHeader & other) const
{
return (mLayerInfo == other.mLayerInfo) \
&& (mMasterHashList == other.mMasterHashList);
}
void nx::HierarchicalIntegrityHeader::copyFrom(const HierarchicalIntegrityHeader & other)
{
if (other.getSize() != 0)
{
importBinary(other.getBytes(), other.getSize());
}
else
{
mLayerInfo = other.mLayerInfo;
mMasterHashList = other.mMasterHashList;
}
}

View file

@ -0,0 +1,133 @@
#include <sstream>
#include <nx/HierarchicalSha256Header.h>
nx::HierarchicalSha256Header::HierarchicalSha256Header()
{
clear();
}
nx::HierarchicalSha256Header::HierarchicalSha256Header(const HierarchicalSha256Header & other)
{
copyFrom(other);
}
nx::HierarchicalSha256Header::HierarchicalSha256Header(const byte_t * bytes, size_t len)
{
importBinary(bytes, len);
}
bool nx::HierarchicalSha256Header::operator==(const HierarchicalSha256Header & other) const
{
return isEqual(other);
}
bool nx::HierarchicalSha256Header::operator!=(const HierarchicalSha256Header & other) const
{
return !isEqual(other);
}
void nx::HierarchicalSha256Header::operator=(const HierarchicalSha256Header & other)
{
copyFrom(other);
}
const byte_t * nx::HierarchicalSha256Header::getBytes() const
{
return mBinaryBlob.getBytes();
}
size_t nx::HierarchicalSha256Header::getSize() const
{
return mBinaryBlob.getSize();
}
void nx::HierarchicalSha256Header::exportBinary()
{
throw fnd::Exception(kModuleName, "exportBinary() not implemented");
}
void nx::HierarchicalSha256Header::importBinary(const byte_t * bytes, size_t len)
{
std::stringstream error_str;
if (len < sizeof(nx::sHierarchicalSha256Header))
{
throw fnd::Exception(kModuleName, "Header too small");
}
const nx::sHierarchicalSha256Header* hdr = (const nx::sHierarchicalSha256Header*)bytes;
if (hdr->layer_num.get() != nx::hierarchicalsha256::kDefaultLayerNum)
{
error_str.clear();
error_str << "Invalid layer count. ";
error_str << "(actual=" << std::dec << hdr->layer_num.get() << ", expected=" << nx::hierarchicalsha256::kDefaultLayerNum << ")";
throw fnd::Exception(kModuleName, error_str.str());
}
mMasterHash = hdr->master_hash;
mHashBlockSize = hdr->hash_block_size.get();
for (size_t i = 0; i < hdr->layer_num.get(); i++)
{
mLayerInfo.addElement({hdr->layer[i].offset.get(), hdr->layer[i].size.get()});
}
}
void nx::HierarchicalSha256Header::clear()
{
memset(mMasterHash.bytes, 0, sizeof(crypto::sha::sSha256Hash));
mHashBlockSize = 0;
mLayerInfo.clear();
}
const crypto::sha::sSha256Hash & nx::HierarchicalSha256Header::getMasterHash() const
{
return mMasterHash;
}
void nx::HierarchicalSha256Header::setMasterHash(const crypto::sha::sSha256Hash & master_hash)
{
mMasterHash = master_hash;
}
size_t nx::HierarchicalSha256Header::getHashBlockSize() const
{
return mHashBlockSize;
}
void nx::HierarchicalSha256Header::setHashBlockSize(size_t hash_block_size)
{
mHashBlockSize = hash_block_size;
}
const fnd::List<nx::HierarchicalSha256Header::sLayer>& nx::HierarchicalSha256Header::getLayerInfo() const
{
return mLayerInfo;
}
void nx::HierarchicalSha256Header::setLayerInfo(const fnd::List<sLayer>& layer_info)
{
mLayerInfo = layer_info;
}
bool nx::HierarchicalSha256Header::isEqual(const HierarchicalSha256Header & other) const
{
return (mMasterHash == other.mMasterHash) \
&& (mHashBlockSize == other.mHashBlockSize) \
&& (mLayerInfo == other.mLayerInfo);
}
void nx::HierarchicalSha256Header::copyFrom(const HierarchicalSha256Header & other)
{
if (other.getSize() != 0)
{
importBinary(other.getBytes(), other.getSize());
}
else
{
mMasterHash = other.mMasterHash;
mHashBlockSize = other.mHashBlockSize;
mLayerInfo = other.mLayerInfo;
}
}

View file

@ -273,7 +273,7 @@ uint64_t NcaHeader::blockNumToSize(uint32_t block_num) const
uint32_t NcaHeader::sizeToBlockNum(uint64_t real_size) const
{
return align(real_size, nca::kSectorSize) / nca::kSectorSize;
return (uint32_t)(align(real_size, nca::kSectorSize) / nca::kSectorSize);
}
bool NcaHeader::isEqual(const NcaHeader & other) const

View file

@ -50,8 +50,9 @@ byte_t nx::NcaUtils::getMasterKeyRevisionFromKeyGeneration(byte_t key_generation
void nx::NcaUtils::getNcaPartitionAesCtr(const nx::sNcaFsHeader* hdr, byte_t* ctr)
{
for (size_t i = 0; i < 16; i++)
for (size_t i = 0; i < 8; i++)
{
ctr[15-i] = hdr->base_ctr.iv[i];
ctr[7-i] = hdr->aes_ctr_upper[i];
ctr[15-i] = 0;
}
}

View file

@ -99,10 +99,10 @@ void nx::NpdmHeader::exportBinary()
strncpy(hdr->product_code, mProductCode.c_str(), npdm::kProductCodeMaxLen);
calculateOffsets();
hdr->aci.offset = mAciPos.offset;
hdr->aci.size = mAciPos.size;
hdr->acid.offset = mAcidPos.offset;
hdr->acid.size = mAcidPos.size;
hdr->aci.offset = (uint32_t)mAciPos.offset;
hdr->aci.size = (uint32_t)mAciPos.size;
hdr->acid.offset = (uint32_t)mAcidPos.offset;
hdr->acid.size = (uint32_t)mAcidPos.size;
}
void nx::NpdmHeader::importBinary(const byte_t * bytes, size_t len)

View file

@ -44,8 +44,8 @@ void nx::PfsHeader::exportBinary()
break;
}
hdr->file_num = mFileList.getSize();
hdr->name_table_size = name_table_size;
hdr->file_num = (uint32_t)mFileList.getSize();
hdr->name_table_size = (uint32_t)name_table_size;
// set file entries
if (mFsType == TYPE_PFS0)
@ -59,10 +59,10 @@ void nx::PfsHeader::exportBinary()
{
raw_files[i].data_offset = (mFileList[i].offset - pfs_header_size);
raw_files[i].size = mFileList[i].size;
raw_files[i].name_offset = raw_name_table_pos;
raw_files[i].name_offset = (uint32_t)raw_name_table_pos;
strcpy(raw_name_table + raw_name_table_pos, mFileList[i].name.c_str());
raw_name_table_pos += mFileList[i].name.length() + 1;
raw_name_table_pos += (uint32_t)(mFileList[i].name.length() + 1);
}
}
else if (mFsType == TYPE_HFS0)
@ -76,8 +76,8 @@ void nx::PfsHeader::exportBinary()
{
raw_files[i].data_offset = (mFileList[i].offset - pfs_header_size);
raw_files[i].size = mFileList[i].size;
raw_files[i].name_offset = raw_name_table_pos;
raw_files[i].hash_protected_size = mFileList[i].hash_protected_size;
raw_files[i].name_offset = (uint32_t)raw_name_table_pos;
raw_files[i].hash_protected_size = (uint32_t)mFileList[i].hash_protected_size;
raw_files[i].hash = mFileList[i].hash;
strcpy(raw_name_table + raw_name_table_pos, mFileList[i].name.c_str());

View file

@ -56,7 +56,7 @@ void nx::SystemCallHandler::exportKernelCapabilityList(fnd::List<KernelCapabilit
fnd::List<SystemCallEntry> entries;
for (size_t i = 0; i < kSyscallTotalEntryNum; i++)
{
entries[i].setSystemCallUpperBits(i);
entries[i].setSystemCallUpperBits((uint32_t)i);
entries[i].setSystemCallLowerBits(0);
}

View file

@ -162,9 +162,15 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClInclude Include="source\AesCtrWrappedIFile.h" />
<ClInclude Include="source\CnmtProcess.h" />
<ClInclude Include="source\CopiedIFile.h" />
<ClInclude Include="source\HashTreeMeta.h" />
<ClInclude Include="source\HashTreeWrappedIFile.h" />
<ClInclude Include="source\NcaProcess.h" />
<ClInclude Include="source\NpdmProcess.h" />
<ClInclude Include="source\nstool.h" />
<ClInclude Include="source\OffsetAdjustedIFile.h" />
<ClInclude Include="source\PfsProcess.h" />
<ClInclude Include="source\RomfsProcess.h" />
<ClInclude Include="source\UserSettings.h" />
@ -172,8 +178,14 @@
<ClInclude Include="source\XciProcess.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="source\AesCtrWrappedIFile.cpp" />
<ClCompile Include="source\CnmtProcess.cpp" />
<ClCompile Include="source\HashTreeMeta.cpp" />
<ClCompile Include="source\HashTreeWrappedIFile.cpp" />
<ClCompile Include="source\main.cpp" />
<ClCompile Include="source\NcaProcess.cpp" />
<ClCompile Include="source\NpdmProcess.cpp" />
<ClCompile Include="source\OffsetAdjustedIFile.cpp" />
<ClCompile Include="source\PfsProcess.cpp" />
<ClCompile Include="source\RomfsProcess.cpp" />
<ClCompile Include="source\UserSettings.cpp" />

View file

@ -39,6 +39,24 @@
<ClInclude Include="source\XciProcess.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="source\AesCtrWrappedIFile.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="source\CopiedIFile.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="source\OffsetAdjustedIFile.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="source\CnmtProcess.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="source\HashTreeMeta.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="source\HashTreeWrappedIFile.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="source\main.cpp">
@ -59,6 +77,24 @@
<ClCompile Include="source\RomfsProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="source\AesCtrWrappedIFile.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="source\NcaProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="source\OffsetAdjustedIFile.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="source\CnmtProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="source\HashTreeMeta.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="source\HashTreeWrappedIFile.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="makefile" />

View file

@ -1,42 +1,64 @@
#include "AesCtrWrappedIFile.h"
AesCtrWrappedIFile::AesCtrWrappedIFile(fnd::IFile& file, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr) :
AesCtrWrappedIFile::AesCtrWrappedIFile(fnd::IFile* file, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr) :
AesCtrWrappedIFile(file, false, key, ctr)
{
}
AesCtrWrappedIFile::AesCtrWrappedIFile(fnd::IFile* file, bool ownIfile, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr) :
mOwnIFile(ownIfile),
mFile(file),
mKey(key),
mBaseCtr(ctr)
mBaseCtr(ctr),
mFileOffset(0)
{
mScratch.alloc(kAesCtrScratchAllocSize);
mCache.alloc(kCacheSizeAllocSize);
}
AesCtrWrappedIFile::~AesCtrWrappedIFile()
{
if (mOwnIFile)
{
delete mFile;
}
}
size_t AesCtrWrappedIFile::size()
{
return mFile.size();
return mFile->size();
}
void AesCtrWrappedIFile::seek(size_t offset)
{
mFile.seek(offset);
crypto::aes::AesIncrementCounter(mBaseCtr.iv, offset>>4, mCurrentCtr.iv);
mBlockOffset = offset & 0xf;
mFileOffset = offset;
}
void AesCtrWrappedIFile::read(byte_t* out, size_t len)
{
for (size_t i = 0; i < (len / kAesCtrScratchSize); i++)
//printf("[%x] AesCtrWrappedIFile::read(offset=0x%" PRIx64 ", size=0x%" PRIx64 ")\n", this, mFileOffset, len);
size_t read_len;
size_t read_pos;
size_t cache_reads = (len / kCacheSize) + ((len % kCacheSize) != 0);
for (size_t i = 0; i < cache_reads; i++)
{
mFile.read(mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize);
crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes());
memcpy(out + (i * kAesCtrScratchSize), mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize);
read_len = MIN(len - (i * kCacheSize), kCacheSize);
read_pos = ((mFileOffset >> 4) << 4) + (i * kCacheSize);
//printf("[%x] AesCtrWrappedIFile::read() CACHE READ: readlen=%" PRIx64 "\n", this, read_len);
mFile->seek(read_pos);
mFile->read(mCache.getBytes(), kCacheSizeAllocSize);
crypto::aes::AesIncrementCounter(mBaseCtr.iv, read_pos>>4, mCurrentCtr.iv);
crypto::aes::AesCtr(mCache.getBytes(), kCacheSizeAllocSize, mKey.key, mCurrentCtr.iv, mCache.getBytes());
memcpy(out + (i * kCacheSize), mCache.getBytes() + (mFileOffset & 0xf), read_len);
}
if (len % kAesCtrScratchSize)
{
size_t read_len = len % kAesCtrScratchSize;
size_t read_pos = ((len / kAesCtrScratchSize) * kAesCtrScratchSize);
mFile.read(mScratch.getBytes() + mBlockOffset, read_len);
crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes());
memcpy(out + read_pos, mScratch.getBytes() + mBlockOffset, read_len);
}
seek(mFileOffset + len);
}
void AesCtrWrappedIFile::read(byte_t* out, size_t offset, size_t len)
@ -45,13 +67,37 @@ void AesCtrWrappedIFile::read(byte_t* out, size_t offset, size_t len)
read(out, len);
}
void AesCtrWrappedIFile::write(const byte_t* out, size_t len)
void AesCtrWrappedIFile::write(const byte_t* in, size_t len)
{
size_t write_len;
size_t write_pos;
size_t cache_writes = (len / kCacheSize) + ((len % kCacheSize) != 0);
for (size_t i = 0; i < cache_writes; i++)
{
write_len = MIN(len - (i * kCacheSize), kCacheSize);
write_pos = ((mFileOffset >> 4) << 4) + (i * kCacheSize);
//printf("[%x] AesCtrWrappedIFile::read() CACHE READ: readlen=%" PRIx64 "\n", this, read_len);
memcpy(mCache.getBytes() + (mFileOffset & 0xf), in + (i * kCacheSize), write_len);
crypto::aes::AesIncrementCounter(mBaseCtr.iv, write_pos>>4, mCurrentCtr.iv);
crypto::aes::AesCtr(mCache.getBytes(), kCacheSizeAllocSize, mKey.key, mCurrentCtr.iv, mCache.getBytes());
mFile->seek(write_pos);
mFile->write(mCache.getBytes(), kCacheSizeAllocSize);
}
seek(mFileOffset + len);
/*
for (size_t i = 0; i < (len / kAesCtrScratchSize); i++)
{
memcpy(mScratch.getBytes() + mBlockOffset, out + (i * kAesCtrScratchSize), kAesCtrScratchSize);
crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes());
mFile.write(mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize);
mFile->write(mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize);
}
if (len % kAesCtrScratchSize)
@ -60,12 +106,14 @@ void AesCtrWrappedIFile::write(const byte_t* out, size_t len)
size_t write_pos = ((len / kAesCtrScratchSize) * kAesCtrScratchSize);
memcpy(mScratch.getBytes() + mBlockOffset, out + write_pos, write_len);
crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes());
mFile.write(mScratch.getBytes() + mBlockOffset, write_len);
mFile->write(mScratch.getBytes() + mBlockOffset, write_len);
}
*/
seek(mFileOffset + len);
}
void AesCtrWrappedIFile::write(const byte_t* out, size_t offset, size_t len)
void AesCtrWrappedIFile::write(const byte_t* in, size_t offset, size_t len)
{
seek(offset);
write(out, len);
write(in, len);
}

View file

@ -5,7 +5,9 @@
class AesCtrWrappedIFile : public fnd::IFile
{
public:
AesCtrWrappedIFile(fnd::IFile& file, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr);
AesCtrWrappedIFile(fnd::IFile* file, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr);
AesCtrWrappedIFile(fnd::IFile* file, bool ownIfile, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr);
~AesCtrWrappedIFile();
size_t size();
void seek(size_t offset);
@ -15,13 +17,16 @@ public:
void write(const byte_t* out, size_t offset, size_t len);
private:
const std::string kModuleName = "AesCtrWrappedIFile";
static const size_t kAesCtrScratchSize = 0x1000000;
static const size_t kAesCtrScratchAllocSize = kAesCtrScratchSize + crypto::aes::kAesBlockSize;
static const size_t kCacheSize = 0x10000;
static const size_t kCacheSizeAllocSize = kCacheSize + crypto::aes::kAesBlockSize;
fnd::IFile& mFile;
bool mOwnIFile;
fnd::IFile* mFile;
crypto::aes::sAes128Key mKey;
crypto::aes::sAesIvCtr mBaseCtr, mCurrentCtr;
size_t mBlockOffset;
size_t mFileOffset;
fnd::MemoryBlob mScratch;
fnd::MemoryBlob mCache;
void internalSeek();
};

View file

@ -0,0 +1,198 @@
#include <fnd/SimpleTextOutput.h>
#include "OffsetAdjustedIFile.h"
#include "CnmtProcess.h"
const std::string kContentTypeStr[7] =
{
"Meta",
"Program",
"Data",
"Control",
"HtmlDocument",
"LegalInformation",
"DeltaFragment"
};
const std::string kContentMetaTypeStr[2][0x80] =
{
{
"",
"SystemProgram",
"SystemData",
"SystemUpdate",
"BootImagePackage",
"BootImagePackageSafe"
},
{
"Application",
"Patch",
"AddOnContent",
"Delta"
}
};
const std::string kUpdateTypeStr[3] =
{
"ApplyAsDelta",
"Overwrite",
"Create"
};
const std::string kContentMetaAttrStr[3] =
{
"IncludesExFatDriver",
"Rebootless"
};
std::string kUnknownStr = "Unknown";
inline const char* getBoolStr(bool isTrue)
{
return isTrue? "TRUE" : "FALSE";
}
inline const char* getContentTypeStr(byte_t i)
{
return i < 7 ? kContentTypeStr[i].c_str() : kUnknownStr.c_str();
}
inline const char* getContentMetaTypeStr(byte_t i)
{
return (i < 0x80) ? kContentMetaTypeStr[0][i].c_str() : kContentMetaTypeStr[1][i - 0x80].c_str();
}
void CnmtProcess::displayCmnt()
{
#define _SPLIT_VER(ver) ( (ver>>26) & 0x3f), ( (ver>>20) & 0x3f), ( (ver>>16) & 0xf), (ver & 0xffff)
#define _HEXDUMP_U(var, len) do { for (size_t a__a__A = 0; a__a__A < len; a__a__A++) printf("%02X", var[a__a__A]); } while(0)
#define _HEXDUMP_L(var, len) do { for (size_t a__a__A = 0; a__a__A < len; a__a__A++) printf("%02x", var[a__a__A]); } while(0)
printf("[ContentMeta]\n");
printf(" TitleId: 0x%016" PRIx64 "\n", (uint64_t)mCnmt.getTitleId());
printf(" Version: v%" PRId32 " (%d.%d.%d.%d)\n", (uint32_t)mCnmt.getTitleVersion(), _SPLIT_VER(mCnmt.getTitleVersion()));
printf(" Type: %s (%d)\n", getContentMetaTypeStr(mCnmt.getType()), mCnmt.getType());
printf(" Attributes: %x\n", mCnmt.getAttributes());
printf(" IncludesExFatDriver: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_INCLUDES_EX_FAT_DRIVER)));
printf(" Rebootless: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_REBOOTLESS)));
printf(" RequiredDownloadSystemVersion: v%" PRId32 " (%d.%d.%d.%d)\n", (uint32_t)mCnmt.getRequiredDownloadSystemVersion(), _SPLIT_VER(mCnmt.getRequiredDownloadSystemVersion()));
switch(mCnmt.getType())
{
case (nx::cnmt::METATYPE_APPLICATION):
printf(" ApplicationExtendedHeader:\n");
printf(" RequiredSystemVersion: v%" PRId32 " (%d.%d.%d.%d)\n", (uint32_t)mCnmt.getApplicationMetaExtendedHeader().required_system_version, _SPLIT_VER(mCnmt.getApplicationMetaExtendedHeader().required_system_version));
printf(" PatchId: 0x%016" PRIx64 "\n", (uint64_t)mCnmt.getApplicationMetaExtendedHeader().patch_id);
break;
case (nx::cnmt::METATYPE_PATCH):
printf(" PatchMetaExtendedHeader:\n");
printf(" RequiredSystemVersion: v%" PRId32 " (%d.%d.%d.%d))\n", (uint32_t)mCnmt.getPatchMetaExtendedHeader().required_system_version, _SPLIT_VER(mCnmt.getPatchMetaExtendedHeader().required_system_version));
printf(" ApplicationId: 0x%016" PRIx64 "\n", (uint64_t)mCnmt.getPatchMetaExtendedHeader().application_id);
break;
case (nx::cnmt::METATYPE_ADD_ON_CONTENT):
printf(" AddOnContentMetaExtendedHeader:\n");
printf(" RequiredSystemVersion: v%" PRId32 " (%d.%d.%d.%d)\n", (uint32_t)mCnmt.getAddOnContentMetaExtendedHeader().required_system_version, _SPLIT_VER(mCnmt.getAddOnContentMetaExtendedHeader().required_system_version));
printf(" ApplicationId: 0x%016" PRIx64 "\n", (uint64_t)mCnmt.getAddOnContentMetaExtendedHeader().application_id);
break;
case (nx::cnmt::METATYPE_DELTA):
printf(" DeltaMetaExtendedHeader:\n");
printf(" ApplicationId: 0x%016" PRIx64 "\n", (uint64_t)mCnmt.getDeltaMetaExtendedHeader().application_id);
break;
default:
break;
}
if (mCnmt.getContentInfo().getSize() > 0)
{
printf(" ContentInfo:\n");
for (size_t i = 0; i < mCnmt.getContentInfo().getSize(); i++)
{
const nx::ContentMetaBinary::ContentInfo& info = mCnmt.getContentInfo()[i];
printf(" %d\n", (int)i);
printf(" Type: %s (%d)\n", getContentTypeStr(info.type), info.type);
printf(" Id: ");
_HEXDUMP_L(info.nca_id, nx::cnmt::kContentIdLen);
printf("\n");
printf(" Size: 0x%" PRIx64 "\n", (uint64_t)info.size);
printf(" Hash: ");
fnd::SimpleTextOutput::hexDump(info.hash.bytes, sizeof(info.hash));
_HEXDUMP_L(info.hash.bytes, sizeof(info.hash));
printf("\n");
}
}
if (mCnmt.getContentMetaInfo().getSize() > 0)
{
printf(" ContentMetaInfo:\n");
for (size_t i = 0; i < mCnmt.getContentMetaInfo().getSize(); i++)
{
const nx::ContentMetaBinary::ContentMetaInfo& info = mCnmt.getContentMetaInfo()[i];
printf(" %d\n", (int)i);
printf(" Id: 0x%016" PRIx64 "\n", (uint64_t)info.id);
printf(" Version: v%" PRId32 " (%d.%d.%d.%d)\n", (uint32_t)info.version, _SPLIT_VER(info.version));
printf(" Type: %s (%d)\n", getContentMetaTypeStr(info.type), info.type);
printf(" Attributes: %x\n", mCnmt.getAttributes());
printf(" IncludesExFatDriver: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_INCLUDES_EX_FAT_DRIVER)));
printf(" Rebootless: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_REBOOTLESS)));
}
}
printf(" Digest: ");
_HEXDUMP_L(mCnmt.getDigest().data, nx::cnmt::kDigestLen);
printf("\n");
#undef _HEXDUMP_L
#undef _HEXDUMP_U
#undef _SPLIT_VER
}
CnmtProcess::CnmtProcess() :
mReader(nullptr),
mCliOutputType(OUTPUT_NORMAL),
mVerify(false)
{
}
CnmtProcess::~CnmtProcess()
{
if (mReader != nullptr)
{
delete mReader;
}
}
void CnmtProcess::process()
{
fnd::MemoryBlob scratch;
if (mReader == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
}
scratch.alloc(mReader->size());
mReader->read(scratch.getBytes(), 0, scratch.getSize());
mCnmt.importBinary(scratch.getBytes(), scratch.getSize());
if (mCliOutputType >= OUTPUT_NORMAL)
{
displayCmnt();
}
}
void CnmtProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size)
{
mReader = new OffsetAdjustedIFile(file, offset, size);
}
void CnmtProcess::setCliOutputMode(CliOutputType type)
{
mCliOutputType = type;
}
void CnmtProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
const nx::ContentMetaBinary& CnmtProcess::getContentMetaBinary() const
{
return mCnmt;
}

View file

@ -0,0 +1,33 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/IFile.h>
#include <nx/ContentMetaBinary.h>
#include "nstool.h"
class CnmtProcess
{
public:
CnmtProcess();
~CnmtProcess();
void process();
void setInputFile(fnd::IFile* file, size_t offset, size_t size);
void setCliOutputMode(CliOutputType type);
void setVerifyMode(bool verify);
const nx::ContentMetaBinary& getContentMetaBinary() const;
private:
const std::string kModuleName = "CnmtProcess";
fnd::IFile* mReader;
CliOutputType mCliOutputType;
bool mVerify;
nx::ContentMetaBinary mCnmt;
void displayCmnt();
};

View file

@ -0,0 +1,17 @@
#pragma once
#include <fnd/IFile.h>
class CopiedIFile : public fnd::IFile
{
public:
inline CopiedIFile(fnd::IFile* file) : mFile(file) {}
inline size_t size() { return mFile->size(); }
inline void seek(size_t offset) { mFile->seek(offset); }
inline void read(byte_t* out, size_t len) { mFile->read(out, len); }
inline void read(byte_t* out, size_t offset, size_t len) { mFile->read(out, offset, len); }
inline void write(const byte_t* out, size_t len) { mFile->write(out, len); }
inline void write(const byte_t* out, size_t offset, size_t len) { mFile->write(out, offset, len); }
private:
fnd::IFile* mFile;
};

View file

@ -0,0 +1,141 @@
#include "HashTreeMeta.h"
HashTreeMeta::HashTreeMeta() :
mLayerInfo(),
mDataLayer(),
mMasterHashList(),
mDoAlignHashToBlock(false)
{
}
HashTreeMeta::HashTreeMeta(const nx::HierarchicalIntegrityHeader& hdr) :
mLayerInfo(),
mDataLayer(),
mMasterHashList(),
mDoAlignHashToBlock(false)
{
importHierarchicalIntergityHeader(hdr);
}
HashTreeMeta::HashTreeMeta(const nx::HierarchicalSha256Header& hdr) :
mLayerInfo(),
mDataLayer(),
mMasterHashList(),
mDoAlignHashToBlock(false)
{
importHierarchicalSha256Header(hdr);
}
bool HashTreeMeta::operator==(const HashTreeMeta& other) const
{
return isEqual(other);
}
bool HashTreeMeta::operator!=(const HashTreeMeta& other) const
{
return !isEqual(other);
}
void HashTreeMeta::operator=(const HashTreeMeta& other)
{
copyFrom(other);
}
void HashTreeMeta::importHierarchicalIntergityHeader(const nx::HierarchicalIntegrityHeader& hdr)
{
mDoAlignHashToBlock = true;
for (size_t i = 0; i < hdr.getLayerInfo().getSize(); i++)
{
sLayer layer;
layer.offset = hdr.getLayerInfo()[i].offset;
layer.size = hdr.getLayerInfo()[i].size;
layer.block_size = _BIT(hdr.getLayerInfo()[i].block_size);
if (i+1 == hdr.getLayerInfo().getSize())
{
mDataLayer = layer;
}
else
{
mLayerInfo.addElement(layer);
}
}
mMasterHashList = hdr.getMasterHashList();
}
void HashTreeMeta::importHierarchicalSha256Header(const nx::HierarchicalSha256Header& hdr)
{
mDoAlignHashToBlock = false;
for (size_t i = 0; i < hdr.getLayerInfo().getSize(); i++)
{
sLayer layer;
layer.offset = hdr.getLayerInfo()[i].offset;
layer.size = hdr.getLayerInfo()[i].size;
layer.block_size = hdr.getHashBlockSize();
if (i+1 == hdr.getLayerInfo().getSize())
{
mDataLayer = layer;
}
else
{
mLayerInfo.addElement(layer);
}
}
mMasterHashList.addElement(hdr.getMasterHash());
}
const fnd::List<HashTreeMeta::sLayer>& HashTreeMeta::getHashLayerInfo() const
{
return mLayerInfo;
}
void HashTreeMeta::setHashLayerInfo(const fnd::List<sLayer>& layer_info)
{
mLayerInfo = layer_info;
}
const HashTreeMeta::sLayer& HashTreeMeta::getDataLayer() const
{
return mDataLayer;
}
void HashTreeMeta::setDataLayer(const sLayer& data_info)
{
mDataLayer = data_info;
}
const fnd::List<crypto::sha::sSha256Hash>& HashTreeMeta::getMasterHashList() const
{
return mMasterHashList;
}
void HashTreeMeta::setMasterHashList(const fnd::List<crypto::sha::sSha256Hash>& master_hash_list)
{
mMasterHashList = master_hash_list;
}
bool HashTreeMeta::getAlignHashToBlock() const
{
return mDoAlignHashToBlock;
}
void HashTreeMeta::setAlignHashToBlock(bool doAlign)
{
mDoAlignHashToBlock = doAlign;
}
bool HashTreeMeta::isEqual(const HashTreeMeta& other) const
{
return (mLayerInfo == other.mLayerInfo) \
&& (mDataLayer == other.mDataLayer) \
&& (mMasterHashList == other.mMasterHashList) \
&& (mDoAlignHashToBlock == other.mDoAlignHashToBlock);
}
void HashTreeMeta::copyFrom(const HashTreeMeta& other)
{
mLayerInfo = other.mLayerInfo;
mDataLayer = other.mDataLayer;
mMasterHashList = other.mMasterHashList;
mDoAlignHashToBlock = other.mDoAlignHashToBlock;
}

View file

@ -0,0 +1,64 @@
#pragma once
#include <nx/HierarchicalIntegrityHeader.h>
#include <nx/HierarchicalSha256Header.h>
class HashTreeMeta
{
public:
struct sLayer
{
size_t offset;
size_t size;
size_t block_size;
void operator=(const sLayer& other)
{
offset = other.offset;
size = other.size;
block_size = other.block_size;
}
bool operator==(const sLayer& other) const
{
return (offset == other.offset && size == other.size && block_size == other.block_size);
}
bool operator!=(const sLayer& other) const
{
return !(*this == other);
}
};
HashTreeMeta();
HashTreeMeta(const nx::HierarchicalIntegrityHeader& hdr);
HashTreeMeta(const nx::HierarchicalSha256Header& hdr);
bool operator==(const HashTreeMeta& other) const;
bool operator!=(const HashTreeMeta& other) const;
void operator=(const HashTreeMeta& other);
void importHierarchicalIntergityHeader(const nx::HierarchicalIntegrityHeader& hdr);
void importHierarchicalSha256Header(const nx::HierarchicalSha256Header& hdr);
const fnd::List<sLayer>& getHashLayerInfo() const;
void setHashLayerInfo(const fnd::List<sLayer>& layer_info);
const sLayer& getDataLayer() const;
void setDataLayer(const sLayer& data_info);
const fnd::List<crypto::sha::sSha256Hash>& getMasterHashList() const;
void setMasterHashList(const fnd::List<crypto::sha::sSha256Hash>& master_hash_list);
bool getAlignHashToBlock() const;
void setAlignHashToBlock(bool doAlign);
private:
// data
fnd::List<sLayer> mLayerInfo;
sLayer mDataLayer;
fnd::List<crypto::sha::sSha256Hash> mMasterHashList;
bool mDoAlignHashToBlock;
bool isEqual(const HashTreeMeta& other) const;
void copyFrom(const HashTreeMeta& other);
};

View file

@ -0,0 +1,215 @@
#include "HashTreeWrappedIFile.h"
#include "CopiedIFile.h"
#include "OffsetAdjustedIFile.h"
HashTreeWrappedIFile::HashTreeWrappedIFile(fnd::IFile* file, const HashTreeMeta& hdr) :
mOwnIFile(true),
mFile(file),
mData(nullptr),
mDataHashLayer(),
mAlignHashCalcToBlock(false)
{
initialiseDataLayer(hdr);
}
HashTreeWrappedIFile::HashTreeWrappedIFile(fnd::IFile* file, bool ownIFile, const HashTreeMeta& hdr) :
mOwnIFile(ownIFile),
mFile(file),
mData(nullptr),
mDataHashLayer(),
mAlignHashCalcToBlock(false)
{
initialiseDataLayer(hdr);
}
HashTreeWrappedIFile::~HashTreeWrappedIFile()
{
if (mOwnIFile)
{
delete mFile;
}
delete mData;
}
size_t HashTreeWrappedIFile::size()
{
return mData->size();
}
void HashTreeWrappedIFile::seek(size_t offset)
{
mDataOffset = offset;
}
void HashTreeWrappedIFile::read(byte_t* out, size_t len)
{
size_t offset_in_start_block = getOffsetInBlock(mDataOffset);
size_t offset_in_end_block = getOffsetInBlock(offset_in_start_block + len);
size_t start_block = getOffsetBlock(mDataOffset);
size_t block_num = align(offset_in_start_block + len, mDataBlockSize) / mDataBlockSize;
size_t partial_last_block_num = block_num % mCacheBlockNum;
bool has_partial_block_num = partial_last_block_num > 0;
size_t read_iterations = (block_num / mCacheBlockNum) + has_partial_block_num;
size_t block_read_len;
size_t block_export_offset;
size_t block_export_size;
size_t block_export_pos = 0;
for (size_t i = 0; i < read_iterations; i++)
{
// how many blocks to read from source file
block_read_len = (i+1 == read_iterations && has_partial_block_num) ? partial_last_block_num : mCacheBlockNum;
// offset in this current read to copy from
block_export_offset = (i == 0) ? offset_in_start_block : 0;
// size of current read to copy
block_export_size = (block_read_len * mDataBlockSize) - block_export_offset;
// if last read, reduce the export size by one block less offset_in_end_block
if (i+1 == read_iterations)
{
block_export_size -= (mDataBlockSize - offset_in_end_block);
}
// read the blocks
readData(start_block + (i * mCacheBlockNum), block_read_len);
// export the section of data that is relevant
memcpy(out + block_export_pos, mCache.getBytes() + block_export_offset, block_export_size);
// update export position
block_export_pos += block_export_size;
}
// update offset
seek(mDataOffset + len);
}
void HashTreeWrappedIFile::read(byte_t* out, size_t offset, size_t len)
{
seek(offset);
read(out, len);
}
void HashTreeWrappedIFile::write(const byte_t* out, size_t len)
{
throw fnd::Exception(kModuleName, "write() not supported");
}
void HashTreeWrappedIFile::write(const byte_t* out, size_t offset, size_t len)
{
throw fnd::Exception(kModuleName, "write() not supported");
}
void HashTreeWrappedIFile::initialiseDataLayer(const HashTreeMeta& hdr)
{
crypto::sha::sSha256Hash hash;
fnd::MemoryBlob cur, prev;
mAlignHashCalcToBlock = hdr.getAlignHashToBlock();
// copy master hash into prev
prev.alloc(sizeof(crypto::sha::sSha256Hash) * hdr.getMasterHashList().getSize());
for (size_t i = 0; i < hdr.getMasterHashList().getSize(); i++)
{
((crypto::sha::sSha256Hash*)prev.getBytes())[i] = hdr.getMasterHashList()[i];
}
// check each hash layer
for (size_t i = 0; i < hdr.getHashLayerInfo().getSize(); i++)
{
// get block size
const HashTreeMeta::sLayer& layer = hdr.getHashLayerInfo()[i];
// allocate layer
cur.alloc(align(layer.size, layer.block_size));
// read layer
mFile->read(cur.getBytes(), layer.offset, layer.size);
// validate blocks
size_t validate_size;
for (size_t j = 0; j < cur.getSize() / layer.block_size; j++)
{
validate_size = mAlignHashCalcToBlock? layer.block_size : MIN(layer.size - (j * layer.block_size), layer.block_size);
crypto::sha::Sha256(cur.getBytes() + (j * layer.block_size), validate_size, hash.bytes);
if (hash.compare(prev.getBytes() + j * sizeof(crypto::sha::sSha256Hash)) == false)
{
mErrorSs << "Hash tree layer verification failed (layer: " << i << ", block: " << j << ")";
throw fnd::Exception(kModuleName, mErrorSs.str());
}
}
// set prev to cur
prev = cur;
}
// save last layer as hash table for data layer
crypto::sha::sSha256Hash* hash_list = (crypto::sha::sSha256Hash*)prev.getBytes();
for (size_t i = 0; i < prev.getSize() / sizeof(crypto::sha::sSha256Hash); i++)
{
mDataHashLayer.addElement(hash_list[i]);
}
// generate reader for data layer
mData = new OffsetAdjustedIFile(mFile, false, hdr.getDataLayer().offset, hdr.getDataLayer().size);
mDataOffset = 0;
mDataBlockSize = hdr.getDataLayer().block_size;
// allocate scratchpad
//mScratch.alloc(mDataBlockSize * 0x10);
size_t cache_size = align(kDefaultCacheSize, mDataBlockSize);
mCacheBlockNum = cache_size / mDataBlockSize;
//printf("Block Size: 0x%" PRIx64 "\n", mDataBlockSize);
//printf("Cache size: 0x%" PRIx64 ", (block_num: %" PRId64 ")\n", cache_size, mCacheBlockNum);
mCache.alloc(cache_size);
}
void HashTreeWrappedIFile::readData(size_t block_offset, size_t block_num)
{
mData->seek(block_offset * mDataBlockSize);
crypto::sha::sSha256Hash hash;
// determine read size
size_t read_len = 0;
if ((block_offset + block_num) == getBlockNum(mData->size()))
{
read_len = (block_num-1) * mDataBlockSize + getRemanderBlockReadSize(mData->size());
memset(mCache.getBytes(), 0, block_num * mDataBlockSize);
}
else if ((block_offset + block_num) < getBlockNum(mData->size()))
{
read_len = block_num * mDataBlockSize;
}
else
{
throw fnd::Exception(kModuleName, "Out of bounds file read");
}
// read
mData->read(mCache.getBytes(), block_offset * mDataBlockSize, read_len);
if (block_num > mCacheBlockNum)
{
throw fnd::Exception(kModuleName, "Read excessive of cache size");
}
//printf("readlen=0x%" PRIx64 "\n", read_len);
// validate blocks
size_t validate_size;
for (size_t i = 0; i < block_num; i++)
{
validate_size = mAlignHashCalcToBlock? mDataBlockSize : MIN(read_len - (i * mDataBlockSize), mDataBlockSize);
crypto::sha::Sha256(mCache.getBytes() + (i * mDataBlockSize), validate_size, hash.bytes);
if (hash != mDataHashLayer[block_offset + i])
{
mErrorSs << "Hash tree layer verification failed (layer: data, block: " << (block_offset + i) << " ( " << i << "/" << block_num-1 << " ), offset: 0x" << std::hex << ((block_offset + i) * mDataBlockSize) << ", size: 0x" << std::hex << validate_size <<")";
throw fnd::Exception(kModuleName, mErrorSs.str());
}
}
}

View file

@ -0,0 +1,47 @@
#pragma once
#include <sstream>
#include <fnd/IFile.h>
#include <fnd/MemoryBlob.h>
#include <crypto/sha.h>
#include "HashTreeMeta.h"
class HashTreeWrappedIFile : public fnd::IFile
{
public:
HashTreeWrappedIFile(fnd::IFile* file, const HashTreeMeta& hdr);
HashTreeWrappedIFile(fnd::IFile* file, bool ownIFile, const HashTreeMeta& hdr);
~HashTreeWrappedIFile();
size_t size();
void seek(size_t offset);
void read(byte_t* out, size_t len);
void read(byte_t* out, size_t offset, size_t len);
void write(const byte_t* out, size_t len);
void write(const byte_t* out, size_t offset, size_t len);
private:
const std::string kModuleName = "HashTreeWrappedIFile";
static const size_t kDefaultCacheSize = 0x10000;
std::stringstream mErrorSs;
bool mOwnIFile;
fnd::IFile* mFile;
// data file
fnd::IFile* mData;
size_t mDataOffset;
size_t mDataBlockSize;
fnd::List<crypto::sha::sSha256Hash> mDataHashLayer;
bool mAlignHashCalcToBlock;
fnd::MemoryBlob mCache;
size_t mCacheBlockNum;
inline size_t getOffsetBlock(size_t offset) const { return offset / mDataBlockSize; }
inline size_t getOffsetInBlock(size_t offset) const { return offset % mDataBlockSize; }
inline size_t getRemanderBlockReadSize(size_t total_size) const { return total_size % mDataBlockSize; }
inline size_t getBlockNum(size_t total_size) const { return (total_size / mDataBlockSize) + (getRemanderBlockReadSize(total_size) > 0); }
void initialiseDataLayer(const HashTreeMeta& hdr);
void readData(size_t block_offset, size_t block_num);
};

View file

@ -1,16 +0,0 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/SimpleFile.h>
#include <nx/NcaHeader.h>
#include "nstool.h"
class NcaPartitionProcess
{
public:
NcaPartitionProcess();
private:
const std::string kModuleName = "NcaPartitionProcess";
};

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,8 @@
#include <fnd/types.h>
#include <fnd/SimpleFile.h>
#include <nx/NcaHeader.h>
#include "HashTreeMeta.h"
#include "nstool.h"
@ -15,8 +17,7 @@ public:
void process();
// generic
void setInputFile(fnd::IFile* reader);
void setInputFileOffset(size_t offset);
void setInputFile(fnd::IFile* file, size_t offset, size_t size);
void setKeyset(const sKeyset* keyset);
void setCliOutputMode(CliOutputType type);
void setVerifyMode(bool verify);
@ -30,10 +31,10 @@ public:
private:
const std::string kModuleName = "NcaProcess";
const std::string kNpdmExefsPath = "main.npdm";
// user options
fnd::IFile* mReader;
size_t mOffset;
const sKeyset* mKeyset;
CliOutputType mCliOutputType;
bool mVerify;
@ -54,15 +55,58 @@ private:
// crypto
struct sKeys
{
struct sKeyAreaKey
{
byte_t index;
bool decrypted;
crypto::aes::sAes128Key enc;
crypto::aes::sAes128Key dec;
void operator=(const sKeyAreaKey& other)
{
index = other.index;
decrypted = other.decrypted;
enc = other.enc;
dec = other.dec;
}
bool operator==(const sKeyAreaKey& other) const
{
return (index == other.index) \
&& (decrypted == other.decrypted) \
&& (enc == other.enc) \
&& (dec == other.dec);
}
bool operator!=(const sKeyAreaKey& other) const
{
return !(*this == other);
}
};
fnd::List<sKeyAreaKey> keak_list;
sOptional<crypto::aes::sAes128Key> aes_ctr;
sOptional<crypto::aes::sAesXts128Key> aes_xts;
} mBodyKeys;
struct sPartitionInfo
{
fnd::IFile* reader;
std::string fail_reason;
size_t offset;
size_t size;
void displayHeader();
// meta data
nx::nca::FormatType format_type;
nx::nca::HashType hash_type;
nx::nca::EncryptionType enc_type;
HashTreeMeta hash_tree_meta;
crypto::aes::sAesIvCtr aes_ctr;
} mPartitions[nx::nca::kPartitionNum];
void generateNcaBodyEncryptionKeys();
void generatePartitionConfiguration();
void validateNcaSignatures();
void displayHeader();
void processPartitions();
};

View file

@ -1,6 +1,5 @@
#include "OffsetAdjustedIFile.h"
#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" };
@ -464,7 +463,7 @@ void NpdmProcess::displayAciHdr(const nx::AciHeader& aci)
else if (aci.getAciType() == nx::AciBinary::TYPE_ACID)
{
printf(" ACID Size: %" PRIx64 "\n", aci.getAcidSize());
printf(" ACID Size: %" PRIx64 "\n", (uint64_t)aci.getAcidSize());
printf(" Flags: \n");
printf(" Production: %s\n", aci.isProduction() ? "TRUE" : "FALSE");
printf(" UnqualifiedApproval: %s\n", aci.isUnqualifiedApproval() ? "TRUE" : "FALSE");
@ -619,13 +618,20 @@ void NpdmProcess::displayKernelCap(const nx::KcBinary& kern)
NpdmProcess::NpdmProcess() :
mReader(nullptr),
mOffset(0),
mKeyset(nullptr),
mCliOutputType(OUTPUT_NORMAL),
mVerify(false)
{
}
NpdmProcess::~NpdmProcess()
{
if (mReader != nullptr)
{
delete mReader;
}
}
void NpdmProcess::process()
{
fnd::MemoryBlob scratch;
@ -665,14 +671,9 @@ void NpdmProcess::process()
}
}
void NpdmProcess::setInputFile(fnd::IFile& reader)
void NpdmProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size)
{
mReader = &reader;
}
void NpdmProcess::setInputFileOffset(size_t offset)
{
mOffset = offset;
mReader = new OffsetAdjustedIFile(file, offset, size);
}
void NpdmProcess::setKeyset(const sKeyset* keyset)
@ -689,3 +690,8 @@ void NpdmProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
const nx::NpdmBinary& NpdmProcess::getNpdmBinary() const
{
return mNpdm;
}

View file

@ -10,20 +10,21 @@ class NpdmProcess
{
public:
NpdmProcess();
~NpdmProcess();
void process();
void setInputFile(fnd::IFile& reader);
void setInputFileOffset(size_t offset);
void setInputFile(fnd::IFile* file, size_t offset, size_t size);
void setKeyset(const sKeyset* keyset);
void setCliOutputMode(CliOutputType type);
void setVerifyMode(bool verify);
const nx::NpdmBinary& getNpdmBinary() const;
private:
const std::string kModuleName = "NpdmProcess";
fnd::IFile* mReader;
size_t mOffset;
const sKeyset* mKeyset;
CliOutputType mCliOutputType;
bool mVerify;

View file

@ -1,6 +1,7 @@
#include "OffsetAdjustedIFile.h"
OffsetAdjustedIFile::OffsetAdjustedIFile(fnd::IFile& file, size_t offset, size_t size) :
OffsetAdjustedIFile::OffsetAdjustedIFile(fnd::IFile* file, size_t offset, size_t size) :
mOwnIFile(false),
mFile(file),
mBaseOffset(offset),
mCurrentOffset(0),
@ -9,6 +10,24 @@ OffsetAdjustedIFile::OffsetAdjustedIFile(fnd::IFile& file, size_t offset, size_t
}
OffsetAdjustedIFile::OffsetAdjustedIFile(fnd::IFile* file, bool ownIFile, size_t offset, size_t size) :
mOwnIFile(ownIFile),
mFile(file),
mBaseOffset(offset),
mCurrentOffset(0),
mSize(size)
{
}
OffsetAdjustedIFile::~OffsetAdjustedIFile()
{
if (mOwnIFile)
{
delete mFile;
}
}
size_t OffsetAdjustedIFile::size()
{
return mSize;
@ -16,14 +35,15 @@ size_t OffsetAdjustedIFile::size()
void OffsetAdjustedIFile::seek(size_t offset)
{
mCurrentOffset = offset;
mFile.seek(offset + mBaseOffset);
mCurrentOffset = MIN(offset, mSize);
}
void OffsetAdjustedIFile::read(byte_t* out, size_t len)
{
seek(mCurrentOffset);
mFile.read(out, len);
// assert proper position in file
mFile->seek(mCurrentOffset + mBaseOffset);
mFile->read(out, len);
seek(mCurrentOffset + len);
}
void OffsetAdjustedIFile::read(byte_t* out, size_t offset, size_t len)
@ -34,8 +54,10 @@ void OffsetAdjustedIFile::read(byte_t* out, size_t offset, size_t len)
void OffsetAdjustedIFile::write(const byte_t* out, size_t len)
{
seek(mCurrentOffset);
mFile.write(out, len);
// assert proper position in file
mFile->seek(mCurrentOffset + mBaseOffset);
mFile->write(out, len);
seek(mCurrentOffset + len);
}
void OffsetAdjustedIFile::write(const byte_t* out, size_t offset, size_t len)

View file

@ -3,7 +3,9 @@
class OffsetAdjustedIFile : public fnd::IFile
{
public:
OffsetAdjustedIFile(fnd::IFile& file, size_t offset, size_t size);
OffsetAdjustedIFile(fnd::IFile* file, size_t offset, size_t size);
OffsetAdjustedIFile(fnd::IFile* file, bool ownIFile, size_t offset, size_t size);
~OffsetAdjustedIFile();
size_t size();
void seek(size_t offset);
@ -12,7 +14,8 @@ public:
void write(const byte_t* out, size_t len);
void write(const byte_t* out, size_t offset, size_t len);
private:
fnd::IFile& mFile;
bool mOwnIFile;
fnd::IFile* mFile;
size_t mBaseOffset, mCurrentOffset;
size_t mSize;
};

View file

@ -1,104 +1,10 @@
#include "PfsProcess.h"
#include <fnd/SimpleFile.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();
std::string file_path;
for (size_t i = 0; i < file.getSize(); i++)
{
file_path.clear();
fnd::io::appendToPath(file_path, mExtractPath);
fnd::io::appendToPath(file_path, file[i].name);
outFile.open(file_path, 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();
}
}
#include "OffsetAdjustedIFile.h"
#include "PfsProcess.h"
PfsProcess::PfsProcess() :
mReader(nullptr),
mOffset(0),
mKeyset(nullptr),
mCliOutputType(OUTPUT_NORMAL),
mVerify(false),
mExtractPath(),
@ -107,7 +13,14 @@ PfsProcess::PfsProcess() :
mListFs(false),
mPfs()
{
}
PfsProcess::~PfsProcess()
{
if (mReader != nullptr)
{
delete mReader;
}
}
void PfsProcess::process()
@ -121,12 +34,16 @@ void PfsProcess::process()
// open minimum header to get full header size
scratch.alloc(sizeof(nx::sPfsHeader));
mReader->read(scratch.getBytes(), mOffset, scratch.getSize());
mReader->read(scratch.getBytes(), 0, scratch.getSize());
if (validateHeaderMagic(((nx::sPfsHeader*)scratch.getBytes())) == false)
{
throw fnd::Exception(kModuleName, "Corrupt Header");
}
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());
mReader->read(scratch.getBytes(), 0, scratch.getSize());
mPfs.importBinary(scratch.getBytes(), scratch.getSize());
if (mCliOutputType >= OUTPUT_NORMAL)
@ -139,19 +56,9 @@ void PfsProcess::process()
extractFs();
}
void PfsProcess::setInputFile(fnd::IFile& reader)
void PfsProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size)
{
mReader = &reader;
}
void PfsProcess::setInputFileOffset(size_t offset)
{
mOffset = offset;
}
void PfsProcess::setKeyset(const sKeyset* keyset)
{
mKeyset = keyset;
mReader = new OffsetAdjustedIFile(file, offset, size);
}
void PfsProcess::setCliOutputMode(CliOutputType type)
@ -184,3 +91,98 @@ const nx::PfsHeader& PfsProcess::getPfsHeader() const
{
return mPfs;
}
void PfsProcess::displayHeader()
{
printf("[PartitionFS]\n");
printf(" Type: %s\n", mPfs.getFsType() == mPfs.TYPE_PFS0? "PFS0" : "HFS0");
printf(" FileNum: %" PRId64 "\n", (uint64_t)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", (uint64_t)mPfs.getFileList()[i].offset, (uint64_t)mPfs.getFileList()[i].size);
else
printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ", hash_protected_size=0x%" PRIx64 ")\n", (uint64_t)mPfs.getFileList()[i].offset, (uint64_t)mPfs.getFileList()[i].size, (uint64_t)mPfs.getFileList()[i].hash_protected_size);
}
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();
}
bool PfsProcess::validateHeaderMagic(const nx::sPfsHeader* hdr)
{
return std::string(hdr->signature, 4) == nx::pfs::kPfsSig || std::string(hdr->signature, 4) == nx::pfs::kHashedPfsSig;
}
void PfsProcess::validateHfs()
{
crypto::sha::sSha256Hash hash;
const fnd::List<nx::PfsHeader::sFile>& file = mPfs.getFileList();
for (size_t i = 0; i < file.getSize(); i++)
{
mCache.alloc(file[i].hash_protected_size);
mReader->read(mCache.getBytes(), file[i].offset, file[i].hash_protected_size);
crypto::sha::Sha256(mCache.getBytes(), mCache.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 only when extractDir is invoked
mCache.alloc(kCacheSize);
// make extract dir
fnd::io::makeDirectory(mExtractPath);
fnd::SimpleFile outFile;
const fnd::List<nx::PfsHeader::sFile>& file = mPfs.getFileList();
std::string file_path;
for (size_t i = 0; i < file.getSize(); i++)
{
file_path.clear();
fnd::io::appendToPath(file_path, mExtractPath);
fnd::io::appendToPath(file_path, file[i].name);
if (mCliOutputType >= OUTPUT_VERBOSE)
printf("extract=[%s]\n", file_path.c_str());
outFile.open(file_path, outFile.Create);
mReader->seek(file[i].offset);
for (size_t j = 0; j < ((file[i].size / kCacheSize) + ((file[i].size % kCacheSize) != 0)); j++)
{
mReader->read(mCache.getBytes(), MIN(file[i].size - (kCacheSize * j),kCacheSize));
outFile.write(mCache.getBytes(), MIN(file[i].size - (kCacheSize * j),kCacheSize));
}
outFile.close();
}
}

View file

@ -10,13 +10,12 @@ class PfsProcess
{
public:
PfsProcess();
~PfsProcess();
void process();
// generic
void setInputFile(fnd::IFile& reader);
void setInputFileOffset(size_t offset);
void setKeyset(const sKeyset* keyset);
void setInputFile(fnd::IFile* file, size_t offset, size_t size);
void setCliOutputMode(CliOutputType type);
void setVerifyMode(bool verify);
@ -29,25 +28,25 @@ public:
private:
const std::string kModuleName = "PfsProcess";
static const size_t kFileExportBlockSize = 0x1000000;
static const size_t kCacheSize = 0x10000;
fnd::IFile* mReader;
size_t mOffset;
const sKeyset* mKeyset;
CliOutputType mCliOutputType;
bool mVerify;
std::string mExtractPath;
bool mExtract;
std::string mMountName;
bool mListFs;
fnd::MemoryBlob mCache;
nx::PfsHeader mPfs;
void displayHeader();
void displayFs();
size_t determineHeaderSize(const nx::sPfsHeader* hdr);
bool validateHeaderMagic(const nx::sPfsHeader* hdr);
void validateHfs();
void extractFs();
};

View file

@ -1,7 +1,85 @@
#include "RomfsProcess.h"
#include <fnd/SimpleTextOutput.h>
#include <fnd/SimpleFile.h>
#include <fnd/io.h>
#include "OffsetAdjustedIFile.h"
#include "RomfsProcess.h"
RomfsProcess::RomfsProcess() :
mReader(nullptr),
mCliOutputType(OUTPUT_NORMAL),
mVerify(false),
mExtractPath(),
mExtract(false),
mMountName(),
mListFs(false),
mDirNum(0),
mFileNum(0)
{
mRootDir.name.clear();
mRootDir.dir_list.clear();
mRootDir.file_list.clear();
}
RomfsProcess::~RomfsProcess()
{
if (mReader != nullptr)
{
delete mReader;
}
}
void RomfsProcess::process()
{
if (mReader == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
}
resolveRomfs();
if (mCliOutputType >= OUTPUT_NORMAL)
displayHeader();
if (mListFs || mCliOutputType >= OUTPUT_VERBOSE)
displayFs();
if (mExtract)
extractFs();
}
void RomfsProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size)
{
mReader = new OffsetAdjustedIFile(file, offset, size);
}
void RomfsProcess::setCliOutputMode(CliOutputType type)
{
mCliOutputType = type;
}
void RomfsProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void RomfsProcess::setMountPointName(const std::string& mount_name)
{
mMountName = mount_name;
}
void RomfsProcess::setExtractPath(const std::string& path)
{
mExtract = true;
mExtractPath = path;
}
void RomfsProcess::setListFs(bool list_fs)
{
mListFs = list_fs;
}
const RomfsProcess::sDirectory& RomfsProcess::getRootDir() const
{
return mRootDir;
}
void RomfsProcess::printTab(size_t tab) const
{
@ -43,8 +121,8 @@ void RomfsProcess::displayDir(const sDirectory& dir, size_t tab) const
void RomfsProcess::displayHeader()
{
printf("[RomFS]\n");
printf(" DirNum: %u\n", mDirNum);
printf(" FileNum: %u\n", mFileNum);
printf(" DirNum: %" PRId64 "\n", (uint64_t)mDirNum);
printf(" FileNum: %" PRId64 "\n", (uint64_t)mFileNum);
if (mMountName.empty() == false)
printf(" MountPoint: %s%s\n", mMountName.c_str(), mMountName.at(mMountName.length()-1) != '/' ? "/" : "");
}
@ -64,16 +142,9 @@ void RomfsProcess::extractDir(const std::string& path, const sDirectory& dir)
if (dir.name.empty() == false)
fnd::io::appendToPath(dir_path, dir.name);
//printf("dirpath=[%s]\n", dir_path.c_str());
// make directory
fnd::io::makeDirectory(dir_path);
// allocate memory for file extraction
fnd::MemoryBlob scratch;
scratch.alloc(kFileExportBlockSize);
// extract files
fnd::SimpleFile outFile;
for (size_t i = 0; i < dir.file_list.getSize(); i++)
@ -87,16 +158,11 @@ void RomfsProcess::extractDir(const std::string& path, const sDirectory& dir)
outFile.open(file_path, outFile.Create);
mReader->seek(mOffset + dir.file_list[i].offset);
for (size_t j = 0; j < (dir.file_list[i].size / kFileExportBlockSize); j++)
mReader->seek(dir.file_list[i].offset);
for (size_t j = 0; j < ((dir.file_list[i].size / kCacheSize) + ((dir.file_list[i].size % kCacheSize) != 0)); j++)
{
mReader->read(scratch.getBytes(), kFileExportBlockSize);
outFile.write(scratch.getBytes(), kFileExportBlockSize);
}
if (dir.file_list[i].size % kFileExportBlockSize)
{
mReader->read(scratch.getBytes(), dir.file_list[i].size % kFileExportBlockSize);
outFile.write(scratch.getBytes(), dir.file_list[i].size % kFileExportBlockSize);
mReader->read(mCache.getBytes(), MIN(dir.file_list[i].size - (kCacheSize * j),kCacheSize));
outFile.write(mCache.getBytes(), MIN(dir.file_list[i].size - (kCacheSize * j),kCacheSize));
}
outFile.close();
}
@ -110,6 +176,8 @@ void RomfsProcess::extractDir(const std::string& path, const sDirectory& dir)
void RomfsProcess::extractFs()
{
// allocate only when extractDir is invoked
mCache.alloc(kCacheSize);
extractDir(mExtractPath, mRootDir);
}
@ -165,7 +233,7 @@ void RomfsProcess::importDirectory(uint32_t dir_offset, sDirectory& dir)
printf(" name=%s\n", f_node->name);
*/
dir.file_list.addElement({std::string(f_node->name, f_node->name_size.get()), mHdr.data_offset.get() + f_node->offset.get(), f_node->size.get()});
dir.file_list.addElement({std::string(f_node->name(), f_node->name_size.get()), mHdr.data_offset.get() + f_node->offset.get(), f_node->size.get()});
file_addr = f_node->sibling.get();
mFileNum++;
@ -175,7 +243,7 @@ void RomfsProcess::importDirectory(uint32_t dir_offset, sDirectory& dir)
{
nx::sRomfsDirEntry* c_node = get_dir_node(child_addr);
dir.dir_list.addElement({std::string(c_node->name, c_node->name_size.get())});
dir.dir_list.addElement({std::string(c_node->name(), c_node->name_size.get())});
importDirectory(child_addr, dir.dir_list.atBack());
child_addr = c_node->sibling.get();
@ -186,7 +254,7 @@ void RomfsProcess::importDirectory(uint32_t dir_offset, sDirectory& dir)
void RomfsProcess::resolveRomfs()
{
// read header
mReader->read((byte_t*)&mHdr, mOffset, sizeof(nx::sRomfsHeader));
mReader->read((byte_t*)&mHdr, 0, sizeof(nx::sRomfsHeader));
// logic check on the header layout
if (validateHeaderLayout(&mHdr) == false)
@ -196,13 +264,13 @@ void RomfsProcess::resolveRomfs()
// read directory nodes
mDirNodes.alloc(mHdr.sections[nx::romfs::DIR_NODE_TABLE].size.get());
mReader->read(mDirNodes.getBytes(), mOffset + mHdr.sections[nx::romfs::DIR_NODE_TABLE].offset.get(), mDirNodes.getSize());
mReader->read(mDirNodes.getBytes(), mHdr.sections[nx::romfs::DIR_NODE_TABLE].offset.get(), mDirNodes.getSize());
//printf("[RAW DIR NODES]\n");
//fnd::SimpleTextOutput::hxdStyleDump(mDirNodes.getBytes(), mDirNodes.getSize());
// read file nodes
mFileNodes.alloc(mHdr.sections[nx::romfs::FILE_NODE_TABLE].size.get());
mReader->read(mFileNodes.getBytes(), mOffset + mHdr.sections[nx::romfs::FILE_NODE_TABLE].offset.get(), mFileNodes.getSize());
mReader->read(mFileNodes.getBytes(), mHdr.sections[nx::romfs::FILE_NODE_TABLE].offset.get(), mFileNodes.getSize());
//printf("[RAW FILE NODES]\n");
//fnd::SimpleTextOutput::hxdStyleDump(mFileNodes.getBytes(), mFileNodes.getSize());
@ -220,84 +288,3 @@ void RomfsProcess::resolveRomfs()
mFileNum = 0;
importDirectory(0, mRootDir);
}
RomfsProcess::RomfsProcess() :
mReader(nullptr),
mOffset(0),
mKeyset(nullptr),
mCliOutputType(OUTPUT_NORMAL),
mVerify(false),
mExtractPath(),
mExtract(false),
mMountName(),
mListFs(false),
mDirNum(0),
mFileNum(0)
{
mRootDir.name.clear();
mRootDir.dir_list.clear();
mRootDir.file_list.clear();
}
void RomfsProcess::process()
{
if (mReader == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
}
resolveRomfs();
if (mCliOutputType >= OUTPUT_NORMAL)
displayHeader();
if (mListFs || mCliOutputType >= OUTPUT_VERBOSE)
displayFs();
if (mExtract)
extractFs();
}
void RomfsProcess::setInputFile(fnd::IFile& reader)
{
mReader = &reader;
}
void RomfsProcess::setInputFileOffset(size_t offset)
{
mOffset = offset;
}
void RomfsProcess::setKeyset(const sKeyset* keyset)
{
mKeyset = keyset;
}
void RomfsProcess::setCliOutputMode(CliOutputType type)
{
mCliOutputType = type;
}
void RomfsProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void RomfsProcess::setMountPointName(const std::string& mount_name)
{
mMountName = mount_name;
}
void RomfsProcess::setExtractPath(const std::string& path)
{
mExtract = true;
mExtractPath = path;
}
void RomfsProcess::setListFs(bool list_fs)
{
mListFs = list_fs;
}
const RomfsProcess::sDirectory& RomfsProcess::getRootDir() const
{
return mRootDir;
}

View file

@ -89,13 +89,12 @@ public:
};
RomfsProcess();
~RomfsProcess();
void process();
// generic
void setInputFile(fnd::IFile& reader);
void setInputFileOffset(size_t offset);
void setKeyset(const sKeyset* keyset);
void setInputFile(fnd::IFile* file, size_t offset, size_t size);
void setCliOutputMode(CliOutputType type);
void setVerifyMode(bool verify);
@ -107,11 +106,9 @@ public:
const sDirectory& getRootDir() const;
private:
const std::string kModuleName = "RomfsProcess";
static const size_t kFileExportBlockSize = 0x1000000;
static const size_t kCacheSize = 0x10000;
fnd::IFile* mReader;
size_t mOffset;
const sKeyset* mKeyset;
CliOutputType mCliOutputType;
bool mVerify;
@ -120,6 +117,8 @@ private:
std::string mMountName;
bool mListFs;
fnd::MemoryBlob mCache;
size_t mDirNum;
size_t mFileNum;
nx::sRomfsHeader mHdr;
@ -138,7 +137,6 @@ private:
void displayHeader();
void displayFs();
void extractFile(const std::string& path, const sFile& file);
void extractDir(const std::string& path, const sDirectory& dir);
void extractFs();

View file

@ -38,7 +38,7 @@ void UserSettings::showHelp()
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(" -t, --type Specify input file type [xci, pfs, romfs, nca, npdm, cnmt]\n");
printf(" -y, --verify Verify file\n");
printf(" -v, --verbose Verbose output\n");
printf(" -q, --quiet Minimal output\n");
@ -362,8 +362,8 @@ void UserSettings::populateKeyset(sCmdArgs& args)
}
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kPackage2Base, kKeyStr, kSourceStr), package2_key_source.key, 0x10);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kTicketCommonKeyBase[0], kKeyStr, kSourceStr), ticket_titlekek_source.key, 0x10);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kTicketCommonKeyBase[1], kKeyStr, kSourceStr), ticket_titlekek_source.key, 0x10);
_SAVE_KEYDATA(_CONCAT_2_STRINGS(kTicketCommonKeyBase[0], kSourceStr), ticket_titlekek_source.key, 0x10);
_SAVE_KEYDATA(_CONCAT_2_STRINGS(kTicketCommonKeyBase[1], kSourceStr), ticket_titlekek_source.key, 0x10);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaBodyBase[0], kNcaBodyKeakIndexName[0], kSourceStr), key_area_key_source[0].key, 0x10);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaBodyBase[0], kNcaBodyKeakIndexName[1], kSourceStr), key_area_key_source[1].key, 0x10);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaBodyBase[0], kNcaBodyKeakIndexName[2], kSourceStr), key_area_key_source[2].key, 0x10);
@ -429,7 +429,7 @@ void UserSettings::populateKeyset(sCmdArgs& args)
if (args.nca_titlekey.isSet)
{
if (args.nca_bodykey.var.length() == (sizeof(crypto::aes::sAes128Key)*2))
if (args.nca_titlekey.var.length() == (sizeof(crypto::aes::sAes128Key)*2))
{
decodeHexStringToBytes("--titlekey", args.nca_titlekey.var, mKeyset.nca.manual_title_key_aesctr.key, sizeof(crypto::aes::sAes128Key));
}
@ -565,17 +565,20 @@ FileType UserSettings::getFileTypeFromString(const std::string& type_str)
FileType type;
if (str == "xci")
type = FILE_XCI;
else if ( str == "partitionfs" \
|| str == "pfs" || str == "pfs0" \
|| str == "hfs" || str == "hfs0" \
|| str == "nsp")
else if (str == "nsp")
type = FILE_NSP;
else if (str == "partitionfs" || str == "hashedpartitionfs" \
|| str == "pfs" || str == "pfs0" \
|| str == "hfs" || str == "hfs0")
type = FILE_PARTITIONFS;
else if (str == "romfs")
else if (str == "romfs")
type = FILE_ROMFS;
else if (str == "nca")
else if (str == "nca")
type = FILE_NCA;
else if (str == "npdm")
else if (str == "npdm")
type = FILE_NPDM;
else if (str == "cnmt")
type = FILE_CNMT;
else
type = FILE_INVALID;
@ -587,14 +590,14 @@ FileType UserSettings::determineFileTypeFromFile(const std::string& path)
static const size_t kMaxReadSize = 0x1000;
FileType file_type = FILE_INVALID;
fnd::SimpleFile file;
fnd::MemoryBlob blob;
fnd::MemoryBlob scratch;
// open file
file.open(path, file.Read);
// read file
blob.alloc(MIN(kMaxReadSize, file.size()));
file.read(blob.getBytes(), 0, blob.getSize());
scratch.alloc(MIN(kMaxReadSize, file.size()));
file.read(scratch.getBytes(), 0, scratch.getSize());
// close file
file.close();
@ -602,14 +605,14 @@ FileType UserSettings::determineFileTypeFromFile(const std::string& path)
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)
if (scratch.getSize() >= nx::nca::kHeaderSize)
{
nx::NcaUtils::decryptNcaHeader(blob.getBytes(), nca_raw, mKeyset.nca.header_key);
nx::NcaUtils::decryptNcaHeader(scratch.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))
// _QUICK_CAST resolves to a pointer of type 'st' located at scratch.getBytes() + 'oft'
#define _QUICK_CAST(st, oft) ((st*)(scratch.getBytes() + (oft)))
#define _ASSERT_SIZE(size) (scratch.getSize() >= (size))
// test npdm
if (_ASSERT_SIZE(sizeof(nx::sXciHeaderPage)) && std::string(_QUICK_CAST(nx::sXciHeaderPage, 0)->header.signature, 4) == nx::xci::kXciSig)

View file

@ -1,6 +1,7 @@
#include "XciProcess.h"
#include <fnd/SimpleTextOutput.h>
#include <nx/XciUtils.h>
#include "OffsetAdjustedIFile.h"
#include "XciProcess.h"
inline const char* getBoolStr(bool isTrue)
{
@ -103,7 +104,9 @@ void XciProcess::displayHeader()
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());
#define _SPLIT_VER(ver) ( (ver>>26) & 0x3f), ( (ver>>20) & 0x3f), ( (ver>>16) & 0xf), (ver & 0xffff)
printf(" UppVersion: v%" PRId32 " (%d.%d.%d.%d)\n", mHdr.getUppVersion(), _SPLIT_VER(mHdr.getUppVersion()));
#undef _SPLIT_VER
printf(" UppHash: ");
fnd::SimpleTextOutput::hexDump(mHdr.getUppHash(), 8);
printf(" UppId: %016" PRIx64 "\n", mHdr.getUppId());
@ -136,13 +139,12 @@ void XciProcess::processRootPfs()
{
if (mVerify)
{
if (validateRegionOfFile(mOffset + mHdr.getPartitionFsAddress(), mHdr.getPartitionFsSize(), mHdr.getPartitionFsHash().bytes) == false)
if (validateRegionOfFile(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.setInputFile(mReader, mHdr.getPartitionFsAddress(), mHdr.getPartitionFsSize());
mRootPfs.setListFs(mListFs);
mRootPfs.setVerifyMode(mVerify);
mRootPfs.setCliOutputMode(mCliOutputType);
@ -156,8 +158,7 @@ void XciProcess::processPartitionPfs()
for (size_t i = 0; i < rootPartitions.getSize(); i++)
{
PfsProcess tmp;
tmp.setInputFile(*mReader);
tmp.setInputFileOffset(mOffset + mHdr.getPartitionFsAddress() + rootPartitions[i].offset);
tmp.setInputFile(mReader, mHdr.getPartitionFsAddress() + rootPartitions[i].offset, rootPartitions[i].size);
tmp.setListFs(mListFs);
tmp.setVerifyMode(mVerify);
tmp.setCliOutputMode(mCliOutputType);
@ -174,7 +175,6 @@ void XciProcess::processPartitionPfs()
XciProcess::XciProcess() :
mReader(nullptr),
mOffset(0),
mKeyset(nullptr),
mCliOutputType(OUTPUT_NORMAL),
mVerify(false),
@ -189,6 +189,14 @@ XciProcess::XciProcess() :
mSecurePath.doExtract = false;
}
XciProcess::~XciProcess()
{
if (mReader != nullptr)
{
delete mReader;
}
}
void XciProcess::process()
{
fnd::MemoryBlob scratch;
@ -199,7 +207,7 @@ void XciProcess::process()
}
// read header page
mReader->read((byte_t*)&mHdrPage, mOffset, sizeof(nx::sXciHeaderPage));
mReader->read((byte_t*)&mHdrPage, 0, sizeof(nx::sXciHeaderPage));
// allocate memory for and decrypt sXciHeader
scratch.alloc(sizeof(nx::sXciHeader));
@ -227,14 +235,9 @@ void XciProcess::process()
processPartitionPfs();
}
void XciProcess::setInputFile(fnd::IFile& reader)
void XciProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size)
{
mReader = &reader;
}
void XciProcess::setInputFileOffset(size_t offset)
{
mOffset = offset;
mReader = new OffsetAdjustedIFile(file, offset, size);
}
void XciProcess::setKeyset(const sKeyset* keyset)

View file

@ -13,12 +13,12 @@ class XciProcess
{
public:
XciProcess();
~XciProcess();
void process();
// generic
void setInputFile(fnd::IFile& reader);
void setInputFileOffset(size_t offset);
void setInputFile(fnd::IFile* file, size_t offset, size_t size);
void setKeyset(const sKeyset* keyset);
void setCliOutputMode(CliOutputType type);
void setVerifyMode(bool verify);
@ -35,7 +35,6 @@ private:
static const size_t kFileExportBlockSize = 0x1000000;
fnd::IFile* mReader;
size_t mOffset;
const sKeyset* mKeyset;
CliOutputType mCliOutputType;
bool mVerify;

View file

@ -6,6 +6,7 @@
#include "RomfsProcess.h"
#include "NcaProcess.h"
#include "NpdmProcess.h"
#include "CnmtProcess.h"
int main(int argc, char** argv)
@ -21,7 +22,7 @@ int main(int argc, char** argv)
{
XciProcess xci;
xci.setInputFile(inputFile);
xci.setInputFile(&inputFile, 0, inputFile.size());
xci.setKeyset(&user_set.getKeyset());
xci.setCliOutputMode(user_set.getCliOutputType());
@ -37,12 +38,11 @@ int main(int argc, char** argv)
xci.process();
}
else if (user_set.getFileType() == FILE_PARTITIONFS)
else if (user_set.getFileType() == FILE_PARTITIONFS || user_set.getFileType() == FILE_NSP)
{
PfsProcess pfs;
pfs.setInputFile(inputFile);
pfs.setKeyset(&user_set.getKeyset());
pfs.setInputFile(&inputFile, 0, inputFile.size());
pfs.setCliOutputMode(user_set.getCliOutputType());
pfs.setVerifyMode(user_set.isVerifyFile());
@ -56,8 +56,7 @@ int main(int argc, char** argv)
{
RomfsProcess romfs;
romfs.setInputFile(inputFile);
romfs.setKeyset(&user_set.getKeyset());
romfs.setInputFile(&inputFile, 0, inputFile.size());
romfs.setCliOutputMode(user_set.getCliOutputType());
romfs.setVerifyMode(user_set.isVerifyFile());
@ -71,7 +70,7 @@ int main(int argc, char** argv)
{
NcaProcess nca;
nca.setInputFile(&inputFile);
nca.setInputFile(&inputFile, 0, inputFile.size());
nca.setKeyset(&user_set.getKeyset());
nca.setCliOutputMode(user_set.getCliOutputType());
nca.setVerifyMode(user_set.isVerifyFile());
@ -93,13 +92,23 @@ int main(int argc, char** argv)
{
NpdmProcess npdm;
npdm.setInputFile(inputFile);
npdm.setInputFile(&inputFile, 0, inputFile.size());
npdm.setKeyset(&user_set.getKeyset());
npdm.setCliOutputMode(user_set.getCliOutputType());
npdm.setVerifyMode(user_set.isVerifyFile());
npdm.process();
}
else if (user_set.getFileType() == FILE_CNMT)
{
CnmtProcess cnmt;
cnmt.setInputFile(&inputFile, 0, inputFile.size());
cnmt.setCliOutputMode(user_set.getCliOutputType());
cnmt.setVerifyMode(user_set.isVerifyFile());
cnmt.process();
}
}
catch (const fnd::Exception& e) {
printf("\n\n%s\n", e.what());

View file

@ -13,10 +13,12 @@ static const size_t kNcaKeakNum = nx::nca::kKeyAreaEncryptionKeyNum;
enum FileType
{
FILE_XCI,
FILE_NSP,
FILE_PARTITIONFS,
FILE_ROMFS,
FILE_NCA,
FILE_NPDM,
FILE_CNMT,
FILE_INVALID = -1,
};