This commit is contained in:
Clearmist 2025-11-16 00:11:38 -07:00 committed by GitHub
commit c058c464ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 240 additions and 104 deletions

View file

@ -1,31 +1,52 @@
# Building # Building
## Git Submodules ## Git Submodules
This project makes use of git submodules to import dependencies into the source tree. This project makes use of git submodules to import dependencies into the source tree.
After cloning this repository using git, prior to building NSTool the dependencies need to be downloaded. After cloning this repository using git, prior to building NSTool the dependencies need to be downloaded.
Run these two commands to initialise and download the dependencies: Run these two commands to initialise and download the dependencies:
```
```bash
git submodule init git submodule init
git submodule update git submodule update
``` ```
## Linux (incl. Windows Subsystem for Linux) & MacOS - Makefile ## Linux (including Windows Subsystem for Linux) & MacOS - Makefile
### Requirements ### Requirements
* `make` * `make`
* Terminal access * Terminal access
* Typical GNU compatible development tools (e.g. `clang`, `g++`, `c++`, `ar` etc) with __C++11__ support * Typical GNU compatible development tools (e.g. `clang`, `g++`, `c++`, `ar` etc) with __C++11__ support
### Using Makefile ### Using Makefile
* `make` (default) - Compile program * `make` (default) - Compile program
* Compiling the program requires local dependencies to be compiled via `make deps` beforehand * Compiling the program requires local dependencies to be compiled via `make deps` beforehand
* `make clean` - Remove executable and object files * `make clean` - Remove executable and object files
* `make deps` - Compile locally included dependency libraries * `make deps` - Compile locally included dependency libraries
* `make clean_deps` - Remove compiled library binaries and object files * `make clean_deps` - Remove compiled library binaries and object files
## Native Windows - Visual Studio ## Native Windows - Visual Studio
### Requirements
* [Visual Studio Community](https://visualstudio.microsoft.com/vs/community/) 2015 / 2017 / 2019
### Compiling NSTool ### Requirements
* [Visual Studio Community](https://visualstudio.microsoft.com/vs/community/) 2015 / 2017 / 2019 / 2022
### Compiling using the Visual Studio GUI
* Open `build/visualstudio/nstool.sln` in Visual Studio * Open `build/visualstudio/nstool.sln` in Visual Studio
* Select Target (e.g `Debug`|`Release` & `x86`|`x64`) * Select Target (e.g `Debug`|`Release` & `x86`|`x64`)
* Navigate to `Build`->`Build Solution` * Navigate to `Build`->`Build Solution`
### Compiling using the command line
* Open PowerShell
* Paste and run the following command after double-checking the paths.
```bash
& "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe" `
"{full path to the cloned repository}\nstool\build\visualstudio\nstool.sln" `
/p:Platform=x64 `
/p:Configuration=Release
```

View file

@ -1,7 +1,9 @@
# Nintendo Switch Tool (NSTool) ![DeviceTag](https://img.shields.io/badge/Device-SWITCH-e60012.svg) # Nintendo Switch Tool (NSTool) ![DeviceTag](https://img.shields.io/badge/Device-SWITCH-e60012.svg)
General purpose reading/extraction tool for Nintendo Switch file formats. General purpose reading/extraction tool for Nintendo Switch file formats.
## Supported File Formats ## Supported File Formats
* PartitionFs (`PFS0`) (.pfs0) * PartitionFs (`PFS0`) (.pfs0)
* Sha256PartitionFs (`HFS0`) (.hfs0) * Sha256PartitionFs (`HFS0`) (.hfs0)
* RomFs (.romfs) * RomFs (.romfs)
@ -19,38 +21,49 @@ General purpose reading/extraction tool for Nintendo Switch file formats.
* Initial Program (`KIP1`) (.kip) * Initial Program (`KIP1`) (.kip)
# Usage # Usage
## General usage ## General usage
The default mode of NSTool is to show general information about a file. The default mode of NSTool is to show general information about a file.
To display general information the usage is as follows: To display general information the usage is as follows:
```
```bash
nstool some_file.bin nstool some_file.bin
``` ```
However not all information is shown in this mode; file-layout, key data and properties set to default values are omitted. However not all information is shown in this mode; file-layout, key data and properties set to default values are omitted.
## Alternative output modes ## Alternative output modes
To output file-layout information, use the `--showlayout` option: To output file-layout information, use the `--showlayout` option:
```
```bash
nstool --showlayout some_file.bin nstool --showlayout some_file.bin
``` ```
To output key data generation and selection, use the `--showkeys` option: To output key data generation and selection, use the `--showkeys` option:
```
```bash
nstool --showkeys some_file.bin nstool --showkeys some_file.bin
``` ```
To output all information, enable the verbose output mode with the `-v` or `--verbose` option: To output all information, enable the verbose output mode with the `-v` or `--verbose` option:
```
```bash
nstool -v some_file.bin nstool -v some_file.bin
``` ```
## Specify File Type ## Specify File Type
NSTool will in most cases correctly identify the file type. However you can override this and manually specify the file type with the `-t` or `--type` option: NSTool will in most cases correctly identify the file type. However you can override this and manually specify the file type with the `-t` or `--type` option:
```
```bash
nstool -t cnmt some_file.bin nstool -t cnmt some_file.bin
``` ```
In that example `cnmt` was selected, NSTool would process the file as `Content Metadata`. See below for a list of supported file type codes: In that example `cnmt` was selected, NSTool would process the file as `Content Metadata`. See below for a list of supported file type codes:
| Code | Description | | Code | Description |
| ----------- | --------------- | | ----------- | --------------- |
| gc, xci | NX GameCard Image | | gc, xci | NX GameCard Image |
@ -71,14 +84,17 @@ In that example `cnmt` was selected, NSTool would process the file as `Content M
| aset, asset | Homebrew NRO Asset Binary | | aset, asset | Homebrew NRO Asset Binary |
## Validate Input File ## Validate Input File
Some file types have signatures/hashes/fields that can be validated by NSTool, but this mode isn't enabled by default. Some file types have signatures/hashes/fields that can be validated by NSTool, but this mode isn't enabled by default.
To validate files with NSTool, enable the verify mode with the `-y` or `--verify` option: To validate files with NSTool, enable the verify mode with the `-y` or `--verify` option:
```
```bash
nstool -y some_file.bin nstool -y some_file.bin
``` ```
See the below table for file types that support optional validation: See the below table for file types that support optional validation:
| File Type | Validation | Comments | | File Type | Validation | Comments |
| --------- | ---------- | -------- | | --------- | ---------- | -------- |
| ES Certificate | Signature | If certificate is part of a certificate chain it will validate it as part of that chain. `Root` signed certificates are verified with user supplied `Root` public key. | | ES Certificate | Signature | If certificate is part of a certificate chain it will validate it as part of that chain. `Root` signed certificates are verified with user supplied `Root` public key. |
@ -91,17 +107,21 @@ See the below table for file types that support optional validation:
* As of NSTool v1.6.0 the public key(s) for `Root Certificate`, `XCI Header`, `ACID` and `NCA Header` are built-in, and will be used if the user does not supply the public key in a key file. * As of NSTool v1.6.0 the public key(s) for `Root Certificate`, `XCI Header`, `ACID` and `NCA Header` are built-in, and will be used if the user does not supply the public key in a key file.
## DevKit Mode ## DevKit Mode
Files generated for `Production` use different (for the most part) encryption/signing keys than files generated for `Development`. NSTool will select `Production` encryption/signing keys by default. Files generated for `Production` use different (for the most part) encryption/signing keys than files generated for `Development`. NSTool will select `Production` encryption/signing keys by default.
When handling files intended for developer consoles (e.g. systemupdaters, devtools, test builds, etc), you should enable developer mode with the `-d`, `--dev` option: When handling files intended for developer consoles (e.g. systemupdaters, devtools, test builds, etc), you should enable developer mode with the `-d`, `--dev` option:
```
```bash
nstool -d some_file.bin nstool -d some_file.bin
``` ```
## Extract Files ## Extract Files
Some file types have an internal file system. This can be displayed and extracted. Some file types have an internal file system. This can be displayed and extracted.
To display the file system tree, use the file tree option `--fstree`: To display the file system tree, use the file tree option `--fstree`:
```
```bash
nstool --fstree some_file.bin nstool --fstree some_file.bin
``` ```
@ -110,32 +130,29 @@ To extract the file system, use the extract option `-x`, `--extract`. Which has
1) Extract the entire file system. 1) Extract the entire file system.
This extracts the contents of the entire file system to `./extract_dir/`. `extract_dir` will be created if it doesn't exist. This extracts the contents of the entire file system to `./extract_dir/`. `extract_dir` will be created if it doesn't exist.
```
nstool -x ./extract_dir/ some_file.bin ```bash
nstool --extract ./extract_dir/ some_file.bin
``` ```
2) Extract a sub directory. 2) Extract a sub directory.
This extracts the contents of `/a/sub/directory/` to `./extract_dir/`. `extract_dir` will be created if it doesn't exist. This extracts the contents of `/a/sub/directory/` to `./extract_dir/`. `extract_dir` will be created if it doesn't exist.
```
nstool -x /a/sub/directory/ ./extract_dir/ some_file.bin ```bash
nstool --extract /a/sub/directory/ ./extract_dir/ some_file.bin
``` ```
3) Extract a specific file, preserving the original name. 3) Extract a specific file while preserving the original name.
This extracts `/path/to/a/file.bin` to `./extract_dir/file.bin`. This extracts `/path/to/a/file.bin` to `./extract_dir/file.bin`.
```
nstool -x /path/to/a/file.bin ./extract_dir/ some_file.bin
```
4) Extract a specific file with a custom name. ```bash
nstool --extract ./extract_dir/ --file filename.bin some_file.bin
This extracts `/path/to/a/file.bin` to `./extract_dir/different_name.bin`.
```
nstool -x /path/to/a/file.bin ./extract_dir/different_name.bin some_file.bin
``` ```
### Supported File Types ### Supported File Types
* PartitionFs * PartitionFs
* Sha256PartitionFs * Sha256PartitionFs
* RomFs (including RomFs embedded in Homebrew NRO) * RomFs (including RomFs embedded in Homebrew NRO)
@ -144,24 +161,29 @@ nstool -x /path/to/a/file.bin ./extract_dir/different_name.bin some_file.bin
* XCI * XCI
## NCA Patches ## NCA Patches
Nintendo distributes game patches/updates in the style of a diff to keep file sizes down. This means extracting game patches requires the base version of the game to be able to process patch data. Typically this is only done for the Program NCA. Nintendo distributes game patches/updates in the style of a diff to keep file sizes down. This means extracting game patches requires the base version of the game to be able to process patch data. Typically this is only done for the Program NCA.
If `basegame_v0.nca` is the base Program NCA, and `gamepatch_v13219.nca` is the patch Program NCA, simply specify the base NCA using the base NCA option `--basenca` when processing the patch NCA. If `basegame_v0.nca` is the base Program NCA, and `gamepatch_v13219.nca` is the patch Program NCA, simply specify the base NCA using the base NCA option `--basenca` when processing the patch NCA.
``` ```bash
nstool --basenca ./basegame_v0.nca -x ./patchdata gamepatch_v13219.nca nstool --basenca ./basegame_v0.nca -x ./patchdata gamepatch_v13219.nca
``` ```
In the above example the patch NCA is being extracted to `./patchdata` In the above example the patch NCA is being extracted to `./patchdata`
## Encrypted Files ## Encrypted Files
Some Nintendo Switch files are partially or completely encrypted. These require the user to supply the encryption keys to NSTool so that it can process them. Some Nintendo Switch files are partially or completely encrypted. These require the user to supply the encryption keys to NSTool so that it can process them.
See [SWITCH_KEYS.md](/SWITCH_KEYS.md) for more info. See [SWITCH_KEYS.md](/SWITCH_KEYS.md) for more info.
# External Keys # External Keys
NSTool doesn't embed any keys that are copyright protected. However keys can be imported via various keyset files. NSTool doesn't embed any keys that are copyright protected. However keys can be imported via various keyset files.
See [SWITCH_KEYS.md](/SWITCH_KEYS.md) for more info. See [SWITCH_KEYS.md](/SWITCH_KEYS.md) for more info.
# Building # Building
See [BUILDING.md](/BUILDING.md). See [BUILDING.md](/BUILDING.md).

View file

@ -12,6 +12,7 @@ nstool::FsProcess::FsProcess() :
mShowFsInfo(false), mShowFsInfo(false),
mProperties(), mProperties(),
mShowFsTree(false), mShowFsTree(false),
mOutputFile(),
mFsRootLabel(), mFsRootLabel(),
mExtractJobs(), mExtractJobs(),
mDataCache(0x10000) mDataCache(0x10000)
@ -81,6 +82,11 @@ void nstool::FsProcess::setExtractJobs(const std::vector<nstool::ExtractJob>& ex
mExtractJobs = extract_jobs; mExtractJobs = extract_jobs;
} }
void nstool::FsProcess::setExtractFile(std::string outputFile)
{
mOutputFile = outputFile;
}
void nstool::FsProcess::printFs() void nstool::FsProcess::printFs()
{ {
fmt::print("[{:s}/Tree]\n", (mFsFormatName.isSet() ? mFsFormatName.get() : "FileSystem")); fmt::print("[{:s}/Tree]\n", (mFsFormatName.isSet() ? mFsFormatName.get() : "FileSystem"));
@ -124,7 +130,7 @@ void nstool::FsProcess::extractFs()
tc::io::Path file_extract_path = itr->extract_path + itr->virtual_path.back(); tc::io::Path file_extract_path = itr->extract_path + itr->virtual_path.back();
fmt::print("Saving {:s}...\n", file_extract_path.to_string()); fmt::print("Extracting file to {:s}...\n", file_extract_path.to_string());
writeStreamToFile(file_stream, itr->extract_path + itr->virtual_path.back(), mDataCache); writeStreamToFile(file_stream, itr->extract_path + itr->virtual_path.back(), mDataCache);
@ -216,17 +222,17 @@ void nstool::FsProcess::visitDir(const tc::io::Path& v_path, const tc::io::Path&
std::shared_ptr<tc::io::IStream> out_stream; std::shared_ptr<tc::io::IStream> out_stream;
for (auto itr = info.file_list.begin(); itr != info.file_list.end(); itr++) for (auto itr = info.file_list.begin(); itr != info.file_list.end(); itr++)
{ {
// build out path
out_path = l_path + *itr;
if (print_fs) if (print_fs)
{ {
for (size_t i = 0; i < v_path.size(); i++) for (size_t i = 0; i < v_path.size(); i++)
fmt::print(" "); fmt::print(" ");
fmt::print(" {:s}\n", *itr); fmt::print(" {:s}\n", *itr);
} }
if (extract_fs) if (extract_fs && (mOutputFile == "" || (mOutputFile == *itr)))
{ {
// build out path
out_path = l_path + *itr;
fmt::print("Saving {:s}...\n", out_path.to_string()); fmt::print("Saving {:s}...\n", out_path.to_string());
// begin export // begin export
@ -240,7 +246,7 @@ void nstool::FsProcess::visitDir(const tc::io::Path& v_path, const tc::io::Path&
cache_read_len = in_stream->read(mDataCache.data(), mDataCache.size()); cache_read_len = in_stream->read(mDataCache.data(), mDataCache.size());
if (cache_read_len == 0) if (cache_read_len == 0)
{ {
throw tc::io::IOException(mModuleLabel, fmt::format("Failed to read from {:s}file.", (mFsFormatName.isSet() ? (mFsFormatName.get() + " ") : ""))); throw tc::io::IOException(mModuleLabel, fmt::format("Failed to read from {:s} file.", (mFsFormatName.isSet() ? (mFsFormatName.get() + " ") : "")));
} }
out_stream->write(mDataCache.data(), cache_read_len); out_stream->write(mDataCache.data(), cache_read_len);
@ -250,9 +256,12 @@ void nstool::FsProcess::visitDir(const tc::io::Path& v_path, const tc::io::Path&
} }
} }
// iterate thru child dirs // iterate through child directories
for (auto itr = info.dir_list.begin(); itr != info.dir_list.end(); itr++) for (auto itr = info.dir_list.begin(); itr != info.dir_list.end(); itr++)
{ {
visitDir(v_path + *itr, l_path + *itr, extract_fs, print_fs); // When traversing each directory append the directory to the local path only if we're not looking to extract a single file.
const tc::io::Path localPath = mOutputFile == "" ? l_path + *itr : l_path;
visitDir(v_path + *itr, localPath, extract_fs, print_fs);
} }
} }

View file

@ -21,6 +21,7 @@ public:
void setShowFsTree(bool show_fs_tree); void setShowFsTree(bool show_fs_tree);
void setFsRootLabel(const std::string& root_label); void setFsRootLabel(const std::string& root_label);
void setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs); void setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs);
void setExtractFile(std::string outputFile);
private: private:
std::string mModuleLabel; std::string mModuleLabel;
@ -37,6 +38,7 @@ private:
// extract jobs // extract jobs
std::vector<nstool::ExtractJob> mExtractJobs; std::vector<nstool::ExtractJob> mExtractJobs;
std::string mOutputFile;
// cache for file extract // cache for file extract
tc::ByteData mDataCache; tc::ByteData mDataCache;

View file

@ -46,6 +46,11 @@ void nstool::GameCardProcess::setInputFile(const std::shared_ptr<tc::io::IStream
mFile = file; mFile = file;
} }
void nstool::GameCardProcess::setOutputFile(const std::string& file)
{
mOutputFile = file;
}
void nstool::GameCardProcess::setKeyCfg(const KeyBag& keycfg) void nstool::GameCardProcess::setKeyCfg(const KeyBag& keycfg)
{ {
mKeyCfg = keycfg; mKeyCfg = keycfg;
@ -69,6 +74,7 @@ void nstool::GameCardProcess::setShowFsTree(bool show_fs_tree)
void nstool::GameCardProcess::setExtractJobs(const std::vector<nstool::ExtractJob> extract_jobs) void nstool::GameCardProcess::setExtractJobs(const std::vector<nstool::ExtractJob> extract_jobs)
{ {
mFsProcess.setExtractJobs(extract_jobs); mFsProcess.setExtractJobs(extract_jobs);
mFsProcess.setExtractFile(mOutputFile);
} }
void nstool::GameCardProcess::importHeader() void nstool::GameCardProcess::importHeader()

View file

@ -16,6 +16,7 @@ public:
// generic // generic
void setInputFile(const std::shared_ptr<tc::io::IStream>& file); void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setOutputFile(const std::string& file);
void setKeyCfg(const KeyBag& keycfg); void setKeyCfg(const KeyBag& keycfg);
void setCliOutputMode(CliOutputMode type); void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify); void setVerifyMode(bool verify);
@ -29,6 +30,7 @@ private:
std::string mModuleName; std::string mModuleName;
std::shared_ptr<tc::io::IStream> mFile; std::shared_ptr<tc::io::IStream> mFile;
std::string mOutputFile;
KeyBag mKeyCfg; KeyBag mKeyCfg;
CliOutputMode mCliOutputMode; CliOutputMode mCliOutputMode;
bool mVerify; bool mVerify;

View file

@ -49,6 +49,11 @@ void nstool::NcaProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& fi
mFile = file; mFile = file;
} }
void nstool::NcaProcess::setOutputFile(const std::string& file)
{
mOutputFile = file;
}
void nstool::NcaProcess::setBaseNcaPath(const tc::Optional<tc::io::Path>& nca_path) void nstool::NcaProcess::setBaseNcaPath(const tc::Optional<tc::io::Path>& nca_path)
{ {
mBaseNcaPath = nca_path; mBaseNcaPath = nca_path;
@ -82,6 +87,7 @@ void nstool::NcaProcess::setFsRootLabel(const std::string& root_label)
void nstool::NcaProcess::setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs) void nstool::NcaProcess::setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs)
{ {
mFsProcess.setExtractJobs(extract_jobs); mFsProcess.setExtractJobs(extract_jobs);
mFsProcess.setExtractFile(mOutputFile);
} }
const std::shared_ptr<tc::io::IFileSystem>& nstool::NcaProcess::getFileSystem() const const std::shared_ptr<tc::io::IFileSystem>& nstool::NcaProcess::getFileSystem() const

