[nstool] Add NRO Support.

And cleaned up NSO support.
This commit is contained in:
jakcron 2018-06-09 23:32:35 +08:00
parent 7a09be650f
commit f5997c01f4
8 changed files with 661 additions and 380 deletions

View file

@ -0,0 +1,518 @@
#include <sstream>
#include <fnd/SimpleTextOutput.h>
#include <fnd/MemoryBlob.h>
#include <compress/lz4.h>
#include "OffsetAdjustedIFile.h"
#include "CodeObjectProcess.h"
CodeObjectProcess::CodeObjectProcess():
mFile(nullptr),
mOwnIFile(false),
mCliOutputType(OUTPUT_NORMAL),
mVerify(false),
mObjType(OBJ_INVALID),
mInstructionType(nx::npdm::INSTR_64BIT),
mListApi(false),
mListSymbols(false)
{
}
CodeObjectProcess::~CodeObjectProcess()
{
if (mOwnIFile)
{
delete mFile;
}
}
void CodeObjectProcess::process()
{
if (mFile == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
}
if (mObjType == OBJ_INVALID)
{
throw fnd::Exception(kModuleName, "Object type undefined.");
}
importHeader();
importCodeSegments();
importApiList();
if (mCliOutputType >= OUTPUT_NORMAL)
{
if (mObjType == OBJ_NSO)
displayNsoHeader();
else if (mObjType == OBJ_NRO)
displayNroHeader();
}
displayRoMetaData();
}
void CodeObjectProcess::setInputFile(fnd::IFile* file, bool ownIFile)
{
mFile = file;
mOwnIFile = ownIFile;
}
void CodeObjectProcess::setCliOutputMode(CliOutputType type)
{
mCliOutputType = type;
}
void CodeObjectProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void CodeObjectProcess::setCodeObjectType(CodeObjectType type)
{
mObjType = type;
}
void CodeObjectProcess::setInstructionType(nx::npdm::InstructionType type)
{
mInstructionType = type;
}
void CodeObjectProcess::setListApi(bool listApi)
{
mListApi = listApi;
}
void CodeObjectProcess::setListSymbols(bool listSymbols)
{
mListSymbols = listSymbols;
}
const nx::NsoHeader& CodeObjectProcess::getNsoHeader() const
{
return mNsoHdr;
}
const fnd::MemoryBlob& CodeObjectProcess::getTextBlob() const
{
return mTextBlob;
}
const fnd::MemoryBlob& CodeObjectProcess::getRoBlob() const
{
return mRoBlob;
}
const fnd::MemoryBlob& CodeObjectProcess::getDataBlob() const
{
return mDataBlob;
}
const std::vector<SdkApiString>& CodeObjectProcess::getApiList() const
{
return mApiList;
}
void CodeObjectProcess::importHeader()
{
if (mObjType == OBJ_NSO)
{
fnd::MemoryBlob scratch;
if (mFile->size() < sizeof(nx::sNsoHeader))
{
throw fnd::Exception(kModuleName, "Corrupt NSO file too small");
}
scratch.alloc(sizeof(nx::sNsoHeader));
mFile->read(scratch.getBytes(), 0, scratch.getSize());
mNsoHdr.importBinary(scratch.getBytes(), scratch.getSize());
}
else
{
fnd::MemoryBlob scratch;
if (mFile->size() < sizeof(nx::sNroHeader))
{
throw fnd::Exception(kModuleName, "Corrupt NSO file too small");
}
scratch.alloc(sizeof(nx::sNroHeader));
mFile->read(scratch.getBytes(), 0, scratch.getSize());
mNroHdr.importBinary(scratch.getBytes(), scratch.getSize());
}
}
void CodeObjectProcess::importCodeSegments()
{
if (mObjType == OBJ_NSO)
{
fnd::MemoryBlob scratch;
uint32_t decompressed_len;
crypto::sha::sSha256Hash calc_hash;
// process text segment
if (mNsoHdr.getTextSegmentInfo().is_compressed)
{
scratch.alloc(mNsoHdr.getTextSegmentInfo().file_layout.size);
mFile->read(scratch.getBytes(), mNsoHdr.getTextSegmentInfo().file_layout.offset, scratch.getSize());
mTextBlob.alloc(mNsoHdr.getTextSegmentInfo().memory_layout.size);
compress::lz4::decompressData(scratch.getBytes(), scratch.getSize(), mTextBlob.getBytes(), mTextBlob.getSize(), decompressed_len);
if (decompressed_len != mTextBlob.getSize())
{
throw fnd::Exception(kModuleName, "NSO text segment failed to decompress");
}
}
else
{
mTextBlob.alloc(mNsoHdr.getTextSegmentInfo().file_layout.size);
mFile->read(mTextBlob.getBytes(), mNsoHdr.getTextSegmentInfo().file_layout.offset, mTextBlob.getSize());
}
if (mNsoHdr.getTextSegmentInfo().is_hashed)
{
crypto::sha::Sha256(mTextBlob.getBytes(), mTextBlob.getSize(), calc_hash.bytes);
if (calc_hash != mNsoHdr.getTextSegmentInfo().hash)
{
throw fnd::Exception(kModuleName, "NSO text segment failed SHA256 verification");
}
}
// process ro segment
if (mNsoHdr.getRoSegmentInfo().is_compressed)
{
scratch.alloc(mNsoHdr.getRoSegmentInfo().file_layout.size);
mFile->read(scratch.getBytes(), mNsoHdr.getRoSegmentInfo().file_layout.offset, scratch.getSize());
mRoBlob.alloc(mNsoHdr.getRoSegmentInfo().memory_layout.size);
compress::lz4::decompressData(scratch.getBytes(), scratch.getSize(), mRoBlob.getBytes(), mRoBlob.getSize(), decompressed_len);
if (decompressed_len != mRoBlob.getSize())
{
throw fnd::Exception(kModuleName, "NSO ro segment failed to decompress");
}
}
else
{
mRoBlob.alloc(mNsoHdr.getRoSegmentInfo().file_layout.size);
mFile->read(mRoBlob.getBytes(), mNsoHdr.getRoSegmentInfo().file_layout.offset, mRoBlob.getSize());
}
if (mNsoHdr.getRoSegmentInfo().is_hashed)
{
crypto::sha::Sha256(mRoBlob.getBytes(), mRoBlob.getSize(), calc_hash.bytes);
if (calc_hash != mNsoHdr.getRoSegmentInfo().hash)
{
throw fnd::Exception(kModuleName, "NSO ro segment failed SHA256 verification");
}
}
// process data segment
if (mNsoHdr.getDataSegmentInfo().is_compressed)
{
scratch.alloc(mNsoHdr.getDataSegmentInfo().file_layout.size);
mFile->read(scratch.getBytes(), mNsoHdr.getDataSegmentInfo().file_layout.offset, scratch.getSize());
mDataBlob.alloc(mNsoHdr.getDataSegmentInfo().memory_layout.size);
compress::lz4::decompressData(scratch.getBytes(), scratch.getSize(), mDataBlob.getBytes(), mDataBlob.getSize(), decompressed_len);
if (decompressed_len != mDataBlob.getSize())
{
throw fnd::Exception(kModuleName, "NSO data segment failed to decompress");
}
}
else
{
mDataBlob.alloc(mNsoHdr.getDataSegmentInfo().file_layout.size);
mFile->read(mDataBlob.getBytes(), mNsoHdr.getDataSegmentInfo().file_layout.offset, mDataBlob.getSize());
}
if (mNsoHdr.getDataSegmentInfo().is_hashed)
{
crypto::sha::Sha256(mDataBlob.getBytes(), mDataBlob.getSize(), calc_hash.bytes);
if (calc_hash != mNsoHdr.getDataSegmentInfo().hash)
{
throw fnd::Exception(kModuleName, "NSO data segment failed SHA256 verification");
}
}
}
else if (mObjType == OBJ_NRO)
{
mTextBlob.alloc(mNroHdr.getTextInfo().size);
mFile->read(mTextBlob.getBytes(), mNroHdr.getTextInfo().memory_offset, mTextBlob.getSize());
mRoBlob.alloc(mNroHdr.getRoInfo().size);
mFile->read(mRoBlob.getBytes(), mNroHdr.getRoInfo().memory_offset, mRoBlob.getSize());
mDataBlob.alloc(mNroHdr.getDataInfo().size);
mFile->read(mDataBlob.getBytes(), mNroHdr.getDataInfo().memory_offset, mDataBlob.getSize());
}
}
void CodeObjectProcess::importApiList()
{
struct sLayout { size_t offset; size_t size; } api_info, dyn_str, dyn_sym;
if (mObjType == OBJ_NSO)
{
api_info.offset = mNsoHdr.getRoEmbeddedInfo().offset;
api_info.size = mNsoHdr.getRoEmbeddedInfo().size;
dyn_str.offset = mNsoHdr.getRoDynStrInfo().offset;
dyn_str.size = mNsoHdr.getRoDynStrInfo().size;
dyn_sym.offset = mNsoHdr.getRoDynSymInfo().offset;
dyn_sym.size = mNsoHdr.getRoDynSymInfo().size;
}
else
{
api_info.offset = mNroHdr.getRoEmbeddedInfo().memory_offset;
api_info.size = mNroHdr.getRoEmbeddedInfo().size;
dyn_str.offset = mNroHdr.getRoDynStrInfo().memory_offset;
dyn_str.size = mNroHdr.getRoDynStrInfo().size;
dyn_sym.offset = mNroHdr.getRoDynSymInfo().memory_offset;
dyn_sym.size = mNroHdr.getRoDynSymInfo().size;
}
if (api_info.size > 0)
{
std::stringstream list_stream(std::string((char*)mRoBlob.getBytes() + api_info.offset, api_info.size));
std::string api;
while(std::getline(list_stream, api, (char)0x00))
{
mApiList.push_back(api);
}
}
else
{
mApiList.clear();
}
if (dyn_sym.size > 0)
{
mDynSymbolList.parseData(mRoBlob.getBytes() + dyn_sym.offset, dyn_sym.size, mRoBlob.getBytes() + dyn_str.offset, dyn_str.size, mInstructionType == nx::npdm::INSTR_64BIT);
}
}
void CodeObjectProcess::displayNsoHeader()
{
#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("[NSO Header]\n");
printf(" ModuleId: ");
_HEXDUMP_L(mNsoHdr.getModuleId().data, nx::nso::kModuleIdSize);
printf("\n");
printf(" Program Segments:\n");
printf(" .module_name:\n");
printf(" FileOffset: 0x%" PRIx32 "\n", mNsoHdr.getModuleNameInfo().offset);
printf(" FileSize: 0x%" PRIx32 "\n", mNsoHdr.getModuleNameInfo().size);
printf(" .text:\n");
printf(" FileOffset: 0x%" PRIx32 "\n", mNsoHdr.getTextSegmentInfo().file_layout.offset);
printf(" FileSize: 0x%" PRIx32 "%s\n", mNsoHdr.getTextSegmentInfo().file_layout.size, mNsoHdr.getTextSegmentInfo().is_compressed? " (COMPRESSED)" : "");
printf(" .ro:\n");
printf(" FileOffset: 0x%" PRIx32 "\n", mNsoHdr.getRoSegmentInfo().file_layout.offset);
printf(" FileSize: 0x%" PRIx32 "%s\n", mNsoHdr.getRoSegmentInfo().file_layout.size, mNsoHdr.getRoSegmentInfo().is_compressed? " (COMPRESSED)" : "");
printf(" .data:\n");
printf(" FileOffset: 0x%" PRIx32 "\n", mNsoHdr.getDataSegmentInfo().file_layout.offset);
printf(" FileSize: 0x%" PRIx32 "%s\n", mNsoHdr.getDataSegmentInfo().file_layout.size, mNsoHdr.getDataSegmentInfo().is_compressed? " (COMPRESSED)" : "");
printf(" Program Sections:\n");
printf(" .text:\n");
printf(" MemoryOffset: 0x%" PRIx32 "\n", mNsoHdr.getTextSegmentInfo().memory_layout.offset);
printf(" MemorySize: 0x%" PRIx32 "\n", mNsoHdr.getTextSegmentInfo().memory_layout.size);
if (mNsoHdr.getTextSegmentInfo().is_hashed && mCliOutputType >= OUTPUT_VERBOSE)
{
printf(" Hash: ");
_HEXDUMP_L(mNsoHdr.getTextSegmentInfo().hash.bytes, 32);
printf("\n");
}
printf(" .ro:\n");
printf(" MemoryOffset: 0x%" PRIx32 "\n", mNsoHdr.getRoSegmentInfo().memory_layout.offset);
printf(" MemorySize: 0x%" PRIx32 "\n", mNsoHdr.getRoSegmentInfo().memory_layout.size);
if (mNsoHdr.getRoSegmentInfo().is_hashed && mCliOutputType >= OUTPUT_VERBOSE)
{
printf(" Hash: ");
_HEXDUMP_L(mNsoHdr.getRoSegmentInfo().hash.bytes, 32);
printf("\n");
}
if (mCliOutputType >= OUTPUT_VERBOSE)
{
printf(" .api_info:\n");
printf(" MemoryOffset: 0x%" PRIx32 "\n", mNsoHdr.getRoEmbeddedInfo().offset);
printf(" MemorySize: 0x%" PRIx32 "\n", mNsoHdr.getRoEmbeddedInfo().size);
printf(" .dynstr:\n");
printf(" MemoryOffset: 0x%" PRIx32 "\n", mNsoHdr.getRoDynStrInfo().offset);
printf(" MemorySize: 0x%" PRIx32 "\n", mNsoHdr.getRoDynStrInfo().size);
printf(" .dynsym:\n");
printf(" MemoryOffset: 0x%" PRIx32 "\n", mNsoHdr.getRoDynSymInfo().offset);
printf(" MemorySize: 0x%" PRIx32 "\n", mNsoHdr.getRoDynSymInfo().size);
}
printf(" .data:\n");
printf(" MemoryOffset: 0x%" PRIx32 "\n", mNsoHdr.getDataSegmentInfo().memory_layout.offset);
printf(" MemorySize: 0x%" PRIx32 "\n", mNsoHdr.getDataSegmentInfo().memory_layout.size);
if (mNsoHdr.getDataSegmentInfo().is_hashed && mCliOutputType >= OUTPUT_VERBOSE)
{
printf(" Hash: ");
_HEXDUMP_L(mNsoHdr.getDataSegmentInfo().hash.bytes, 32);
printf("\n");
}
printf(" .bss:\n");
printf(" MemorySize: 0x%" PRIx32 "\n", mNsoHdr.getBssSize());
#undef _HEXDUMP_L
}
void CodeObjectProcess::displayNroHeader()
{
#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("[NRO Header]\n");
printf(" RoCrt: ");
_HEXDUMP_L(mNroHdr.getRoCrt().data, nx::nro::kRoCrtSize);
printf("\n");
printf(" ModuleId: ");
_HEXDUMP_L(mNroHdr.getModuleId().data, nx::nro::kModuleIdSize);
printf("\n");
printf(" NroSize: 0x%" PRIx32 "\n", mNroHdr.getNroSize());
printf(" Program Sections:\n");
printf(" .text:\n");
printf(" Offset: 0x%" PRIx32 "\n", mNroHdr.getTextInfo().memory_offset);
printf(" Size: 0x%" PRIx32 "\n", mNroHdr.getTextInfo().size);
printf(" .ro:\n");
printf(" Offset: 0x%" PRIx32 "\n", mNroHdr.getRoInfo().memory_offset);
printf(" Size: 0x%" PRIx32 "\n", mNroHdr.getRoInfo().size);
if (mCliOutputType >= OUTPUT_VERBOSE)
{
printf(" .api_info:\n");
printf(" Offset: 0x%" PRIx32 "\n", mNroHdr.getRoEmbeddedInfo().memory_offset);
printf(" Size: 0x%" PRIx32 "\n", mNroHdr.getRoEmbeddedInfo().size);
printf(" .dynstr:\n");
printf(" Offset: 0x%" PRIx32 "\n", mNroHdr.getRoDynStrInfo().memory_offset);
printf(" Size: 0x%" PRIx32 "\n", mNroHdr.getRoDynStrInfo().size);
printf(" .dynsym:\n");
printf(" Offset: 0x%" PRIx32 "\n", mNroHdr.getRoDynSymInfo().memory_offset);
printf(" Size: 0x%" PRIx32 "\n", mNroHdr.getRoDynSymInfo().size);
}
printf(" .data:\n");
printf(" Offset: 0x%" PRIx32 "\n", mNroHdr.getDataInfo().memory_offset);
printf(" Size: 0x%" PRIx32 "\n", mNroHdr.getDataInfo().size);
printf(" .bss:\n");
printf(" Size: 0x%" PRIx32 "\n", mNroHdr.getBssSize());
#undef _HEXDUMP_L
}
void CodeObjectProcess::displayRoMetaData()
{
if (mApiList.size() > 0 && (mListApi || mCliOutputType > OUTPUT_NORMAL))
{
printf("[SDK API List]\n");
for (size_t i = 0; i < mApiList.size(); i++)
{
printf(" API %d:\n", (int)i);
printf(" Type: %s\n", getApiTypeStr(mApiList[i].getApiType()));
printf(" Vender: %s\n", mApiList[i].getVenderName().c_str());
printf(" Module: %s\n", mApiList[i].getModuleName().c_str());
}
}
if (mDynSymbolList.getDynamicSymbolList().getSize() > 0 && (mListSymbols || mCliOutputType > OUTPUT_NORMAL))
{
printf("[Symbol List]\n");
for (size_t i = 0; i < mDynSymbolList.getDynamicSymbolList().getSize(); i++)
{
const DynamicSymbolParser::sDynSymbol& symbol = mDynSymbolList.getDynamicSymbolList()[i];
printf(" %s [SHN=%s (%04x)][STT=%s]\n", symbol.name.c_str(), getSectionIndexStr(symbol.shn_index), symbol.shn_index, getSymbolTypeStr(symbol.symbol_type));
}
}
}
const char* CodeObjectProcess::getApiTypeStr(SdkApiString::ApiType type) const
{
const char* str;
switch (type)
{
case (SdkApiString::API_MIDDLEWARE):
str = "Middleware";
break;
case (SdkApiString::API_DEBUG):
str = "Debug";
break;
case (SdkApiString::API_PRIVATE):
str = "Private";
break;
case (SdkApiString::API_SDK_VERSION):
str = "SDK Version";
break;
default:
str = "UNKNOWN";
break;
}
return str;
}
const char* CodeObjectProcess::getSectionIndexStr(nx::dynsym::SpecialSectionIndex shn_index) const
{
const char* str;
switch (shn_index)
{
case (nx::dynsym::SHN_UNDEF):
str = "UNDEF";
break;
case (nx::dynsym::SHN_EXPORT):
str = "EXPORT";
break;
case (nx::dynsym::SHN_LOPROC):
str = "LOPROC";
break;
case (nx::dynsym::SHN_HIPROC):
str = "HIPROC";
break;
case (nx::dynsym::SHN_LOOS):
str = "LOOS";
break;
case (nx::dynsym::SHN_HIOS):
str = "HIOS";
break;
case (nx::dynsym::SHN_ABS):
str = "ABS";
break;
case (nx::dynsym::SHN_COMMON):
str = "COMMON";
break;
default:
str = "UNKNOWN";
break;
}
return str;
}
const char* CodeObjectProcess::getSymbolTypeStr(nx::dynsym::SymbolType symbol_type) const
{
const char* str;
switch (symbol_type)
{
case (nx::dynsym::STT_NOTYPE):
str = "NOTYPE";
break;
case (nx::dynsym::STT_OBJECT):
str = "OBJECT";
break;
case (nx::dynsym::STT_FUNC):
str = "FUNC";
break;
case (nx::dynsym::STT_SECTION):
str = "SECTION";
break;
case (nx::dynsym::STT_FILE):
str = "FILE";
break;
case (nx::dynsym::STT_LOOS):
str = "LOOS";
break;
case (nx::dynsym::STT_HIOS):
str = "HIOS";
break;
case (nx::dynsym::STT_LOPROC):
str = "LOPROC";
break;
case (nx::dynsym::STT_HIPROC):
str = "HIPROC";
break;
default:
str = "UNKNOWN";
break;
}
return str;
}

