mirror of
https://github.com/jakcron/nstool
synced 2024-11-15 02:06:40 +00:00
commit
578e3ac54d
64 changed files with 3529 additions and 1118 deletions
2
KEYS.md
2
KEYS.md
|
@ -46,7 +46,7 @@ acid_sign_key_private : RSA2048 Private Exponent (0x100 bytes)
|
||||||
# Compatibility with hactool keyset files
|
# 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.
|
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_key_source : hactool alias for nca_header_key_source
|
||||||
header_kek_source : hactool alias for nca_header_kek_source
|
header_kek_source : hactool alias for nca_header_kek_source
|
||||||
key_area_key_application_source : hactool alias for nca_body_keak_application_source
|
key_area_key_application_source : hactool alias for nca_body_keak_application_source
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2017 Jack
|
Copyright (c) 2017-2018 Jack
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
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
|
ProjectSection(SolutionItems) = preProject
|
||||||
.gitignore = .gitignore
|
.gitignore = .gitignore
|
||||||
KEYS.md = KEYS.md
|
KEYS.md = KEYS.md
|
||||||
|
LICENSE = LICENSE
|
||||||
makefile = makefile
|
makefile = makefile
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
|
|
|
@ -6,14 +6,14 @@ Tools & Libraries for NX (Nintendo Switch).
|
||||||
|
|
||||||
# Tools
|
# 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
|
# Libraries
|
||||||
|
|
||||||
* __libfnd__ - Foundation library.
|
* __libfnd__ - Foundation library.
|
||||||
* __libcrypto__ - Cryptographic functions (AES,SHA,RSA). Wrapper for [mbedTLS](https://github.com/ARMmbed/mbedtls)
|
* __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)
|
* __libes__ - Handling of (NX relevant) eShop file type processing. (eTickets, etc)
|
||||||
* __libnx__ - Handling of NS file types
|
* __libnx__ - Handling of NX file types
|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
|
|
||||||
|
|
|
@ -121,7 +121,6 @@
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="include\crypto\aes.h" />
|
<ClInclude Include="include\crypto\aes.h" />
|
||||||
<ClInclude Include="include\crypto\AesCtrStream.h" />
|
|
||||||
<ClInclude Include="include\crypto\rsa.h" />
|
<ClInclude Include="include\crypto\rsa.h" />
|
||||||
<ClInclude Include="include\crypto\sha.h" />
|
<ClInclude Include="include\crypto\sha.h" />
|
||||||
<ClInclude Include="source\libpolarssl\include\polarssl\aes.h" />
|
<ClInclude Include="source\libpolarssl\include\polarssl\aes.h" />
|
||||||
|
@ -137,7 +136,6 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="source\aes.cpp" />
|
<ClCompile Include="source\aes.cpp" />
|
||||||
<ClCompile Include="source\AesCtrStream.cpp" />
|
|
||||||
<ClCompile Include="source\libpolarssl\source\polar_aes.c" />
|
<ClCompile Include="source\libpolarssl\source\polar_aes.c" />
|
||||||
<ClCompile Include="source\libpolarssl\source\polar_base64.c" />
|
<ClCompile Include="source\libpolarssl\source\polar_base64.c" />
|
||||||
<ClCompile Include="source\libpolarssl\source\polar_bignum.c" />
|
<ClCompile Include="source\libpolarssl\source\polar_bignum.c" />
|
||||||
|
|
|
@ -27,9 +27,6 @@
|
||||||
<ClInclude Include="include\crypto\aes.h">
|
<ClInclude Include="include\crypto\aes.h">
|
||||||
<Filter>Header Files\crypto</Filter>
|
<Filter>Header Files\crypto</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="include\crypto\AesCtrStream.h">
|
|
||||||
<Filter>Header Files\crypto</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\crypto\rsa.h">
|
<ClInclude Include="include\crypto\rsa.h">
|
||||||
<Filter>Header Files\crypto</Filter>
|
<Filter>Header Files\crypto</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
@ -71,9 +68,6 @@
|
||||||
<ClCompile Include="source\aes.cpp">
|
<ClCompile Include="source\aes.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="source\AesCtrStream.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="source\rsa.cpp">
|
<ClCompile Include="source\rsa.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</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;
|
uint64_t total = ctr[i] + block_num;
|
||||||
// if there wasn't a wrap around, add the two together and exit
|
// if there wasn't a wrap around, add the two together and exit
|
||||||
if (total <= 0xffffffff) {
|
if (total <= 0xffffffff) {
|
||||||
ctr[i] += block_num;
|
ctr[i] += (uint32_t)block_num;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@ namespace fnd
|
||||||
class IFile
|
class IFile
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
inline virtual ~IFile() {}
|
||||||
|
|
||||||
virtual size_t size() = 0;
|
virtual size_t size() = 0;
|
||||||
virtual void seek(size_t offset) = 0;
|
virtual void seek(size_t offset) = 0;
|
||||||
virtual void read(byte_t* out, size_t len) = 0;
|
virtual void read(byte_t* out, size_t len) = 0;
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <fnd/IFile.h>
|
#include <fnd/IFile.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <Windows.h>
|
||||||
|
#else
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace fnd
|
namespace fnd
|
||||||
{
|
{
|
||||||
|
@ -34,9 +38,16 @@ namespace fnd
|
||||||
|
|
||||||
bool mOpen;
|
bool mOpen;
|
||||||
OpenMode mMode;
|
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
|
const char * fnd::Exception::error() const noexcept
|
||||||
{
|
{
|
||||||
return nullptr;
|
return error_.c_str();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
#include <fnd/SimpleFile.h>
|
#include <fnd/SimpleFile.h>
|
||||||
|
#include <fnd/StringConv.h>
|
||||||
|
|
||||||
using namespace fnd;
|
using namespace fnd;
|
||||||
|
|
||||||
SimpleFile::SimpleFile() :
|
SimpleFile::SimpleFile() :
|
||||||
mOpen(false),
|
mOpen(false),
|
||||||
mMode(Read),
|
mMode(Read),
|
||||||
|
#ifdef _WIN32
|
||||||
|
mFileHandle()
|
||||||
|
#else
|
||||||
mFp(nullptr)
|
mFp(nullptr)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +21,29 @@ SimpleFile::~SimpleFile()
|
||||||
|
|
||||||
void SimpleFile::open(const std::string& path, OpenMode mode)
|
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();
|
//close();
|
||||||
mMode = mode;
|
mMode = mode;
|
||||||
//printf("fopen(%s,%s);\n", path.c_str(), getOpenModeStr(mMode));
|
//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)
|
if (mFp == nullptr)
|
||||||
throw fnd::Exception(kModuleName, "Failed to open file.");
|
throw fnd::Exception(kModuleName, "Failed to open file.");
|
||||||
mOpen = true;
|
mOpen = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
seek(0);
|
seek(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SimpleFile::isOpen() const
|
bool SimpleFile::isOpen() const
|
||||||
{
|
{
|
||||||
return mOpen == true && mFp != nullptr;
|
return mOpen == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SimpleFile::close()
|
void SimpleFile::close()
|
||||||
{
|
{
|
||||||
if (isOpen())
|
if (isOpen())
|
||||||
{
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
CloseHandle(mFileHandle);
|
||||||
|
#else
|
||||||
fclose(mFp);
|
fclose(mFp);
|
||||||
|
mFp = nullptr;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
mFp = nullptr;
|
|
||||||
mOpen = false;
|
mOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t SimpleFile::size()
|
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();
|
size_t cur_pos = pos();
|
||||||
fseek(mFp, 0, SEEK_END);
|
fseek(mFp, 0, SEEK_END);
|
||||||
size_t fsize = pos();
|
fsize = pos();
|
||||||
seek(cur_pos);
|
seek(cur_pos);
|
||||||
|
#endif
|
||||||
return fsize;
|
return fsize;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SimpleFile::seek(size_t offset)
|
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);
|
fseek(mFp, offset, SEEK_SET);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t SimpleFile::pos()
|
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);
|
return ftell(mFp);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void SimpleFile::read(byte_t* out, size_t len)
|
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);
|
fread(out, len, 1, mFp);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void SimpleFile::read(byte_t* out, size_t offset, size_t len)
|
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)
|
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);
|
fwrite(out, len, 1, mFp);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void SimpleFile::write(const byte_t* out, size_t offset, size_t len)
|
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);
|
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* SimpleFile::getOpenModeStr(OpenMode mode)
|
||||||
{
|
{
|
||||||
const char* str = "";
|
const char* str = "";
|
||||||
|
@ -100,4 +321,5 @@ const char* SimpleFile::getOpenModeStr(OpenMode mode)
|
||||||
throw fnd::Exception(kModuleName, "Unknown open mode");
|
throw fnd::Exception(kModuleName, "Unknown open mode");
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
#endif
|
|
@ -41,7 +41,7 @@ std::u16string StringConv::ConvertChar8ToChar16(const std::string & in)
|
||||||
throw std::logic_error("not a UTF-8 string");
|
throw std::logic_error("not a UTF-8 string");
|
||||||
}
|
}
|
||||||
|
|
||||||
uni <= 6;
|
uni <<= 6;
|
||||||
uni |= get_utf8_data(1, in[i + j]);
|
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];
|
char32_t uni = unicode[i];
|
||||||
if (uni < kUtf16NonNativeStart)
|
if (uni < kUtf16NonNativeStart)
|
||||||
{
|
{
|
||||||
utf16.push_back(uni);
|
utf16.push_back((char16_t)uni);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -117,25 +117,25 @@ std::string StringConv::ConvertChar16ToChar8(const std::u16string & in)
|
||||||
{
|
{
|
||||||
if (unicode[i] <= kUtf8AsciiEnd)
|
if (unicode[i] <= kUtf8AsciiEnd)
|
||||||
{
|
{
|
||||||
utf8.push_back(unicode[i]);
|
utf8.push_back((char)unicode[i]);
|
||||||
}
|
}
|
||||||
else if (unicode[i] <= kUtf82ByteEnd)
|
else if (unicode[i] <= kUtf82ByteEnd)
|
||||||
{
|
{
|
||||||
utf8.push_back(make_utf8(2, (unicode[i] >> 6)));
|
utf8.push_back(make_utf8(2, (uint8_t)(unicode[i] >> 6)));
|
||||||
utf8.push_back(make_utf8(1, (unicode[i] >> 0)));
|
utf8.push_back(make_utf8(1, (uint8_t)(unicode[i] >> 0)));
|
||||||
}
|
}
|
||||||
else if (unicode[i] <= kUtf83ByteEnd)
|
else if (unicode[i] <= kUtf83ByteEnd)
|
||||||
{
|
{
|
||||||
utf8.push_back(make_utf8(3, (unicode[i] >> 12)));
|
utf8.push_back(make_utf8(3, (uint8_t)(unicode[i] >> 12)));
|
||||||
utf8.push_back(make_utf8(1, (unicode[i] >> 6)));
|
utf8.push_back(make_utf8(1, (uint8_t)(unicode[i] >> 6)));
|
||||||
utf8.push_back(make_utf8(1, (unicode[i] >> 0)));
|
utf8.push_back(make_utf8(1, (uint8_t)(unicode[i] >> 0)));
|
||||||
}
|
}
|
||||||
else if (unicode[i] <= kUtf84ByteEnd)
|
else if (unicode[i] <= kUtf84ByteEnd)
|
||||||
{
|
{
|
||||||
utf8.push_back(make_utf8(4, (unicode[i] >> 18)));
|
utf8.push_back(make_utf8(4, (uint8_t)(unicode[i] >> 18)));
|
||||||
utf8.push_back(make_utf8(1, (unicode[i] >> 12)));
|
utf8.push_back(make_utf8(1, (uint8_t)(unicode[i] >> 12)));
|
||||||
utf8.push_back(make_utf8(1, (unicode[i] >> 6)));
|
utf8.push_back(make_utf8(1, (uint8_t)(unicode[i] >> 6)));
|
||||||
utf8.push_back(make_utf8(1, (unicode[i] >> 0)));
|
utf8.push_back(make_utf8(1, (uint8_t)(unicode[i] >> 0)));
|
||||||
}
|
}
|
||||||
else
|
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
|
namespace hierarchicalsha256
|
||||||
{
|
{
|
||||||
static const size_t kDefaultLevelNum = 2;
|
static const size_t kDefaultLayerNum = 2;
|
||||||
|
static const size_t kMaxLayerNum = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma pack(push,1)
|
#pragma pack(push,1)
|
||||||
|
@ -16,12 +17,12 @@ namespace nx
|
||||||
{
|
{
|
||||||
crypto::sha::sSha256Hash master_hash;
|
crypto::sha::sSha256Hash master_hash;
|
||||||
le_uint32_t hash_block_size;
|
le_uint32_t hash_block_size;
|
||||||
le_uint32_t hash_level_num;
|
le_uint32_t layer_num;
|
||||||
struct sLayout
|
struct sLayer
|
||||||
{
|
{
|
||||||
le_uint64_t offset;
|
le_uint64_t offset;
|
||||||
le_uint64_t size;
|
le_uint64_t size;
|
||||||
} hash_data, hash_target;
|
} layer[hierarchicalsha256::kMaxLayerNum];
|
||||||
};
|
};
|
||||||
#pragma pack(pop)
|
#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/sha.h>
|
||||||
#include <crypto/rsa.h>
|
#include <crypto/rsa.h>
|
||||||
#include <fnd/ISerialiseableBinary.h>
|
#include <fnd/ISerialiseableBinary.h>
|
||||||
#include <nx/ivfc.h>
|
|
||||||
#include <nx/hierarchicalsha256.h>
|
|
||||||
|
|
||||||
namespace nx
|
namespace nx
|
||||||
{
|
{
|
||||||
|
@ -21,7 +19,7 @@ namespace nx
|
||||||
static const size_t kAesKeyNum = 16;
|
static const size_t kAesKeyNum = 16;
|
||||||
static const size_t kRightsIdLen = 0x10;
|
static const size_t kRightsIdLen = 0x10;
|
||||||
static const size_t kKeyAreaEncryptionKeyNum = 3;
|
static const size_t kKeyAreaEncryptionKeyNum = 3;
|
||||||
static const size_t kFsHeaderHashSuperblockLen = 0x130;
|
static const size_t kFsHeaderHashSuperblockLen = 0x138;
|
||||||
static const uint16_t kDefaultFsHeaderVersion = 2;
|
static const uint16_t kDefaultFsHeaderVersion = 2;
|
||||||
|
|
||||||
enum ProgramPartitionId
|
enum ProgramPartitionId
|
||||||
|
@ -120,12 +118,8 @@ namespace nx
|
||||||
byte_t hash_type;
|
byte_t hash_type;
|
||||||
byte_t encryption_type;
|
byte_t encryption_type;
|
||||||
byte_t reserved_0[3];
|
byte_t reserved_0[3];
|
||||||
union {
|
byte_t hash_superblock[nca::kFsHeaderHashSuperblockLen];
|
||||||
byte_t hash_superblock[nca::kFsHeaderHashSuperblockLen];
|
byte_t aes_ctr_upper[8];
|
||||||
nx::sHierarchicalSha256Header hierarchicalsha256_header;
|
|
||||||
nx::sIvfcHeader ivfc_header;
|
|
||||||
};
|
|
||||||
crypto::aes::sAesIvCtr base_ctr;
|
|
||||||
byte_t reserved_1[0xB8];
|
byte_t reserved_1[0xB8];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,8 @@ namespace nx
|
||||||
le_uint32_t file;
|
le_uint32_t file;
|
||||||
le_uint32_t hash;
|
le_uint32_t hash;
|
||||||
le_uint32_t name_size;
|
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
|
struct sRomfsFileEntry
|
||||||
|
@ -53,7 +54,8 @@ namespace nx
|
||||||
le_uint64_t size;
|
le_uint64_t size;
|
||||||
le_uint32_t hash;
|
le_uint32_t hash;
|
||||||
le_uint32_t name_size;
|
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)
|
#pragma pack(pop)
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,9 +115,11 @@ namespace nx
|
||||||
struct sKeyDataArea
|
struct sKeyDataArea
|
||||||
{
|
{
|
||||||
sInitialData initial_data; // AES128-CCM encrypted {titlekey[16]}
|
sInitialData initial_data; // AES128-CCM encrypted {titlekey[16]}
|
||||||
byte_t encrypted_00[0x200*6]; // AES128-CTR encrypted {titlekey[16]}
|
byte_t reserved_00[xci::kPageSize - sizeof(sInitialData)];
|
||||||
byte_t encrypted_00_aesctr_data[0x100]; // RSA2048-OAEP-SHA256 encrypted AES-CTR data used for encrypted_00 {key[16],iv[16]}
|
byte_t encrypted_00[xci::kPageSize * 6]; // AES128-CTR encrypted {titlekey[16]}
|
||||||
byte_t reserved_01[0x100];
|
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)
|
}; // sizeof() = 512*8 (8 pages)
|
||||||
|
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,14 +24,19 @@
|
||||||
<ClInclude Include="include\nx\AcidBinary.h" />
|
<ClInclude Include="include\nx\AcidBinary.h" />
|
||||||
<ClInclude Include="include\nx\AciHeader.h" />
|
<ClInclude Include="include\nx\AciHeader.h" />
|
||||||
<ClInclude Include="include\nx\AesKeygen.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\FacBinary.h" />
|
||||||
<ClInclude Include="include\nx\FacHeader.h" />
|
<ClInclude Include="include\nx\FacHeader.h" />
|
||||||
<ClInclude Include="include\nx\HandleTableSizeEntry.h" />
|
<ClInclude Include="include\nx\HandleTableSizeEntry.h" />
|
||||||
<ClInclude Include="include\nx\HandleTableSizeHandler.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\IKernelCapabilityHandler.h" />
|
||||||
<ClInclude Include="include\nx\InteruptEntry.h" />
|
<ClInclude Include="include\nx\InteruptEntry.h" />
|
||||||
<ClInclude Include="include\nx\InteruptHandler.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\KcBinary.h" />
|
||||||
<ClInclude Include="include\nx\KernelCapability.h" />
|
<ClInclude Include="include\nx\KernelCapability.h" />
|
||||||
<ClInclude Include="include\nx\KernelVersionEntry.h" />
|
<ClInclude Include="include\nx\KernelVersionEntry.h" />
|
||||||
|
@ -67,10 +72,13 @@
|
||||||
<ClCompile Include="source\AcidBinary.cpp" />
|
<ClCompile Include="source\AcidBinary.cpp" />
|
||||||
<ClCompile Include="source\AciHeader.cpp" />
|
<ClCompile Include="source\AciHeader.cpp" />
|
||||||
<ClCompile Include="source\AesKeygen.cpp" />
|
<ClCompile Include="source\AesKeygen.cpp" />
|
||||||
|
<ClCompile Include="source\ContentMetaBinary.cpp" />
|
||||||
<ClCompile Include="source\FacBinary.cpp" />
|
<ClCompile Include="source\FacBinary.cpp" />
|
||||||
<ClCompile Include="source\FacHeader.cpp" />
|
<ClCompile Include="source\FacHeader.cpp" />
|
||||||
<ClCompile Include="source\HandleTableSizeEntry.cpp" />
|
<ClCompile Include="source\HandleTableSizeEntry.cpp" />
|
||||||
<ClCompile Include="source\HandleTableSizeHandler.cpp" />
|
<ClCompile Include="source\HandleTableSizeHandler.cpp" />
|
||||||
|
<ClCompile Include="source\HierarchicalIntegrityHeader.cpp" />
|
||||||
|
<ClCompile Include="source\HierarchicalSha256Header.cpp" />
|
||||||
<ClCompile Include="source\InteruptEntry.cpp" />
|
<ClCompile Include="source\InteruptEntry.cpp" />
|
||||||
<ClCompile Include="source\InteruptHandler.cpp" />
|
<ClCompile Include="source\InteruptHandler.cpp" />
|
||||||
<ClCompile Include="source\KcBinary.cpp" />
|
<ClCompile Include="source\KcBinary.cpp" />
|
||||||
|
|
|
@ -123,9 +123,6 @@
|
||||||
<ClInclude Include="include\nx\AesKeygen.h">
|
<ClInclude Include="include\nx\AesKeygen.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="include\nx\ivfc.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="include\nx\NcaUtils.h">
|
<ClInclude Include="include\nx\NcaUtils.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
@ -141,6 +138,24 @@
|
||||||
<ClInclude Include="include\nx\XciUtils.h">
|
<ClInclude Include="include\nx\XciUtils.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</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>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="source\AciBinary.cpp">
|
<ClCompile Include="source\AciBinary.cpp">
|
||||||
|
@ -242,6 +257,15 @@
|
||||||
<ClCompile Include="source\XciUtils.cpp">
|
<ClCompile Include="source\XciUtils.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</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>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="makefile" />
|
<None Include="makefile" />
|
||||||
|
|
|
@ -106,12 +106,12 @@ void AciHeader::exportBinary()
|
||||||
|
|
||||||
// set offset/size
|
// set offset/size
|
||||||
calculateSectionOffsets();
|
calculateSectionOffsets();
|
||||||
hdr->fac.offset = mFac.offset;
|
hdr->fac.offset = (uint32_t)mFac.offset;
|
||||||
hdr->fac.size = mFac.size;
|
hdr->fac.size = (uint32_t)mFac.size;
|
||||||
hdr->sac.offset = mSac.offset;
|
hdr->sac.offset = (uint32_t)mSac.offset;
|
||||||
hdr->sac.size = mSac.size;
|
hdr->sac.size = (uint32_t)mSac.size;
|
||||||
hdr->kc.offset = mKc.offset;
|
hdr->kc.offset = (uint32_t)mKc.offset;
|
||||||
hdr->kc.size = mKc.size;
|
hdr->kc.size = (uint32_t)mKc.size;
|
||||||
|
|
||||||
uint32_t flags = 0;
|
uint32_t flags = 0;
|
||||||
if (mIsProduction)
|
if (mIsProduction)
|
||||||
|
@ -129,7 +129,7 @@ void AciHeader::exportBinary()
|
||||||
else if (mType == TYPE_ACID)
|
else if (mType == TYPE_ACID)
|
||||||
{
|
{
|
||||||
mAcidSize = getAciSize();
|
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.min = mProgramIdMin;
|
||||||
hdr->program_id_info.program_id_restrict.max = mProgramIdMax;
|
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);
|
hdr->fac_flags = (flag);
|
||||||
|
|
||||||
calculateOffsets();
|
calculateOffsets();
|
||||||
hdr->content_owner_ids.start = (mContentOwnerIdPos.offset);
|
hdr->content_owner_ids.start = (uint32_t)(mContentOwnerIdPos.offset);
|
||||||
hdr->content_owner_ids.end = (mContentOwnerIdPos.offset + mContentOwnerIdPos.size);
|
hdr->content_owner_ids.end = (uint32_t)(mContentOwnerIdPos.offset + mContentOwnerIdPos.size);
|
||||||
hdr->save_data_owner_ids.start = (mSaveDataOwnerIdPos.offset);
|
hdr->save_data_owner_ids.start = (uint32_t)(mSaveDataOwnerIdPos.offset);
|
||||||
hdr->save_data_owner_ids.end = (mSaveDataOwnerIdPos.offset + mSaveDataOwnerIdPos.size);
|
hdr->save_data_owner_ids.end = (uint32_t)(mSaveDataOwnerIdPos.offset + mSaveDataOwnerIdPos.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void nx::FacHeader::importBinary(const byte_t * bytes, size_t len)
|
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
|
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
|
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)
|
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);
|
strncpy(hdr->product_code, mProductCode.c_str(), npdm::kProductCodeMaxLen);
|
||||||
|
|
||||||
calculateOffsets();
|
calculateOffsets();
|
||||||
hdr->aci.offset = mAciPos.offset;
|
hdr->aci.offset = (uint32_t)mAciPos.offset;
|
||||||
hdr->aci.size = mAciPos.size;
|
hdr->aci.size = (uint32_t)mAciPos.size;
|
||||||
hdr->acid.offset = mAcidPos.offset;
|
hdr->acid.offset = (uint32_t)mAcidPos.offset;
|
||||||
hdr->acid.size = mAcidPos.size;
|
hdr->acid.size = (uint32_t)mAcidPos.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
void nx::NpdmHeader::importBinary(const byte_t * bytes, size_t len)
|
void nx::NpdmHeader::importBinary(const byte_t * bytes, size_t len)
|
||||||
|
|
|
@ -44,8 +44,8 @@ void nx::PfsHeader::exportBinary()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
hdr->file_num = mFileList.getSize();
|
hdr->file_num = (uint32_t)mFileList.getSize();
|
||||||
hdr->name_table_size = name_table_size;
|
hdr->name_table_size = (uint32_t)name_table_size;
|
||||||
|
|
||||||
// set file entries
|
// set file entries
|
||||||
if (mFsType == TYPE_PFS0)
|
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].data_offset = (mFileList[i].offset - pfs_header_size);
|
||||||
raw_files[i].size = mFileList[i].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());
|
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)
|
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].data_offset = (mFileList[i].offset - pfs_header_size);
|
||||||
raw_files[i].size = mFileList[i].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;
|
||||||
raw_files[i].hash_protected_size = mFileList[i].hash_protected_size;
|
raw_files[i].hash_protected_size = (uint32_t)mFileList[i].hash_protected_size;
|
||||||
raw_files[i].hash = mFileList[i].hash;
|
raw_files[i].hash = mFileList[i].hash;
|
||||||
|
|
||||||
strcpy(raw_name_table + raw_name_table_pos, mFileList[i].name.c_str());
|
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;
|
fnd::List<SystemCallEntry> entries;
|
||||||
for (size_t i = 0; i < kSyscallTotalEntryNum; i++)
|
for (size_t i = 0; i < kSyscallTotalEntryNum; i++)
|
||||||
{
|
{
|
||||||
entries[i].setSystemCallUpperBits(i);
|
entries[i].setSystemCallUpperBits((uint32_t)i);
|
||||||
entries[i].setSystemCallLowerBits(0);
|
entries[i].setSystemCallLowerBits(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -162,9 +162,15 @@
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<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\NcaProcess.h" />
|
||||||
<ClInclude Include="source\NpdmProcess.h" />
|
<ClInclude Include="source\NpdmProcess.h" />
|
||||||
<ClInclude Include="source\nstool.h" />
|
<ClInclude Include="source\nstool.h" />
|
||||||
|
<ClInclude Include="source\OffsetAdjustedIFile.h" />
|
||||||
<ClInclude Include="source\PfsProcess.h" />
|
<ClInclude Include="source\PfsProcess.h" />
|
||||||
<ClInclude Include="source\RomfsProcess.h" />
|
<ClInclude Include="source\RomfsProcess.h" />
|
||||||
<ClInclude Include="source\UserSettings.h" />
|
<ClInclude Include="source\UserSettings.h" />
|
||||||
|
@ -172,8 +178,14 @@
|
||||||
<ClInclude Include="source\XciProcess.h" />
|
<ClInclude Include="source\XciProcess.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<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\main.cpp" />
|
||||||
|
<ClCompile Include="source\NcaProcess.cpp" />
|
||||||
<ClCompile Include="source\NpdmProcess.cpp" />
|
<ClCompile Include="source\NpdmProcess.cpp" />
|
||||||
|
<ClCompile Include="source\OffsetAdjustedIFile.cpp" />
|
||||||
<ClCompile Include="source\PfsProcess.cpp" />
|
<ClCompile Include="source\PfsProcess.cpp" />
|
||||||
<ClCompile Include="source\RomfsProcess.cpp" />
|
<ClCompile Include="source\RomfsProcess.cpp" />
|
||||||
<ClCompile Include="source\UserSettings.cpp" />
|
<ClCompile Include="source\UserSettings.cpp" />
|
||||||
|
|
|
@ -39,6 +39,24 @@
|
||||||
<ClInclude Include="source\XciProcess.h">
|
<ClInclude Include="source\XciProcess.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</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>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="source\main.cpp">
|
<ClCompile Include="source\main.cpp">
|
||||||
|
@ -59,6 +77,24 @@
|
||||||
<ClCompile Include="source\RomfsProcess.cpp">
|
<ClCompile Include="source\RomfsProcess.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</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>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="makefile" />
|
<None Include="makefile" />
|
||||||
|
|
|
@ -1,42 +1,64 @@
|
||||||
#include "AesCtrWrappedIFile.h"
|
#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),
|
mFile(file),
|
||||||
mKey(key),
|
mKey(key),
|
||||||
mBaseCtr(ctr)
|
mBaseCtr(ctr),
|
||||||
|
mFileOffset(0)
|
||||||
{
|
{
|
||||||
mScratch.alloc(kAesCtrScratchAllocSize);
|
mCache.alloc(kCacheSizeAllocSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
AesCtrWrappedIFile::~AesCtrWrappedIFile()
|
||||||
|
{
|
||||||
|
if (mOwnIFile)
|
||||||
|
{
|
||||||
|
delete mFile;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t AesCtrWrappedIFile::size()
|
size_t AesCtrWrappedIFile::size()
|
||||||
{
|
{
|
||||||
return mFile.size();
|
return mFile->size();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AesCtrWrappedIFile::seek(size_t offset)
|
void AesCtrWrappedIFile::seek(size_t offset)
|
||||||
{
|
{
|
||||||
mFile.seek(offset);
|
mFileOffset = offset;
|
||||||
crypto::aes::AesIncrementCounter(mBaseCtr.iv, offset>>4, mCurrentCtr.iv);
|
|
||||||
mBlockOffset = offset & 0xf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AesCtrWrappedIFile::read(byte_t* out, size_t len)
|
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);
|
read_len = MIN(len - (i * kCacheSize), kCacheSize);
|
||||||
crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes());
|
read_pos = ((mFileOffset >> 4) << 4) + (i * kCacheSize);
|
||||||
memcpy(out + (i * kAesCtrScratchSize), mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize);
|
|
||||||
|
//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)
|
seek(mFileOffset + len);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AesCtrWrappedIFile::read(byte_t* out, size_t offset, size_t 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);
|
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++)
|
for (size_t i = 0; i < (len / kAesCtrScratchSize); i++)
|
||||||
{
|
{
|
||||||
memcpy(mScratch.getBytes() + mBlockOffset, out + (i * kAesCtrScratchSize), kAesCtrScratchSize);
|
memcpy(mScratch.getBytes() + mBlockOffset, out + (i * kAesCtrScratchSize), kAesCtrScratchSize);
|
||||||
crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes());
|
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)
|
if (len % kAesCtrScratchSize)
|
||||||
|
@ -60,12 +106,14 @@ void AesCtrWrappedIFile::write(const byte_t* out, size_t len)
|
||||||
size_t write_pos = ((len / kAesCtrScratchSize) * kAesCtrScratchSize);
|
size_t write_pos = ((len / kAesCtrScratchSize) * kAesCtrScratchSize);
|
||||||
memcpy(mScratch.getBytes() + mBlockOffset, out + write_pos, write_len);
|
memcpy(mScratch.getBytes() + mBlockOffset, out + write_pos, write_len);
|
||||||
crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes());
|
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);
|
seek(offset);
|
||||||
write(out, len);
|
write(in, len);
|
||||||
}
|
}
|
|
@ -5,7 +5,9 @@
|
||||||
class AesCtrWrappedIFile : public fnd::IFile
|
class AesCtrWrappedIFile : public fnd::IFile
|
||||||
{
|
{
|
||||||
public:
|
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();
|
size_t size();
|
||||||
void seek(size_t offset);
|
void seek(size_t offset);
|
||||||
|
@ -15,13 +17,16 @@ public:
|
||||||
void write(const byte_t* out, size_t offset, size_t len);
|
void write(const byte_t* out, size_t offset, size_t len);
|
||||||
private:
|
private:
|
||||||
const std::string kModuleName = "AesCtrWrappedIFile";
|
const std::string kModuleName = "AesCtrWrappedIFile";
|
||||||
static const size_t kAesCtrScratchSize = 0x1000000;
|
static const size_t kCacheSize = 0x10000;
|
||||||
static const size_t kAesCtrScratchAllocSize = kAesCtrScratchSize + crypto::aes::kAesBlockSize;
|
static const size_t kCacheSizeAllocSize = kCacheSize + crypto::aes::kAesBlockSize;
|
||||||
|
|
||||||
fnd::IFile& mFile;
|
bool mOwnIFile;
|
||||||
|
fnd::IFile* mFile;
|
||||||
crypto::aes::sAes128Key mKey;
|
crypto::aes::sAes128Key mKey;
|
||||||
crypto::aes::sAesIvCtr mBaseCtr, mCurrentCtr;
|
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/types.h>
|
||||||
#include <fnd/SimpleFile.h>
|
#include <fnd/SimpleFile.h>
|
||||||
#include <nx/NcaHeader.h>
|
#include <nx/NcaHeader.h>
|
||||||
|
#include "HashTreeMeta.h"
|
||||||
|
|
||||||
|
|
||||||
#include "nstool.h"
|
#include "nstool.h"
|
||||||
|
|
||||||
|
@ -15,8 +17,7 @@ public:
|
||||||
void process();
|
void process();
|
||||||
|
|
||||||
// generic
|
// generic
|
||||||
void setInputFile(fnd::IFile* reader);
|
void setInputFile(fnd::IFile* file, size_t offset, size_t size);
|
||||||
void setInputFileOffset(size_t offset);
|
|
||||||
void setKeyset(const sKeyset* keyset);
|
void setKeyset(const sKeyset* keyset);
|
||||||
void setCliOutputMode(CliOutputType type);
|
void setCliOutputMode(CliOutputType type);
|
||||||
void setVerifyMode(bool verify);
|
void setVerifyMode(bool verify);
|
||||||
|
@ -30,10 +31,10 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const std::string kModuleName = "NcaProcess";
|
const std::string kModuleName = "NcaProcess";
|
||||||
|
const std::string kNpdmExefsPath = "main.npdm";
|
||||||
|
|
||||||
// user options
|
// user options
|
||||||
fnd::IFile* mReader;
|
fnd::IFile* mReader;
|
||||||
size_t mOffset;
|
|
||||||
const sKeyset* mKeyset;
|
const sKeyset* mKeyset;
|
||||||
CliOutputType mCliOutputType;
|
CliOutputType mCliOutputType;
|
||||||
bool mVerify;
|
bool mVerify;
|
||||||
|
@ -54,15 +55,58 @@ private:
|
||||||
// crypto
|
// crypto
|
||||||
struct sKeys
|
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::sAes128Key> aes_ctr;
|
||||||
sOptional<crypto::aes::sAesXts128Key> aes_xts;
|
sOptional<crypto::aes::sAesXts128Key> aes_xts;
|
||||||
} mBodyKeys;
|
} mBodyKeys;
|
||||||
|
|
||||||
|
struct sPartitionInfo
|
||||||
|
{
|
||||||
|
fnd::IFile* reader;
|
||||||
|
std::string fail_reason;
|
||||||
|
size_t offset;
|
||||||
|
size_t size;
|
||||||
|
|
||||||
|
// meta data
|
||||||
void displayHeader();
|
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 generateNcaBodyEncryptionKeys();
|
||||||
|
void generatePartitionConfiguration();
|
||||||
|
void validateNcaSignatures();
|
||||||
|
void displayHeader();
|
||||||
void processPartitions();
|
void processPartitions();
|
||||||
};
|
};
|
|
@ -1,6 +1,5 @@
|
||||||
|
#include "OffsetAdjustedIFile.h"
|
||||||
#include "NpdmProcess.h"
|
#include "NpdmProcess.h"
|
||||||
#include <fnd/SimpleFile.h>
|
|
||||||
#include <fnd/MemoryBlob.h>
|
|
||||||
|
|
||||||
const std::string kInstructionType[2] = { "32Bit", "64Bit" };
|
const std::string kInstructionType[2] = { "32Bit", "64Bit" };
|
||||||
const std::string kProcAddrSpace[4] = { "Unknown", "64Bit", "32Bit", "32Bit no reserved" };
|
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)
|
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(" Flags: \n");
|
||||||
printf(" Production: %s\n", aci.isProduction() ? "TRUE" : "FALSE");
|
printf(" Production: %s\n", aci.isProduction() ? "TRUE" : "FALSE");
|
||||||
printf(" UnqualifiedApproval: %s\n", aci.isUnqualifiedApproval() ? "TRUE" : "FALSE");
|
printf(" UnqualifiedApproval: %s\n", aci.isUnqualifiedApproval() ? "TRUE" : "FALSE");
|
||||||
|
@ -619,13 +618,20 @@ void NpdmProcess::displayKernelCap(const nx::KcBinary& kern)
|
||||||
|
|
||||||
NpdmProcess::NpdmProcess() :
|
NpdmProcess::NpdmProcess() :
|
||||||
mReader(nullptr),
|
mReader(nullptr),
|
||||||
mOffset(0),
|
|
||||||
mKeyset(nullptr),
|
mKeyset(nullptr),
|
||||||
mCliOutputType(OUTPUT_NORMAL),
|
mCliOutputType(OUTPUT_NORMAL),
|
||||||
mVerify(false)
|
mVerify(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NpdmProcess::~NpdmProcess()
|
||||||
|
{
|
||||||
|
if (mReader != nullptr)
|
||||||
|
{
|
||||||
|
delete mReader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void NpdmProcess::process()
|
void NpdmProcess::process()
|
||||||
{
|
{
|
||||||
fnd::MemoryBlob scratch;
|
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;
|
mReader = new OffsetAdjustedIFile(file, offset, size);
|
||||||
}
|
|
||||||
|
|
||||||
void NpdmProcess::setInputFileOffset(size_t offset)
|
|
||||||
{
|
|
||||||
mOffset = offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NpdmProcess::setKeyset(const sKeyset* keyset)
|
void NpdmProcess::setKeyset(const sKeyset* keyset)
|
||||||
|
@ -688,4 +689,9 @@ void NpdmProcess::setCliOutputMode(CliOutputType type)
|
||||||
void NpdmProcess::setVerifyMode(bool verify)
|
void NpdmProcess::setVerifyMode(bool verify)
|
||||||
{
|
{
|
||||||
mVerify = verify;
|
mVerify = verify;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nx::NpdmBinary& NpdmProcess::getNpdmBinary() const
|
||||||
|
{
|
||||||
|
return mNpdm;
|
||||||
}
|
}
|
|
@ -10,20 +10,21 @@ class NpdmProcess
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NpdmProcess();
|
NpdmProcess();
|
||||||
|
~NpdmProcess();
|
||||||
|
|
||||||
void process();
|
void process();
|
||||||
|
|
||||||
void setInputFile(fnd::IFile& reader);
|
void setInputFile(fnd::IFile* file, size_t offset, size_t size);
|
||||||
void setInputFileOffset(size_t offset);
|
|
||||||
void setKeyset(const sKeyset* keyset);
|
void setKeyset(const sKeyset* keyset);
|
||||||
void setCliOutputMode(CliOutputType type);
|
void setCliOutputMode(CliOutputType type);
|
||||||
void setVerifyMode(bool verify);
|
void setVerifyMode(bool verify);
|
||||||
|
|
||||||
|
const nx::NpdmBinary& getNpdmBinary() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const std::string kModuleName = "NpdmProcess";
|
const std::string kModuleName = "NpdmProcess";
|
||||||
|
|
||||||
fnd::IFile* mReader;
|
fnd::IFile* mReader;
|
||||||
size_t mOffset;
|
|
||||||
const sKeyset* mKeyset;
|
const sKeyset* mKeyset;
|
||||||
CliOutputType mCliOutputType;
|
CliOutputType mCliOutputType;
|
||||||
bool mVerify;
|
bool mVerify;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "OffsetAdjustedIFile.h"
|
#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),
|
mFile(file),
|
||||||
mBaseOffset(offset),
|
mBaseOffset(offset),
|
||||||
mCurrentOffset(0),
|
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()
|
size_t OffsetAdjustedIFile::size()
|
||||||
{
|
{
|
||||||
return mSize;
|
return mSize;
|
||||||
|
@ -16,14 +35,15 @@ size_t OffsetAdjustedIFile::size()
|
||||||
|
|
||||||
void OffsetAdjustedIFile::seek(size_t offset)
|
void OffsetAdjustedIFile::seek(size_t offset)
|
||||||
{
|
{
|
||||||
mCurrentOffset = offset;
|
mCurrentOffset = MIN(offset, mSize);
|
||||||
mFile.seek(offset + mBaseOffset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffsetAdjustedIFile::read(byte_t* out, size_t len)
|
void OffsetAdjustedIFile::read(byte_t* out, size_t len)
|
||||||
{
|
{
|
||||||
seek(mCurrentOffset);
|
// assert proper position in file
|
||||||
mFile.read(out, len);
|
mFile->seek(mCurrentOffset + mBaseOffset);
|
||||||
|
mFile->read(out, len);
|
||||||
|
seek(mCurrentOffset + len);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffsetAdjustedIFile::read(byte_t* out, size_t offset, size_t 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)
|
void OffsetAdjustedIFile::write(const byte_t* out, size_t len)
|
||||||
{
|
{
|
||||||
seek(mCurrentOffset);
|
// assert proper position in file
|
||||||
mFile.write(out, len);
|
mFile->seek(mCurrentOffset + mBaseOffset);
|
||||||
|
mFile->write(out, len);
|
||||||
|
seek(mCurrentOffset + len);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffsetAdjustedIFile::write(const byte_t* out, size_t offset, size_t len)
|
void OffsetAdjustedIFile::write(const byte_t* out, size_t offset, size_t len)
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
class OffsetAdjustedIFile : public fnd::IFile
|
class OffsetAdjustedIFile : public fnd::IFile
|
||||||
{
|
{
|
||||||
public:
|
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();
|
size_t size();
|
||||||
void seek(size_t offset);
|
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 len);
|
||||||
void write(const byte_t* out, size_t offset, size_t len);
|
void write(const byte_t* out, size_t offset, size_t len);
|
||||||
private:
|
private:
|
||||||
fnd::IFile& mFile;
|
bool mOwnIFile;
|
||||||
|
fnd::IFile* mFile;
|
||||||
size_t mBaseOffset, mCurrentOffset;
|
size_t mBaseOffset, mCurrentOffset;
|
||||||
size_t mSize;
|
size_t mSize;
|
||||||
};
|
};
|
|
@ -1,104 +1,10 @@
|
||||||
#include "PfsProcess.h"
|
|
||||||
#include <fnd/SimpleFile.h>
|
#include <fnd/SimpleFile.h>
|
||||||
#include <fnd/io.h>
|
#include <fnd/io.h>
|
||||||
|
#include "OffsetAdjustedIFile.h"
|
||||||
void PfsProcess::displayHeader()
|
#include "PfsProcess.h"
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PfsProcess::PfsProcess() :
|
PfsProcess::PfsProcess() :
|
||||||
mReader(nullptr),
|
mReader(nullptr),
|
||||||
mOffset(0),
|
|
||||||
mKeyset(nullptr),
|
|
||||||
mCliOutputType(OUTPUT_NORMAL),
|
mCliOutputType(OUTPUT_NORMAL),
|
||||||
mVerify(false),
|
mVerify(false),
|
||||||
mExtractPath(),
|
mExtractPath(),
|
||||||
|
@ -107,7 +13,14 @@ PfsProcess::PfsProcess() :
|
||||||
mListFs(false),
|
mListFs(false),
|
||||||
mPfs()
|
mPfs()
|
||||||
{
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
PfsProcess::~PfsProcess()
|
||||||
|
{
|
||||||
|
if (mReader != nullptr)
|
||||||
|
{
|
||||||
|
delete mReader;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PfsProcess::process()
|
void PfsProcess::process()
|
||||||
|
@ -121,12 +34,16 @@ void PfsProcess::process()
|
||||||
|
|
||||||
// open minimum header to get full header size
|
// open minimum header to get full header size
|
||||||
scratch.alloc(sizeof(nx::sPfsHeader));
|
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()));
|
size_t pfsHeaderSize = determineHeaderSize(((nx::sPfsHeader*)scratch.getBytes()));
|
||||||
|
|
||||||
// open minimum header to get full header size
|
// open minimum header to get full header size
|
||||||
scratch.alloc(pfsHeaderSize);
|
scratch.alloc(pfsHeaderSize);
|
||||||
mReader->read(scratch.getBytes(), mOffset, scratch.getSize());
|
mReader->read(scratch.getBytes(), 0, scratch.getSize());
|
||||||
mPfs.importBinary(scratch.getBytes(), scratch.getSize());
|
mPfs.importBinary(scratch.getBytes(), scratch.getSize());
|
||||||
|
|
||||||
if (mCliOutputType >= OUTPUT_NORMAL)
|
if (mCliOutputType >= OUTPUT_NORMAL)
|
||||||
|
@ -139,19 +56,9 @@ void PfsProcess::process()
|
||||||
extractFs();
|
extractFs();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PfsProcess::setInputFile(fnd::IFile& reader)
|
void PfsProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size)
|
||||||
{
|
{
|
||||||
mReader = &reader;
|
mReader = new OffsetAdjustedIFile(file, offset, size);
|
||||||
}
|
|
||||||
|
|
||||||
void PfsProcess::setInputFileOffset(size_t offset)
|
|
||||||
{
|
|
||||||
mOffset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PfsProcess::setKeyset(const sKeyset* keyset)
|
|
||||||
{
|
|
||||||
mKeyset = keyset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PfsProcess::setCliOutputMode(CliOutputType type)
|
void PfsProcess::setCliOutputMode(CliOutputType type)
|
||||||
|
@ -184,3 +91,98 @@ const nx::PfsHeader& PfsProcess::getPfsHeader() const
|
||||||
{
|
{
|
||||||
return mPfs;
|
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:
|
public:
|
||||||
PfsProcess();
|
PfsProcess();
|
||||||
|
~PfsProcess();
|
||||||
|
|
||||||
void process();
|
void process();
|
||||||
|
|
||||||
// generic
|
// generic
|
||||||
void setInputFile(fnd::IFile& reader);
|
void setInputFile(fnd::IFile* file, size_t offset, size_t size);
|
||||||
void setInputFileOffset(size_t offset);
|
|
||||||
void setKeyset(const sKeyset* keyset);
|
|
||||||
void setCliOutputMode(CliOutputType type);
|
void setCliOutputMode(CliOutputType type);
|
||||||
void setVerifyMode(bool verify);
|
void setVerifyMode(bool verify);
|
||||||
|
|
||||||
|
@ -29,25 +28,25 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const std::string kModuleName = "PfsProcess";
|
const std::string kModuleName = "PfsProcess";
|
||||||
static const size_t kFileExportBlockSize = 0x1000000;
|
static const size_t kCacheSize = 0x10000;
|
||||||
|
|
||||||
fnd::IFile* mReader;
|
fnd::IFile* mReader;
|
||||||
size_t mOffset;
|
|
||||||
const sKeyset* mKeyset;
|
|
||||||
CliOutputType mCliOutputType;
|
CliOutputType mCliOutputType;
|
||||||
bool mVerify;
|
bool mVerify;
|
||||||
|
|
||||||
|
|
||||||
std::string mExtractPath;
|
std::string mExtractPath;
|
||||||
bool mExtract;
|
bool mExtract;
|
||||||
std::string mMountName;
|
std::string mMountName;
|
||||||
bool mListFs;
|
bool mListFs;
|
||||||
|
|
||||||
|
fnd::MemoryBlob mCache;
|
||||||
|
|
||||||
nx::PfsHeader mPfs;
|
nx::PfsHeader mPfs;
|
||||||
|
|
||||||
void displayHeader();
|
void displayHeader();
|
||||||
void displayFs();
|
void displayFs();
|
||||||
size_t determineHeaderSize(const nx::sPfsHeader* hdr);
|
size_t determineHeaderSize(const nx::sPfsHeader* hdr);
|
||||||
|
bool validateHeaderMagic(const nx::sPfsHeader* hdr);
|
||||||
void validateHfs();
|
void validateHfs();
|
||||||
void extractFs();
|
void extractFs();
|
||||||
};
|
};
|
|
@ -1,7 +1,85 @@
|
||||||
#include "RomfsProcess.h"
|
|
||||||
#include <fnd/SimpleTextOutput.h>
|
#include <fnd/SimpleTextOutput.h>
|
||||||
#include <fnd/SimpleFile.h>
|
#include <fnd/SimpleFile.h>
|
||||||
#include <fnd/io.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
|
void RomfsProcess::printTab(size_t tab) const
|
||||||
{
|
{
|
||||||
|
@ -43,8 +121,8 @@ void RomfsProcess::displayDir(const sDirectory& dir, size_t tab) const
|
||||||
void RomfsProcess::displayHeader()
|
void RomfsProcess::displayHeader()
|
||||||
{
|
{
|
||||||
printf("[RomFS]\n");
|
printf("[RomFS]\n");
|
||||||
printf(" DirNum: %u\n", mDirNum);
|
printf(" DirNum: %" PRId64 "\n", (uint64_t)mDirNum);
|
||||||
printf(" FileNum: %u\n", mFileNum);
|
printf(" FileNum: %" PRId64 "\n", (uint64_t)mFileNum);
|
||||||
if (mMountName.empty() == false)
|
if (mMountName.empty() == false)
|
||||||
printf(" MountPoint: %s%s\n", mMountName.c_str(), mMountName.at(mMountName.length()-1) != '/' ? "/" : "");
|
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)
|
if (dir.name.empty() == false)
|
||||||
fnd::io::appendToPath(dir_path, dir.name);
|
fnd::io::appendToPath(dir_path, dir.name);
|
||||||
|
|
||||||
//printf("dirpath=[%s]\n", dir_path.c_str());
|
|
||||||
|
|
||||||
// make directory
|
// make directory
|
||||||
fnd::io::makeDirectory(dir_path);
|
fnd::io::makeDirectory(dir_path);
|
||||||
|
|
||||||
|
|
||||||
// allocate memory for file extraction
|
|
||||||
fnd::MemoryBlob scratch;
|
|
||||||
scratch.alloc(kFileExportBlockSize);
|
|
||||||
|
|
||||||
// extract files
|
// extract files
|
||||||
fnd::SimpleFile outFile;
|
fnd::SimpleFile outFile;
|
||||||
for (size_t i = 0; i < dir.file_list.getSize(); i++)
|
for (size_t i = 0; i < dir.file_list.getSize(); i++)
|
||||||
|
@ -87,17 +158,12 @@ void RomfsProcess::extractDir(const std::string& path, const sDirectory& dir)
|
||||||
|
|
||||||
|
|
||||||
outFile.open(file_path, outFile.Create);
|
outFile.open(file_path, outFile.Create);
|
||||||
mReader->seek(mOffset + dir.file_list[i].offset);
|
mReader->seek(dir.file_list[i].offset);
|
||||||
for (size_t j = 0; j < (dir.file_list[i].size / kFileExportBlockSize); j++)
|
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);
|
mReader->read(mCache.getBytes(), MIN(dir.file_list[i].size - (kCacheSize * j),kCacheSize));
|
||||||
outFile.write(scratch.getBytes(), kFileExportBlockSize);
|
outFile.write(mCache.getBytes(), MIN(dir.file_list[i].size - (kCacheSize * j),kCacheSize));
|
||||||
}
|
}
|
||||||
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);
|
|
||||||
}
|
|
||||||
outFile.close();
|
outFile.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,6 +176,8 @@ void RomfsProcess::extractDir(const std::string& path, const sDirectory& dir)
|
||||||
|
|
||||||
void RomfsProcess::extractFs()
|
void RomfsProcess::extractFs()
|
||||||
{
|
{
|
||||||
|
// allocate only when extractDir is invoked
|
||||||
|
mCache.alloc(kCacheSize);
|
||||||
extractDir(mExtractPath, mRootDir);
|
extractDir(mExtractPath, mRootDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +233,7 @@ void RomfsProcess::importDirectory(uint32_t dir_offset, sDirectory& dir)
|
||||||
printf(" name=%s\n", f_node->name);
|
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();
|
file_addr = f_node->sibling.get();
|
||||||
mFileNum++;
|
mFileNum++;
|
||||||
|
@ -175,7 +243,7 @@ void RomfsProcess::importDirectory(uint32_t dir_offset, sDirectory& dir)
|
||||||
{
|
{
|
||||||
nx::sRomfsDirEntry* c_node = get_dir_node(child_addr);
|
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());
|
importDirectory(child_addr, dir.dir_list.atBack());
|
||||||
|
|
||||||
child_addr = c_node->sibling.get();
|
child_addr = c_node->sibling.get();
|
||||||
|
@ -186,7 +254,7 @@ void RomfsProcess::importDirectory(uint32_t dir_offset, sDirectory& dir)
|
||||||
void RomfsProcess::resolveRomfs()
|
void RomfsProcess::resolveRomfs()
|
||||||
{
|
{
|
||||||
// read header
|
// 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
|
// logic check on the header layout
|
||||||
if (validateHeaderLayout(&mHdr) == false)
|
if (validateHeaderLayout(&mHdr) == false)
|
||||||
|
@ -196,13 +264,13 @@ void RomfsProcess::resolveRomfs()
|
||||||
|
|
||||||
// read directory nodes
|
// read directory nodes
|
||||||
mDirNodes.alloc(mHdr.sections[nx::romfs::DIR_NODE_TABLE].size.get());
|
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");
|
//printf("[RAW DIR NODES]\n");
|
||||||
//fnd::SimpleTextOutput::hxdStyleDump(mDirNodes.getBytes(), mDirNodes.getSize());
|
//fnd::SimpleTextOutput::hxdStyleDump(mDirNodes.getBytes(), mDirNodes.getSize());
|
||||||
|
|
||||||
// read file nodes
|
// read file nodes
|
||||||
mFileNodes.alloc(mHdr.sections[nx::romfs::FILE_NODE_TABLE].size.get());
|
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");
|
//printf("[RAW FILE NODES]\n");
|
||||||
//fnd::SimpleTextOutput::hxdStyleDump(mFileNodes.getBytes(), mFileNodes.getSize());
|
//fnd::SimpleTextOutput::hxdStyleDump(mFileNodes.getBytes(), mFileNodes.getSize());
|
||||||
|
|
||||||
|
@ -219,85 +287,4 @@ void RomfsProcess::resolveRomfs()
|
||||||
mDirNum = 0;
|
mDirNum = 0;
|
||||||
mFileNum = 0;
|
mFileNum = 0;
|
||||||
importDirectory(0, mRootDir);
|
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();
|
||||||
|
~RomfsProcess();
|
||||||
|
|
||||||
void process();
|
void process();
|
||||||
|
|
||||||
// generic
|
// generic
|
||||||
void setInputFile(fnd::IFile& reader);
|
void setInputFile(fnd::IFile* file, size_t offset, size_t size);
|
||||||
void setInputFileOffset(size_t offset);
|
|
||||||
void setKeyset(const sKeyset* keyset);
|
|
||||||
void setCliOutputMode(CliOutputType type);
|
void setCliOutputMode(CliOutputType type);
|
||||||
void setVerifyMode(bool verify);
|
void setVerifyMode(bool verify);
|
||||||
|
|
||||||
|
@ -107,11 +106,9 @@ public:
|
||||||
const sDirectory& getRootDir() const;
|
const sDirectory& getRootDir() const;
|
||||||
private:
|
private:
|
||||||
const std::string kModuleName = "RomfsProcess";
|
const std::string kModuleName = "RomfsProcess";
|
||||||
static const size_t kFileExportBlockSize = 0x1000000;
|
static const size_t kCacheSize = 0x10000;
|
||||||
|
|
||||||
fnd::IFile* mReader;
|
fnd::IFile* mReader;
|
||||||
size_t mOffset;
|
|
||||||
const sKeyset* mKeyset;
|
|
||||||
CliOutputType mCliOutputType;
|
CliOutputType mCliOutputType;
|
||||||
bool mVerify;
|
bool mVerify;
|
||||||
|
|
||||||
|
@ -120,6 +117,8 @@ private:
|
||||||
std::string mMountName;
|
std::string mMountName;
|
||||||
bool mListFs;
|
bool mListFs;
|
||||||
|
|
||||||
|
fnd::MemoryBlob mCache;
|
||||||
|
|
||||||
size_t mDirNum;
|
size_t mDirNum;
|
||||||
size_t mFileNum;
|
size_t mFileNum;
|
||||||
nx::sRomfsHeader mHdr;
|
nx::sRomfsHeader mHdr;
|
||||||
|
@ -138,7 +137,6 @@ private:
|
||||||
void displayHeader();
|
void displayHeader();
|
||||||
void displayFs();
|
void displayFs();
|
||||||
|
|
||||||
void extractFile(const std::string& path, const sFile& file);
|
|
||||||
void extractDir(const std::string& path, const sDirectory& dir);
|
void extractDir(const std::string& path, const sDirectory& dir);
|
||||||
void extractFs();
|
void extractFs();
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ void UserSettings::showHelp()
|
||||||
printf("\n General Options:\n");
|
printf("\n General Options:\n");
|
||||||
printf(" -d, --dev Use devkit keyset\n");
|
printf(" -d, --dev Use devkit keyset\n");
|
||||||
printf(" -k, --keyset Specify keyset file\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(" -y, --verify Verify file\n");
|
||||||
printf(" -v, --verbose Verbose output\n");
|
printf(" -v, --verbose Verbose output\n");
|
||||||
printf(" -q, --quiet Minimal 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(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_2_STRINGS(kTicketCommonKeyBase[0], 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[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[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[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);
|
_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_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));
|
decodeHexStringToBytes("--titlekey", args.nca_titlekey.var, mKeyset.nca.manual_title_key_aesctr.key, sizeof(crypto::aes::sAes128Key));
|
||||||
}
|
}
|
||||||
|
@ -565,17 +565,20 @@ FileType UserSettings::getFileTypeFromString(const std::string& type_str)
|
||||||
FileType type;
|
FileType type;
|
||||||
if (str == "xci")
|
if (str == "xci")
|
||||||
type = FILE_XCI;
|
type = FILE_XCI;
|
||||||
else if ( str == "partitionfs" \
|
else if (str == "nsp")
|
||||||
|| str == "pfs" || str == "pfs0" \
|
type = FILE_NSP;
|
||||||
|| str == "hfs" || str == "hfs0" \
|
else if (str == "partitionfs" || str == "hashedpartitionfs" \
|
||||||
|| str == "nsp")
|
|| str == "pfs" || str == "pfs0" \
|
||||||
|
|| str == "hfs" || str == "hfs0")
|
||||||
type = FILE_PARTITIONFS;
|
type = FILE_PARTITIONFS;
|
||||||
else if (str == "romfs")
|
else if (str == "romfs")
|
||||||
type = FILE_ROMFS;
|
type = FILE_ROMFS;
|
||||||
else if (str == "nca")
|
else if (str == "nca")
|
||||||
type = FILE_NCA;
|
type = FILE_NCA;
|
||||||
else if (str == "npdm")
|
else if (str == "npdm")
|
||||||
type = FILE_NPDM;
|
type = FILE_NPDM;
|
||||||
|
else if (str == "cnmt")
|
||||||
|
type = FILE_CNMT;
|
||||||
else
|
else
|
||||||
type = FILE_INVALID;
|
type = FILE_INVALID;
|
||||||
|
|
||||||
|
@ -587,14 +590,14 @@ FileType UserSettings::determineFileTypeFromFile(const std::string& path)
|
||||||
static const size_t kMaxReadSize = 0x1000;
|
static const size_t kMaxReadSize = 0x1000;
|
||||||
FileType file_type = FILE_INVALID;
|
FileType file_type = FILE_INVALID;
|
||||||
fnd::SimpleFile file;
|
fnd::SimpleFile file;
|
||||||
fnd::MemoryBlob blob;
|
fnd::MemoryBlob scratch;
|
||||||
|
|
||||||
// open file
|
// open file
|
||||||
file.open(path, file.Read);
|
file.open(path, file.Read);
|
||||||
|
|
||||||
// read file
|
// read file
|
||||||
blob.alloc(MIN(kMaxReadSize, file.size()));
|
scratch.alloc(MIN(kMaxReadSize, file.size()));
|
||||||
file.read(blob.getBytes(), 0, blob.getSize());
|
file.read(scratch.getBytes(), 0, scratch.getSize());
|
||||||
// close file
|
// close file
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
|
@ -602,14 +605,14 @@ FileType UserSettings::determineFileTypeFromFile(const std::string& path)
|
||||||
byte_t nca_raw[nx::nca::kHeaderSize];
|
byte_t nca_raw[nx::nca::kHeaderSize];
|
||||||
nx::sNcaHeader* nca_header = (nx::sNcaHeader*)(nca_raw + nx::NcaUtils::sectorToOffset(1));
|
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'
|
// _QUICK_CAST resolves to a pointer of type 'st' located at scratch.getBytes() + 'oft'
|
||||||
#define _QUICK_CAST(st, oft) ((st*)(blob.getBytes() + (oft)))
|
#define _QUICK_CAST(st, oft) ((st*)(scratch.getBytes() + (oft)))
|
||||||
#define _ASSERT_SIZE(size) (blob.getSize() >= (size))
|
#define _ASSERT_SIZE(size) (scratch.getSize() >= (size))
|
||||||
|
|
||||||
// test npdm
|
// test npdm
|
||||||
if (_ASSERT_SIZE(sizeof(nx::sXciHeaderPage)) && std::string(_QUICK_CAST(nx::sXciHeaderPage, 0)->header.signature, 4) == nx::xci::kXciSig)
|
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 <fnd/SimpleTextOutput.h>
|
||||||
#include <nx/XciUtils.h>
|
#include <nx/XciUtils.h>
|
||||||
|
#include "OffsetAdjustedIFile.h"
|
||||||
|
#include "XciProcess.h"
|
||||||
|
|
||||||
inline const char* getBoolStr(bool isTrue)
|
inline const char* getBoolStr(bool isTrue)
|
||||||
{
|
{
|
||||||
|
@ -103,7 +104,9 @@ void XciProcess::displayHeader()
|
||||||
printf(" Wait1TimeWrite: 0x%x\n", mHdr.getWait1TimeWrite());
|
printf(" Wait1TimeWrite: 0x%x\n", mHdr.getWait1TimeWrite());
|
||||||
printf(" Wait2TimeWrite: 0x%x\n", mHdr.getWait2TimeWrite());
|
printf(" Wait2TimeWrite: 0x%x\n", mHdr.getWait2TimeWrite());
|
||||||
printf(" FwMode: 0x%x\n", mHdr.getFwMode());
|
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: ");
|
printf(" UppHash: ");
|
||||||
fnd::SimpleTextOutput::hexDump(mHdr.getUppHash(), 8);
|
fnd::SimpleTextOutput::hexDump(mHdr.getUppHash(), 8);
|
||||||
printf(" UppId: %016" PRIx64 "\n", mHdr.getUppId());
|
printf(" UppId: %016" PRIx64 "\n", mHdr.getUppId());
|
||||||
|
@ -136,13 +139,12 @@ void XciProcess::processRootPfs()
|
||||||
{
|
{
|
||||||
if (mVerify)
|
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");
|
printf("[WARNING] XCI Root HFS0: FAIL (bad hash)\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mRootPfs.setInputFile(*mReader);
|
mRootPfs.setInputFile(mReader, mHdr.getPartitionFsAddress(), mHdr.getPartitionFsSize());
|
||||||
mRootPfs.setInputFileOffset(mOffset + mHdr.getPartitionFsAddress());
|
|
||||||
mRootPfs.setListFs(mListFs);
|
mRootPfs.setListFs(mListFs);
|
||||||
mRootPfs.setVerifyMode(mVerify);
|
mRootPfs.setVerifyMode(mVerify);
|
||||||
mRootPfs.setCliOutputMode(mCliOutputType);
|
mRootPfs.setCliOutputMode(mCliOutputType);
|
||||||
|
@ -156,8 +158,7 @@ void XciProcess::processPartitionPfs()
|
||||||
for (size_t i = 0; i < rootPartitions.getSize(); i++)
|
for (size_t i = 0; i < rootPartitions.getSize(); i++)
|
||||||
{
|
{
|
||||||
PfsProcess tmp;
|
PfsProcess tmp;
|
||||||
tmp.setInputFile(*mReader);
|
tmp.setInputFile(mReader, mHdr.getPartitionFsAddress() + rootPartitions[i].offset, rootPartitions[i].size);
|
||||||
tmp.setInputFileOffset(mOffset + mHdr.getPartitionFsAddress() + rootPartitions[i].offset);
|
|
||||||
tmp.setListFs(mListFs);
|
tmp.setListFs(mListFs);
|
||||||
tmp.setVerifyMode(mVerify);
|
tmp.setVerifyMode(mVerify);
|
||||||
tmp.setCliOutputMode(mCliOutputType);
|
tmp.setCliOutputMode(mCliOutputType);
|
||||||
|
@ -174,7 +175,6 @@ void XciProcess::processPartitionPfs()
|
||||||
|
|
||||||
XciProcess::XciProcess() :
|
XciProcess::XciProcess() :
|
||||||
mReader(nullptr),
|
mReader(nullptr),
|
||||||
mOffset(0),
|
|
||||||
mKeyset(nullptr),
|
mKeyset(nullptr),
|
||||||
mCliOutputType(OUTPUT_NORMAL),
|
mCliOutputType(OUTPUT_NORMAL),
|
||||||
mVerify(false),
|
mVerify(false),
|
||||||
|
@ -189,6 +189,14 @@ XciProcess::XciProcess() :
|
||||||
mSecurePath.doExtract = false;
|
mSecurePath.doExtract = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
XciProcess::~XciProcess()
|
||||||
|
{
|
||||||
|
if (mReader != nullptr)
|
||||||
|
{
|
||||||
|
delete mReader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void XciProcess::process()
|
void XciProcess::process()
|
||||||
{
|
{
|
||||||
fnd::MemoryBlob scratch;
|
fnd::MemoryBlob scratch;
|
||||||
|
@ -199,7 +207,7 @@ void XciProcess::process()
|
||||||
}
|
}
|
||||||
|
|
||||||
// read header page
|
// 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
|
// allocate memory for and decrypt sXciHeader
|
||||||
scratch.alloc(sizeof(nx::sXciHeader));
|
scratch.alloc(sizeof(nx::sXciHeader));
|
||||||
|
@ -227,14 +235,9 @@ void XciProcess::process()
|
||||||
processPartitionPfs();
|
processPartitionPfs();
|
||||||
}
|
}
|
||||||
|
|
||||||
void XciProcess::setInputFile(fnd::IFile& reader)
|
void XciProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size)
|
||||||
{
|
{
|
||||||
mReader = &reader;
|
mReader = new OffsetAdjustedIFile(file, offset, size);
|
||||||
}
|
|
||||||
|
|
||||||
void XciProcess::setInputFileOffset(size_t offset)
|
|
||||||
{
|
|
||||||
mOffset = offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void XciProcess::setKeyset(const sKeyset* keyset)
|
void XciProcess::setKeyset(const sKeyset* keyset)
|
||||||
|
|
|
@ -13,12 +13,12 @@ class XciProcess
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
XciProcess();
|
XciProcess();
|
||||||
|
~XciProcess();
|
||||||
|
|
||||||
void process();
|
void process();
|
||||||
|
|
||||||
// generic
|
// generic
|
||||||
void setInputFile(fnd::IFile& reader);
|
void setInputFile(fnd::IFile* file, size_t offset, size_t size);
|
||||||
void setInputFileOffset(size_t offset);
|
|
||||||
void setKeyset(const sKeyset* keyset);
|
void setKeyset(const sKeyset* keyset);
|
||||||
void setCliOutputMode(CliOutputType type);
|
void setCliOutputMode(CliOutputType type);
|
||||||
void setVerifyMode(bool verify);
|
void setVerifyMode(bool verify);
|
||||||
|
@ -35,7 +35,6 @@ private:
|
||||||
static const size_t kFileExportBlockSize = 0x1000000;
|
static const size_t kFileExportBlockSize = 0x1000000;
|
||||||
|
|
||||||
fnd::IFile* mReader;
|
fnd::IFile* mReader;
|
||||||
size_t mOffset;
|
|
||||||
const sKeyset* mKeyset;
|
const sKeyset* mKeyset;
|
||||||
CliOutputType mCliOutputType;
|
CliOutputType mCliOutputType;
|
||||||
bool mVerify;
|
bool mVerify;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "RomfsProcess.h"
|
#include "RomfsProcess.h"
|
||||||
#include "NcaProcess.h"
|
#include "NcaProcess.h"
|
||||||
#include "NpdmProcess.h"
|
#include "NpdmProcess.h"
|
||||||
|
#include "CnmtProcess.h"
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
int main(int argc, char** argv)
|
||||||
|
@ -21,7 +22,7 @@ int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
XciProcess xci;
|
XciProcess xci;
|
||||||
|
|
||||||
xci.setInputFile(inputFile);
|
xci.setInputFile(&inputFile, 0, inputFile.size());
|
||||||
|
|
||||||
xci.setKeyset(&user_set.getKeyset());
|
xci.setKeyset(&user_set.getKeyset());
|
||||||
xci.setCliOutputMode(user_set.getCliOutputType());
|
xci.setCliOutputMode(user_set.getCliOutputType());
|
||||||
|
@ -37,12 +38,11 @@ int main(int argc, char** argv)
|
||||||
|
|
||||||
xci.process();
|
xci.process();
|
||||||
}
|
}
|
||||||
else if (user_set.getFileType() == FILE_PARTITIONFS)
|
else if (user_set.getFileType() == FILE_PARTITIONFS || user_set.getFileType() == FILE_NSP)
|
||||||
{
|
{
|
||||||
PfsProcess pfs;
|
PfsProcess pfs;
|
||||||
|
|
||||||
pfs.setInputFile(inputFile);
|
pfs.setInputFile(&inputFile, 0, inputFile.size());
|
||||||
pfs.setKeyset(&user_set.getKeyset());
|
|
||||||
pfs.setCliOutputMode(user_set.getCliOutputType());
|
pfs.setCliOutputMode(user_set.getCliOutputType());
|
||||||
pfs.setVerifyMode(user_set.isVerifyFile());
|
pfs.setVerifyMode(user_set.isVerifyFile());
|
||||||
|
|
||||||
|
@ -56,8 +56,7 @@ int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
RomfsProcess romfs;
|
RomfsProcess romfs;
|
||||||
|
|
||||||
romfs.setInputFile(inputFile);
|
romfs.setInputFile(&inputFile, 0, inputFile.size());
|
||||||
romfs.setKeyset(&user_set.getKeyset());
|
|
||||||
romfs.setCliOutputMode(user_set.getCliOutputType());
|
romfs.setCliOutputMode(user_set.getCliOutputType());
|
||||||
romfs.setVerifyMode(user_set.isVerifyFile());
|
romfs.setVerifyMode(user_set.isVerifyFile());
|
||||||
|
|
||||||
|
@ -71,7 +70,7 @@ int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
NcaProcess nca;
|
NcaProcess nca;
|
||||||
|
|
||||||
nca.setInputFile(&inputFile);
|
nca.setInputFile(&inputFile, 0, inputFile.size());
|
||||||
nca.setKeyset(&user_set.getKeyset());
|
nca.setKeyset(&user_set.getKeyset());
|
||||||
nca.setCliOutputMode(user_set.getCliOutputType());
|
nca.setCliOutputMode(user_set.getCliOutputType());
|
||||||
nca.setVerifyMode(user_set.isVerifyFile());
|
nca.setVerifyMode(user_set.isVerifyFile());
|
||||||
|
@ -93,13 +92,23 @@ int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
NpdmProcess npdm;
|
NpdmProcess npdm;
|
||||||
|
|
||||||
npdm.setInputFile(inputFile);
|
npdm.setInputFile(&inputFile, 0, inputFile.size());
|
||||||
npdm.setKeyset(&user_set.getKeyset());
|
npdm.setKeyset(&user_set.getKeyset());
|
||||||
npdm.setCliOutputMode(user_set.getCliOutputType());
|
npdm.setCliOutputMode(user_set.getCliOutputType());
|
||||||
npdm.setVerifyMode(user_set.isVerifyFile());
|
npdm.setVerifyMode(user_set.isVerifyFile());
|
||||||
|
|
||||||
npdm.process();
|
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) {
|
catch (const fnd::Exception& e) {
|
||||||
printf("\n\n%s\n", e.what());
|
printf("\n\n%s\n", e.what());
|
||||||
|
|
|
@ -13,10 +13,12 @@ static const size_t kNcaKeakNum = nx::nca::kKeyAreaEncryptionKeyNum;
|
||||||
enum FileType
|
enum FileType
|
||||||
{
|
{
|
||||||
FILE_XCI,
|
FILE_XCI,
|
||||||
|
FILE_NSP,
|
||||||
FILE_PARTITIONFS,
|
FILE_PARTITIONFS,
|
||||||
FILE_ROMFS,
|
FILE_ROMFS,
|
||||||
FILE_NCA,
|
FILE_NCA,
|
||||||
FILE_NPDM,
|
FILE_NPDM,
|
||||||
|
FILE_CNMT,
|
||||||
FILE_INVALID = -1,
|
FILE_INVALID = -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue