diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index e78f61a..ec69aa1 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -13,7 +13,8 @@ "${workspaceRoot}/lib/libcompress/include", "${workspaceRoot}/lib/libes/include", "${workspaceRoot}/lib/libfnd/include", - "${workspaceRoot}/lib/libnx/include" + "${workspaceRoot}/lib/libnx/include", + "${workspaceRoot}/lib/libnx-hb/include" ], "defines": [], "intelliSenseMode": "clang-x64", diff --git a/.vscode/settings.json b/.vscode/settings.json index 331ed15..d1f6ffb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -28,5 +28,6 @@ "tuple": "cpp", "__locale": "cpp", "cinttypes": "cpp" + "__bit_reference": "cpp", } } \ No newline at end of file diff --git a/NXTools.sln b/NXTools.sln index 57de5b8..ade6849 100644 --- a/NXTools.sln +++ b/NXTools.sln @@ -37,10 +37,13 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nstool", "programs\nstool\nstool.vcxproj", "{AF09FA96-4463-417D-8FE6-526063F41349}" ProjectSection(ProjectDependencies) = postProject {CF01B5B7-730A-447F-9BB2-5EDA9B082177} = {CF01B5B7-730A-447F-9BB2-5EDA9B082177} + {738CB4FC-CD9E-4B81-A04B-DEADBFA71C63} = {738CB4FC-CD9E-4B81-A04B-DEADBFA71C63} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libcompress", "lib\libcompress\libcompress.vcxproj", "{CF01B5B7-730A-447F-9BB2-5EDA9B082177}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libnx-hb", "lib\libnx-hb\libnx-hb.vcxproj", "{738CB4FC-CD9E-4B81-A04B-DEADBFA71C63}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -97,6 +100,14 @@ Global {CF01B5B7-730A-447F-9BB2-5EDA9B082177}.Release|x64.Build.0 = Release|x64 {CF01B5B7-730A-447F-9BB2-5EDA9B082177}.Release|x86.ActiveCfg = Release|Win32 {CF01B5B7-730A-447F-9BB2-5EDA9B082177}.Release|x86.Build.0 = Release|Win32 + {738CB4FC-CD9E-4B81-A04B-DEADBFA71C63}.Debug|x64.ActiveCfg = Debug|x64 + {738CB4FC-CD9E-4B81-A04B-DEADBFA71C63}.Debug|x64.Build.0 = Debug|x64 + {738CB4FC-CD9E-4B81-A04B-DEADBFA71C63}.Debug|x86.ActiveCfg = Debug|Win32 + {738CB4FC-CD9E-4B81-A04B-DEADBFA71C63}.Debug|x86.Build.0 = Debug|Win32 + {738CB4FC-CD9E-4B81-A04B-DEADBFA71C63}.Release|x64.ActiveCfg = Release|x64 + {738CB4FC-CD9E-4B81-A04B-DEADBFA71C63}.Release|x64.Build.0 = Release|x64 + {738CB4FC-CD9E-4B81-A04B-DEADBFA71C63}.Release|x86.ActiveCfg = Release|Win32 + {738CB4FC-CD9E-4B81-A04B-DEADBFA71C63}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -108,6 +119,7 @@ Global {7BE99936-0D40-410D-944B-4513C2EFF8DC} = {170B4A09-1B67-4A62-93AB-116EBCFF4A8C} {AF09FA96-4463-417D-8FE6-526063F41349} = {E0863FCC-8E72-490D-BE1B-458F12CA8298} {CF01B5B7-730A-447F-9BB2-5EDA9B082177} = {170B4A09-1B67-4A62-93AB-116EBCFF4A8C} + {738CB4FC-CD9E-4B81-A04B-DEADBFA71C63} = {170B4A09-1B67-4A62-93AB-116EBCFF4A8C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {07DCCACC-D10D-47C9-85AE-FB9C54DB7D62} diff --git a/README.md b/README.md index 37c26fb..7051033 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ Tools & Libraries for NX (Nintendo Switch). * __libcrypto__ - Cryptographic functions (AES,SHA,RSA). Wrapper for [mbedTLS](https://github.com/ARMmbed/mbedtls) * __libcompress__ - Compression algorithms (LZ4). Wrapper for [lz4](https://github.com/lz4/lz4) * __libes__ - Handling of (NX relevant) eShop file type processing. (eTickets, etc) -* __libnx__ - Handling of NX file types +* __libnx__ - Handling of NX file types. +* __libnx-hb__ - Handling of NX (homebrew extensions) file types. # Building diff --git a/lib/libnx-hb/include/nx/AssetHeader.h b/lib/libnx-hb/include/nx/AssetHeader.h new file mode 100644 index 0000000..17da642 --- /dev/null +++ b/lib/libnx-hb/include/nx/AssetHeader.h @@ -0,0 +1,79 @@ +#pragma once +#include +#include +#include +#include + +namespace nx +{ + class AssetHeader : + public fnd::ISerialiseableBinary + { + public: + struct sSection + { + uint64_t offset; + uint64_t size; + + void operator=(const sSection& other) + { + offset = other.offset; + size = other.size; + } + + bool operator==(const sSection& other) const + { + return (offset == other.offset) \ + && (size == other.size); + } + + bool operator!=(const sSection& other) const + { + return !(*this == other); + } + }; + + AssetHeader(); + AssetHeader(const AssetHeader& other); + AssetHeader(const byte_t* bytes, size_t len); + + bool operator==(const AssetHeader& other) const; + bool operator!=(const AssetHeader& other) const; + void operator=(const AssetHeader& 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 sSection& getIconInfo() const; + void setIconInfo(const sSection& info); + + const sSection& getNacpInfo() const; + void setNacpInfo(const sSection& info); + + const sSection& getRomfsInfo() const; + void setRomfsInfo(const sSection& info); + private: + const std::string kModuleName = "NRO_ASSET_HEADER"; + + // binary + fnd::MemoryBlob mBinaryBlob; + + // data + sSection mIconInfo; + sSection mNacpInfo; + sSection mRomfsInfo; + + // helpers + bool isEqual(const AssetHeader& other) const; + void copyFrom(const AssetHeader& other); + }; + +} \ No newline at end of file diff --git a/lib/libnx-hb/include/nx/aset.h b/lib/libnx-hb/include/nx/aset.h new file mode 100644 index 0000000..58701bc --- /dev/null +++ b/lib/libnx-hb/include/nx/aset.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +namespace nx +{ + namespace aset + { + static const uint32_t kAssetSig = _MAKE_STRUCT_SIGNATURE("ASET"); + + static const uint32_t kDefaultAssetFormatVersion = 0; + } + +#pragma pack(push,1) + struct sAssetSection + { + le_uint64_t offset; + le_uint64_t size; + }; + + struct sAssetHeader + { + le_uint32_t signature; + le_uint32_t format_version; + sAssetSection icon; + sAssetSection nacp; + sAssetSection romfs; + }; +#pragma pack(pop) +} \ No newline at end of file diff --git a/lib/libnx-hb/include/nx/nro-hb.h b/lib/libnx-hb/include/nx/nro-hb.h new file mode 100644 index 0000000..7c13988 --- /dev/null +++ b/lib/libnx-hb/include/nx/nro-hb.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include +namespace nx +{ + namespace nro + { + static const uint64_t kNroHomebrewSig = _MAKE_STRUCT_SIGNATURE_U64("HOMEBREW"); + } +} \ No newline at end of file diff --git a/lib/libnx-hb/libnx-hb.vcxproj b/lib/libnx-hb/libnx-hb.vcxproj new file mode 100644 index 0000000..927d5d9 --- /dev/null +++ b/lib/libnx-hb/libnx-hb.vcxproj @@ -0,0 +1,135 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {738CB4FC-CD9E-4B81-A04B-DEADBFA71C63} + libnxhb + 10.0.16299.0 + + + + StaticLibrary + true + v141 + MultiByte + + + StaticLibrary + false + v141 + true + MultiByte + + + StaticLibrary + true + v141 + MultiByte + + + StaticLibrary + false + v141 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + Level3 + MaxSpeed + true + true + true + true + ..\libfnd\include;..\libcrypto\include;..\libnx\include;..\libnx-hb\include; + + + true + true + + + + + Level3 + Disabled + true + true + ..\libfnd\include;..\libcrypto\include;..\libnx\include;..\libnx-hb\include; + + + + + Level3 + Disabled + true + true + ..\libfnd\include;..\libcrypto\include;..\libnx\include;..\libnx-hb\include; + + + + + Level3 + MaxSpeed + true + true + true + true + ..\libfnd\include;..\libcrypto\include;..\libnx\include;..\libnx-hb\include; + + + true + true + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/libnx-hb/libnx-hb.vcxproj.filters b/lib/libnx-hb/libnx-hb.vcxproj.filters new file mode 100644 index 0000000..01df9d5 --- /dev/null +++ b/lib/libnx-hb/libnx-hb.vcxproj.filters @@ -0,0 +1,36 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + + + + \ No newline at end of file diff --git a/lib/libnx-hb/libnx-hb.vcxproj.user b/lib/libnx-hb/libnx-hb.vcxproj.user new file mode 100644 index 0000000..be25078 --- /dev/null +++ b/lib/libnx-hb/libnx-hb.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/lib/libnx-hb/makefile b/lib/libnx-hb/makefile new file mode 100644 index 0000000..f069c2b --- /dev/null +++ b/lib/libnx-hb/makefile @@ -0,0 +1,47 @@ +# Sources +SRC_DIR = source +OBJS = $(foreach dir,$(SRC_DIR),$(subst .cpp,.o,$(wildcard $(dir)/*.cpp))) $(foreach dir,$(SRC_DIR),$(subst .c,.o,$(wildcard $(dir)/*.c))) + +# External dependencies +DEPENDS = fnd crypto nx +LIB_DIR = .. +INCS = -I"include" $(foreach dep,$(DEPENDS), -I"$(LIB_DIR)/lib$(dep)/include") + + +# Compiler Settings +CXXFLAGS = -std=c++11 $(INCS) -D__STDC_FORMAT_MACROS -Wall -Wno-unused-value +CFLAGS = -std=c11 $(INCS) -Wall -Wno-unused-value +ARFLAGS = cr -o +ifeq ($(OS),Windows_NT) + # Windows Only Flags/Libs + CC = x86_64-w64-mingw32-gcc + CXX = x86_64-w64-mingw32-g++ + CFLAGS += -Wno-unused-but-set-variable + CXXFLAGS += -Wno-unused-but-set-variable +else + UNAME = $(shell uname -s) + ifeq ($(UNAME), Darwin) + # MacOS Only Flags/Libs + CFLAGS += -Wno-unused-private-field + CXXFLAGS += -Wno-unused-private-field + ARFLAGS = rc + else + # *nix Only Flags/Libs + CFLAGS += -Wno-unused-but-set-variable + CXXFLAGS += -Wno-unused-but-set-variable + endif + +endif + +# Output +OUTPUT = $(shell basename $(CURDIR)).a + +main: build + +rebuild: clean build + +build: $(OBJS) + ar $(ARFLAGS) $(OUTPUT) $(OBJS) + +clean: + rm -rf $(OUTPUT) $(OBJS) \ No newline at end of file diff --git a/lib/libnx-hb/source/AssetHeader.cpp b/lib/libnx-hb/source/AssetHeader.cpp new file mode 100644 index 0000000..3d61036 --- /dev/null +++ b/lib/libnx-hb/source/AssetHeader.cpp @@ -0,0 +1,153 @@ +#include + +nx::AssetHeader::AssetHeader() +{ + clear(); +} + +nx::AssetHeader::AssetHeader(const AssetHeader& other) +{ + copyFrom(other); +} + +nx::AssetHeader::AssetHeader(const byte_t* bytes, size_t len) +{ + importBinary(bytes, len); +} + +bool nx::AssetHeader::operator==(const AssetHeader& other) const +{ + return isEqual(other); +} + +bool nx::AssetHeader::operator!=(const AssetHeader& other) const +{ + return !(*this == other); +} + +void nx::AssetHeader::operator=(const AssetHeader& other) +{ + copyFrom(other); +} + +const byte_t* nx::AssetHeader::getBytes() const +{ + return mBinaryBlob.getBytes(); +} + +size_t nx::AssetHeader::getSize() const +{ + return mBinaryBlob.getSize(); +} + +void nx::AssetHeader::exportBinary() +{ + mBinaryBlob.alloc(sizeof(sAssetHeader)); + nx::sAssetHeader* hdr = (nx::sAssetHeader*)mBinaryBlob.getBytes(); + + // set header identifers + hdr->signature = aset::kAssetSig; + hdr->format_version = aset::kDefaultAssetFormatVersion; + + // set icon section + hdr->icon.offset = mIconInfo.offset; + hdr->icon.size = mIconInfo.size; + + // set nacp section + hdr->nacp.offset = mNacpInfo.offset; + hdr->nacp.size = mNacpInfo.size; + + // set romfs section + hdr->romfs.offset = mRomfsInfo.offset; + hdr->romfs.size = mRomfsInfo.size; +} + +void nx::AssetHeader::importBinary(const byte_t* bytes, size_t len) +{ + // check input data size + if (len < sizeof(sAssetHeader)) + { + throw fnd::Exception(kModuleName, "ASET header size is too small"); + } + + // clear internal members + clear(); + + // allocate internal local binary copy + mBinaryBlob.alloc(sizeof(sAssetHeader)); + memcpy(mBinaryBlob.getBytes(), bytes, mBinaryBlob.getSize()); + + // get sAssetHeader ptr + const nx::sAssetHeader* hdr = (const nx::sAssetHeader*)mBinaryBlob.getBytes(); + + // check NRO signature + if (hdr->signature.get() != aset::kAssetSig) + { + throw fnd::Exception(kModuleName, "ASET header corrupt (unrecognised header signature)"); + } + + // check NRO format version + if (hdr->format_version.get() != aset::kDefaultAssetFormatVersion) + { + throw fnd::Exception(kModuleName, "ASET header corrupt (unsupported format version)"); + } + + mIconInfo.offset = hdr->icon.offset.get(); + mIconInfo.size = hdr->icon.size.get(); + mNacpInfo.offset = hdr->nacp.offset.get(); + mNacpInfo.size = hdr->nacp.size.get(); + mRomfsInfo.offset = hdr->romfs.offset.get(); + mRomfsInfo.size = hdr->romfs.size.get(); +} + +void nx::AssetHeader::clear() +{ + mBinaryBlob.clear(); + memset(&mIconInfo, 0, sizeof(mIconInfo)); + memset(&mNacpInfo, 0, sizeof(mNacpInfo)); + memset(&mRomfsInfo, 0, sizeof(mRomfsInfo)); +} + +const nx::AssetHeader::sSection& nx::AssetHeader::getIconInfo() const +{ + return mIconInfo; +} + +void nx::AssetHeader::setIconInfo(const nx::AssetHeader::sSection& info) +{ + mIconInfo = info; +} + +const nx::AssetHeader::sSection& nx::AssetHeader::getNacpInfo() const +{ + return mNacpInfo; +} + +void nx::AssetHeader::setNacpInfo(const sSection& info) +{ + mNacpInfo = info; +} + +const nx::AssetHeader::sSection& nx::AssetHeader::getRomfsInfo() const +{ + return mRomfsInfo; +} + +void nx::AssetHeader::setRomfsInfo(const sSection& info) +{ + mRomfsInfo = info; +} + +bool nx::AssetHeader::isEqual(const AssetHeader& other) const +{ + return (mIconInfo == other.mIconInfo) \ + && (mNacpInfo == other.mNacpInfo) \ + && (mRomfsInfo == other.mRomfsInfo); +} + +void nx::AssetHeader::copyFrom(const AssetHeader& other) +{ + mIconInfo = other.mIconInfo; + mNacpInfo = other.mNacpInfo; + mRomfsInfo = other.mRomfsInfo; +} \ No newline at end of file diff --git a/lib/libnx/include/nx/macro.h b/lib/libnx/include/nx/macro.h index 9ad295c..4ef53e8 100644 --- a/lib/libnx/include/nx/macro.h +++ b/lib/libnx/include/nx/macro.h @@ -1,3 +1,5 @@ #pragma once +#include #define _MAKE_STRUCT_SIGNATURE(x) ((uint32_t)(x[3]) << 24 | (uint32_t)(x[2]) << 16 | (uint32_t)(x[1]) << 8 | (uint32_t)(x[0])) +#define _MAKE_STRUCT_SIGNATURE_U64(x) ((uint64_t)(x[7]) << 56 | (uint64_t)(x[6]) << 48 | (uint64_t)(x[5]) << 40 | (uint64_t)(x[4]) << 32 | (uint64_t)(x[3]) << 24 | (uint64_t)(x[2]) << 16 | (uint64_t)(x[1]) << 8 | (uint64_t)(x[0])) diff --git a/lib/libnx/nx.vcxproj b/lib/libnx/nx.vcxproj index dcd2072..87b23d0 100644 --- a/lib/libnx/nx.vcxproj +++ b/lib/libnx/nx.vcxproj @@ -56,6 +56,7 @@ + @@ -104,6 +105,7 @@ + diff --git a/lib/libnx/nx.vcxproj.filters b/lib/libnx/nx.vcxproj.filters index d4d2667..bd88b48 100644 --- a/lib/libnx/nx.vcxproj.filters +++ b/lib/libnx/nx.vcxproj.filters @@ -174,6 +174,9 @@ Header Files + + Header Files + @@ -260,6 +263,9 @@ Source Files + + Source Files + Source Files diff --git a/lib/libnx/source/NroHeader.cpp b/lib/libnx/source/NroHeader.cpp index d79f5fb..8d031b8 100644 --- a/lib/libnx/source/NroHeader.cpp +++ b/lib/libnx/source/NroHeader.cpp @@ -22,7 +22,7 @@ bool nx::NroHeader::operator==(const NroHeader& other) const bool nx::NroHeader::operator!=(const NroHeader& other) const { - return !(*this != other); + return !(*this == other); } void nx::NroHeader::operator=(const NroHeader& other) @@ -42,7 +42,49 @@ size_t nx::NroHeader::getSize() const void nx::NroHeader::exportBinary() { - throw fnd::Exception(kModuleName, "exportBinary() unsupported"); + mBinaryBlob.alloc(sizeof(sNroHeader)); + nx::sNroHeader* hdr = (nx::sNroHeader*)mBinaryBlob.getBytes(); + + // set header identifers + hdr->signature = nro::kNroSig; + hdr->format_version = nro::kDefaultFormatVersion; + hdr->flags = 0; + + // set ro crt + memcpy(hdr->ro_crt, mRoCrt.data, nro::kRoCrtSize); + + // set nro size + hdr->size = mNroSize; + + // set text section + hdr->text.memory_offset = mTextInfo.memory_offset; + hdr->text.size = mTextInfo.size; + + // set ro section + hdr->ro.memory_offset = mRoInfo.memory_offset; + hdr->ro.size = mRoInfo.size; + + // set data section + hdr->data.memory_offset = mDataInfo.memory_offset; + hdr->data.size = mDataInfo.size; + + // set bss size + hdr->bss_size = mBssSize; + + // set moduleid + memcpy(hdr->module_id, mModuleId.data, nro::kModuleIdSize); + + // set ro embedded info + hdr->embedded.memory_offset = mRoEmbeddedInfo.memory_offset; + hdr->embedded.size = mRoEmbeddedInfo.size; + + // set ro dyn str info + hdr->dyn_str.memory_offset = mRoDynStrInfo.memory_offset; + hdr->dyn_str.size = mRoDynStrInfo.size; + + // set ro dyn sym info + hdr->dyn_sym.memory_offset = mRoDynSymInfo.memory_offset; + hdr->dyn_sym.size = mRoDynSymInfo.size; } void nx::NroHeader::importBinary(const byte_t* bytes, size_t len) diff --git a/lib/makefile b/lib/makefile index db1fbe0..89636d0 100644 --- a/lib/makefile +++ b/lib/makefile @@ -1,4 +1,4 @@ -LIBS = libfnd libcrypto libcompress libes libnx +LIBS = libfnd libcrypto libcompress libes libnx libnx-hb main: build rebuild: clean build diff --git a/programs/nstool/makefile b/programs/nstool/makefile index cc25b93..85aa304 100644 --- a/programs/nstool/makefile +++ b/programs/nstool/makefile @@ -3,7 +3,7 @@ SRC_DIR = source OBJS = $(foreach dir,$(SRC_DIR),$(subst .cpp,.o,$(wildcard $(dir)/*.cpp))) $(foreach dir,$(SRC_DIR),$(subst .c,.o,$(wildcard $(dir)/*.c))) # External dependencies -DEPENDS = nx crypto compress fnd +DEPENDS = nx-hb nx crypto compress fnd LIB_DIR = ../../lib LIBS = $(foreach dep,$(DEPENDS), -L"$(LIB_DIR)/lib$(dep)" -l$(dep)) INCS = $(foreach dep,$(DEPENDS), -I"$(LIB_DIR)/lib$(dep)/include") diff --git a/programs/nstool/nstool.vcxproj b/programs/nstool/nstool.vcxproj index 1d211b8..565e8ae 100644 --- a/programs/nstool/nstool.vcxproj +++ b/programs/nstool/nstool.vcxproj @@ -90,7 +90,7 @@ true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true - ..\..\lib\libfnd\include;..\..\lib\libcompress\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include + ..\..\lib\libfnd\include;..\..\lib\libcompress\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;..\..\lib\libnx-hb\include Console @@ -105,7 +105,7 @@ true WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true - ..\..\lib\libfnd\include;..\..\lib\libcompress\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include + ..\..\lib\libfnd\include;..\..\lib\libcompress\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;..\..\lib\libnx-hb\include Console @@ -122,7 +122,7 @@ true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true - ..\..\lib\libfnd\include;..\..\lib\libcompress\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include + ..\..\lib\libfnd\include;..\..\lib\libcompress\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;..\..\lib\libnx-hb\include Console @@ -141,7 +141,7 @@ true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true - ..\..\lib\libfnd\include;..\..\lib\libcompress\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include + ..\..\lib\libfnd\include;..\..\lib\libcompress\include;..\..\lib\libcrypto\include;..\..\lib\libnx\include;..\..\lib\libnx-hb\include Console @@ -160,29 +160,36 @@ {4d27edb9-5110-44fe-8ce2-d46c5ad3c55b} + + {738cb4fc-cd9e-4b81-a04b-deadbfa71c63} + {91ba9e79-8242-4f7d-b997-0dfec95ea22b} + + + + @@ -190,10 +197,12 @@ + + diff --git a/programs/nstool/nstool.vcxproj.filters b/programs/nstool/nstool.vcxproj.filters index 558275e..d9bc781 100644 --- a/programs/nstool/nstool.vcxproj.filters +++ b/programs/nstool/nstool.vcxproj.filters @@ -60,6 +60,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + @@ -104,6 +113,15 @@ Source Files + + Source Files + + + Source Files + + + Source Files + diff --git a/programs/nstool/source/AssetProcess.cpp b/programs/nstool/source/AssetProcess.cpp new file mode 100644 index 0000000..d7d7b30 --- /dev/null +++ b/programs/nstool/source/AssetProcess.cpp @@ -0,0 +1,146 @@ +#include +#include +#include "AssetProcess.h" +#include "OffsetAdjustedIFile.h" + + +AssetProcess::AssetProcess() : + mFile(nullptr), + mOwnIFile(false), + mCliOutputType(OUTPUT_NORMAL), + mVerify(false) +{ + +} + +AssetProcess::~AssetProcess() +{ + if (mOwnIFile) + { + delete mFile; + } +} + +void AssetProcess::process() +{ + if (mFile == nullptr) + { + throw fnd::Exception(kModuleName, "No file reader set."); + } + + importHeader(); + displayHeader(); + processSections(); +} + +void AssetProcess::setInputFile(fnd::IFile* file, bool ownIFile) +{ + mFile = file; + mOwnIFile = ownIFile; +} + +void AssetProcess::setCliOutputMode(CliOutputType type) +{ + mCliOutputType = type; +} + +void AssetProcess::setVerifyMode(bool verify) +{ + mVerify = verify; +} + +void AssetProcess::setListFs(bool list) +{ + mRomfs.setListFs(list); +} + +void AssetProcess::setIconExtractPath(const std::string& path) +{ + mIconExtractPath = path; +} + +void AssetProcess::setNacpExtractPath(const std::string& path) +{ + mNacpExtractPath = path; +} + +void AssetProcess::setRomfsExtractPath(const std::string& path) +{ + mRomfs.setExtractPath(path); +} + + +void AssetProcess::importHeader() +{ + fnd::MemoryBlob scratch; + if (mFile->size() < sizeof(nx::sAssetHeader)) + { + throw fnd::Exception(kModuleName, "Corrupt ASET: file too small"); + } + + scratch.alloc(sizeof(nx::sAssetHeader)); + mFile->read(scratch.getBytes(), 0, scratch.getSize()); + + mHdr.importBinary(scratch.getBytes(), scratch.getSize()); +} + +void AssetProcess::processSections() +{ + if (mHdr.getIconInfo().size > 0 && mIconExtractPath.isSet) + { + if ((mHdr.getIconInfo().size + mHdr.getIconInfo().offset) > mFile->size()) + throw fnd::Exception(kModuleName, "ASET geometry for icon beyond file size"); + + fnd::SimpleFile outfile(mIconExtractPath.var, fnd::SimpleFile::Create); + fnd::MemoryBlob cache; + + cache.alloc(mHdr.getIconInfo().size); + mFile->read(cache.getBytes(), mHdr.getIconInfo().offset, cache.getSize()); + outfile.write(cache.getBytes(), cache.getSize()); + outfile.close(); + } + + if (mHdr.getNacpInfo().size > 0 && mNacpExtractPath.isSet) + { + if ((mHdr.getNacpInfo().size + mHdr.getNacpInfo().offset) > mFile->size()) + throw fnd::Exception(kModuleName, "ASET geometry for nacp beyond file size"); + + fnd::SimpleFile outfile(mNacpExtractPath.var, fnd::SimpleFile::Create); + fnd::MemoryBlob cache; + + cache.alloc(mHdr.getNacpInfo().size); + mFile->read(cache.getBytes(), mHdr.getNacpInfo().offset, cache.getSize()); + outfile.write(cache.getBytes(), cache.getSize()); + outfile.close(); + } + + if (mHdr.getRomfsInfo().size > 0) + { + if ((mHdr.getRomfsInfo().size + mHdr.getRomfsInfo().offset) > mFile->size()) + throw fnd::Exception(kModuleName, "ASET geometry for romfs beyond file size"); + + mRomfs.setInputFile(new OffsetAdjustedIFile(mFile, false, mHdr.getRomfsInfo().offset, mHdr.getRomfsInfo().size), true); + mRomfs.setCliOutputMode(mCliOutputType); + mRomfs.setVerifyMode(mVerify); + + mRomfs.process(); + } +} + +void AssetProcess::displayHeader() +{ + if (mCliOutputType >= OUTPUT_NORMAL) + { + printf("[ASET Header]\n"); + printf(" Icon:\n"); + printf(" Offset: 0x%" PRIx64 "\n", mHdr.getIconInfo().offset); + printf(" Size: 0x%" PRIx64 "\n", mHdr.getIconInfo().size); + printf(" NACP:\n"); + printf(" Offset: 0x%" PRIx64 "\n", mHdr.getNacpInfo().offset); + printf(" Size: 0x%" PRIx64 "\n", mHdr.getNacpInfo().size); + printf(" RomFS:\n"); + printf(" Offset: 0x%" PRIx64 "\n", mHdr.getRomfsInfo().offset); + printf(" Size: 0x%" PRIx64 "\n", mHdr.getRomfsInfo().size); + } +} + diff --git a/programs/nstool/source/AssetProcess.h b/programs/nstool/source/AssetProcess.h new file mode 100644 index 0000000..2cb80fd --- /dev/null +++ b/programs/nstool/source/AssetProcess.h @@ -0,0 +1,46 @@ +#pragma once +#include +#include +#include +#include +#include "RomfsProcess.h" + +#include "nstool.h" + +class AssetProcess +{ +public: + AssetProcess(); + ~AssetProcess(); + + void process(); + + void setInputFile(fnd::IFile* file, bool ownIFile); + void setCliOutputMode(CliOutputType type); + void setVerifyMode(bool verify); + + void setListFs(bool list); + + void setIconExtractPath(const std::string& path); + void setNacpExtractPath(const std::string& path); + void setRomfsExtractPath(const std::string& path); + + +private: + const std::string kModuleName = "AssetProcess"; + + fnd::IFile* mFile; + bool mOwnIFile; + CliOutputType mCliOutputType; + bool mVerify; + + sOptional mIconExtractPath; + sOptional mNacpExtractPath; + + nx::AssetHeader mHdr; + RomfsProcess mRomfs; + + void importHeader(); + void processSections(); + void displayHeader(); +}; \ No newline at end of file diff --git a/programs/nstool/source/CodeObjectProcess.cpp b/programs/nstool/source/CodeObjectProcess.cpp deleted file mode 100644 index fd90f0b..0000000 --- a/programs/nstool/source/CodeObjectProcess.cpp +++ /dev/null @@ -1,518 +0,0 @@ -#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/NroProcess.cpp b/programs/nstool/source/NroProcess.cpp new file mode 100644 index 0000000..f41ae30 --- /dev/null +++ b/programs/nstool/source/NroProcess.cpp @@ -0,0 +1,328 @@ +#include +#include +#include +#include +#include +#include "OffsetAdjustedIFile.h" +#include "NroProcess.h" + +NroProcess::NroProcess(): + mFile(nullptr), + mOwnIFile(false), + mCliOutputType(OUTPUT_NORMAL), + mVerify(false), + mInstructionType(nx::npdm::INSTR_64BIT), + mListApi(false), + mListSymbols(false) +{ +} + +NroProcess::~NroProcess() +{ + if (mOwnIFile) + { + delete mFile; + } +} + +void NroProcess::process() +{ + if (mFile == nullptr) + { + throw fnd::Exception(kModuleName, "No file reader set."); + } + + importHeader(); + importCodeSegments(); + importApiList(); + displayHeader(); + displayRoMetaData(); + if (mIsHomebrewNro) + mAssetProc.process(); +} + +void NroProcess::setInputFile(fnd::IFile* file, bool ownIFile) +{ + mFile = file; + mOwnIFile = ownIFile; +} + +void NroProcess::setCliOutputMode(CliOutputType type) +{ + mCliOutputType = type; +} + +void NroProcess::setVerifyMode(bool verify) +{ + mVerify = verify; +} + +void NroProcess::setInstructionType(nx::npdm::InstructionType type) +{ + mInstructionType = type; +} + +void NroProcess::setListApi(bool listApi) +{ + mListApi = listApi; +} + +void NroProcess::setListSymbols(bool listSymbols) +{ + mListSymbols = listSymbols; +} + +void NroProcess::setAssetListFs(bool list) +{ + mAssetProc.setListFs(list); +} + +void NroProcess::setAssetIconExtractPath(const std::string& path) +{ + mAssetProc.setIconExtractPath(path); +} + +void NroProcess::setAssetNacpExtractPath(const std::string& path) +{ + mAssetProc.setNacpExtractPath(path); +} + +void NroProcess::setAssetRomfsExtractPath(const std::string& path) +{ + mAssetProc.setRomfsExtractPath(path); +} + +void NroProcess::importHeader() +{ + fnd::MemoryBlob scratch; + if (mFile->size() < sizeof(nx::sNroHeader)) + { + throw fnd::Exception(kModuleName, "Corrupt NRO: file too small"); + } + + scratch.alloc(sizeof(nx::sNroHeader)); + mFile->read(scratch.getBytes(), 0, scratch.getSize()); + + mHdr.importBinary(scratch.getBytes(), scratch.getSize()); + + nx::sNroHeader* raw_hdr = (nx::sNroHeader*)scratch.getBytes(); + + if (((le_uint64_t*)raw_hdr->reserved_0)->get() == nx::nro::kNroHomebrewSig && mFile->size() > mHdr.getNroSize()) + { + mIsHomebrewNro = true; + mAssetProc.setInputFile(new OffsetAdjustedIFile(mFile, false, mHdr.getNroSize(), mFile->size() - mHdr.getNroSize()), true); + mAssetProc.setCliOutputMode(mCliOutputType); + mAssetProc.setVerifyMode(mVerify); + } + else + mIsHomebrewNro = false; + +} + +void NroProcess::importCodeSegments() +{ + mTextBlob.alloc(mHdr.getTextInfo().size); + mFile->read(mTextBlob.getBytes(), mHdr.getTextInfo().memory_offset, mTextBlob.getSize()); + mRoBlob.alloc(mHdr.getRoInfo().size); + mFile->read(mRoBlob.getBytes(), mHdr.getRoInfo().memory_offset, mRoBlob.getSize()); + mDataBlob.alloc(mHdr.getDataInfo().size); + mFile->read(mDataBlob.getBytes(), mHdr.getDataInfo().memory_offset, mDataBlob.getSize()); +} + +void NroProcess::importApiList() +{ + struct sLayout { size_t offset; size_t size; } api_info, dyn_str, dyn_sym; + + api_info.offset = mHdr.getRoEmbeddedInfo().memory_offset; + api_info.size = mHdr.getRoEmbeddedInfo().size; + dyn_str.offset = mHdr.getRoDynStrInfo().memory_offset; + dyn_str.size = mHdr.getRoDynStrInfo().size; + dyn_sym.offset = mHdr.getRoDynSymInfo().memory_offset; + dyn_sym.size = mHdr.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 NroProcess::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) + if (mCliOutputType >= OUTPUT_NORMAL) + { + printf("[NRO Header]\n"); + printf(" RoCrt: "); + _HEXDUMP_L(mHdr.getRoCrt().data, nx::nro::kRoCrtSize); + printf("\n"); + printf(" ModuleId: "); + _HEXDUMP_L(mHdr.getModuleId().data, nx::nro::kModuleIdSize); + printf("\n"); + printf(" NroSize: 0x%" PRIx32 "\n", mHdr.getNroSize()); + printf(" Program Sections:\n"); + printf(" .text:\n"); + printf(" Offset: 0x%" PRIx32 "\n", mHdr.getTextInfo().memory_offset); + printf(" Size: 0x%" PRIx32 "\n", mHdr.getTextInfo().size); + printf(" .ro:\n"); + printf(" Offset: 0x%" PRIx32 "\n", mHdr.getRoInfo().memory_offset); + printf(" Size: 0x%" PRIx32 "\n", mHdr.getRoInfo().size); + if (mCliOutputType >= OUTPUT_VERBOSE) + { + printf(" .api_info:\n"); + printf(" Offset: 0x%" PRIx32 "\n", mHdr.getRoEmbeddedInfo().memory_offset); + printf(" Size: 0x%" PRIx32 "\n", mHdr.getRoEmbeddedInfo().size); + printf(" .dynstr:\n"); + printf(" Offset: 0x%" PRIx32 "\n", mHdr.getRoDynStrInfo().memory_offset); + printf(" Size: 0x%" PRIx32 "\n", mHdr.getRoDynStrInfo().size); + printf(" .dynsym:\n"); + printf(" Offset: 0x%" PRIx32 "\n", mHdr.getRoDynSymInfo().memory_offset); + printf(" Size: 0x%" PRIx32 "\n", mHdr.getRoDynSymInfo().size); + } + printf(" .data:\n"); + printf(" Offset: 0x%" PRIx32 "\n", mHdr.getDataInfo().memory_offset); + printf(" Size: 0x%" PRIx32 "\n", mHdr.getDataInfo().size); + printf(" .bss:\n"); + printf(" Size: 0x%" PRIx32 "\n", mHdr.getBssSize()); + } + +#undef _HEXDUMP_L +} + +void NroProcess::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* NroProcess::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* NroProcess::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* NroProcess::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/NroProcess.h b/programs/nstool/source/NroProcess.h new file mode 100644 index 0000000..1a81b1c --- /dev/null +++ b/programs/nstool/source/NroProcess.h @@ -0,0 +1,63 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include "AssetProcess.h" + +#include "nstool.h" +#include "SdkApiString.h" +#include "DynamicSymbolParser.h" + +class NroProcess +{ +public: + NroProcess(); + ~NroProcess(); + + void process(); + + void setInputFile(fnd::IFile* file, bool ownIFile); + void setCliOutputMode(CliOutputType type); + void setVerifyMode(bool verify); + + void setInstructionType(nx::npdm::InstructionType type); + void setListApi(bool listApi); + void setListSymbols(bool listSymbols); + + // for homebrew NROs with Asset blobs appended + void setAssetListFs(bool list); + void setAssetIconExtractPath(const std::string& path); + void setAssetNacpExtractPath(const std::string& path); + void setAssetRomfsExtractPath(const std::string& path); +private: + const std::string kModuleName = "NroProcess"; + + fnd::IFile* mFile; + bool mOwnIFile; + + CliOutputType mCliOutputType; + bool mVerify; + nx::npdm::InstructionType mInstructionType; + bool mListApi; + bool mListSymbols; + + nx::NroHeader mHdr; + bool mIsHomebrewNro; + AssetProcess mAssetProc; + fnd::MemoryBlob mTextBlob, mRoBlob, mDataBlob; + std::vector mApiList; + DynamicSymbolParser mDynSymbolList; + + void importHeader(); + void importCodeSegments(); + void importApiList(); + void displayHeader(); + 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 new file mode 100644 index 0000000..3af7460 --- /dev/null +++ b/programs/nstool/source/NsoProcess.cpp @@ -0,0 +1,395 @@ +#include +#include +#include +#include +#include "OffsetAdjustedIFile.h" +#include "NsoProcess.h" + +NsoProcess::NsoProcess(): + mFile(nullptr), + mOwnIFile(false), + mCliOutputType(OUTPUT_NORMAL), + mVerify(false), + mInstructionType(nx::npdm::INSTR_64BIT), + mListApi(false), + mListSymbols(false) +{ +} + +NsoProcess::~NsoProcess() +{ + if (mOwnIFile) + { + delete mFile; + } +} + +void NsoProcess::process() +{ + if (mFile == nullptr) + { + throw fnd::Exception(kModuleName, "No file reader set."); + } + + importHeader(); + importCodeSegments(); + importApiList(); + displayNsoHeader(); + displayRoMetaData(); +} + +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::setInstructionType(nx::npdm::InstructionType type) +{ + mInstructionType = type; +} + +void NsoProcess::setListApi(bool listApi) +{ + mListApi = listApi; +} + +void NsoProcess::setListSymbols(bool listSymbols) +{ + mListSymbols = listSymbols; +} + +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()); + + mNsoHdr.importBinary(scratch.getBytes(), scratch.getSize()); +} + +void NsoProcess::importCodeSegments() +{ + 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(), (uint32_t)scratch.getSize(), mTextBlob.getBytes(), (uint32_t)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(), (uint32_t)scratch.getSize(), mRoBlob.getBytes(), (uint32_t)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(), (uint32_t)scratch.getSize(), mDataBlob.getBytes(), (uint32_t)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"); + } + } +} + +void NsoProcess::importApiList() +{ + struct sLayout { size_t offset; size_t size; } api_info, dyn_str, dyn_sym; + + 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; + + 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 NsoProcess::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) + + if (mCliOutputType >= OUTPUT_NORMAL) + { + 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 NsoProcess::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* NsoProcess::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* NsoProcess::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* NsoProcess::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/NsoProcess.h similarity index 66% rename from programs/nstool/source/CodeObjectProcess.h rename to programs/nstool/source/NsoProcess.h index b0861e1..50714fb 100644 --- a/programs/nstool/source/CodeObjectProcess.h +++ b/programs/nstool/source/NsoProcess.h @@ -5,24 +5,16 @@ #include #include #include -#include #include "nstool.h" #include "SdkApiString.h" #include "DynamicSymbolParser.h" -class CodeObjectProcess +class NsoProcess { public: - enum CodeObjectType - { - OBJ_NSO, - OBJ_NRO, - OBJ_INVALID - }; - - CodeObjectProcess(); - ~CodeObjectProcess(); + NsoProcess(); + ~NsoProcess(); void process(); @@ -30,33 +22,22 @@ public: 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"; + const std::string kModuleName = "NsoProcess"; 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; @@ -65,7 +46,6 @@ private: void importCodeSegments(); void importApiList(); void displayNsoHeader(); - void displayNroHeader(); void displayRoMetaData(); const char* getApiTypeStr(SdkApiString::ApiType type) const; diff --git a/programs/nstool/source/UserSettings.cpp b/programs/nstool/source/UserSettings.cpp index 591f9a8..d4d38fb 100644 --- a/programs/nstool/source/UserSettings.cpp +++ b/programs/nstool/source/UserSettings.cpp @@ -19,6 +19,7 @@ #include #include #include +#include UserSettings::UserSettings() {} @@ -40,7 +41,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, nro]\n"); + printf(" -t, --type Specify input file type [xci, pfs, romfs, nca, npdm, cnmt, nso, nro, aset]\n"); printf(" -y, --verify Verify file\n"); printf(" -v, --verbose Verbose output\n"); printf(" -q, --quiet Minimal output\n"); @@ -69,6 +70,12 @@ void UserSettings::showHelp() printf(" --listapi Print SDK API List.\n"); printf(" --listsym Print Dynamic Symbols.\n"); printf(" --insttype Specify instruction type [64bit|32bit] (64bit is assumed).\n"); + printf("\n ASET (Homebrew Asset Blob)\n"); + printf(" nstool [--listfs] [--icon --nacp --fsdir ] \n"); + printf(" --listfs Print filesystem in embedded RomFS partition.\n"); + printf(" --icon Extract icon partition to file.\n"); + printf(" --nacp Extract NACP partition to file.\n"); + printf(" --fsdir Extract RomFS partition to directory.\n"); } @@ -161,6 +168,15 @@ const sOptional& UserSettings::getNcaPart3Path() const return mNcaPart3Path; } +const sOptional& UserSettings::getAssetIconPath() const +{ + return mAssetIconPath; +} + +const sOptional& UserSettings::getAssetNacpPath() const +{ + return mAssetNacpPath; +} void UserSettings::populateCmdArgs(int argc, char** argv, sCmdArgs& cmd_args) { @@ -319,6 +335,18 @@ void UserSettings::populateCmdArgs(int argc, char** argv, sCmdArgs& cmd_args) cmd_args.inst_type = args[i + 1]; } + else if (args[i] == "--icon") + { + if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter."); + cmd_args.asset_icon_path = args[i + 1]; + } + + else if (args[i] == "--nacp") + { + if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter."); + cmd_args.asset_nacp_path = args[i + 1]; + } + else { throw fnd::Exception(kModuleName, args[i] + " is not recognised."); @@ -585,6 +613,9 @@ void UserSettings::populateUserSettings(sCmdArgs& args) mListApi = args.list_api.isSet; mListSymbols = args.list_sym.isSet; + mAssetIconPath = args.asset_icon_path; + mAssetNacpPath = args.asset_nacp_path; + // determine output path if (args.verbose_output.isSet) mOutputType = OUTPUT_VERBOSE; @@ -645,6 +676,8 @@ FileType UserSettings::getFileTypeFromString(const std::string& type_str) type = FILE_NSO; else if (str == "nro") type = FILE_NRO; + else if (str == "aset" || str == "asset") + type = FILE_HB_ASSET; else type = FILE_INVALID; @@ -667,7 +700,7 @@ FileType UserSettings::determineFileTypeFromFile(const std::string& path) // close file file.close(); - fnd::SimpleTextOutput::hxdStyleDump(scratch.getBytes(), scratch.getSize()); + //fnd::SimpleTextOutput::hxdStyleDump(scratch.getBytes(), scratch.getSize()); // prepare decrypted NCA data byte_t nca_raw[nx::nca::kHeaderSize]; @@ -709,6 +742,9 @@ FileType UserSettings::determineFileTypeFromFile(const std::string& path) // test nso else if (_ASSERT_SIZE(sizeof(nx::sNroHeader)) && _QUICK_CAST(nx::sNroHeader, 0)->signature.get() == nx::nro::kNroSig) file_type = FILE_NRO; + // test hb asset + else if (_ASSERT_SIZE(sizeof(nx::sAssetHeader)) && _QUICK_CAST(nx::sAssetHeader, 0)->signature.get() == nx::aset::kAssetSig) + file_type = FILE_HB_ASSET; // else unrecognised else file_type = FILE_INVALID; diff --git a/programs/nstool/source/UserSettings.h b/programs/nstool/source/UserSettings.h index c1a6ccd..b876ff8 100644 --- a/programs/nstool/source/UserSettings.h +++ b/programs/nstool/source/UserSettings.h @@ -35,6 +35,8 @@ public: const sOptional& getNcaPart1Path() const; const sOptional& getNcaPart2Path() const; const sOptional& getNcaPart3Path() const; + const sOptional& getAssetIconPath() const; + const sOptional& getAssetNacpPath() const; private: const std::string kModuleName = "UserSettings"; @@ -64,6 +66,8 @@ private: sOptional list_api; sOptional list_sym; sOptional inst_type; + sOptional asset_icon_path; + sOptional asset_nacp_path; }; std::string mInputPath; @@ -84,6 +88,9 @@ private: sOptional mNcaPart2Path; sOptional mNcaPart3Path; + sOptional mAssetIconPath; + sOptional mAssetNacpPath; + bool mListApi; bool mListSymbols; nx::npdm::InstructionType mInstructionType; diff --git a/programs/nstool/source/XciProcess.cpp b/programs/nstool/source/XciProcess.cpp index 45fad92..277811a 100644 --- a/programs/nstool/source/XciProcess.cpp +++ b/programs/nstool/source/XciProcess.cpp @@ -179,7 +179,7 @@ void XciProcess::displayHeader() printf(" SelT1Key: 0x%x\n", mHdr.getSelT1Key()); printf(" SelKey: 0x%x\n", mHdr.getSelKey()); printf(" LimArea: 0x%x", mHdr.getLimAreaPage()); - if (mHdr.getLimAreaPage() != -1) + if (mHdr.getLimAreaPage() != (uint32_t)(-1)) printf(" (0x%" PRIx64 ")", nx::XciUtils::blockToAddr(mHdr.getLimAreaPage())); printf("\n"); diff --git a/programs/nstool/source/main.cpp b/programs/nstool/source/main.cpp index e5a3222..d95e3d8 100644 --- a/programs/nstool/source/main.cpp +++ b/programs/nstool/source/main.cpp @@ -7,7 +7,9 @@ #include "NcaProcess.h" #include "NpdmProcess.h" #include "CnmtProcess.h" -#include "CodeObjectProcess.h" +#include "NsoProcess.h" +#include "NroProcess.h" +#include "AssetProcess.h" int main(int argc, char** argv) { @@ -108,19 +110,60 @@ int main(int argc, char** argv) cnmt.process(); } - else if (user_set.getFileType() == FILE_NSO || user_set.getFileType() == FILE_NRO) + else if (user_set.getFileType() == FILE_NSO) { - CodeObjectProcess obj; + NsoProcess obj; obj.setInputFile(new fnd::SimpleFile(user_set.getInputPath(), fnd::SimpleFile::Read), OWN_IFILE); obj.setCliOutputMode(user_set.getCliOutputType()); obj.setVerifyMode(user_set.isVerifyFile()); - 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()); + obj.process(); + } + else if (user_set.getFileType() == FILE_NRO) + { + NroProcess obj; + + obj.setInputFile(new fnd::SimpleFile(user_set.getInputPath(), fnd::SimpleFile::Read), OWN_IFILE); + obj.setCliOutputMode(user_set.getCliOutputType()); + obj.setVerifyMode(user_set.isVerifyFile()); + + obj.setInstructionType(user_set.getInstType()); + obj.setListApi(user_set.isListApi()); + obj.setListSymbols(user_set.isListSymbols()); + + if (user_set.getAssetIconPath().isSet) + obj.setAssetIconExtractPath(user_set.getAssetIconPath().var); + if (user_set.getAssetNacpPath().isSet) + obj.setAssetNacpExtractPath(user_set.getAssetNacpPath().var); + + if (user_set.getFsPath().isSet) + obj.setAssetRomfsExtractPath(user_set.getFsPath().var); + obj.setAssetListFs(user_set.isListFs()); + + obj.process(); + } + else if (user_set.getFileType() == FILE_HB_ASSET) + { + AssetProcess obj; + + 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.getAssetIconPath().isSet) + obj.setIconExtractPath(user_set.getAssetIconPath().var); + if (user_set.getAssetNacpPath().isSet) + obj.setNacpExtractPath(user_set.getAssetNacpPath().var); + + if (user_set.getFsPath().isSet) + obj.setRomfsExtractPath(user_set.getFsPath().var); + obj.setListFs(user_set.isListFs()); + obj.process(); } } diff --git a/programs/nstool/source/nstool.h b/programs/nstool/source/nstool.h index a8948c9..8b46e1a 100644 --- a/programs/nstool/source/nstool.h +++ b/programs/nstool/source/nstool.h @@ -26,6 +26,7 @@ enum FileType FILE_CNMT, FILE_NSO, FILE_NRO, + FILE_HB_ASSET, FILE_INVALID = -1, };