View file

@ -0,0 +1,74 @@
#pragma once
#include <vector>
#include <string>
#include <fnd/types.h>
#include <fnd/IFile.h>
#include <nx/npdm.h>
#include <nx/NsoHeader.h>
#include <nx/NroHeader.h>
#include "nstool.h"
#include "SdkApiString.h"
#include "DynamicSymbolParser.h"
class CodeObjectProcess
{
public:
enum CodeObjectType
{
OBJ_NSO,
OBJ_NRO,
OBJ_INVALID
};
CodeObjectProcess();
~CodeObjectProcess();
void process();
void setInputFile(fnd::IFile* file, bool ownIFile);
void setCliOutputMode(CliOutputType type);
void setVerifyMode(bool verify);
void setCodeObjectType(CodeObjectType type);
void setInstructionType(nx::npdm::InstructionType type);
void setListApi(bool listApi);
void setListSymbols(bool listSymbols);
// processed data
const nx::NsoHeader& getNsoHeader() const;
const fnd::MemoryBlob& getTextBlob() const;
const fnd::MemoryBlob& getRoBlob() const;
const fnd::MemoryBlob& getDataBlob() const;
const std::vector<SdkApiString>& getApiList() const;
private:
const std::string kModuleName = "CodeObjectProcess";
fnd::IFile* mFile;
bool mOwnIFile;
CliOutputType mCliOutputType;
bool mVerify;
CodeObjectType mObjType;
nx::npdm::InstructionType mInstructionType;
bool mListApi;
bool mListSymbols;
nx::NsoHeader mNsoHdr;
nx::NroHeader mNroHdr;
fnd::MemoryBlob mTextBlob, mRoBlob, mDataBlob;
std::vector<SdkApiString> mApiList;
DynamicSymbolParser mDynSymbolList;
void importHeader();
void importCodeSegments();
void importApiList();
void displayNsoHeader();
void displayNroHeader();
void displayRoMetaData();
const char* getApiTypeStr(SdkApiString::ApiType type) const;
const char* getSectionIndexStr(nx::dynsym::SpecialSectionIndex shn_index) const;
const char* getSymbolTypeStr(nx::dynsym::SymbolType symbol_type) const;
};

View file

@ -1,303 +0,0 @@
#include <sstream>
#include <fnd/SimpleTextOutput.h>
#include <fnd/MemoryBlob.h>
#include <compress/lz4.h>
#include "OffsetAdjustedIFile.h"
#include "NsoProcess.h"
NsoProcess::NsoProcess():
mFile(nullptr),
mOwnIFile(false),
mCliOutputType(OUTPUT_NORMAL),
mVerify(false)
{
mArchType.isSet = false;
}
NsoProcess::~NsoProcess()
{
if (mOwnIFile)
{
delete mFile;
}
}
void NsoProcess::process()
{
if (mFile == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
}
importHeader();
importCodeSegments();
importApiList();
if (mCliOutputType >= OUTPUT_NORMAL)
{
displayHeader();
displayRoApiList();
}
}
void NsoProcess::setInputFile(fnd::IFile* file, bool ownIFile)
{
mFile = file;
mOwnIFile = ownIFile;
}
void NsoProcess::setCliOutputMode(CliOutputType type)
{
mCliOutputType = type;
}
void NsoProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void NsoProcess::setArchType(nx::npdm::InstructionType type)
{
mArchType = type;
}
const nx::NsoHeader& NsoProcess::getNsoHeader() const
{
return mHdr;
}
const fnd::MemoryBlob& NsoProcess::getTextBlob() const
{
return mTextBlob;
}
const fnd::MemoryBlob& NsoProcess::getRoBlob() const
{
return mRoBlob;
}
const fnd::MemoryBlob& NsoProcess::getDataBlob() const
{
return mDataBlob;
}
const std::vector<std::string>& NsoProcess::getApiList() const
{
return mApiList;
}
void NsoProcess::importHeader()
{
fnd::MemoryBlob scratch;
if (mFile->size() < sizeof(nx::sNsoHeader))
{
throw fnd::Exception(kModuleName, "Corrupt NSO file too small");
}
scratch.alloc(sizeof(nx::sNsoHeader));
mFile->read(scratch.getBytes(), 0, scratch.getSize());
mHdr.importBinary(scratch.getBytes(), scratch.getSize());
}
void NsoProcess::importCodeSegments()
{
fnd::MemoryBlob scratch;
uint32_t decompressed_len;
crypto::sha::sSha256Hash calc_hash;
// process text segment
if (mHdr.getTextSegmentInfo().is_compressed)
{
scratch.alloc(mHdr.getTextSegmentInfo().file_layout.size);
mFile->read(scratch.getBytes(), mHdr.getTextSegmentInfo().file_layout.offset, scratch.getSize());
mTextBlob.alloc(mHdr.getTextSegmentInfo().memory_layout.size);
compress::lz4::decompressData(scratch.getBytes(), scratch.getSize(), mTextBlob.getBytes(), mTextBlob.getSize(), decompressed_len);
if (decompressed_len != mTextBlob.getSize())
{
throw fnd::Exception(kModuleName, "NSO text segment failed to decompress");
}
}
else
{
mTextBlob.alloc(mHdr.getTextSegmentInfo().file_layout.size);
mFile->read(mTextBlob.getBytes(), mHdr.getTextSegmentInfo().file_layout.offset, mTextBlob.getSize());
}
if (mHdr.getTextSegmentInfo().is_hashed)
{
crypto::sha::Sha256(mTextBlob.getBytes(), mTextBlob.getSize(), calc_hash.bytes);
if (calc_hash != mHdr.getTextSegmentInfo().hash)
{
throw fnd::Exception(kModuleName, "NSO text segment failed SHA256 verification");
}
}
// process ro segment
if (mHdr.getRoSegmentInfo().is_compressed)
{
scratch.alloc(mHdr.getRoSegmentInfo().file_layout.size);
mFile->read(scratch.getBytes(), mHdr.getRoSegmentInfo().file_layout.offset, scratch.getSize());
mRoBlob.alloc(mHdr.getRoSegmentInfo().memory_layout.size);
compress::lz4::decompressData(scratch.getBytes(), scratch.getSize(), mRoBlob.getBytes(), mRoBlob.getSize(), decompressed_len);
if (decompressed_len != mRoBlob.getSize())
{
throw fnd::Exception(kModuleName, "NSO ro segment failed to decompress");
}
}
else
{
mRoBlob.alloc(mHdr.getRoSegmentInfo().file_layout.size);
mFile->read(mRoBlob.getBytes(), mHdr.getRoSegmentInfo().file_layout.offset, mRoBlob.getSize());
}
if (mHdr.getRoSegmentInfo().is_hashed)
{
crypto::sha::Sha256(mRoBlob.getBytes(), mRoBlob.getSize(), calc_hash.bytes);
if (calc_hash != mHdr.getRoSegmentInfo().hash)
{
throw fnd::Exception(kModuleName, "NSO ro segment failed SHA256 verification");
}
}
// process data segment
if (mHdr.getDataSegmentInfo().is_compressed)
{
scratch.alloc(mHdr.getDataSegmentInfo().file_layout.size);
mFile->read(scratch.getBytes(), mHdr.getDataSegmentInfo().file_layout.offset, scratch.getSize());
mDataBlob.alloc(mHdr.getDataSegmentInfo().memory_layout.size);
compress::lz4::decompressData(scratch.getBytes(), scratch.getSize(), mDataBlob.getBytes(), mDataBlob.getSize(), decompressed_len);
if (decompressed_len != mDataBlob.getSize())
{
throw fnd::Exception(kModuleName, "NSO data segment failed to decompress");
}
}
else
{
mDataBlob.alloc(mHdr.getDataSegmentInfo().file_layout.size);
mFile->read(mDataBlob.getBytes(), mHdr.getDataSegmentInfo().file_layout.offset, mDataBlob.getSize());
}
if (mHdr.getDataSegmentInfo().is_hashed)
{
crypto::sha::Sha256(mDataBlob.getBytes(), mDataBlob.getSize(), calc_hash.bytes);
if (calc_hash != mHdr.getDataSegmentInfo().hash)
{
throw fnd::Exception(kModuleName, "NSO data segment failed SHA256 verification");
}
}
}
void NsoProcess::importApiList()
{
if (mHdr.getRoEmbeddedInfo().size > 0)
{
std::stringstream list_stream(std::string((char*)mRoBlob.getBytes() + mHdr.getRoEmbeddedInfo().offset, mHdr.getRoEmbeddedInfo().size));
std::string api;
while(std::getline(list_stream, api, (char)0x00))
{
mApiList.push_back(api);
}
}
else
{
mApiList.clear();
}
if (mHdr.getRoDynSymInfo().size > 0 && mArchType.isSet == true)
{
mDynSymbolList.parseData(mRoBlob.getBytes() + mHdr.getRoDynSymInfo().offset, mHdr.getRoDynSymInfo().size, mRoBlob.getBytes() + mHdr.getRoDynStrInfo().offset, mHdr.getRoDynStrInfo().size, *mArchType == nx::npdm::INSTR_64BIT);
}
}
void NsoProcess::displayHeader()
{
#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("[NSO Header]\n");
printf(" ModuleId: ");
_HEXDUMP_L(mHdr.getModuleId().data, nx::nso::kModuleIdSize);
printf("\n");
printf(" Program Segments:\n");
printf(" .module_name:\n");
printf(" FileOffset: 0x%" PRIx32 "\n", mHdr.getModuleNameInfo().offset);
printf(" FileSize: 0x%" PRIx32 "\n", mHdr.getModuleNameInfo().size);
printf(" .text:\n");
printf(" FileOffset: 0x%" PRIx32 "\n", mHdr.getTextSegmentInfo().file_layout.offset);
printf(" FileSize: 0x%" PRIx32 "%s\n", mHdr.getTextSegmentInfo().file_layout.size, mHdr.getTextSegmentInfo().is_compressed? " (COMPRESSED)" : "");
printf(" .ro:\n");
printf(" FileOffset: 0x%" PRIx32 "\n", mHdr.getRoSegmentInfo().file_layout.offset);
printf(" FileSize: 0x%" PRIx32 "%s\n", mHdr.getRoSegmentInfo().file_layout.size, mHdr.getRoSegmentInfo().is_compressed? " (COMPRESSED)" : "");
printf(" .data:\n");
printf(" FileOffset: 0x%" PRIx32 "\n", mHdr.getDataSegmentInfo().file_layout.offset);
printf(" FileSize: 0x%" PRIx32 "%s\n", mHdr.getDataSegmentInfo().file_layout.size, mHdr.getDataSegmentInfo().is_compressed? " (COMPRESSED)" : "");
printf(" Program Sections:\n");
printf(" .text:\n");
printf(" MemoryOffset: 0x%" PRIx32 "\n", mHdr.getTextSegmentInfo().memory_layout.offset);
printf(" MemorySize: 0x%" PRIx32 "\n", mHdr.getTextSegmentInfo().memory_layout.size);
if (mHdr.getTextSegmentInfo().is_hashed && mCliOutputType >= OUTPUT_VERBOSE)
{
printf(" Hash: ");
_HEXDUMP_L(mHdr.getTextSegmentInfo().hash.bytes, 32);
printf("\n");
}
printf(" .ro:\n");
printf(" MemoryOffset: 0x%" PRIx32 "\n", mHdr.getRoSegmentInfo().memory_layout.offset);
printf(" MemorySize: 0x%" PRIx32 "\n", mHdr.getRoSegmentInfo().memory_layout.size);
if (mHdr.getRoSegmentInfo().is_hashed && mCliOutputType >= OUTPUT_VERBOSE)
{
printf(" Hash: ");
_HEXDUMP_L(mHdr.getRoSegmentInfo().hash.bytes, 32);
printf("\n");
}
if (mCliOutputType >= OUTPUT_VERBOSE)
{
printf(" .api_info:\n");
printf(" MemoryOffset: 0x%" PRIx32 "\n", mHdr.getRoEmbeddedInfo().offset);
printf(" MemorySize: 0x%" PRIx32 "\n", mHdr.getRoEmbeddedInfo().size);
printf(" .dynstr:\n");
printf(" MemoryOffset: 0x%" PRIx32 "\n", mHdr.getRoDynStrInfo().offset);
printf(" MemorySize: 0x%" PRIx32 "\n", mHdr.getRoDynStrInfo().size);
printf(" .dynsym:\n");
printf(" MemoryOffset: 0x%" PRIx32 "\n", mHdr.getRoDynSymInfo().offset);
printf(" MemorySize: 0x%" PRIx32 "\n", mHdr.getRoDynSymInfo().size);
}
printf(" .data:\n");
printf(" MemoryOffset: 0x%" PRIx32 "\n", mHdr.getDataSegmentInfo().memory_layout.offset);
printf(" MemorySize: 0x%" PRIx32 "\n", mHdr.getDataSegmentInfo().memory_layout.size);
if (mHdr.getDataSegmentInfo().is_hashed && mCliOutputType >= OUTPUT_VERBOSE)
{
printf(" Hash: ");
_HEXDUMP_L(mHdr.getDataSegmentInfo().hash.bytes, 32);
printf("\n");
}
printf(" .bss:\n");
printf(" MemorySize: 0x%" PRIx32 "\n", mHdr.getBssSize());
#undef _HEXDUMP_L
}
void NsoProcess::displayRoApiList()
{
if (mApiList.size() > 0 || mDynSymbolList.getDynamicSymbolList().getSize() > 0)
{
printf("[NSO RO Segment]\n");
if (mApiList.size() > 0)
{
printf(" API List:\n");
for (size_t i = 0; i < mApiList.size(); i++)
{
printf(" %s\n", mApiList[i].c_str());
}
}
if (mDynSymbolList.getDynamicSymbolList().getSize() > 0)
{
printf(" Undefined Symbol List:\n");
for (size_t i = 0; i < mDynSymbolList.getDynamicSymbolList().getSize(); i++)
{
if (mDynSymbolList.getDynamicSymbolList()[i].shn_index == nx::dynsym::SHN_UNDEF && (mDynSymbolList.getDynamicSymbolList()[i].symbol_type == nx::dynsym::STT_FUNC || mDynSymbolList.getDynamicSymbolList()[i].symbol_type == nx::dynsym::STT_NOTYPE))
printf(" %s\n", mDynSymbolList.getDynamicSymbolList()[i].name.c_str());
}
}
}
}

