diff --git a/README.md b/README.md index 7cff58d..37c26fb 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Tools & Libraries for NX (Nintendo Switch). # Tools -* __nstool__ - read *.npdm, read/extract PartitionFS (PFS0|HFS0) blobs (including *.nsp), read/extract *.xci, read/extract *.nca, read *.cnmt, read *.nso +* __nstool__ - read *.npdm, read/extract PartitionFS (PFS0|HFS0) blobs (including *.nsp), read/extract RomFS blobs, read/extract *.xci, read/extract *.nca, read *.cnmt, read *.nso, read *.nro # Libraries diff --git a/lib/libnx/include/nx/NroHeader.h b/lib/libnx/include/nx/NroHeader.h new file mode 100644 index 0000000..f259e5d --- /dev/null +++ b/lib/libnx/include/nx/NroHeader.h @@ -0,0 +1,147 @@ +#pragma once +#include +#include +#include +#include + +namespace nx +{ + class NroHeader : + public fnd::ISerialiseableBinary + { + public: + struct sRoCrt + { + byte_t data[nro::kRoCrtSize]; + + void operator=(const sRoCrt& other) + { + memcpy(data, other.data, nro::kRoCrtSize); + } + + bool operator==(const sRoCrt& other) const + { + return memcmp(data, other.data, nro::kRoCrtSize) == 0; + } + + bool operator!=(const sRoCrt& other) const + { + return !(*this == other); + } + }; + + struct sModuleId + { + byte_t data[nro::kModuleIdSize]; + + void operator=(const sModuleId& other) + { + memcpy(data, other.data, nro::kModuleIdSize); + } + + bool operator==(const sModuleId& other) const + { + return memcmp(data, other.data, nro::kModuleIdSize) == 0; + } + + bool operator!=(const sModuleId& other) const + { + return !(*this == other); + } + }; + + struct sSection + { + uint32_t memory_offset; + uint32_t size; + + void operator=(const sSection& other) + { + memory_offset = other.memory_offset; + size = other.size; + } + + bool operator==(const sSection& other) const + { + return (memory_offset == other.memory_offset) \ + && (size == other.size); + } + + bool operator!=(const sSection& other) const + { + return !(*this == other); + } + }; + + NroHeader(); + NroHeader(const NroHeader& other); + NroHeader(const byte_t* bytes, size_t len); + + bool operator==(const NroHeader& other) const; + bool operator!=(const NroHeader& other) const; + void operator=(const NroHeader& 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 sRoCrt& getRoCrt() const; + void setRoCrt(const sRoCrt& ro_crt); + + uint32_t getNroSize() const; + void setNroSize(uint32_t size); + + const sSection& getTextInfo() const; + void setTextInfo(const sSection& info); + + const sSection& getRoInfo() const; + void setRoInfo(const sSection& info); + + const sSection& getDataInfo() const; + void setDataInfo(const sSection& info); + + uint32_t getBssSize() const; + void setBssSize(uint32_t size); + + const sModuleId& getModuleId() const; + void setModuleId(const sModuleId& id); + + const sSection& getRoEmbeddedInfo() const; + void setRoEmbeddedInfo(const sSection& info); + + const sSection& getRoDynStrInfo() const; + void setRoDynStrInfo(const sSection& info); + + const sSection& getRoDynSymInfo() const; + void setRoDynSymInfo(const sSection& info); + private: + const std::string kModuleName = "NRO_HEADER"; + + // binary + fnd::MemoryBlob mBinaryBlob; + + // data + sRoCrt mRoCrt; + uint32_t mNroSize; + sSection mTextInfo; + sSection mRoInfo; + sSection mDataInfo; + uint32_t mBssSize; + sModuleId mModuleId; + sSection mRoEmbeddedInfo; + sSection mRoDynStrInfo; + sSection mRoDynSymInfo; + + // helpers + bool isEqual(const NroHeader& other) const; + void copyFrom(const NroHeader& other); + }; + +} \ No newline at end of file diff --git a/lib/libnx/include/nx/dynamic_symbol.h b/lib/libnx/include/nx/dynamic_symbol.h index 899c03c..ca1944a 100644 --- a/lib/libnx/include/nx/dynamic_symbol.h +++ b/lib/libnx/include/nx/dynamic_symbol.h @@ -8,12 +8,13 @@ namespace nx enum SpecialSectionIndex { SHN_UNDEF, - SHN_LORESERVE = 65280, - SHN_LOPROC = 65280, - SHN_HIPROC = 65311, + SHN_EXPORT = 1, + SHN_LORESERVE = 0xFF00, + SHN_LOPROC = 0xFF00, + SHN_HIPROC = 0xFF1F, SHN_LOOS, - SHN_HIOS = 65343, - SHN_ABS = 65521, + SHN_HIOS = 0xFF3F, + SHN_ABS = 0xFFF1, SHN_COMMON, SHN_HIRESERVE = 0xFFFF }; diff --git a/lib/libnx/source/NroHeader.cpp b/lib/libnx/source/NroHeader.cpp new file mode 100644 index 0000000..d79f5fb --- /dev/null +++ b/lib/libnx/source/NroHeader.cpp @@ -0,0 +1,248 @@ +#include + +nx::NroHeader::NroHeader() +{ + clear(); +} + +nx::NroHeader::NroHeader(const NroHeader& other) +{ + copyFrom(other); +} + +nx::NroHeader::NroHeader(const byte_t* bytes, size_t len) +{ + importBinary(bytes, len); +} + +bool nx::NroHeader::operator==(const NroHeader& other) const +{ + return isEqual(other); +} + +bool nx::NroHeader::operator!=(const NroHeader& other) const +{ + return !(*this != other); +} + +void nx::NroHeader::operator=(const NroHeader& other) +{ + copyFrom(other); +} + +const byte_t* nx::NroHeader::getBytes() const +{ + return mBinaryBlob.getBytes(); +} + +size_t nx::NroHeader::getSize() const +{ + return mBinaryBlob.getSize(); +} + +void nx::NroHeader::exportBinary() +{ + throw fnd::Exception(kModuleName, "exportBinary() unsupported"); +} + +void nx::NroHeader::importBinary(const byte_t* bytes, size_t len) +{ + // check input data size + if (len < sizeof(sNroHeader)) + { + throw fnd::Exception(kModuleName, "NRO header size is too small"); + } + + // clear internal members + clear(); + + // allocate internal local binary copy + mBinaryBlob.alloc(sizeof(sNroHeader)); + memcpy(mBinaryBlob.getBytes(), bytes, mBinaryBlob.getSize()); + + // get sNroHeader ptr + const nx::sNroHeader* hdr = (const nx::sNroHeader*)mBinaryBlob.getBytes(); + + // check NRO signature + if (hdr->signature.get() != nro::kNroSig) + { + throw fnd::Exception(kModuleName, "NRO header corrupt (unrecognised header signature)"); + } + + // check NRO format version + if (hdr->format_version.get() != nro::kDefaultFormatVersion) + { + throw fnd::Exception(kModuleName, "NRO header corrupt (unsupported format version)"); + } + + // check NRO flags + if (hdr->flags.get() != 0) + { + throw fnd::Exception(kModuleName, "NRO header corrupt (unsupported flag)"); + } + + memcpy(mRoCrt.data, hdr->ro_crt, nro::kRoCrtSize); + mNroSize = hdr->size.get(); + mTextInfo.memory_offset = hdr->text.memory_offset.get(); + mTextInfo.size = hdr->text.size.get(); + mRoInfo.memory_offset = hdr->ro.memory_offset.get(); + mRoInfo.size = hdr->ro.size.get(); + mDataInfo.memory_offset = hdr->data.memory_offset.get(); + mDataInfo.size = hdr->data.size.get(); + mBssSize = hdr->bss_size.get(); + memcpy(mModuleId.data, hdr->module_id, nro::kModuleIdSize); + + mRoEmbeddedInfo.memory_offset = hdr->embedded.memory_offset.get(); + mRoEmbeddedInfo.size = hdr->embedded.size.get(); + + mRoDynStrInfo.memory_offset = hdr->dyn_str.memory_offset.get(); + mRoDynStrInfo.size = hdr->dyn_str.size.get(); + + mRoDynSymInfo.memory_offset = hdr->dyn_sym.memory_offset.get(); + mRoDynSymInfo.size = hdr->dyn_sym.size.get(); +} + +void nx::NroHeader::clear() +{ + mBinaryBlob.clear(); + memset(&mRoCrt, 0, sizeof(mRoCrt)); + memset(&mTextInfo, 0, sizeof(mTextInfo)); + memset(&mRoInfo, 0, sizeof(mRoInfo)); + memset(&mDataInfo, 0, sizeof(mDataInfo)); + mBssSize = 0; + memset(&mModuleId, 0, sizeof(mModuleId)); + memset(&mRoEmbeddedInfo, 0, sizeof(mRoEmbeddedInfo)); + memset(&mRoDynStrInfo, 0, sizeof(mRoDynStrInfo)); + memset(&mRoDynSymInfo, 0, sizeof(mRoDynSymInfo)); +} + +const nx::NroHeader::sRoCrt& nx::NroHeader::getRoCrt() const +{ + return mRoCrt; +} + +void nx::NroHeader::setRoCrt(const sRoCrt& ro_crt) +{ + mRoCrt = ro_crt; +} + +uint32_t nx::NroHeader::getNroSize() const +{ + return mNroSize; +} + +void nx::NroHeader::setNroSize(uint32_t size) +{ + mNroSize = size; +} + +const nx::NroHeader::sSection& nx::NroHeader::getTextInfo() const +{ + return mTextInfo; +} + +void nx::NroHeader::setTextInfo(const sSection& info) +{ + mTextInfo = info; +} + +const nx::NroHeader::sSection& nx::NroHeader::getRoInfo() const +{ + return mRoInfo; +} + +void nx::NroHeader::setRoInfo(const sSection& info) +{ + mRoInfo = info; +} + +const nx::NroHeader::sSection& nx::NroHeader::getDataInfo() const +{ + return mDataInfo; +} + +void nx::NroHeader::setDataInfo(const sSection& info) +{ + mDataInfo = info; +} + +uint32_t nx::NroHeader::getBssSize() const +{ + return mBssSize; +} + +void nx::NroHeader::setBssSize(uint32_t size) +{ + mBssSize = size; +} + +const nx::NroHeader::sModuleId& nx::NroHeader::getModuleId() const +{ + return mModuleId; +} + +void nx::NroHeader::setModuleId(const sModuleId& id) +{ + mModuleId = id; +} + +const nx::NroHeader::sSection& nx::NroHeader::getRoEmbeddedInfo() const +{ + return mRoEmbeddedInfo; +} + +void nx::NroHeader::setRoEmbeddedInfo(const sSection& info) +{ + mRoEmbeddedInfo = info; +} + +const nx::NroHeader::sSection& nx::NroHeader::getRoDynStrInfo() const +{ + return mRoDynStrInfo; +} + +void nx::NroHeader::setRoDynStrInfo(const sSection& info) +{ + mRoDynStrInfo = info; +} + +const nx::NroHeader::sSection& nx::NroHeader::getRoDynSymInfo() const +{ + return mRoDynSymInfo; +} + +void nx::NroHeader::setRoDynSymInfo(const sSection& info) +{ + mRoDynSymInfo = info; +} + +bool nx::NroHeader::isEqual(const NroHeader& other) const +{ + return (mRoCrt == other.mRoCrt) \ + && (mNroSize == other.mNroSize) \ + && (mTextInfo == other.mTextInfo) \ + && (mTextInfo == other.mTextInfo) \ + && (mRoInfo == other.mRoInfo) \ + && (mDataInfo == other.mDataInfo) \ + && (mBssSize == other.mBssSize) \ + && (mModuleId == other.mModuleId) \ + && (mRoEmbeddedInfo == other.mRoEmbeddedInfo) \ + && (mRoDynStrInfo == other.mRoDynStrInfo) \ + && (mRoDynSymInfo == other.mRoDynSymInfo); +} + +void nx::NroHeader::copyFrom(const NroHeader& other) +{ + clear(); + mRoCrt = other.mRoCrt; + mNroSize = other.mNroSize; + mTextInfo = other.mTextInfo; + mTextInfo = other.mTextInfo; + mRoInfo = other.mRoInfo; + mDataInfo = other.mDataInfo; + mBssSize = other.mBssSize; + mModuleId = other.mModuleId; + mRoEmbeddedInfo = other.mRoEmbeddedInfo; + mRoDynStrInfo = other.mRoDynStrInfo; + mRoDynSymInfo = other.mRoDynSymInfo; +} \ No newline at end of file diff --git a/lib/libnx/source/NsoHeader.cpp b/lib/libnx/source/NsoHeader.cpp index 3b73113..55521ec 100644 --- a/lib/libnx/source/NsoHeader.cpp +++ b/lib/libnx/source/NsoHeader.cpp @@ -311,6 +311,7 @@ bool nx::NsoHeader::isEqual(const NsoHeader& other) const } void nx::NsoHeader::copyFrom(const NsoHeader& other) { + clear(); mModuleId = other.mModuleId; mBssSize = other.mBssSize; mTextSegmentInfo = other.mTextSegmentInfo; diff --git a/programs/nstool/source/CodeObjectProcess.cpp b/programs/nstool/source/CodeObjectProcess.cpp new file mode 100644 index 0000000..fd90f0b --- /dev/null +++ b/programs/nstool/source/CodeObjectProcess.cpp @@ -0,0 +1,518 @@ +#include +#include +#include +#include +#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& 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; +} \ No newline at end of file diff --git a/programs/nstool/source/CodeObjectProcess.h b/programs/nstool/source/CodeObjectProcess.h new file mode 100644 index 0000000..b0861e1 --- /dev/null +++ b/programs/nstool/source/CodeObjectProcess.h @@ -0,0 +1,74 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +#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& 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 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; +}; \ No newline at end of file diff --git a/programs/nstool/source/NsoProcess.cpp b/programs/nstool/source/NsoProcess.cpp deleted file mode 100644 index d412c2a..0000000 --- a/programs/nstool/source/NsoProcess.cpp +++ /dev/null @@ -1,303 +0,0 @@ -#include -#include -#include -#include -#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& 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()); - } - } - } -} \ No newline at end of file diff --git a/programs/nstool/source/NsoProcess.h b/programs/nstool/source/NsoProcess.h deleted file mode 100644 index fe0271e..0000000 --- a/programs/nstool/source/NsoProcess.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include - -#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& getApiList() const; - -private: - const std::string kModuleName = "NsoProcess"; - - fnd::IFile* mFile; - bool mOwnIFile; - - CliOutputType mCliOutputType; - bool mVerify; - sOptional mArchType; - - nx::NsoHeader mHdr; - fnd::MemoryBlob mTextBlob, mRoBlob, mDataBlob; - std::vector mApiList; - DynamicSymbolParser mDynSymbolList; - - void importHeader(); - void importCodeSegments(); - void importApiList(); - void displayHeader(); - void displayRoApiList(); -}; \ No newline at end of file diff --git a/programs/nstool/source/SdkApiString.cpp b/programs/nstool/source/SdkApiString.cpp new file mode 100644 index 0000000..2268fd6 --- /dev/null +++ b/programs/nstool/source/SdkApiString.cpp @@ -0,0 +1,87 @@ +#include +#include "SdkApiString.h" + +SdkApiString::SdkApiString(const std::string& full_str) : + SdkApiString(API_MIDDLEWARE, "", "") +{ + resolveApiString(full_str); +} + +SdkApiString::SdkApiString(ApiType type, const std::string& vender_name, const std::string& module_name) : + mApiType(type), + mVenderName(vender_name), + mModuleName(module_name) +{ + +} + +void SdkApiString::operator=(const SdkApiString& other) +{ + mApiType = other.mApiType; + mVenderName = other.mVenderName; + mModuleName = other.mModuleName; +} + +SdkApiString::ApiType SdkApiString::getApiType() const +{ + return mApiType; +} + +void SdkApiString::setApiType(ApiType type) +{ + mApiType = type; +} + +const std::string& SdkApiString::getVenderName() const +{ + return mVenderName; +} + +void SdkApiString::setVenderName(const std::string& name) +{ + mVenderName = name; +} + +const std::string& SdkApiString::getModuleName() const +{ + return mModuleName; +} + +void SdkApiString::setModuleName(const std::string& name) +{ + mModuleName = name; +} + +void SdkApiString::resolveApiString(const std::string& full_str) +{ + std::stringstream list_stream(full_str); + std::string api_type, vender, module; + + std::getline(list_stream, api_type, kSplitChar); + std::getline(list_stream, vender, kSplitChar); + std::getline(list_stream, module); + + + if (api_type == kSdkMiddleWareApiString) + { + if (vender == kVenderNintendo && module.find(kSdkVersionString) != std::string::npos) + { + mApiType = API_SDK_VERSION; + } + else + { + mApiType = API_MIDDLEWARE; + } + } + else if (api_type == kSdkDebugApiString) + { + mApiType = API_DEBUG; + } + else if (api_type == kSdkPrivateApiString) + { + mApiType = API_PRIVATE; + } + + mVenderName = vender; + mModuleName = module; +} \ No newline at end of file diff --git a/programs/nstool/source/SdkApiString.h b/programs/nstool/source/SdkApiString.h new file mode 100644 index 0000000..5c794df --- /dev/null +++ b/programs/nstool/source/SdkApiString.h @@ -0,0 +1,43 @@ +#pragma once +#include + +class SdkApiString +{ +public: + enum ApiType + { + API_MIDDLEWARE, + API_DEBUG, + API_PRIVATE, + API_SDK_VERSION + }; + + SdkApiString(const std::string& full_str); + SdkApiString(ApiType type, const std::string& vender_name, const std::string& module_name); + + void operator=(const SdkApiString& other); + + ApiType getApiType() const; + void setApiType(ApiType type); + + const std::string& getVenderName() const; + void setVenderName(const std::string& name); + + const std::string& getModuleName() const; + void setModuleName(const std::string& name); +private: + const std::string kModuleName = "SdkApiString"; + + const char kSplitChar = '+'; + const std::string kSdkMiddleWareApiString = "SDK MW"; + const std::string kSdkDebugApiString = "SDK Debug"; + const std::string kSdkPrivateApiString = "SDK Private"; + const std::string kVenderNintendo = "Nintendo"; + const std::string kSdkVersionString = "NintendoSdk_nnSdk-"; + + ApiType mApiType; + std::string mVenderName; + std::string mModuleName; + + void resolveApiString(const std::string& full_str); +}; \ No newline at end of file diff --git a/programs/nstool/source/UserSettings.cpp b/programs/nstool/source/UserSettings.cpp index 75dc481..591f9a8 100644 --- a/programs/nstool/source/UserSettings.cpp +++ b/programs/nstool/source/UserSettings.cpp @@ -18,6 +18,7 @@ #include #include #include +#include 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 ] <.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 ] \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& 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& 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; } diff --git a/programs/nstool/source/UserSettings.h b/programs/nstool/source/UserSettings.h index 2177e67..c1a6ccd 100644 --- a/programs/nstool/source/UserSettings.h +++ b/programs/nstool/source/UserSettings.h @@ -21,7 +21,9 @@ public: // specialised toggles bool isListFs() const; - const sOptional& getArchType() const; + bool isListApi() const; + bool isListSymbols() const; + nx::npdm::InstructionType getInstType() const; // specialised paths const sOptional& getXciUpdatePath() const; @@ -59,7 +61,9 @@ private: sOptional part1_path; sOptional part2_path; sOptional part3_path; - sOptional arch_type; + sOptional list_api; + sOptional list_sym; + sOptional inst_type; }; std::string mInputPath; @@ -80,7 +84,9 @@ private: sOptional mNcaPart2Path; sOptional mNcaPart3Path; - sOptional mArchType; + bool mListApi; + bool mListSymbols; + nx::npdm::InstructionType mInstructionType; void populateCmdArgs(int argc, char** argv, sCmdArgs& cmd_args); void populateKeyset(sCmdArgs& args); diff --git a/programs/nstool/source/main.cpp b/programs/nstool/source/main.cpp index 4a3a448..e5a3222 100644 --- a/programs/nstool/source/main.cpp +++ b/programs/nstool/source/main.cpp @@ -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) { diff --git a/programs/nstool/source/nstool.h b/programs/nstool/source/nstool.h index 0a07356..a8948c9 100644 --- a/programs/nstool/source/nstool.h +++ b/programs/nstool/source/nstool.h @@ -25,6 +25,7 @@ enum FileType FILE_NPDM, FILE_CNMT, FILE_NSO, + FILE_NRO, FILE_INVALID = -1, };