View file

@ -18,6 +18,7 @@ public:
// generic // generic
void setInputFile(const std::shared_ptr<tc::io::IStream>& file); void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setOutputFile(const std::string& file);
void setKeyCfg(const KeyBag& keycfg); void setKeyCfg(const KeyBag& keycfg);
void setCliOutputMode(CliOutputMode type); void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify); void setVerifyMode(bool verify);
@ -38,6 +39,7 @@ private:
// user options // user options
std::shared_ptr<tc::io::IStream> mFile; std::shared_ptr<tc::io::IStream> mFile;
std::string mOutputFile;
KeyBag mKeyCfg; KeyBag mKeyCfg;
CliOutputMode mCliOutputMode; CliOutputMode mCliOutputMode;
bool mVerify; bool mVerify;

View file

@ -79,6 +79,11 @@ void nstool::PfsProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& fi
mFile = file; mFile = file;
} }
void nstool::PfsProcess::setOutputFile(const std::string& file)
{
mOutputFile = file;
}
void nstool::PfsProcess::setCliOutputMode(CliOutputMode type) void nstool::PfsProcess::setCliOutputMode(CliOutputMode type)
{ {
mCliOutputMode = type; mCliOutputMode = type;
@ -103,6 +108,7 @@ void nstool::PfsProcess::setFsRootLabel(const std::string& root_label)
void nstool::PfsProcess::setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs) void nstool::PfsProcess::setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs)
{ {
mFsProcess.setExtractJobs(extract_jobs); mFsProcess.setExtractJobs(extract_jobs);
mFsProcess.setExtractFile(mOutputFile);
} }
const pie::hac::PartitionFsHeader& nstool::PfsProcess::getPfsHeader() const const pie::hac::PartitionFsHeader& nstool::PfsProcess::getPfsHeader() const
@ -118,6 +124,13 @@ const std::shared_ptr<tc::io::IFileSystem>& nstool::PfsProcess::getFileSystem()
size_t nstool::PfsProcess::determineHeaderSize(const pie::hac::sPfsHeader* hdr) size_t nstool::PfsProcess::determineHeaderSize(const pie::hac::sPfsHeader* hdr)
{ {
size_t fileEntrySize = 0; size_t fileEntrySize = 0;
if (hdr->st_magic.unwrap() == pie::hac::pfs::kPfsStructMagic) {
fileEntrySize = sizeof(pie::hac::sPfsFile);
} else {
fileEntrySize = sizeof(pie::hac::sHashedPfsFile);
}
if (hdr->st_magic.unwrap() == pie::hac::pfs::kPfsStructMagic) if (hdr->st_magic.unwrap() == pie::hac::pfs::kPfsStructMagic)
fileEntrySize = sizeof(pie::hac::sPfsFile); fileEntrySize = sizeof(pie::hac::sPfsFile);
else else

View file

@ -15,6 +15,7 @@ public:
// generic // generic
void setInputFile(const std::shared_ptr<tc::io::IStream>& file); void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setOutputFile(const std::string& file);
void setCliOutputMode(CliOutputMode type); void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify); void setVerifyMode(bool verify);
@ -33,6 +34,7 @@ private:
std::string mModuleName; std::string mModuleName;
std::shared_ptr<tc::io::IStream> mFile; std::shared_ptr<tc::io::IStream> mFile;
std::string mOutputFile;
CliOutputMode mCliOutputMode; CliOutputMode mCliOutputMode;
bool mVerify; bool mVerify;

View file

@ -126,6 +126,11 @@ void nstool::RomfsProcess::setInputFile(const std::shared_ptr<tc::io::IStream>&
mFile = file; mFile = file;
} }
void nstool::RomfsProcess::setOutputFile(const std::string& file)
{
mOutputFile = file;
}
void nstool::RomfsProcess::setCliOutputMode(CliOutputMode type) void nstool::RomfsProcess::setCliOutputMode(CliOutputMode type)
{ {
mCliOutputMode = type; mCliOutputMode = type;
@ -145,6 +150,7 @@ void nstool::RomfsProcess::setFsRootLabel(const std::string& root_label)
void nstool::RomfsProcess::setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs) void nstool::RomfsProcess::setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs)
{ {
mFsProcess.setExtractJobs(extract_jobs); mFsProcess.setExtractJobs(extract_jobs);
mFsProcess.setExtractFile(mOutputFile);
} }
void nstool::RomfsProcess::setShowFsTree(bool list_fs) void nstool::RomfsProcess::setShowFsTree(bool list_fs)