View file

@ -1,53 +0,0 @@
#pragma once
#include <vector>
#include <string>
#include <fnd/types.h>
#include <fnd/IFile.h>
#include <nx/npdm.h>
#include <nx/NsoHeader.h>
#include "nstool.h"
#include "DynamicSymbolParser.h"
class NsoProcess
{
public:
NsoProcess();
~NsoProcess();
void process();
void setInputFile(fnd::IFile* file, bool ownIFile);
void setCliOutputMode(CliOutputType type);
void setVerifyMode(bool verify);
void setArchType(nx::npdm::InstructionType type);
// processed data
const nx::NsoHeader& getNsoHeader() const;
const fnd::MemoryBlob& getTextBlob() const;
const fnd::MemoryBlob& getRoBlob() const;
const fnd::MemoryBlob& getDataBlob() const;
const std::vector<std::string>& getApiList() const;
private:
const std::string kModuleName = "NsoProcess";
fnd::IFile* mFile;
bool mOwnIFile;
CliOutputType mCliOutputType;
bool mVerify;
sOptional<nx::npdm::InstructionType> mArchType;
nx::NsoHeader mHdr;
fnd::MemoryBlob mTextBlob, mRoBlob, mDataBlob;
std::vector<std::string> mApiList;
DynamicSymbolParser mDynSymbolList;
void importHeader();
void importCodeSegments();
void importApiList();
void displayHeader();
void displayRoApiList();
};

View file

@ -18,6 +18,7 @@
#include <nx/npdm.h>
#include <nx/romfs.h>
#include <nx/nso.h>
#include <nx/nro.h>
UserSettings::UserSettings()
{}
@ -39,7 +40,7 @@ void UserSettings::showHelp()
printf("\n General Options:\n");
printf(" -d, --dev Use devkit keyset\n");
printf(" -k, --keyset Specify keyset file\n");
printf(" -t, --type Specify input file type [xci, pfs, romfs, nca, npdm, cnmt, nso]\n");
printf(" -t, --type Specify input file type [xci, pfs, romfs, nca, npdm, cnmt, nso, nro]\n");
printf(" -y, --verify Verify file\n");
printf(" -v, --verbose Verbose output\n");
printf(" -q, --quiet Minimal output\n");
@ -63,9 +64,11 @@ void UserSettings::showHelp()
printf(" --part1 Extract \"partition 1\" to directory \n");
printf(" --part2 Extract \"partition 2\" to directory \n");
printf(" --part3 Extract \"partition 3\" to directory \n");
printf("\n NSO (Nintendo Software Object)\n");
printf(" nstool [--arch <architecture>] <.nso>\n");
printf(" --arch Specify code architecture [32bit, 64bit]\n");
printf("\n NSO (Nintendo Software Object), NRO (Nintendo Relocatable Object)\n");
printf(" nstool [--listapi --listsym] [--insttype <inst. type>] <file>\n");
printf(" --listapi Print SDK API List.\n");
printf(" --listsym Print Dynamic Symbols.\n");
printf(" --insttype Specify instruction type [64bit|32bit] (64bit is assumed).\n");
}
@ -99,9 +102,18 @@ bool UserSettings::isListFs() const
return mListFs;
}
const sOptional<nx::npdm::InstructionType>& UserSettings::getArchType() const
bool UserSettings::isListApi() const
{
return mArchType;
return mListApi;
}
bool UserSettings::isListSymbols() const
{
return mListSymbols;
}
nx::npdm::InstructionType UserSettings::getInstType() const
{
return mInstructionType;
}
const sOptional<std::string>& UserSettings::getXciUpdatePath() const
@ -289,10 +301,22 @@ void UserSettings::populateCmdArgs(int argc, char** argv, sCmdArgs& cmd_args)
cmd_args.part3_path = args[i+1];
}
else if (args[i] == "--arch")
else if (args[i] == "--listapi")
{
if (hasParamter) throw fnd::Exception(kModuleName, args[i] + " does not take a parameter.");
cmd_args.list_api = true;
}
else if (args[i] == "--listsym")
{
if (hasParamter) throw fnd::Exception(kModuleName, args[i] + " does not take a parameter.");
cmd_args.list_sym = true;
}
else if (args[i] == "--insttype")
{
if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter.");
cmd_args.arch_type = args[i + 1];
cmd_args.inst_type = args[i + 1];
}
else
@ -552,9 +576,14 @@ void UserSettings::populateUserSettings(sCmdArgs& args)
mNcaPart2Path = args.part2_path;
mNcaPart3Path = args.part3_path;
// determine the architecture type for NSO
if (args.arch_type.isSet)
mArchType = getInstructionTypeFromString(*args.arch_type);
// determine the architecture type for NSO/NRO
if (args.inst_type.isSet)
mInstructionType = getInstructionTypeFromString(*args.inst_type);
else
mInstructionType = nx::npdm::INSTR_64BIT; // default 64bit
mListApi = args.list_api.isSet;
mListSymbols = args.list_sym.isSet;
// determine output path
if (args.verbose_output.isSet)
@ -614,6 +643,8 @@ FileType UserSettings::getFileTypeFromString(const std::string& type_str)
type = FILE_CNMT;
else if (str == "nso")
type = FILE_NSO;
else if (str == "nro")
type = FILE_NRO;
else
type = FILE_INVALID;
@ -636,6 +667,8 @@ FileType UserSettings::determineFileTypeFromFile(const std::string& path)
// close file
file.close();
fnd::SimpleTextOutput::hxdStyleDump(scratch.getBytes(), scratch.getSize());
// prepare decrypted NCA data
byte_t nca_raw[nx::nca::kHeaderSize];
nx::sNcaHeader* nca_header = (nx::sNcaHeader*)(nca_raw + nx::NcaUtils::sectorToOffset(1));
@ -673,6 +706,9 @@ FileType UserSettings::determineFileTypeFromFile(const std::string& path)
// test nso
else if (_ASSERT_SIZE(sizeof(nx::sNsoHeader)) && _QUICK_CAST(nx::sNsoHeader, 0)->signature.get() == nx::nso::kNsoSig)
file_type = FILE_NSO;
// test nso
else if (_ASSERT_SIZE(sizeof(nx::sNroHeader)) && _QUICK_CAST(nx::sNroHeader, 0)->signature.get() == nx::nro::kNroSig)
file_type = FILE_NRO;
// else unrecognised
else
file_type = FILE_INVALID;
@ -694,7 +730,7 @@ nx::npdm::InstructionType UserSettings::getInstructionTypeFromString(const std::
else if (str == "64bit")
type = nx::npdm::INSTR_64BIT;
else
throw fnd::Exception(kModuleName, "Unsupported architecture type: " + str);
throw fnd::Exception(kModuleName, "Unsupported instruction type: " + str);
return type;
}

View file

@ -21,7 +21,9 @@ public:
// specialised toggles
bool isListFs() const;
const sOptional<nx::npdm::InstructionType>& getArchType() const;
bool isListApi() const;
bool isListSymbols() const;
nx::npdm::InstructionType getInstType() const;
// specialised paths
const sOptional<std::string>& getXciUpdatePath() const;
@ -59,7 +61,9 @@ private:
sOptional<std::string> part1_path;
sOptional<std::string> part2_path;
sOptional<std::string> part3_path;
sOptional<std::string> arch_type;
sOptional<bool> list_api;
sOptional<bool> list_sym;
sOptional<std::string> inst_type;
};
std::string mInputPath;
@ -80,7 +84,9 @@ private:
sOptional<std::string> mNcaPart2Path;
sOptional<std::string> mNcaPart3Path;
sOptional<nx::npdm::InstructionType> mArchType;
bool mListApi;
bool mListSymbols;
nx::npdm::InstructionType mInstructionType;
void populateCmdArgs(int argc, char** argv, sCmdArgs& cmd_args);
void populateKeyset(sCmdArgs& args);

View file

@ -7,7 +7,7 @@
#include "NcaProcess.h"
#include "NpdmProcess.h"
#include "CnmtProcess.h"
#include "NsoProcess.h"
#include "CodeObjectProcess.h"
int main(int argc, char** argv)
{
@ -108,18 +108,20 @@ int main(int argc, char** argv)
cnmt.process();
}
else if (user_set.getFileType() == FILE_NSO)
else if (user_set.getFileType() == FILE_NSO || user_set.getFileType() == FILE_NRO)
{
NsoProcess nso;
CodeObjectProcess obj;
nso.setInputFile(new fnd::SimpleFile(user_set.getInputPath(), fnd::SimpleFile::Read), OWN_IFILE);
nso.setCliOutputMode(user_set.getCliOutputType());
nso.setVerifyMode(user_set.isVerifyFile());
obj.setInputFile(new fnd::SimpleFile(user_set.getInputPath(), fnd::SimpleFile::Read), OWN_IFILE);
obj.setCliOutputMode(user_set.getCliOutputType());
obj.setVerifyMode(user_set.isVerifyFile());
if (user_set.getArchType().isSet)
nso.setArchType(user_set.getArchType().var);
obj.setCodeObjectType(user_set.getFileType() == FILE_NSO ? obj.OBJ_NSO : obj.OBJ_NRO);
obj.setInstructionType(user_set.getInstType());
obj.setListApi(user_set.isListApi());
obj.setListSymbols(user_set.isListSymbols());
nso.process();
obj.process();
}
}
catch (const fnd::Exception& e) {

View file

@ -25,6 +25,7 @@ enum FileType
FILE_NPDM,
FILE_CNMT,
FILE_NSO,
FILE_NRO,
FILE_INVALID = -1,
};