diff --git a/build/visualstudio/nstool/nstool.vcxproj b/build/visualstudio/nstool/nstool.vcxproj index f452499..655fbb7 100644 --- a/build/visualstudio/nstool/nstool.vcxproj +++ b/build/visualstudio/nstool/nstool.vcxproj @@ -76,7 +76,7 @@ Disabled true true - $(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include + $(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\liblz4\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include MultiThreadedDebug @@ -86,7 +86,7 @@ Disabled true true - $(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include + $(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\liblz4\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include MultiThreadedDebug @@ -98,7 +98,7 @@ true true true - $(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include + $(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\liblz4\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include MultiThreaded @@ -114,7 +114,7 @@ true true true - $(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include + $(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\liblz4\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include MultiThreaded @@ -188,6 +188,8 @@ + + diff --git a/build/visualstudio/nstool/nstool.vcxproj.filters b/build/visualstudio/nstool/nstool.vcxproj.filters index 69e6c9f..a866cc6 100644 --- a/build/visualstudio/nstool/nstool.vcxproj.filters +++ b/build/visualstudio/nstool/nstool.vcxproj.filters @@ -143,5 +143,11 @@ Source Files + + Source Files + + + Source Files + \ No newline at end of file diff --git a/src/NroProcess.cpp b/src/NroProcess.cpp new file mode 100644 index 0000000..66a061c --- /dev/null +++ b/src/NroProcess.cpp @@ -0,0 +1,192 @@ +#include "NroProcess.h" + +#include + +nstool::NroProcess::NroProcess() : + mModuleName("nstool::NroProcess"), + mFile(), + mCliOutputMode(true, false, false, false), + mVerify(false) +{ +} + +void nstool::NroProcess::process() +{ + importHeader(); + importCodeSegments(); + + if (mCliOutputMode.show_basic_info) + displayHeader(); + + processRoMeta(); + + if (mIsHomebrewNro) + mAssetProc.process(); +} + +void nstool::NroProcess::setInputFile(const std::shared_ptr& file) +{ + mFile = file; +} + +void nstool::NroProcess::setCliOutputMode(CliOutputMode type) +{ + mCliOutputMode = type; +} + +void nstool::NroProcess::setVerifyMode(bool verify) +{ + mVerify = verify; +} + +void nstool::NroProcess::setIs64BitInstruction(bool flag) +{ + mRoMeta.setIs64BitInstruction(flag); +} + +void nstool::NroProcess::setListApi(bool listApi) +{ + mRoMeta.setListApi(listApi); +} + +void nstool::NroProcess::setListSymbols(bool listSymbols) +{ + mRoMeta.setListSymbols(listSymbols); +} + +void nstool::NroProcess::setAssetIconExtractPath(const tc::io::Path& path) +{ + mAssetProc.setIconExtractPath(path); +} + +void nstool::NroProcess::setAssetNacpExtractPath(const tc::io::Path& path) +{ + mAssetProc.setNacpExtractPath(path); +} + +void nstool::NroProcess::setAssetRomfsShowFsTree(bool show_fs_tree) +{ + mAssetProc.setRomfsShowFsTree(show_fs_tree); +} + +void nstool::NroProcess::setAssetRomfsExtractJobs(const std::vector& extract_jobs) +{ + mAssetProc.setRomfsExtractJobs(extract_jobs); +} + +const nstool::RoMetadataProcess& nstool::NroProcess::getRoMetadataProcess() const +{ + return mRoMeta; +} + +void nstool::NroProcess::importHeader() +{ + if (mFile == nullptr) + { + throw tc::Exception(mModuleName, "No file reader set."); + } + if (mFile->canRead() == false || mFile->canSeek() == false) + { + throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions."); + } + + // check if file_size is smaller than NRO header size + if (tc::io::IOUtil::castInt64ToSize(mFile->length()) < sizeof(nn::hac::sNroHeader)) + { + throw tc::Exception(mModuleName, "Corrupt NRO: file too small."); + } + + // read nro + tc::ByteData scratch = tc::ByteData(sizeof(nn::hac::sNroHeader)); + mFile->seek(0, tc::io::SeekOrigin::Begin); + mFile->read(scratch.data(), scratch.size()); + + // parse nro header + mHdr.fromBytes(scratch.data(), scratch.size()); + + // setup homebrew extension + nn::hac::sNroHeader* raw_hdr = (nn::hac::sNroHeader*)scratch.data(); + + int64_t file_size = mFile->length(); + if (((tc::bn::le64*)raw_hdr->reserved_0.data())->unwrap() == nn::hac::nro::kNroHomebrewStructMagic && file_size > int64_t(mHdr.getNroSize())) + { + mIsHomebrewNro = true; + mAssetProc.setInputFile(std::make_shared(tc::io::SubStream(mFile, int64_t(mHdr.getNroSize()), file_size - int64_t(mHdr.getNroSize())))); + mAssetProc.setCliOutputMode(mCliOutputMode); + mAssetProc.setVerifyMode(mVerify); + } + else + mIsHomebrewNro = false; +} + +void nstool::NroProcess::importCodeSegments() +{ + if (mHdr.getTextInfo().size > 0) + { + mTextBlob = tc::ByteData(mHdr.getTextInfo().size); + mFile->seek(mHdr.getTextInfo().memory_offset, tc::io::SeekOrigin::Begin); + mFile->read(mTextBlob.data(), mTextBlob.size()); + } + + if (mHdr.getRoInfo().size > 0) + { + mRoBlob = tc::ByteData(mHdr.getRoInfo().size); + mFile->seek(mHdr.getRoInfo().memory_offset, tc::io::SeekOrigin::Begin); + mFile->read(mRoBlob.data(), mRoBlob.size()); + } + + if (mHdr.getDataInfo().size > 0) + { + mDataBlob = tc::ByteData(mHdr.getDataInfo().size); + mFile->seek(mHdr.getDataInfo().memory_offset, tc::io::SeekOrigin::Begin); + mFile->read(mDataBlob.data(), mDataBlob.size()); + } +} + +void nstool::NroProcess::displayHeader() +{ + fmt::print("[NRO Header]\n"); + fmt::print(" RoCrt: \n"); + fmt::print(" EntryPoint: 0x{:x}\n", mHdr.getRoCrtEntryPoint()); + fmt::print(" ModOffset: 0x{:x}\n", mHdr.getRoCrtModOffset()); + fmt::print(" ModuleId: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mHdr.getModuleId().data(), mHdr.getModuleId().size(), false, "")); + fmt::print(" NroSize: 0x{:x}\n", mHdr.getNroSize()); + fmt::print(" Program Sections:\n"); + fmt::print(" .text:\n"); + fmt::print(" Offset: 0x{:x}\n", mHdr.getTextInfo().memory_offset); + fmt::print(" Size: 0x{:x}\n", mHdr.getTextInfo().size); + fmt::print(" .ro:\n"); + fmt::print(" Offset: 0x{:x}\n", mHdr.getRoInfo().memory_offset); + fmt::print(" Size: 0x{:x}\n", mHdr.getRoInfo().size); + if (mCliOutputMode.show_extended_info) + { + fmt::print(" .api_info:\n"); + fmt::print(" Offset: 0x{:x}\n", mHdr.getRoEmbeddedInfo().memory_offset); + fmt::print(" Size: 0x{:x}\n", mHdr.getRoEmbeddedInfo().size); + fmt::print(" .dynstr:\n"); + fmt::print(" Offset: 0x{:x}\n", mHdr.getRoDynStrInfo().memory_offset); + fmt::print(" Size: 0x{:x}\n", mHdr.getRoDynStrInfo().size); + fmt::print(" .dynsym:\n"); + fmt::print(" Offset: 0x{:x}\n", mHdr.getRoDynSymInfo().memory_offset); + fmt::print(" Size: 0x{:x}\n", mHdr.getRoDynSymInfo().size); + } + fmt::print(" .data:\n"); + fmt::print(" Offset: 0x{:x}\n", mHdr.getDataInfo().memory_offset); + fmt::print(" Size: 0x{:x}\n", mHdr.getDataInfo().size); + fmt::print(" .bss:\n"); + fmt::print(" Size: 0x{:x}\n", mHdr.getBssSize()); +} + +void nstool::NroProcess::processRoMeta() +{ + if (mRoBlob.size()) + { + // setup ro metadata + mRoMeta.setApiInfo(mHdr.getRoEmbeddedInfo().memory_offset, mHdr.getRoEmbeddedInfo().size); + mRoMeta.setDynSym(mHdr.getRoDynSymInfo().memory_offset, mHdr.getRoDynSymInfo().size); + mRoMeta.setDynStr(mHdr.getRoDynStrInfo().memory_offset, mHdr.getRoDynStrInfo().size); + mRoMeta.setRoBinary(mRoBlob); + mRoMeta.setCliOutputMode(mCliOutputMode); + mRoMeta.process(); + } +} \ No newline at end of file diff --git a/src/NroProcess.h b/src/NroProcess.h index efc8610..ac93711 100644 --- a/src/NroProcess.h +++ b/src/NroProcess.h @@ -3,7 +3,6 @@ #include "RoMetadataProcess.h" #include "AssetProcess.h" -#include #include namespace nstool { @@ -24,14 +23,14 @@ public: void setListSymbols(bool listSymbols); // for homebrew NROs with Asset blobs appended - void setAssetListFs(bool list); void setAssetIconExtractPath(const tc::io::Path& path); void setAssetNacpExtractPath(const tc::io::Path& path); - void setAssetRomfsExtractPath(const tc::io::Path& path); + void setAssetRomfsShowFsTree(bool show_fs_tree); + void setAssetRomfsExtractJobs(const std::vector& extract_jobs); - const RoMetadataProcess& getRoMetadataProcess() const; + const nstool::RoMetadataProcess& getRoMetadataProcess() const; private: - const std::string kModuleName = "NroProcess"; + std::string mModuleName; std::shared_ptr mFile; CliOutputMode mCliOutputMode; @@ -39,9 +38,9 @@ private: nn::hac::NroHeader mHdr; tc::ByteData mTextBlob, mRoBlob, mDataBlob; - RoMetadataProcess mRoMeta; + nstool::RoMetadataProcess mRoMeta; bool mIsHomebrewNro; - AssetProcess mAssetProc; + nstool::AssetProcess mAssetProc; void importHeader(); void importCodeSegments(); diff --git a/src/NsoProcess.cpp b/src/NsoProcess.cpp new file mode 100644 index 0000000..7680d49 --- /dev/null +++ b/src/NsoProcess.cpp @@ -0,0 +1,285 @@ +#include "NsoProcess.h" + +#include + +nstool::NsoProcess::NsoProcess() : + mModuleName("nstool::NsoProcess"), + mFile(), + mCliOutputMode(true, false, false, false), + mVerify(false), + mIs64BitInstruction(true), + mListApi(false), + mListSymbols(false) +{ +} + +void nstool::NsoProcess::process() +{ + importHeader(); + importCodeSegments(); + if (mCliOutputMode.show_basic_info) + displayNsoHeader(); + + processRoMeta(); +} + +void nstool::NsoProcess::setInputFile(const std::shared_ptr& file) +{ + mFile = file; +} + +void nstool::NsoProcess::setCliOutputMode(CliOutputMode type) +{ + mCliOutputMode = type; +} + +void nstool::NsoProcess::setVerifyMode(bool verify) +{ + mVerify = verify; +} + +void nstool::NsoProcess::setIs64BitInstruction(bool flag) +{ + mRoMeta.setIs64BitInstruction(flag); +} + +void nstool::NsoProcess::setListApi(bool listApi) +{ + mRoMeta.setListApi(listApi); +} + +void nstool::NsoProcess::setListSymbols(bool listSymbols) +{ + mRoMeta.setListSymbols(listSymbols); +} + +const nstool::RoMetadataProcess& nstool::NsoProcess::getRoMetadataProcess() const +{ + return mRoMeta; +} + +void nstool::NsoProcess::importHeader() +{ + if (mFile == nullptr) + { + throw tc::Exception(mModuleName, "No file reader set."); + } + if (mFile->canRead() == false || mFile->canSeek() == false) + { + throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions."); + } + + // check if file_size is smaller than NSO header size + size_t file_size = tc::io::IOUtil::castInt64ToSize(mFile->length()); + if (file_size < sizeof(nn::hac::sNsoHeader)) + { + throw tc::Exception(mModuleName, "Corrupt NSO: file too small."); + } + + // read nso + tc::ByteData scratch = tc::ByteData(sizeof(nn::hac::sNsoHeader)); + mFile->seek(0, tc::io::SeekOrigin::Begin); + mFile->read(scratch.data(), scratch.size()); + + // parse nso header + mHdr.fromBytes(scratch.data(), scratch.size()); +} + +void nstool::NsoProcess::importCodeSegments() +{ + tc::ByteData scratch; + nn::hac::detail::sha256_hash_t calc_hash; + + // process text segment + if (mHdr.getTextSegmentInfo().is_compressed) + { + // allocate/read compressed text + scratch = tc::ByteData(mHdr.getTextSegmentInfo().file_layout.size); + mFile->seek(mHdr.getTextSegmentInfo().file_layout.offset, tc::io::SeekOrigin::Begin); + mFile->read(scratch.data(), scratch.size()); + + // allocate for decompressed text segment + mTextBlob = tc::ByteData(mHdr.getTextSegmentInfo().memory_layout.size); + + // decompress text segment + if (decompressData(scratch.data(), scratch.size(), mTextBlob.data(), mTextBlob.size()) != mTextBlob.size()) + { + throw tc::Exception(mModuleName, "NSO text segment failed to decompress"); + } + } + else + { + // read text segment directly (not compressed) + mTextBlob = tc::ByteData(mHdr.getTextSegmentInfo().file_layout.size); + mFile->seek(mHdr.getTextSegmentInfo().file_layout.offset, tc::io::SeekOrigin::Begin); + mFile->read(mTextBlob.data(), mTextBlob.size()); + } + if (mHdr.getTextSegmentInfo().is_hashed) + { + tc::crypto::GenerateSha256Hash(calc_hash.data(), mTextBlob.data(), mTextBlob.size()); + if (calc_hash != mHdr.getTextSegmentInfo().hash) + { + throw tc::Exception(mModuleName, "NSO text segment failed SHA256 verification"); + } + } + + // process ro segment + if (mHdr.getRoSegmentInfo().is_compressed) + { + // allocate/read compressed ro segment + scratch = tc::ByteData(mHdr.getRoSegmentInfo().file_layout.size); + mFile->seek(mHdr.getRoSegmentInfo().file_layout.offset, tc::io::SeekOrigin::Begin); + mFile->read(scratch.data(), scratch.size()); + + // allocate for decompressed ro segment + mRoBlob = tc::ByteData(mHdr.getRoSegmentInfo().memory_layout.size); + + // decompress ro segment + if (decompressData(scratch.data(), scratch.size(), mRoBlob.data(), mRoBlob.size()) != mRoBlob.size()) + { + throw tc::Exception(mModuleName, "NSO ro segment failed to decompress"); + } + } + else + { + // read ro segment directly (not compressed) + mRoBlob = tc::ByteData(mHdr.getRoSegmentInfo().file_layout.size); + mFile->seek(mHdr.getRoSegmentInfo().file_layout.offset, tc::io::SeekOrigin::Begin); + mFile->read(mRoBlob.data(), mRoBlob.size()); + } + if (mHdr.getRoSegmentInfo().is_hashed) + { + tc::crypto::GenerateSha256Hash(calc_hash.data(), mRoBlob.data(), mRoBlob.size()); + if (calc_hash != mHdr.getRoSegmentInfo().hash) + { + throw tc::Exception(mModuleName, "NSO ro segment failed SHA256 verification"); + } + } + + // process ro segment + if (mHdr.getDataSegmentInfo().is_compressed) + { + // allocate/read compressed ro segment + scratch = tc::ByteData(mHdr.getDataSegmentInfo().file_layout.size); + mFile->seek(mHdr.getDataSegmentInfo().file_layout.offset, tc::io::SeekOrigin::Begin); + mFile->read(scratch.data(), scratch.size()); + + // allocate for decompressed ro segment + mDataBlob = tc::ByteData(mHdr.getDataSegmentInfo().memory_layout.size); + + // decompress ro segment + if (decompressData(scratch.data(), scratch.size(), mDataBlob.data(), mDataBlob.size()) != mDataBlob.size()) + { + throw tc::Exception(mModuleName, "NSO data segment failed to decompress"); + } + } + else + { + // read ro segment directly (not compressed) + mDataBlob = tc::ByteData(mHdr.getDataSegmentInfo().file_layout.size); + mFile->seek(mHdr.getDataSegmentInfo().file_layout.offset, tc::io::SeekOrigin::Begin); + mFile->read(mDataBlob.data(), mDataBlob.size()); + } + if (mHdr.getDataSegmentInfo().is_hashed) + { + tc::crypto::GenerateSha256Hash(calc_hash.data(), mDataBlob.data(), mDataBlob.size()); + if (calc_hash != mHdr.getDataSegmentInfo().hash) + { + throw tc::Exception(mModuleName, "NSO data segment failed SHA256 verification"); + } + } +} + +void nstool::NsoProcess::displayNsoHeader() +{ + fmt::print("[NSO Header]\n"); + fmt::print(" ModuleId: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mHdr.getModuleId().data(), mHdr.getModuleId().size(), false, "")); + if (mCliOutputMode.show_layout) + { + fmt::print(" Program Segments:\n"); + fmt::print(" .module_name:\n"); + fmt::print(" FileOffset: 0x{:x}\n", mHdr.getModuleNameInfo().offset); + fmt::print(" FileSize: 0x{:x}\n", mHdr.getModuleNameInfo().size); + fmt::print(" .text:\n"); + fmt::print(" FileOffset: 0x{:x}\n", mHdr.getTextSegmentInfo().file_layout.offset); + fmt::print(" FileSize: 0x{:x}{:s}\n", mHdr.getTextSegmentInfo().file_layout.size, (mHdr.getTextSegmentInfo().is_compressed? " (COMPRESSED)" : "")); + fmt::print(" .ro:\n"); + fmt::print(" FileOffset: 0x{:x}\n", mHdr.getRoSegmentInfo().file_layout.offset); + fmt::print(" FileSize: 0x{:x}{:s}\n", mHdr.getRoSegmentInfo().file_layout.size, (mHdr.getRoSegmentInfo().is_compressed? " (COMPRESSED)" : "")); + fmt::print(" .data:\n"); + fmt::print(" FileOffset: 0x{:x}\n", mHdr.getDataSegmentInfo().file_layout.offset); + fmt::print(" FileSize: 0x{:x}{:s}\n", mHdr.getDataSegmentInfo().file_layout.size, (mHdr.getDataSegmentInfo().is_compressed? " (COMPRESSED)" : "")); + } + fmt::print(" Program Sections:\n"); + fmt::print(" .text:\n"); + fmt::print(" MemoryOffset: 0x{:x}\n", mHdr.getTextSegmentInfo().memory_layout.offset); + fmt::print(" MemorySize: 0x{:x}\n", mHdr.getTextSegmentInfo().memory_layout.size); + if (mHdr.getTextSegmentInfo().is_hashed && mCliOutputMode.show_extended_info) + { + fmt::print(" Hash: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mHdr.getTextSegmentInfo().hash.data(), mHdr.getTextSegmentInfo().hash.size(), false, "")); + } + fmt::print(" .ro:\n"); + fmt::print(" MemoryOffset: 0x{:x}\n", mHdr.getRoSegmentInfo().memory_layout.offset); + fmt::print(" MemorySize: 0x{:x}\n", mHdr.getRoSegmentInfo().memory_layout.size); + if (mHdr.getRoSegmentInfo().is_hashed && mCliOutputMode.show_extended_info) + { + fmt::print(" Hash: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mHdr.getRoSegmentInfo().hash.data(), mHdr.getRoSegmentInfo().hash.size(), false, "")); + } + if (mCliOutputMode.show_extended_info) + { + fmt::print(" .api_info:\n"); + fmt::print(" MemoryOffset: 0x{:x}\n", mHdr.getRoEmbeddedInfo().offset); + fmt::print(" MemorySize: 0x{:x}\n", mHdr.getRoEmbeddedInfo().size); + fmt::print(" .dynstr:\n"); + fmt::print(" MemoryOffset: 0x{:x}\n", mHdr.getRoDynStrInfo().offset); + fmt::print(" MemorySize: 0x{:x}\n", mHdr.getRoDynStrInfo().size); + fmt::print(" .dynsym:\n"); + fmt::print(" MemoryOffset: 0x{:x}\n", mHdr.getRoDynSymInfo().offset); + fmt::print(" MemorySize: 0x{:x}\n", mHdr.getRoDynSymInfo().size); + } + + fmt::print(" .data:\n"); + fmt::print(" MemoryOffset: 0x{:x}\n", mHdr.getDataSegmentInfo().memory_layout.offset); + fmt::print(" MemorySize: 0x{:x}\n", mHdr.getDataSegmentInfo().memory_layout.size); + if (mHdr.getDataSegmentInfo().is_hashed && mCliOutputMode.show_extended_info) + { + fmt::print(" Hash: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mHdr.getDataSegmentInfo().hash.data(), mHdr.getDataSegmentInfo().hash.size(), false, "")); + } + fmt::print(" .bss:\n"); + fmt::print(" MemorySize: 0x{:x}\n", mHdr.getBssSize()); +} + +void nstool::NsoProcess::processRoMeta() +{ + if (mRoBlob.size()) + { + // setup ro metadata + mRoMeta.setApiInfo(mHdr.getRoEmbeddedInfo().offset, mHdr.getRoEmbeddedInfo().size); + mRoMeta.setDynSym(mHdr.getRoDynSymInfo().offset, mHdr.getRoDynSymInfo().size); + mRoMeta.setDynStr(mHdr.getRoDynStrInfo().offset, mHdr.getRoDynStrInfo().size); + mRoMeta.setRoBinary(mRoBlob); + mRoMeta.setCliOutputMode(mCliOutputMode); + mRoMeta.process(); + } +} + +size_t nstool::NsoProcess::decompressData(const byte_t* src, size_t src_len, byte_t* dst, size_t dst_capacity) +{ + if (src_len >= LZ4_MAX_INPUT_SIZE) + { + return 0; + } + + int32_t src_len_input = int32_t(src_len); + int32_t dst_capcacity_input = (dst_capacity < LZ4_MAX_INPUT_SIZE) ? int32_t(dst_capacity) : LZ4_MAX_INPUT_SIZE; + + int32_t decomp_size = LZ4_decompress_safe((const char*)src, (char*)dst, src_len_input, dst_capcacity_input); + + if (decomp_size < 0) + { + memset(dst, 0, dst_capacity); + return 0; + } + + return size_t(decomp_size); +} \ No newline at end of file diff --git a/src/NsoProcess.h b/src/NsoProcess.h index 2f4fbb2..5856e7a 100644 --- a/src/NsoProcess.h +++ b/src/NsoProcess.h @@ -22,9 +22,9 @@ public: void setListApi(bool listApi); void setListSymbols(bool listSymbols); - const RoMetadataProcess& getRoMetadataProcess() const; + const nstool::RoMetadataProcess& getRoMetadataProcess() const; private: - const std::string kModuleName = "NsoProcess"; + std::string mModuleName; std::shared_ptr mFile; CliOutputMode mCliOutputMode; @@ -35,12 +35,15 @@ private: nn::hac::NsoHeader mHdr; tc::ByteData mTextBlob, mRoBlob, mDataBlob; - RoMetadataProcess mRoMeta; + nstool::RoMetadataProcess mRoMeta; void importHeader(); void importCodeSegments(); void displayNsoHeader(); void processRoMeta(); + + size_t decompressData(const byte_t* src, size_t src_len, byte_t* dst, size_t dst_capacity); + }; } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 2eb2230..be46c10 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,8 +9,8 @@ //#include "NcaProcess.h" //#include "MetaProcess.h" #include "CnmtProcess.h" -//#include "NsoProcess.h" -//#include "NroProcess.h" +#include "NsoProcess.h" +#include "NroProcess.h" #include "NacpProcess.h" //#include "IniProcess.h" //#include "KipProcess.h" @@ -115,7 +115,6 @@ int umain(const std::vector& args, const std::vector& obj.process(); } - /* else if (set.infile.filetype == nstool::Settings::FILE_TYPE_NSO) { nstool::NsoProcess obj; @@ -147,13 +146,11 @@ int umain(const std::vector& args, const std::vector& if (set.aset.nacp_extract_path.isSet()) obj.setAssetNacpExtractPath(set.aset.nacp_extract_path.get()); - if (set.fs.extract_path.isSet()) - obj.setAssetRomfsExtractPath(set.fs.extract_path.get()); - obj.setAssetListFs(set.fs.show_fs_tree); + obj.setAssetRomfsShowFsTree(set.fs.show_fs_tree); + obj.setAssetRomfsExtractJobs(set.fs.extract_jobs); obj.process(); } - */ else if (set.infile.filetype == nstool::Settings::FILE_TYPE_NACP) { nstool::NacpProcess obj; diff --git a/src/not_ported/NroProcess.cpp b/src/not_ported/NroProcess.cpp deleted file mode 100644 index c76d845..0000000 --- a/src/not_ported/NroProcess.cpp +++ /dev/null @@ -1,174 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include "NroProcess.h" - -nstool::NroProcess::NroProcess(): - mFile(), - mCliOutputMode(true, false, false, false), - mVerify(false) -{ -} - -void nstool::NroProcess::process() -{ - importHeader(); - importCodeSegments(); - - if (mCliOutputMode.show_basic_info) - displayHeader(); - - processRoMeta(); - - if (mIsHomebrewNro) - mAssetProc.process(); -} - -void nstool::NroProcess::setInputFile(const std::shared_ptr& file) -{ - mFile = file; -} - -void nstool::NroProcess::setCliOutputMode(CliOutputMode type) -{ - mCliOutputMode = type; -} - -void nstool::NroProcess::setVerifyMode(bool verify) -{ - mVerify = verify; -} - -void nstool::NroProcess::setIs64BitInstruction(bool flag) -{ - mRoMeta.setIs64BitInstruction(flag); -} - -void nstool::NroProcess::setListApi(bool listApi) -{ - mRoMeta.setListApi(listApi); -} - -void nstool::NroProcess::setListSymbols(bool listSymbols) -{ - mRoMeta.setListSymbols(listSymbols); -} - -void nstool::NroProcess::setAssetListFs(bool list) -{ - mAssetProc.setListFs(list); -} - -void nstool::NroProcess::setAssetIconExtractPath(const std::string& path) -{ - mAssetProc.setIconExtractPath(path); -} - -void nstool::NroProcess::setAssetNacpExtractPath(const std::string& path) -{ - mAssetProc.setNacpExtractPath(path); -} - -void nstool::NroProcess::setAssetRomfsExtractPath(const std::string& path) -{ - mAssetProc.setRomfsExtractPath(path); -} - -const RoMetadataProcess& nstool::NroProcess::getRoMetadataProcess() const -{ - return mRoMeta; -} - -void nstool::NroProcess::importHeader() -{ - tc::ByteData scratch; - - if (*mFile == nullptr) - { - throw tc::Exception(kModuleName, "No file reader set."); - } - - if ((*mFile)->size() < sizeof(nn::hac::sNroHeader)) - { - throw tc::Exception(kModuleName, "Corrupt NRO: file too small"); - } - - scratch.alloc(sizeof(nn::hac::sNroHeader)); - (*mFile)->read(scratch.data(), 0, scratch.size()); - - mHdr.fromBytes(scratch.data(), scratch.size()); - - // setup homebrew extension - nn::hac::sNroHeader* raw_hdr = (nn::hac::sNroHeader*)scratch.data(); - if (((tc::bn::le64*)raw_hdr->reserved_0)->get() == nn::hac::nro::kNroHomebrewStructMagic && (*mFile)->size() > mHdr.getNroSize()) - { - mIsHomebrewNro = true; - mAssetProc.setInputFile(new fnd::OffsetAdjustedIFile(mFile, mHdr.getNroSize(), (*mFile)->size() - mHdr.getNroSize())); - mAssetProc.setCliOutputMode(mCliOutputMode); - mAssetProc.setVerifyMode(mVerify); - } - else - mIsHomebrewNro = false; -} - -void nstool::NroProcess::importCodeSegments() -{ - mTextBlob.alloc(mHdr.getTextInfo().size); - (*mFile)->read(mTextBlob.data(), mHdr.getTextInfo().memory_offset, mTextBlob.size()); - mRoBlob.alloc(mHdr.getRoInfo().size); - (*mFile)->read(mRoBlob.data(), mHdr.getRoInfo().memory_offset, mRoBlob.size()); - mDataBlob.alloc(mHdr.getDataInfo().size); - (*mFile)->read(mDataBlob.data(), mHdr.getDataInfo().memory_offset, mDataBlob.size()); -} - -void nstool::NroProcess::displayHeader() -{ - std::cout << "[NRO Header]" << std::endl; - std::cout << " RoCrt: " << std::endl; - std::cout << " EntryPoint: 0x" << std::hex << mHdr.getRoCrtEntryPoint() << std::endl; - std::cout << " ModOffset: 0x" << std::hex << mHdr.getRoCrtModOffset() << std::endl; - std::cout << " ModuleId: " << fnd::SimpleTextOutput::arrayToString(mHdr.getModuleId().data, nn::hac::nro::kModuleIdSize, false, "") << std::endl; - std::cout << " NroSize: 0x" << std::hex << mHdr.getNroSize() << std::endl; - std::cout << " Program Sections:" << std::endl; - std::cout << " .text:" << std::endl; - std::cout << " Offset: 0x" << std::hex << mHdr.getTextInfo().memory_offset << std::endl; - std::cout << " Size: 0x" << std::hex << mHdr.getTextInfo().size << std::endl; - std::cout << " .ro:" << std::endl; - std::cout << " Offset: 0x" << std::hex << mHdr.getRoInfo().memory_offset << std::endl; - std::cout << " Size: 0x" << std::hex << mHdr.getRoInfo().size << std::endl; - if (mCliOutputMode.show_extended_info) - { - std::cout << " .api_info:" << std::endl; - std::cout << " Offset: 0x" << std::hex << mHdr.getRoEmbeddedInfo().memory_offset << std::endl; - std::cout << " Size: 0x" << std::hex << mHdr.getRoEmbeddedInfo().size << std::endl; - std::cout << " .dynstr:" << std::endl; - std::cout << " Offset: 0x" << std::hex << mHdr.getRoDynStrInfo().memory_offset << std::endl; - std::cout << " Size: 0x" << std::hex << mHdr.getRoDynStrInfo().size << std::endl; - std::cout << " .dynsym:" << std::endl; - std::cout << " Offset: 0x" << std::hex << mHdr.getRoDynSymInfo().memory_offset << std::endl; - std::cout << " Size: 0x" << std::hex << mHdr.getRoDynSymInfo().size << std::endl; - } - std::cout << " .data:" << std::endl; - std::cout << " Offset: 0x" << std::hex << mHdr.getDataInfo().memory_offset << std::endl; - std::cout << " Size: 0x" << std::hex << mHdr.getDataInfo().size << std::endl; - std::cout << " .bss:" << std::endl; - std::cout << " Size: 0x" << std::hex << mHdr.getBssSize() << std::endl; -} - -void nstool::NroProcess::processRoMeta() -{ - if (mRoBlob.size()) - { - // setup ro metadata - mRoMeta.setApiInfo(mHdr.getRoEmbeddedInfo().memory_offset, mHdr.getRoEmbeddedInfo().size); - mRoMeta.setDynSym(mHdr.getRoDynSymInfo().memory_offset, mHdr.getRoDynSymInfo().size); - mRoMeta.setDynStr(mHdr.getRoDynStrInfo().memory_offset, mHdr.getRoDynStrInfo().size); - mRoMeta.setRoBinary(mRoBlob); - mRoMeta.setCliOutputMode(mCliOutputMode); - mRoMeta.process(); - } -} \ No newline at end of file diff --git a/src/not_ported/NsoProcess.cpp b/src/not_ported/NsoProcess.cpp deleted file mode 100644 index dbdc11f..0000000 --- a/src/not_ported/NsoProcess.cpp +++ /dev/null @@ -1,240 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "NsoProcess.h" - -nstool::NsoProcess::NsoProcess(): - mFile(), - mCliOutputMode(true, false, false, false), - mVerify(false), - mIs64BitInstruction(true), - mListApi(false), - mListSymbols(false) -{ -} - -void nstool::NsoProcess::process() -{ - importHeader(); - importCodeSegments(); - if (mCliOutputMode.show_basic_info) - displayNsoHeader(); - - processRoMeta(); -} - -void nstool::NsoProcess::setInputFile(const std::shared_ptr& file) -{ - mFile = file; -} - -void nstool::NsoProcess::setCliOutputMode(CliOutputMode type) -{ - mCliOutputMode = type; -} - -void nstool::NsoProcess::setVerifyMode(bool verify) -{ - mVerify = verify; -} - -void nstool::NsoProcess::setIs64BitInstruction(bool flag) -{ - mRoMeta.setIs64BitInstruction(flag); -} - -void nstool::NsoProcess::setListApi(bool listApi) -{ - mRoMeta.setListApi(listApi); -} - -void nstool::NsoProcess::setListSymbols(bool listSymbols) -{ - mRoMeta.setListSymbols(listSymbols); -} - -const RoMetadataProcess& nstool::NsoProcess::getRoMetadataProcess() const -{ - return mRoMeta; -} - -void nstool::NsoProcess::importHeader() -{ - tc::ByteData scratch; - - if (*mFile == nullptr) - { - throw tc::Exception(kModuleName, "No file reader set."); - } - - if ((*mFile)->size() < sizeof(nn::hac::sNsoHeader)) - { - throw tc::Exception(kModuleName, "Corrupt NSO: file too small"); - } - - scratch.alloc(sizeof(nn::hac::sNsoHeader)); - (*mFile)->read(scratch.data(), 0, scratch.size()); - - mHdr.fromBytes(scratch.data(), scratch.size()); -} - -void nstool::NsoProcess::importCodeSegments() -{ - tc::ByteData scratch; - uint32_t decompressed_len; - fnd::sha::sSha256Hash calc_hash; - - // process text segment - if (mHdr.getTextSegmentInfo().is_compressed) - { - scratch.alloc(mHdr.getTextSegmentInfo().file_layout.size); - (*mFile)->read(scratch.data(), mHdr.getTextSegmentInfo().file_layout.offset, scratch.size()); - mTextBlob.alloc(mHdr.getTextSegmentInfo().memory_layout.size); - fnd::lz4::decompressData(scratch.data(), (uint32_t)scratch.size(), mTextBlob.data(), (uint32_t)mTextBlob.size(), decompressed_len); - if (decompressed_len != mTextBlob.size()) - { - throw tc::Exception(kModuleName, "NSO text segment failed to decompress"); - } - } - else - { - mTextBlob.alloc(mHdr.getTextSegmentInfo().file_layout.size); - (*mFile)->read(mTextBlob.data(), mHdr.getTextSegmentInfo().file_layout.offset, mTextBlob.size()); - } - if (mHdr.getTextSegmentInfo().is_hashed) - { - fnd::sha::Sha256(mTextBlob.data(), mTextBlob.size(), calc_hash.bytes); - if (calc_hash != mHdr.getTextSegmentInfo().hash) - { - throw tc::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.data(), mHdr.getRoSegmentInfo().file_layout.offset, scratch.size()); - mRoBlob.alloc(mHdr.getRoSegmentInfo().memory_layout.size); - fnd::lz4::decompressData(scratch.data(), (uint32_t)scratch.size(), mRoBlob.data(), (uint32_t)mRoBlob.size(), decompressed_len); - if (decompressed_len != mRoBlob.size()) - { - throw tc::Exception(kModuleName, "NSO ro segment failed to decompress"); - } - } - else - { - mRoBlob.alloc(mHdr.getRoSegmentInfo().file_layout.size); - (*mFile)->read(mRoBlob.data(), mHdr.getRoSegmentInfo().file_layout.offset, mRoBlob.size()); - } - if (mHdr.getRoSegmentInfo().is_hashed) - { - fnd::sha::Sha256(mRoBlob.data(), mRoBlob.size(), calc_hash.bytes); - if (calc_hash != mHdr.getRoSegmentInfo().hash) - { - throw tc::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.data(), mHdr.getDataSegmentInfo().file_layout.offset, scratch.size()); - mDataBlob.alloc(mHdr.getDataSegmentInfo().memory_layout.size); - fnd::lz4::decompressData(scratch.data(), (uint32_t)scratch.size(), mDataBlob.data(), (uint32_t)mDataBlob.size(), decompressed_len); - if (decompressed_len != mDataBlob.size()) - { - throw tc::Exception(kModuleName, "NSO data segment failed to decompress"); - } - } - else - { - mDataBlob.alloc(mHdr.getDataSegmentInfo().file_layout.size); - (*mFile)->read(mDataBlob.data(), mHdr.getDataSegmentInfo().file_layout.offset, mDataBlob.size()); - } - if (mHdr.getDataSegmentInfo().is_hashed) - { - fnd::sha::Sha256(mDataBlob.data(), mDataBlob.size(), calc_hash.bytes); - if (calc_hash != mHdr.getDataSegmentInfo().hash) - { - throw tc::Exception(kModuleName, "NSO data segment failed SHA256 verification"); - } - } -} - -void nstool::NsoProcess::displayNsoHeader() -{ - std::cout << "[NSO Header]" << std::endl; - std::cout << " ModuleId: " << fnd::SimpleTextOutput::arrayToString(mHdr.getModuleId().data, nn::hac::nso::kModuleIdSize, false, "") << std::endl; - if (mCliOutputMode.show_layout) - { - std::cout << " Program Segments:" << std::endl; - std::cout << " .module_name:" << std::endl; - std::cout << " FileOffset: 0x" << std::hex << mHdr.getModuleNameInfo().offset << std::endl; - std::cout << " FileSize: 0x" << std::hex << mHdr.getModuleNameInfo().size << std::endl; - std::cout << " .text:" << std::endl; - std::cout << " FileOffset: 0x" << std::hex << mHdr.getTextSegmentInfo().file_layout.offset << std::endl; - std::cout << " FileSize: 0x" << std::hex << mHdr.getTextSegmentInfo().file_layout.size << (mHdr.getTextSegmentInfo().is_compressed? " (COMPRESSED)" : "") << std::endl; - std::cout << " .ro:" << std::endl; - std::cout << " FileOffset: 0x" << std::hex << mHdr.getRoSegmentInfo().file_layout.offset << std::endl; - std::cout << " FileSize: 0x" << std::hex << mHdr.getRoSegmentInfo().file_layout.size << (mHdr.getRoSegmentInfo().is_compressed? " (COMPRESSED)" : "") << std::endl; - std::cout << " .data:" << std::endl; - std::cout << " FileOffset: 0x" << std::hex << mHdr.getDataSegmentInfo().file_layout.offset << std::endl; - std::cout << " FileSize: 0x" << std::hex << mHdr.getDataSegmentInfo().file_layout.size << (mHdr.getDataSegmentInfo().is_compressed? " (COMPRESSED)" : "") << std::endl; - } - std::cout << " Program Sections:" << std::endl; - std::cout << " .text:" << std::endl; - std::cout << " MemoryOffset: 0x" << std::hex << mHdr.getTextSegmentInfo().memory_layout.offset << std::endl; - std::cout << " MemorySize: 0x" << std::hex << mHdr.getTextSegmentInfo().memory_layout.size << std::endl; - if (mHdr.getTextSegmentInfo().is_hashed && mCliOutputMode.show_extended_info) - { - std::cout << " Hash: " << fnd::SimpleTextOutput::arrayToString(mHdr.getTextSegmentInfo().hash.bytes, 32, false, "") << std::endl; - } - std::cout << " .ro:" << std::endl; - std::cout << " MemoryOffset: 0x" << std::hex << mHdr.getRoSegmentInfo().memory_layout.offset << std::endl; - std::cout << " MemorySize: 0x" << std::hex << mHdr.getRoSegmentInfo().memory_layout.size << std::endl; - if (mHdr.getRoSegmentInfo().is_hashed && mCliOutputMode.show_extended_info) - { - std::cout << " Hash: " << fnd::SimpleTextOutput::arrayToString(mHdr.getRoSegmentInfo().hash.bytes, 32, false, "") << std::endl; - } - if (mCliOutputMode.show_extended_info) - { - std::cout << " .api_info:" << std::endl; - std::cout << " MemoryOffset: 0x" << std::hex << mHdr.getRoEmbeddedInfo().offset << std::endl; - std::cout << " MemorySize: 0x" << std::hex << mHdr.getRoEmbeddedInfo().size << std::endl; - std::cout << " .dynstr:" << std::endl; - std::cout << " MemoryOffset: 0x" << std::hex << mHdr.getRoDynStrInfo().offset << std::endl; - std::cout << " MemorySize: 0x" << std::hex << mHdr.getRoDynStrInfo().size << std::endl; - std::cout << " .dynsym:" << std::endl; - std::cout << " MemoryOffset: 0x" << std::hex << mHdr.getRoDynSymInfo().offset << std::endl; - std::cout << " MemorySize: 0x" << std::hex << mHdr.getRoDynSymInfo().size << std::endl; - } - - std::cout << " .data:" << std::endl; - std::cout << " MemoryOffset: 0x" << std::hex << mHdr.getDataSegmentInfo().memory_layout.offset << std::endl; - std::cout << " MemorySize: 0x" << std::hex << mHdr.getDataSegmentInfo().memory_layout.size << std::endl; - if (mHdr.getDataSegmentInfo().is_hashed && mCliOutputMode.show_extended_info) - { - std::cout << " Hash: " << fnd::SimpleTextOutput::arrayToString(mHdr.getDataSegmentInfo().hash.bytes, 32, false, "") << std::endl; - } - std::cout << " .bss:" << std::endl; - std::cout << " MemorySize: 0x" << std::hex << mHdr.getBssSize() << std::endl; -} - -void nstool::NsoProcess::processRoMeta() -{ - if (mRoBlob.size()) - { - // setup ro metadata - mRoMeta.setApiInfo(mHdr.getRoEmbeddedInfo().offset, mHdr.getRoEmbeddedInfo().size); - mRoMeta.setDynSym(mHdr.getRoDynSymInfo().offset, mHdr.getRoDynSymInfo().size); - mRoMeta.setDynStr(mHdr.getRoDynStrInfo().offset, mHdr.getRoDynStrInfo().size); - mRoMeta.setRoBinary(mRoBlob); - mRoMeta.setCliOutputMode(mCliOutputMode); - mRoMeta.process(); - } -} \ No newline at end of file