View file

@ -15,6 +15,7 @@ public:
// generic // generic
void setInputFile(const std::shared_ptr<tc::io::IStream>& file); void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setOutputFile(const std::string& file);
void setCliOutputMode(CliOutputMode type); void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify); void setVerifyMode(bool verify);
@ -28,6 +29,7 @@ private:
std::string mModuleName; std::string mModuleName;
std::shared_ptr<tc::io::IStream> mFile; std::shared_ptr<tc::io::IStream> mFile;
std::string mOutputFile;
CliOutputMode mCliOutputMode; CliOutputMode mCliOutputMode;
bool mVerify; bool mVerify;

View file

@ -397,6 +397,40 @@ private:
std::vector<std::string> mOptRegex; std::vector<std::string> mOptRegex;
}; };
class SingleFileExtractionHandler : public tc::cli::OptionParser::IOptionHandler
{
public:
SingleFileExtractionHandler(std::string& param, const std::vector<std::string>& opts) :
mParam(param),
mOptStrings(opts),
mOptRegex()
{}
const std::vector<std::string>& getOptionStrings() const
{
return mOptStrings;
}
const std::vector<std::string>& getOptionRegexPatterns() const
{
return mOptRegex;
}
void processOption(const std::string& option, const std::vector<std::string>& params)
{
if (params.size() != 1)
{
throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter. Give the name and extension of the file you want to extract.", option));
}
mParam = params[0];
}
private:
std::string& mParam;
std::vector<std::string> mOptStrings;
std::vector<std::string> mOptRegex;
};
class InstructionTypeOptionHandler : public tc::cli::OptionParser::IOptionHandler class InstructionTypeOptionHandler : public tc::cli::OptionParser::IOptionHandler
{ {
public: public:
@ -636,6 +670,7 @@ void nstool::SettingsInitializer::parse_args(const std::vector<std::string>& arg
// process input file type // process input file type
opts.registerOptionHandler(std::shared_ptr<FileTypeOptionHandler>(new FileTypeOptionHandler(infile.filetype, { "-t", "--type" }))); opts.registerOptionHandler(std::shared_ptr<FileTypeOptionHandler>(new FileTypeOptionHandler(infile.filetype, { "-t", "--type" })));
opts.registerOptionHandler(std::shared_ptr<SingleFileExtractionHandler>(new SingleFileExtractionHandler(outfile.filename, { "--file" })));
// get user-provided keydata // get user-provided keydata
opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(mKeysetPath, {"-k", "--keyset"}))); opts.registerOptionHandler(std::shared_ptr<SingleParamPathOptionHandler>(new SingleParamPathOptionHandler(mKeysetPath, {"-k", "--keyset"})));
@ -808,11 +843,11 @@ void nstool::SettingsInitializer::usage_text() const
fmt::print(" --showlayout Show layout metadata.\n"); fmt::print(" --showlayout Show layout metadata.\n");
fmt::print(" -v, --verbose Verbose output.\n"); fmt::print(" -v, --verbose Verbose output.\n");
fmt::print("\n PFS0/HFS0 (PartitionFs), RomFs, NSP (Nintendo Submission Package)\n"); fmt::print("\n PFS0/HFS0 (PartitionFs), RomFs, NSP (Nintendo Submission Package)\n");
fmt::print(" {:s} [--fstree] [-x [<virtual path>] <out path>] <file>\n", BIN_NAME); fmt::print(" {:s} [--fstree] [-x <out path> --file <virtual file name>] <file>\n", BIN_NAME);
fmt::print(" --fstree Print filesystem tree.\n"); fmt::print(" --fstree Print filesystem tree.\n");
fmt::print(" -x, --extract Extract a file or directory to local filesystem.\n"); fmt::print(" -x, --extract Extract a file or directory to local filesystem.\n");
fmt::print("\n XCI (GameCard Image)\n"); fmt::print("\n XCI (GameCard Image)\n");
fmt::print(" {:s} [--fstree] [-x [<virtual path>] <out path>] <.xci file>\n", BIN_NAME); fmt::print(" {:s} [--fstree] [-x <out path> --file <virtual file name>] <.xci file>\n", BIN_NAME);
fmt::print(" --fstree Print filesystem tree.\n"); fmt::print(" --fstree Print filesystem tree.\n");
fmt::print(" -x, --extract Extract a file or directory to local filesystem.\n"); fmt::print(" -x, --extract Extract a file or directory to local filesystem.\n");
fmt::print(" --update Extract \"update\" partition to directory. (Alias for \"-x /update <out path>\")\n"); fmt::print(" --update Extract \"update\" partition to directory. (Alias for \"-x /update <out path>\")\n");
@ -820,7 +855,7 @@ void nstool::SettingsInitializer::usage_text() const
fmt::print(" --normal Extract \"normal\" partition to directory. (Alias for \"-x /normal <out path>\")\n"); fmt::print(" --normal Extract \"normal\" partition to directory. (Alias for \"-x /normal <out path>\")\n");
fmt::print(" --secure Extract \"secure\" partition to directory. (Alias for \"-x /secure <out path>\")\n"); fmt::print(" --secure Extract \"secure\" partition to directory. (Alias for \"-x /secure <out path>\")\n");
fmt::print("\n NCA (Nintendo Content Archive)\n"); fmt::print("\n NCA (Nintendo Content Archive)\n");
fmt::print(" {:s} [--fstree] [-x [<virtual path>] <out path>] [--bodykey <key> --titlekey <key> -tik <tik path> --basenca <.nca file>] <.nca file>\n", BIN_NAME); fmt::print(" {:s} [--fstree] [-x <out path> --file <virtual file name>] [--bodykey <key> --titlekey <key> -tik <tik path> --basenca <.nca file>] <.nca file>\n", BIN_NAME);
fmt::print(" --fstree Print filesystem tree.\n"); fmt::print(" --fstree Print filesystem tree.\n");
fmt::print(" -x, --extract Extract a file or directory to local filesystem.\n"); fmt::print(" -x, --extract Extract a file or directory to local filesystem.\n");
fmt::print(" --titlekey Specify (encrypted) title key extracted from ticket.\n"); fmt::print(" --titlekey Specify (encrypted) title key extracted from ticket.\n");

View file

@ -37,6 +37,11 @@ struct Settings
tc::Optional<tc::io::Path> path; tc::Optional<tc::io::Path> path;
} infile; } infile;
struct OutputFileOptions
{
std::string filename;
} outfile;
struct Options struct Options
{ {
CliOutputMode cli_output_mode; CliOutputMode cli_output_mode;
@ -97,6 +102,8 @@ struct Settings
infile.filetype = FILE_TYPE_ERROR; infile.filetype = FILE_TYPE_ERROR;
infile.path = tc::Optional<tc::io::Path>(); infile.path = tc::Optional<tc::io::Path>();
outfile.filename = "";
opt.cli_output_mode = CliOutputMode(); opt.cli_output_mode = CliOutputMode();
opt.verify = false; opt.verify = false;
opt.is_dev = false; opt.is_dev = false;

View file

@ -32,7 +32,7 @@ int umain(const std::vector<std::string>& args, const std::vector<std::string>&
nstool::GameCardProcess obj; nstool::GameCardProcess obj;
obj.setInputFile(infile_stream); obj.setInputFile(infile_stream);
obj.setOutputFile(set.outfile.filename);
obj.setKeyCfg(set.opt.keybag); obj.setKeyCfg(set.opt.keybag);
obj.setCliOutputMode(set.opt.cli_output_mode); obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify); obj.setVerifyMode(set.opt.verify);
@ -47,7 +47,7 @@ int umain(const std::vector<std::string>& args, const std::vector<std::string>&
nstool::PfsProcess obj; nstool::PfsProcess obj;
obj.setInputFile(infile_stream); obj.setInputFile(infile_stream);
obj.setOutputFile(set.outfile.filename);
obj.setCliOutputMode(set.opt.cli_output_mode); obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify); obj.setVerifyMode(set.opt.verify);
@ -56,12 +56,12 @@ int umain(const std::vector<std::string>& args, const std::vector<std::string>&
obj.process(); obj.process();
} }
else if (set.infile.filetype == nstool::Settings::FILE_TYPE_ROMFS) else if (set.infile.filetype == nstool::Settings::FILE_TYPE_ROMFS)
{ {
nstool::RomfsProcess obj; nstool::RomfsProcess obj;
obj.setInputFile(infile_stream); obj.setInputFile(infile_stream);
obj.setOutputFile(set.outfile.filename);
obj.setCliOutputMode(set.opt.cli_output_mode); obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify); obj.setVerifyMode(set.opt.verify);
@ -75,6 +75,7 @@ int umain(const std::vector<std::string>& args, const std::vector<std::string>&
nstool::NcaProcess obj; nstool::NcaProcess obj;
obj.setInputFile(infile_stream); obj.setInputFile(infile_stream);
obj.setOutputFile(set.outfile.filename);
obj.setBaseNcaPath(set.nca.base_nca_path); obj.setBaseNcaPath(set.nca.base_nca_path);
obj.setKeyCfg(set.opt.keybag); obj.setKeyCfg(set.opt.keybag);
obj.setCliOutputMode(set.opt.cli_output_mode); obj.setCliOutputMode(set.opt.cli_output_mode);