mirror of
https://github.com/jakcron/nstool.git
synced 2024-12-22 18:55:29 +00:00
commit
578e3ac54d
2
KEYS.md
2
KEYS.md
|
@ -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
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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 = ®ions_[idx];
|
||||
}
|
||||
else if (regions_[idx].start() > (start + pos) && (next_region == nullptr || next_region->start() > regions_[idx].start()))
|
||||
{
|
||||
next_region = ®ions_[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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -44,5 +44,5 @@ const char* Exception::module() const noexcept
|
|||
|
||||
const char * fnd::Exception::error() const noexcept
|
||||
{
|
||||
return nullptr;
|
||||
return error_.c_str();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
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
|
|
@ -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
|
||||
{
|
||||
|
|
260
lib/libnx/include/nx/ContentMetaBinary.h
Normal file
260
lib/libnx/include/nx/ContentMetaBinary.h
Normal 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);
|
||||
};
|
||||
}
|
75
lib/libnx/include/nx/HierarchicalIntegrityHeader.h
Normal file
75
lib/libnx/include/nx/HierarchicalIntegrityHeader.h
Normal 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);
|
||||
};
|
||||
|
||||
}
|
77
lib/libnx/include/nx/HierarchicalSha256Header.h
Normal file
77
lib/libnx/include/nx/HierarchicalSha256Header.h
Normal 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
138
lib/libnx/include/nx/cnmt.h
Normal 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)
|
||||
}
|
35
lib/libnx/include/nx/hierarchicalintegrity.h
Normal file
35
lib/libnx/include/nx/hierarchicalintegrity.h
Normal 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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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 aes_ctr_upper[8];
|
||||
byte_t reserved_1[0xB8];
|
||||
};
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
382
lib/libnx/source/ContentMetaBinary.cpp
Normal file
382
lib/libnx/source/ContentMetaBinary.cpp
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
156
lib/libnx/source/HierarchicalIntegrityHeader.cpp
Normal file
156
lib/libnx/source/HierarchicalIntegrityHeader.cpp
Normal 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;
|
||||
}
|
||||
}
|
133
lib/libnx/source/HierarchicalSha256Header.cpp
Normal file
133
lib/libnx/source/HierarchicalSha256Header.cpp
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
};
|
198
programs/nstool/source/CnmtProcess.cpp
Normal file
198
programs/nstool/source/CnmtProcess.cpp
Normal 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;
|
||||
}
|
33
programs/nstool/source/CnmtProcess.h
Normal file
33
programs/nstool/source/CnmtProcess.h
Normal 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();
|
||||
};
|
17
programs/nstool/source/CopiedIFile.h
Normal file
17
programs/nstool/source/CopiedIFile.h
Normal 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;
|
||||
};
|
141
programs/nstool/source/HashTreeMeta.cpp
Normal file
141
programs/nstool/source/HashTreeMeta.cpp
Normal 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;
|
||||
}
|
64
programs/nstool/source/HashTreeMeta.h
Normal file
64
programs/nstool/source/HashTreeMeta.h
Normal 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);
|
||||
};
|
215
programs/nstool/source/HashTreeWrappedIFile.cpp
Normal file
215
programs/nstool/source/HashTreeWrappedIFile.cpp
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
47
programs/nstool/source/HashTreeWrappedIFile.h
Normal file
47
programs/nstool/source/HashTreeWrappedIFile.h
Normal 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);
|
||||
};
|
|
@ -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
|
@ -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();
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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,10 +565,11 @@ FileType UserSettings::getFileTypeFromString(const std::string& type_str)
|
|||
FileType type;
|
||||
if (str == "xci")
|
||||
type = FILE_XCI;
|
||||
else if ( str == "partitionfs" \
|
||||
else if (str == "nsp")
|
||||
type = FILE_NSP;
|
||||
else if (str == "partitionfs" || str == "hashedpartitionfs" \
|
||||
|| str == "pfs" || str == "pfs0" \
|
||||
|| str == "hfs" || str == "hfs0" \
|
||||
|| str == "nsp")
|
||||
|| str == "hfs" || str == "hfs0")
|
||||
type = FILE_PARTITIONFS;
|
||||
else if (str == "romfs")
|
||||
type = FILE_ROMFS;
|
||||
|
@ -576,6 +577,8 @@ FileType UserSettings::getFileTypeFromString(const std::string& type_str)
|
|||
type = FILE_NCA;
|
||||
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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue