Merge pull request #73 from jakcron/v1.6-stable

Update NSTool to v1.6.0
This commit is contained in:
Jack 2021-11-14 13:08:09 +08:00 committed by GitHub
commit 31f67325f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 6670 additions and 5654 deletions

View file

@ -14,19 +14,24 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
dist: [ubuntu_x86_64, macos_x86_64]
dist: [ubuntu_x86_64, macos_x86_64, macos_arm64]
prog: [nstool]
include:
- dist: ubuntu_x86_64
os: ubuntu-latest
arch: x86_64
- dist: macos_x86_64
os: macos-latest
arch: x86_64
- dist: macos_arm64
os: macos-latest
arch: arm64
steps:
- uses: actions/checkout@v1
- name: Clone submodules
run: git submodule init && git submodule update
- name: Compile ${{ matrix.prog }}
run: make deps && make
run: make PROJECT_PLATFORM_ARCH=${{ matrix.arch }} deps all
- uses: actions/upload-artifact@v2
with:
name: ${{ matrix.prog }}-${{ matrix.dist }}
@ -36,17 +41,23 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
dist: [win_x86_64]
dist: [win_x64, win_x86]
prog: [nstool]
include:
- dist: win_x86_64
- dist: win_x64
os: windows-latest
platform: x64
configuration: Release
build_path: x64\Release
- dist: win_x86
os: windows-latest
platform: x86
configuration: Release
build_path: Release
steps:
- uses: actions/checkout@v1
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.0.0
uses: microsoft/setup-msbuild@v1.1
- name: Clone submodules
run: git submodule init && git submodule update
- name: Compile ${{ matrix.prog }}
@ -54,5 +65,5 @@ jobs:
- uses: actions/upload-artifact@v2
with:
name: ${{ matrix.prog }}-${{ matrix.dist }}
path: .\build\visualstudio\${{ matrix.platform }}\${{ matrix.configuration }}\${{ matrix.prog }}.exe
path: .\build\visualstudio\${{ matrix.build_path }}\${{ matrix.prog }}.exe

15
.gitmodules vendored
View file

@ -1,9 +1,15 @@
[submodule "deps/liblz4"]
path = deps/liblz4
url = https://github.com/jakcron/liblz4.git
[submodule "deps/libfnd"]
path = deps/libfnd
url = https://github.com/jakcron/libfnd.git
[submodule "deps/libmbedtls"]
path = deps/libmbedtls
url = https://github.com/jakcron/libmbedtls
[submodule "deps/libtoolchain"]
path = deps/libtoolchain
url = https://github.com/jakcron/libtoolchain.git
[submodule "deps/libfmt"]
path = deps/libfmt
url = https://github.com/jakcron/libfmt.git
[submodule "deps/libnintendo-pki"]
path = deps/libnintendo-pki
url = https://github.com/jakcron/libnintendo-pki.git
@ -16,6 +22,3 @@
[submodule "deps/libnintendo-hac-hb"]
path = deps/libnintendo-hac-hb
url = https://github.com/jakcron/libnintendo-hac-hb.git
[submodule "deps/libmbedtls"]
path = deps/libmbedtls
url = https://github.com/jakcron/libmbedtls

View file

@ -12,9 +12,9 @@
* `make deps` - Compile locally included dependency libraries
* `make clean_deps` - Remove compiled library binaries and object files
## Native Win32 - Visual Studio
## Native Windows - Visual Studio
### Requirements
* [Visual Studio Community](https://visualstudio.microsoft.com/vs/community/) 2015 or 2017
* [Visual Studio Community](https://visualstudio.microsoft.com/vs/community/) 2015 / 2017 / 2019
### Compiling NSTool
* Open `build/visualstudio/nstool.sln` in Visual Studio

202
README.md
View file

@ -2,78 +2,152 @@
General purpose reading/extraction tool for Nintendo Switch file formats.
## Supported File Formats
* Meta (.npdm)
* PartitionFS (and HashedPartitionFS) (includes raw .nsp)
* RomFS (and CompressedRomFS)
* NX GameCard Image (.xci)
* PartitionFs (`PFS0`) (.pfs0)
* Sha256PartitionFs (`HFS0`) (.hfs0)
* RomFs (.romfs)
* Nintendo Content Archive (.nca)
* Content Metadata (.cnmt)
* Nintendo Software Object (.nso)
* Nintendo Relocatable Software Object (.nro)
* Kernel Initial Process List (.ini)
* Kernel Initial Process (.kip)
* Nintendo Submission Package (.nsp)
* NX GameCard Image (.xci)
* Meta (`META`) (.npdm)
* Nintendo Application Control Property (.nacp)
* Content Metadata (.cnmt)
* ES Certificate (.cert)
* ES Ticket (v2 only) (.tik)
* PKI Certificate (.cert)
* Nintendo Shared Object (`NSO0`) (.nso)
* Nintendo Relocatable Object (`NRO0`) (.nro)
* Initial Program Bundle (`INI1`) (.ini)
* Initial Program (`KIP1`) (.kip)
# Usage
## General usage
The default mode of NSTool is to show general information about a file.
To display general information the usage is as follows:
```
Usage: nstool [options... ] <file>
General Options:
-d, --dev Use devkit keyset.
-k, --keyset Specify keyset file.
-t, --type Specify input file type. [xci, pfs, romfs, nca, meta, cnmt, nso, nro, ini, kip, nacp, aset, cert, tik]
-y, --verify Verify file.
Output Options:
--showkeys Show keys generated.
--showlayout Show layout metadata.
-v, --verbose Verbose output.
XCI (GameCard Image)
nstool [--listfs] [--update <dir> --logo <dir> --normal <dir> --secure <dir>] <.xci file>
--listfs Print file system in embedded partitions.
--update Extract "update" partition to directory.
--logo Extract "logo" partition to directory.
--normal Extract "normal" partition to directory.
--secure Extract "secure" partition to directory.
PFS0/HFS0 (PartitionFs), RomFs, NSP (Ninendo Submission Package)
nstool [--listfs] [--fsdir <dir>] <file>
--listfs Print file system.
--fsdir Extract file system to directory.
NCA (Nintendo Content Archive)
nstool [--listfs] [--bodykey <key> --titlekey <key>] [--part0 <dir> ...] <.nca file>
--listfs Print file system in embedded partitions.
--titlekey Specify title key extracted from ticket.
--bodykey Specify body encryption key.
--tik Specify ticket to source title key.
--cert Specify certificate chain to verify ticket.
--part0 Extract "partition 0" to directory.
--part1 Extract "partition 1" to directory.
--part2 Extract "partition 2" to directory.
--part3 Extract "partition 3" to directory.
NSO (Nintendo Software Object), NRO (Nintendo Relocatable Object)
nstool [--listapi --listsym] [--insttype <inst. type>] <file>
--listapi Print SDK API List.
--listsym Print Code Symbols.
--insttype Specify instruction type [64bit|32bit] (64bit is assumed).
INI (Initial Process List Blob)
nstool [--kipdir <dir>] <file>
--kipdir Extract embedded KIPs to directory.
ASET (Homebrew Asset Blob)
nstool [--listfs] [--icon <file> --nacp <file> --fsdir <dir>] <file>
--listfs Print filesystem in embedded RomFS partition.
--icon Extract icon partition to file.
--nacp Extract NACP partition to file.
--fsdir Extract RomFS partition to directory.
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.
## Alternative output modes
To output file-layout information, use the `--showlayout` option:
```
nstool --showlayout some_file.bin
```
To output key data generation and selection, use the `--showkeys` option:
```
nstool --showkeys some_file.bin
```
To output all information, enable the verbose output mode with the `-v` or `--verbose` option:
```
nstool -v some_file.bin
```
## 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 `--intype` option:
```
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:
| Code | Description |
| ----------- | --------------- |
| gc, xci | NX GameCard Image |
| nsp | Nintendo Submission Package |
| pfs | PartitionFs |
| hfs | Sha256PartitionFs |
| romfs | RomFs |
| nca | Nintendo Content Archive |
| meta, npdm | Meta (.npdm) |
| cnmt | Content Metadata |
| nso | Nintendo Shared Object |
| nro | Nintendo Relocatable Object |
| ini | Initial Program Bundle |
| kip | Initial Program |
| nacp | Nintendo Application Control Property |
| cert | ES Certificate |
| tik | ES Ticket |
| aset, asset | Homebrew NRO Asset Binary |
## Validate Input File
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:
```
nstool -y some_file.bin
```
See the below table for file types that support optional validation:
| 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 Ticket | Signature | If the user specifies a certificate chain with `--cert` option, the ticket will be verified against that certificate chain. |
| NX GameCard Image | XCI Header Signature, HFS0 Hashes | XCI header signature is verified with user supplied `XCI Header` public key. |
| META | AccessControlInfo fields, AccessControlInfoDesc signature | AccessControlInfo fields are validated against the AccessControlInfoDesc. AccessControlInfoDesc signature is verfied with the appropriate user supplied `ACID` public key. |
| NCA | Header Signature[0], Header Signature[1] | Header Signature[0] is verified with the appropriate user supplied `NCA Header` public key. Header Signature[1] is verified only in Program titles, by retrieving the with public key from the AccessControlInfoDesc stored in the `code` partition. |
* As of Nintendo Switch Firmware 9.0.0, Nintendo retroactively added key generations for some public keys, including `NCA Header` and `ACID` public keys, so the various generations for these public keys will have to be supplied by the user.
* 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
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:
```
nstool -d some_file.bin
```
## Extract Files
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`:
```
nstool --fstree some_file.bin
```
To extract the file system, use the extract option `-x`, `--extract`. Which has four modes.
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.
```
nstool -x ./extract_dir/ some_file.bin
```
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.
```
nstool -x /a/sub/directory/ ./extract_dir/ some_file.bin
```
3) Extract a specific file, preserving the original name.
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.
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
* PartitionFs
* Sha256PartitionFs
* RomFs (including RomFs embedded in Homebrew NRO)
* NCA
* NSP
* XCI
## 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.
See [SWITCH_KEYS.md](/SWITCH_KEYS.md) for more info.
# External Keys
NSTool doesn't embed any keys that are copyright protected. However keys can be imported via various keyset files.

View file

@ -8,25 +8,26 @@ If a keyset file is located in ___$HOME/.switch/___ it will be loaded automatica
# General Keys (prod.keys and dev.keys)
Some switch files formats feature encryption and or cryptographic signatures. In order to process these file formats, some keys are required. These keys can be supplied via a keyfile: ___prod.keys___ (or ___dev.keys___ for devkit variants).
This keyset file can be provided via the command line (refer to usage for details).
This keyset file can be provided via the command line, use the `-k` or `--keyset` option:
```
nstool -k prod.keys some_file.bin
```
## Format
The following keys are recognised (## represents a hexadecimal number between 00 and 1F):
The following keys are recognised (## represents the key revision, a hexadecimal number between 00 and FF):
```
; Key Sources
master_key_## : Master key, used to derive other keys. (0x10 bytes)
aes_kek_generation_source : Used to derive other aes-keks. (0x10 bytes)
aes_key_generation_source : Used to derive other aes-keys. (0x10 bytes)
package2_key_source : Used with master_key_## to derive package2_key_##. (0x10 bytes)
ticket_commonkey_source : Used with master_key_## to derive ticket_commonkey_##. (0x10 bytes)
nca_header_kek_source : Used with master_key_00, aes_kek_generation_source and aes_key_generation_source to generate nca_header_kek. (0x10 bytes)
nca_header_key_source : Used with nca_header_kek to generate nca_header_key. (0x20 bytes)
nca_body_keak_application_source : Used with master_key_##, aes_kek_generation_source and aes_key_generation_source to generate nca_body_keak_application_##. (0x10 bytes)
nca_body_keak_ocean_source : Used with master_key_##, aes_kek_generation_source and aes_key_generation_source to generate nca_body_keak_ocean_##. (0x10 bytes)
nca_body_keak_system_source : Used with master_key_##, aes_kek_generation_source and aes_key_generation_source to generate nca_body_keak_system_##. (0x10 bytes)
master_key_## : Master key, used to derive other keys. (0x10 bytes)
aes_kek_generation_source : Used to derive other aes-keks. (0x10 bytes)
aes_key_generation_source : Used to derive other aes-keys. (0x10 bytes)
package2_key_source : Used with master_key_## to derive package2_key_##. (0x10 bytes)
ticket_commonkey_source : Used with master_key_## to derive ticket_commonkey_##. (0x10 bytes)
nca_header_kek_source : Used with master_key_00, aes_kek_generation_source and aes_key_generation_source to generate nca_header_kek. (0x10 bytes)
nca_header_key_source : Used with nca_header_kek to generate nca_header_key. (0x20 bytes)
nca_key_area_key_application_source : Used with master_key_##, aes_kek_generation_source and aes_key_generation_source to generate nca_key_area_key_application_##. (0x10 bytes)
nca_key_area_key_ocean_source : Used with master_key_##, aes_kek_generation_source and aes_key_generation_source to generate nca_key_area_key_ocean_##. (0x10 bytes)
nca_key_area_key_system_source : Used with master_key_##, aes_kek_generation_source and aes_key_generation_source to generate nca_key_area_key_system_##. (0x10 bytes)
; Package1 keys
package1_key_## : AES128 Key (0x10 bytes)
@ -47,18 +48,24 @@ pki_root_sign_key_private : RSA4096 Private Exponent (0x200 bytes)
nca_header_key : AES128-XTS Key (0x20 bytes)
nca_header_sign_key_##_modulus : RSA2048 Modulus (0x100 bytes)
nca_header_sign_key_##_private : RSA2048 Private Exponent (0x100 bytes)
nca_body_keak_application_## : AES128 Key (0x10 bytes)
nca_body_keak_ocean_## : AES128 Key (0x10 bytes)
nca_body_keak_system_## : AES128 Key (0x10 bytes)
nca_key_area_key_application_## : AES128 Key (0x10 bytes)
nca_key_area_key_ocean_## : AES128 Key (0x10 bytes)
nca_key_area_key_system_## : AES128 Key (0x10 bytes)
nca_key_area_key_hw_application_## : AES128 Key (0x10 bytes)
nca_key_area_key_hw_ocean_## : AES128 Key (0x10 bytes)
nca_key_area_key_hw_system_## : AES128 Key (0x10 bytes)
; NRR Keys
nrr_certificate_sign_key_##_modulus : RSA2048 Modulus (0x100 bytes)
nrr_certificate_sign_key_##_private : RSA2048 Private Exponent (0x100 bytes)
; XCI Keys
xci_header_key : AES128 Key (0x10 bytes)
xci_header_key_## : AES128 Key (0x10 bytes)
xci_header_sign_key_modulus : RSA2048 Modulus (0x100 bytes)
xci_header_sign_key_private : RSA2048 Private Exponent (0x100 bytes)
xci_initial_data_kek_## : AES128 Key (0x10 bytes)
xci_cert_sign_key_modulus : RSA2048 Modulus (0x100 bytes)
xci_cert_sign_key_private : RSA2048 Private Exponent (0x100 bytes)
; ACID Keys
acid_sign_key_##_modulus : RSA2048 Modulus (0x100 bytes)
@ -80,12 +87,120 @@ acid_sign_key_private : alias for acid_sign_key_00_private
titlekek_source : hactool alias for ticket_commonkey_source
header_key_source : hactool alias for nca_header_key_source
header_kek_source : hactool alias for nca_header_kek_source
key_area_key_application_source : hactool alias for nca_body_keak_application_source
key_area_key_ocean_source : hactool alias for nca_body_keak_ocean_source
key_area_key_system_source : hactool alias for nca_body_keak_system_source
key_area_key_application_source : hactool alias for nca_key_area_key_application_source
key_area_key_ocean_source : hactool alias for nca_key_area_key_ocean_source
key_area_key_system_source : hactool alias for nca_key_area_key_system_source
titlekek_## : hactool alias for ticket_commonkey_##
header_key : hactool alias for nca_header_key
key_area_key_application_## : hactool alias for nca_body_keak_application_##
key_area_key_ocean_## : hactool alias for nca_body_keak_ocean_##
key_area_key_system_## : hactool alias for nca_body_keak_system_##
key_area_key_application_## : hactool alias for nca_key_area_key_application_##
key_area_key_ocean_## : hactool alias for nca_key_area_key_ocean_##
key_area_key_system_## : hactool alias for nca_key_area_key_system_##
```
## Encrypted File Types
See below for advice on what keys are required to decrypt certain file types.
### NX GameCard Image
The `GameCard ExtendedHeader` is encrypted with one of 8 keys, specified by the `KekIndex` in the `GameCard Header`.
It isn't required to extract game data, it just contains metadata.
Only two keys are currently defined:
| KeyIndex | Name | Description |
| ----------- | --------------- | ----------- |
| 00 | Production | Usually selected for prod images. Some dev images use this key index. |
| 01 | Development | Usually selected for dev images. This was changed from key index 00 at some point. |
Define the header key(s) in `prod.keys`/`dev.keys` (Prod and dev share the same keydata):
```
xci_header_key_00 = <32 char AES128 key here>
xci_header_key_01 = <32 char AES128 key here>
```
### Nintendo Content Archive
Nintendo Content Archive (NCA) files have both an encrypted header and content. The encrypted header determines the layout/format/encryption method of the content, which contains the game data.
Define the header key in `prod.keys`/`dev.keys`.
```
nca_header_key = <64 char AES128-XTS key-data here>
```
Or allow NSTool to derive it from key sources:
```
master_key_00 = <32 char AES128 key-data here>
aes_kek_generation_source = <32 char AES128 key-data here>
aes_key_generation_source = <32 char AES128 key-data here>
nca_header_kek_source = <32 char AES128 key-data here>
nca_header_key_source = <64 char AES128 key-data here>
```
In order to read the NCA content, the content key must be determined. Unlike the header key which is fixed, each NCA will have a unique content key.
Content keys are either:
1) "Internal", where they are encrypted the NCA Header KeyArea
2) "External", where they are encrypted in an external Ticket file (.tik) (external content keys are sometimes called title keys)
#### Internal Content Key
Decrypting the content key from the NCA Header Key Area requires the appropriate `nca_key_area_key` to be defined in `prod.keys`/`dev.keys`.
However for security reasons Nintendo revises this key periodically, and within each key revision there are 3 separate keys for different categories of applications.
It's best to define as many of these as possible, to reduce the number of times you need to edit the keyfiles.
So for a given key revision these key area keys can be defined explicitly (`##` represents the key revision in hexadecimal):
```
nca_key_area_key_application_## = <32 char AES128 key-data here>
nca_key_area_key_ocean_## = <32 char AES128 key-data here>
nca_key_area_key_system_## = <32 char AES128 key-data here>
```
Or allow NSTool to derive them from key sources: (`##` represents the key revision in hexadecimal):
```
master_key_## = <32 char AES128 key-data here>
aes_kek_generation_source = <32 char AES128 key-data here>
aes_key_generation_source = <32 char AES128 key-data here>
nca_key_area_key_application_source = <32 char AES128 key-data here>
nca_key_area_key_ocean_source = <32 char AES128 key-data here>
nca_key_area_key_system_source = <32 char AES128 key-data here>
```
#### External Content Key
For NCAs that use an external content key, the user must supplied the key to NSTool.
Most NCAs that use an external content key will be bundled with a ticket file (*.tik) that contains the content key in an encrypted form.
The ticket can be supplied by the user using the `--tik` option:
```
nstool --tik <32 char rightsid>.tik <32 char contentid>.nca
```
This however requires the the appropriate commonkey to be defined in `prod.keys`/`dev.keys` to decrypt the content key in the ticket. However for security reasons Nintendo revises this key periodically.
It's best to define as many of these as possible, to reduce the number of times you need to edit the keyfiles.
So for a given key revision the common key can be defined explicitly (`##` represents the key revision in hexadecimal):
```
ticket_commonkey_## = <32 char AES128 key-data here>
```
Or allow NSTool to derive them from key sources: (`##` represents the key revision in hexadecimal):
```
master_key_## = <32 char AES128 key-data here>
ticket_commonkey_source = <32 char AES128 key-data here>
```
##### Supply the external content key directly to NSTool
Alternatively you can supply the raw encrypted content key (also called a title key) directly with the `--titlekey` option:
```
nstool --titlekey <32 char AES128 key-data here> <32 char contentid>.nca
```
It is also possible to supply the decrypted content key directly with the `--contentkey` option:
```
nstool --contentkey <32 char AES128 key-data here> <32 char contentid>.nca
```
##### Scene Tickets
Please note that "Scene" tickets have been known to have errors. If you have issues using the `--tik` option, try passing the raw encrypted titlekey directly with the `--titlekey` option. The titlekey can be found by reading the ticket with NSTool:
```
nstool <32 char rightsid>.tik
```
##### Personalised Tickets
If the ticket is personalised (encrypted with console unique RSA key), NSTool will not support it. You will need to use extract the title key with another tool and pass the encrypted title key directly with the `--titlekey` option.
# Title

View file

@ -1,43 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28010.2036
# Visual Studio Version 16
VisualStudioVersion = 16.0.31229.75
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nstool", "nstool\nstool.vcxproj", "{775EF5EB-CA49-4994-8AC4-47B4A5385266}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deps", "deps", "{05929EAE-4471-4E8E-A6F3-793A81623D7F}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libfnd", "..\..\deps\libfnd\build\visualstudio\libfnd\libfnd.vcxproj", "{4E578016-34BA-4A1E-B8EC-37A48780B6CA}"
ProjectSection(ProjectDependencies) = postProject
{E741ADED-7900-4E07-8DB0-D008C336C3FB} = {E741ADED-7900-4E07-8DB0-D008C336C3FB}
{7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C} = {7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "liblz4", "..\..\deps\liblz4\build\visualstudio\liblz4\liblz4.vcxproj", "{E741ADED-7900-4E07-8DB0-D008C336C3FB}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libnintendo-es", "..\..\deps\libnintendo-es\build\visualstudio\libnintendo-es\libnintendo-es.vcxproj", "{8616D6C9-C8DE-4C3F-AFC2-625636664C2B}"
ProjectSection(ProjectDependencies) = postProject
{4E578016-34BA-4A1E-B8EC-37A48780B6CA} = {4E578016-34BA-4A1E-B8EC-37A48780B6CA}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libnintendo-hac", "..\..\deps\libnintendo-hac\build\visualstudio\libnintendo-hac\libnintendo-hac.vcxproj", "{8885C125-83FB-4F73-A93A-C712B1434D54}"
ProjectSection(ProjectDependencies) = postProject
{4E578016-34BA-4A1E-B8EC-37A48780B6CA} = {4E578016-34BA-4A1E-B8EC-37A48780B6CA}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libnintendo-hac-hb", "..\..\deps\libnintendo-hac-hb\build\visualstudio\libnintendo-hac-hb\libnintendo-hac-hb.vcxproj", "{24D001B4-D439-4967-9371-DC3E0523EB19}"
ProjectSection(ProjectDependencies) = postProject
{4E578016-34BA-4A1E-B8EC-37A48780B6CA} = {4E578016-34BA-4A1E-B8EC-37A48780B6CA}
{8885C125-83FB-4F73-A93A-C712B1434D54} = {8885C125-83FB-4F73-A93A-C712B1434D54}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libnintendo-pki", "..\..\deps\libnintendo-pki\build\visualstudio\libnintendo-pki\libnintendo-pki.vcxproj", "{0BEF63A0-2801-4563-AB65-1E2FD881C3AF}"
ProjectSection(ProjectDependencies) = postProject
{4E578016-34BA-4A1E-B8EC-37A48780B6CA} = {4E578016-34BA-4A1E-B8EC-37A48780B6CA}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libmbedtls", "..\..\deps\libmbedtls\build\visualstudio\libmbedtls\libmbedtls.vcxproj", "{7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libtoolchain", "..\..\deps\libtoolchain\build\visualstudio\libtoolchain\libtoolchain.vcxproj", "{E194E4B8-1482-40A2-901B-75D4387822E9}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libfmt", "..\..\deps\libfmt\build\visualstudio\libfmt\libfmt.vcxproj", "{F4B0540E-0AAE-4006-944B-356944EF61FA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@ -54,14 +42,6 @@ Global
{775EF5EB-CA49-4994-8AC4-47B4A5385266}.Release|x64.Build.0 = Release|x64
{775EF5EB-CA49-4994-8AC4-47B4A5385266}.Release|x86.ActiveCfg = Release|Win32
{775EF5EB-CA49-4994-8AC4-47B4A5385266}.Release|x86.Build.0 = Release|Win32
{4E578016-34BA-4A1E-B8EC-37A48780B6CA}.Debug|x64.ActiveCfg = Debug|x64
{4E578016-34BA-4A1E-B8EC-37A48780B6CA}.Debug|x64.Build.0 = Debug|x64
{4E578016-34BA-4A1E-B8EC-37A48780B6CA}.Debug|x86.ActiveCfg = Debug|Win32
{4E578016-34BA-4A1E-B8EC-37A48780B6CA}.Debug|x86.Build.0 = Debug|Win32
{4E578016-34BA-4A1E-B8EC-37A48780B6CA}.Release|x64.ActiveCfg = Release|x64
{4E578016-34BA-4A1E-B8EC-37A48780B6CA}.Release|x64.Build.0 = Release|x64
{4E578016-34BA-4A1E-B8EC-37A48780B6CA}.Release|x86.ActiveCfg = Release|Win32
{4E578016-34BA-4A1E-B8EC-37A48780B6CA}.Release|x86.Build.0 = Release|Win32
{E741ADED-7900-4E07-8DB0-D008C336C3FB}.Debug|x64.ActiveCfg = Debug|x64
{E741ADED-7900-4E07-8DB0-D008C336C3FB}.Debug|x64.Build.0 = Debug|x64
{E741ADED-7900-4E07-8DB0-D008C336C3FB}.Debug|x86.ActiveCfg = Debug|Win32
@ -110,18 +90,35 @@ Global
{7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Release|x64.Build.0 = Release|x64
{7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Release|x86.ActiveCfg = Release|Win32
{7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Release|x86.Build.0 = Release|Win32
{E194E4B8-1482-40A2-901B-75D4387822E9}.Debug|x64.ActiveCfg = Debug|x64
{E194E4B8-1482-40A2-901B-75D4387822E9}.Debug|x64.Build.0 = Debug|x64
{E194E4B8-1482-40A2-901B-75D4387822E9}.Debug|x86.ActiveCfg = Debug|Win32
{E194E4B8-1482-40A2-901B-75D4387822E9}.Debug|x86.Build.0 = Debug|Win32
{E194E4B8-1482-40A2-901B-75D4387822E9}.Release|x64.ActiveCfg = Release|x64
{E194E4B8-1482-40A2-901B-75D4387822E9}.Release|x64.Build.0 = Release|x64
{E194E4B8-1482-40A2-901B-75D4387822E9}.Release|x86.ActiveCfg = Release|Win32
{E194E4B8-1482-40A2-901B-75D4387822E9}.Release|x86.Build.0 = Release|Win32
{F4B0540E-0AAE-4006-944B-356944EF61FA}.Debug|x64.ActiveCfg = Debug|x64
{F4B0540E-0AAE-4006-944B-356944EF61FA}.Debug|x64.Build.0 = Debug|x64
{F4B0540E-0AAE-4006-944B-356944EF61FA}.Debug|x86.ActiveCfg = Debug|Win32
{F4B0540E-0AAE-4006-944B-356944EF61FA}.Debug|x86.Build.0 = Debug|Win32
{F4B0540E-0AAE-4006-944B-356944EF61FA}.Release|x64.ActiveCfg = Release|x64
{F4B0540E-0AAE-4006-944B-356944EF61FA}.Release|x64.Build.0 = Release|x64
{F4B0540E-0AAE-4006-944B-356944EF61FA}.Release|x86.ActiveCfg = Release|Win32
{F4B0540E-0AAE-4006-944B-356944EF61FA}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{4E578016-34BA-4A1E-B8EC-37A48780B6CA} = {05929EAE-4471-4E8E-A6F3-793A81623D7F}
{E741ADED-7900-4E07-8DB0-D008C336C3FB} = {05929EAE-4471-4E8E-A6F3-793A81623D7F}
{8616D6C9-C8DE-4C3F-AFC2-625636664C2B} = {05929EAE-4471-4E8E-A6F3-793A81623D7F}
{8885C125-83FB-4F73-A93A-C712B1434D54} = {05929EAE-4471-4E8E-A6F3-793A81623D7F}
{24D001B4-D439-4967-9371-DC3E0523EB19} = {05929EAE-4471-4E8E-A6F3-793A81623D7F}
{0BEF63A0-2801-4563-AB65-1E2FD881C3AF} = {05929EAE-4471-4E8E-A6F3-793A81623D7F}
{7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C} = {05929EAE-4471-4E8E-A6F3-793A81623D7F}
{E194E4B8-1482-40A2-901B-75D4387822E9} = {05929EAE-4471-4E8E-A6F3-793A81623D7F}
{F4B0540E-0AAE-4006-944B-356944EF61FA} = {05929EAE-4471-4E8E-A6F3-793A81623D7F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {ABDCFB40-D6B3-44A9-92B5-0D7AB38D9FB8}

View file

@ -22,32 +22,32 @@
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{775EF5EB-CA49-4994-8AC4-47B4A5385266}</ProjectGuid>
<RootNamespace>nstool</RootNamespace>
<WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
@ -76,7 +76,8 @@
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\liblz4\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@ -85,7 +86,8 @@
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\liblz4\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@ -96,7 +98,8 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\liblz4\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
@ -111,7 +114,8 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\liblz4\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
@ -119,37 +123,68 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\deps\libfnd\build\visualstudio\libfnd\libfnd.vcxproj">
<Project>{4e578016-34ba-4a1e-b8ec-37a48780b6ca}</Project>
<ProjectReference Include="$(SolutionDir)..\..\deps\libfmt\build\visualstudio\libfmt\libfmt.vcxproj">
<Project>{f4b0540e-0aae-4006-944b-356944ef61fa}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\deps\liblz4\build\visualstudio\liblz4\liblz4.vcxproj">
<ProjectReference Include="$(SolutionDir)..\..\deps\liblz4\build\visualstudio\liblz4\liblz4.vcxproj">
<Project>{e741aded-7900-4e07-8db0-d008c336c3fb}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\deps\libnintendo-es\build\visualstudio\libnintendo-es\libnintendo-es.vcxproj">
<ProjectReference Include="$(SolutionDir)..\..\deps\libnintendo-es\build\visualstudio\libnintendo-es\libnintendo-es.vcxproj">
<Project>{8616d6c9-c8de-4c3f-afc2-625636664c2b}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\deps\libnintendo-hac-hb\build\visualstudio\libnintendo-hac-hb\libnintendo-hac-hb.vcxproj">
<ProjectReference Include="$(SolutionDir)..\..\deps\libnintendo-hac-hb\build\visualstudio\libnintendo-hac-hb\libnintendo-hac-hb.vcxproj">
<Project>{24d001b4-d439-4967-9371-dc3e0523eb19}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\deps\libnintendo-hac\build\visualstudio\libnintendo-hac\libnintendo-hac.vcxproj">
<ProjectReference Include="$(SolutionDir)..\..\deps\libnintendo-hac\build\visualstudio\libnintendo-hac\libnintendo-hac.vcxproj">
<Project>{8885c125-83fb-4f73-a93a-c712b1434d54}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\deps\libnintendo-pki\build\visualstudio\libnintendo-pki\libnintendo-pki.vcxproj">
<ProjectReference Include="$(SolutionDir)..\..\deps\libnintendo-pki\build\visualstudio\libnintendo-pki\libnintendo-pki.vcxproj">
<Project>{0bef63a0-2801-4563-ab65-1e2fd881c3af}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\deps\libmbedtls\build\visualstudio\libmbedtls\libmbedtls.vcxproj">
<ProjectReference Include="$(SolutionDir)..\..\deps\libmbedtls\build\visualstudio\libmbedtls\libmbedtls.vcxproj">
<Project>{7a7c66f3-2b5b-4e23-85d8-2a74fedad92c}</Project>
</ProjectReference>
<ProjectReference Include="$(SolutionDir)..\..\deps\libtoolchain\build\visualstudio\libtoolchain\libtoolchain.vcxproj">
<Project>{e194e4b8-1482-40a2-901b-75d4387822e9}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\src\AssetProcess.h" />
<ClInclude Include="..\..\..\src\CnmtProcess.h" />
<ClInclude Include="..\..\..\src\elf.h" />
<ClInclude Include="..\..\..\src\ElfSymbolParser.h" />
<ClInclude Include="..\..\..\src\EsCertProcess.h" />
<ClInclude Include="..\..\..\src\EsTikProcess.h" />
<ClInclude Include="..\..\..\src\FsProcess.h" />
<ClInclude Include="..\..\..\src\GameCardProcess.h" />
<ClInclude Include="..\..\..\src\IniProcess.h" />
<ClInclude Include="..\..\..\src\KeyBag.h" />
<ClInclude Include="..\..\..\src\KipProcess.h" />
<ClInclude Include="..\..\..\src\MetaProcess.h" />
<ClInclude Include="..\..\..\src\NacpProcess.h" />
<ClInclude Include="..\..\..\src\NcaProcess.h" />
<ClInclude Include="..\..\..\src\NroProcess.h" />
<ClInclude Include="..\..\..\src\NsoProcess.h" />
<ClInclude Include="..\..\..\src\PfsProcess.h" />
<ClInclude Include="..\..\..\src\PkiValidator.h" />
<ClInclude Include="..\..\..\src\RoMetadataProcess.h" />
<ClInclude Include="..\..\..\src\RomfsProcess.h" />
<ClInclude Include="..\..\..\src\SdkApiString.h" />
<ClInclude Include="..\..\..\src\Settings.h" />
<ClInclude Include="..\..\..\src\types.h" />
<ClInclude Include="..\..\..\src\util.h" />
<ClInclude Include="..\..\..\src\version.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\..\src\AssetProcess.cpp" />
<ClCompile Include="..\..\..\src\CnmtProcess.cpp" />
<ClCompile Include="..\..\..\src\CompressedArchiveIFile.cpp" />
<ClCompile Include="..\..\..\src\ElfSymbolParser.cpp" />
<ClCompile Include="..\..\..\src\EsCertProcess.cpp" />
<ClCompile Include="..\..\..\src\EsTikProcess.cpp" />
<ClCompile Include="..\..\..\src\FsProcess.cpp" />
<ClCompile Include="..\..\..\src\GameCardProcess.cpp" />
<ClCompile Include="..\..\..\src\IniProcess.cpp" />
<ClCompile Include="..\..\..\src\KeyConfiguration.cpp" />
<ClCompile Include="..\..\..\src\KeyBag.cpp" />
<ClCompile Include="..\..\..\src\KipProcess.cpp" />
<ClCompile Include="..\..\..\src\main.cpp" />
<ClCompile Include="..\..\..\src\MetaProcess.cpp" />
@ -158,37 +193,12 @@
<ClCompile Include="..\..\..\src\NroProcess.cpp" />
<ClCompile Include="..\..\..\src\NsoProcess.cpp" />
<ClCompile Include="..\..\..\src\PfsProcess.cpp" />
<ClCompile Include="..\..\..\src\PkiCertProcess.cpp" />
<ClCompile Include="..\..\..\src\PkiValidator.cpp" />
<ClCompile Include="..\..\..\src\RoMetadataProcess.cpp" />
<ClCompile Include="..\..\..\src\RomfsProcess.cpp" />
<ClCompile Include="..\..\..\src\SdkApiString.cpp" />
<ClCompile Include="..\..\..\src\UserSettings.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\src\AssetProcess.h" />
<ClInclude Include="..\..\..\src\CnmtProcess.h" />
<ClInclude Include="..\..\..\src\common.h" />
<ClInclude Include="..\..\..\src\CompressedArchiveIFile.h" />
<ClInclude Include="..\..\..\src\ElfSymbolParser.h" />
<ClInclude Include="..\..\..\src\EsTikProcess.h" />
<ClInclude Include="..\..\..\src\GameCardProcess.h" />
<ClInclude Include="..\..\..\src\IniProcess.h" />
<ClInclude Include="..\..\..\src\KeyConfiguration.h" />
<ClInclude Include="..\..\..\src\KipProcess.h" />
<ClInclude Include="..\..\..\src\MetaProcess.h" />
<ClInclude Include="..\..\..\src\NacpProcess.h" />
<ClInclude Include="..\..\..\src\NcaProcess.h" />
<ClInclude Include="..\..\..\src\NroProcess.h" />
<ClInclude Include="..\..\..\src\NsoProcess.h" />
<ClInclude Include="..\..\..\src\PfsProcess.h" />
<ClInclude Include="..\..\..\src\PkiCertProcess.h" />
<ClInclude Include="..\..\..\src\PkiValidator.h" />
<ClInclude Include="..\..\..\src\RoMetadataProcess.h" />
<ClInclude Include="..\..\..\src\RomfsProcess.h" />
<ClInclude Include="..\..\..\src\SdkApiString.h" />
<ClInclude Include="..\..\..\src\UserSettings.h" />
<ClInclude Include="..\..\..\src\version.h" />
<ClCompile Include="..\..\..\src\Settings.cpp" />
<ClCompile Include="..\..\..\src\util.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View file

@ -14,74 +14,6 @@
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\..\src\AssetProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\CnmtProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\CompressedArchiveIFile.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\ElfSymbolParser.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\EsTikProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\GameCardProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\IniProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\KeyConfiguration.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\KipProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\MetaProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\NacpProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\NcaProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\NroProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\NsoProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\PfsProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\PkiCertProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\PkiValidator.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\RoMetadataProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\RomfsProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\SdkApiString.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\UserSettings.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\src\AssetProcess.h">
<Filter>Header Files</Filter>
@ -89,25 +21,28 @@
<ClInclude Include="..\..\..\src\CnmtProcess.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\common.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\CompressedArchiveIFile.h">
<ClInclude Include="..\..\..\src\elf.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\ElfSymbolParser.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\EsCertProcess.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\EsTikProcess.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\FsProcess.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\GameCardProcess.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\IniProcess.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\KeyConfiguration.h">
<ClInclude Include="..\..\..\src\KeyBag.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\KipProcess.h">
@ -131,9 +66,6 @@
<ClInclude Include="..\..\..\src\PfsProcess.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\PkiCertProcess.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\PkiValidator.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -146,11 +78,88 @@
<ClInclude Include="..\..\..\src\SdkApiString.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\UserSettings.h">
<ClInclude Include="..\..\..\src\Settings.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\types.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\util.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\..\src\version.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\..\src\AssetProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\CnmtProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\ElfSymbolParser.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\EsCertProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\EsTikProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\FsProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\GameCardProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\IniProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\KeyBag.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\KipProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\MetaProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\NacpProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\NcaProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\NsoProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\NroProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\PfsProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\PkiValidator.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\RoMetadataProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\RomfsProcess.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\SdkApiString.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\Settings.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\..\..\src\util.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

1
deps/libfmt vendored Submodule

@ -0,0 +1 @@
Subproject commit 53d084cc0c6ea61bbb535a873299b0ae5ff9a05d

1
deps/libfnd vendored

@ -1 +0,0 @@
Subproject commit 19c1683060a8b39b737da8505b5e23660ed86282

2
deps/liblz4 vendored

@ -1 +1 @@
Subproject commit 0e5a8c29295a9046fff4ad5371a8ea682c7e0cb3
Subproject commit b40b46406e87a753328abfda3b53dfabd2408da2

2
deps/libmbedtls vendored

@ -1 +1 @@
Subproject commit bc43e5e079529455749d81d1a3b77a9574d5ab01
Subproject commit e9e56bb773111f831af8dd36ad74de73b0c31aa2

2
deps/libnintendo-es vendored

@ -1 +1 @@
Subproject commit 9e3f1ea763be033f60b3b2db0b2d6e2aac462a37
Subproject commit af4d79ad2aca410ce2d451456023b0a03904ceeb

@ -1 +1 @@
Subproject commit afbbe3900d4c0dab6b3c4cd06927aff227cc1f95
Subproject commit e7d93cea7c7bac93661c9ef6500279db19c264a0

@ -1 +1 @@
Subproject commit 95fb4d7762eb5b395fa5023fd3a9b6f34151505a
Subproject commit 42abe9b9a90ef05ea13df6caf320ac4f50e34537

@ -1 +1 @@
Subproject commit 5097871222f6e2cd07b7b8e8b58551e913eb1c15
Subproject commit 11b99025b11862b0828a186bd462b0097e341da7

1
deps/libtoolchain vendored Submodule

@ -0,0 +1 @@
Subproject commit 5966167aa5065c0e582bfbca151e28db580e972f

View file

@ -1,6 +1,6 @@
# C++/C Recursive Project Makefile
# (c) Jack
# Version 3
# Version 6 (20211110)
# Project Name
PROJECT_NAME = nstool
@ -31,8 +31,8 @@ PROJECT_SONAME = $(PROJECT_NAME).so.$(PROJECT_SO_VER_MAJOR)
PROJECT_SO_FILENAME = $(PROJECT_SONAME).$(PROJECT_SO_VER_MINOR).$(PROJECT_SO_VER_PATCH)
# Project Dependencies
PROJECT_DEPEND_LOCAL = nintendo-hac-hb nintendo-hac nintendo-es nintendo-pki fnd mbedtls lz4
PROJECT_DEPEND_EXTERNAL =
PROJECT_DEPEND = nintendo-hac-hb nintendo-hac nintendo-es nintendo-pki toolchain fmt lz4 mbedtls
PROJECT_DEPEND_LOCAL_DIR = libnintendo-hac-hb libnintendo-hac libnintendo-es libnintendo-pki libtoolchain libfmt liblz4 libmbedtls
# Generate compiler flags for including project include path
ifneq ($(PROJECT_INCLUDE_PATH),)
@ -40,14 +40,14 @@ ifneq ($(PROJECT_INCLUDE_PATH),)
endif
# Generate compiler flags for local included dependencies
ifneq ($(PROJECT_DEPEND_LOCAL),)
LIB += $(foreach dep,$(PROJECT_DEPEND_LOCAL), -L"$(ROOT_PROJECT_DEPENDENCY_PATH)/lib$(dep)/bin" -l$(dep))
INC += $(foreach dep,$(PROJECT_DEPEND_LOCAL), -I"$(ROOT_PROJECT_DEPENDENCY_PATH)/lib$(dep)/include")
ifneq ($(PROJECT_DEPEND_LOCAL_DIR),)
LIB += $(foreach dep,$(PROJECT_DEPEND_LOCAL_DIR), -L"$(ROOT_PROJECT_DEPENDENCY_PATH)/$(dep)/bin")
INC += $(foreach dep,$(PROJECT_DEPEND_LOCAL_DIR), -I"$(ROOT_PROJECT_DEPENDENCY_PATH)/$(dep)/include")
endif
# Generate compiler flags for external dependencies
ifneq ($(PROJECT_DEPEND_EXTERNAL),)
LIB += $(foreach dep,$(PROJECT_DEPEND_EXTERNAL), -l$(dep))
ifneq ($(PROJECT_DEPEND),)
LIB += $(foreach dep,$(PROJECT_DEPEND), -l$(dep))
endif
# Detect Platform
@ -64,12 +64,26 @@ ifeq ($(PROJECT_PLATFORM),)
endif
endif
# Detect Architecture
ifeq ($(PROJECT_PLATFORM_ARCH),)
ifeq ($(PROJECT_PLATFORM), WIN32)
export PROJECT_PLATFORM_ARCH = x86_64
else ifeq ($(PROJECT_PLATFORM), GNU)
export PROJECT_PLATFORM_ARCH = $(shell uname -m)
else ifeq ($(PROJECT_PLATFORM), MACOS)
export PROJECT_PLATFORM_ARCH = $(shell uname -m)
else
export PROJECT_PLATFORM_ARCH = x86_64
endif
endif
# Generate platform specific compiler flags
ifeq ($(PROJECT_PLATFORM), WIN32)
# Windows Flags/Libs
CC = x86_64-w64-mingw32-gcc
CXX = x86_64-w64-mingw32-g++
WARNFLAGS = -Wall -Wno-unused-value -Wno-unused-but-set-variable
ARCHFLAGS =
INC +=
LIB += -static
ARFLAGS = cr -o
@ -78,6 +92,7 @@ else ifeq ($(PROJECT_PLATFORM), GNU)
#CC =
#CXX =
WARNFLAGS = -Wall -Wno-unused-value -Wno-unused-but-set-variable
ARCHFLAGS =
INC +=
LIB +=
ARFLAGS = cr -o
@ -86,18 +101,19 @@ else ifeq ($(PROJECT_PLATFORM), MACOS)
#CC =
#CXX =
WARNFLAGS = -Wall -Wno-unused-value -Wno-unused-private-field
ARCHFLAGS = -arch $(PROJECT_PLATFORM_ARCH)
INC +=
LIB +=
ARFLAGS = rc
endif
# Compiler Flags
CXXFLAGS = -std=c++11 $(INC) $(WARNFLAGS) -fPIC
CFLAGS = -std=c11 $(INC) $(WARNFLAGS) -fPIC
CXXFLAGS = -std=c++11 $(INC) $(WARNFLAGS) $(ARCHFLAGS) -fPIC
CFLAGS = -std=c11 $(INC) $(WARNFLAGS) $(ARCHFLAGS) -fPIC
# Object Files
SRC_OBJ = $(foreach dir,$(PROJECT_SRC_SUBDIRS),$(subst .cpp,.o,$(wildcard $(dir)/*.cpp))) $(foreach dir,$(PROJECT_SRC_SUBDIRS),$(subst .c,.o,$(wildcard $(dir)/*.c)))
TESTSRC_OBJ = $(foreach dir,$(PROJECT_TESTSRC_SUBDIRS),$(subst .cpp,.o,$(wildcard $(dir)/*.cpp))) $(foreach dir,$(PROJECT_TESTSRC_SUBDIRS),$(subst .c,.o,$(wildcard $(dir)/*.c)))
SRC_OBJ = $(foreach dir,$(PROJECT_SRC_SUBDIRS),$(subst .cpp,.o,$(wildcard $(dir)/*.cpp))) $(foreach dir,$(PROJECT_SRC_SUBDIRS),$(subst .cc,.o,$(wildcard $(dir)/*.cc))) $(foreach dir,$(PROJECT_SRC_SUBDIRS),$(subst .c,.o,$(wildcard $(dir)/*.c)))
TESTSRC_OBJ = $(foreach dir,$(PROJECT_TESTSRC_SUBDIRS),$(subst .cpp,.o,$(wildcard $(dir)/*.cpp))) $(foreach dir,$(PROJECT_TESTSRC_SUBDIRS),$(subst .cc,.o,$(wildcard $(dir)/*.cc))) $(foreach dir,$(PROJECT_TESTSRC_SUBDIRS),$(subst .c,.o,$(wildcard $(dir)/*.c)))
# all is the default, user should specify what the default should do
# - 'static_lib' for building static library
@ -118,6 +134,10 @@ clean: clean_object_files remove_binary_dir
@echo CXX $<
@$(CXX) $(CXXFLAGS) -c $< -o $@
%.o: %.cc
@echo CXX $<
@$(CXX) $(CXXFLAGS) -c $< -o $@
# Binary Directory
.PHONY: create_binary_dir
create_binary_dir:
@ -145,13 +165,13 @@ shared_lib: $(SRC_OBJ) create_binary_dir
# Build Program
program: $(SRC_OBJ) create_binary_dir
@echo LINK $(PROJECT_BIN_PATH)/$(PROJECT_NAME)
@$(CXX) $(SRC_OBJ) $(LIB) -o "$(PROJECT_BIN_PATH)/$(PROJECT_NAME)"
@$(CXX) $(ARCHFLAGS) $(SRC_OBJ) $(LIB) -o "$(PROJECT_BIN_PATH)/$(PROJECT_NAME)"
# Build Test Program
test_program: $(TESTSRC_OBJ) $(SRC_OBJ) create_binary_dir
ifneq ($(PROJECT_TESTSRC_PATH),)
@echo LINK $(PROJECT_BIN_PATH)/$(PROJECT_NAME)_test
@$(CXX) $(TESTSRC_OBJ) $(SRC_OBJ) $(LIB) -o "$(PROJECT_BIN_PATH)/$(PROJECT_NAME)_test"
@$(CXX) $(ARCHFLAGS) $(TESTSRC_OBJ) $(SRC_OBJ) $(LIB) -o "$(PROJECT_BIN_PATH)/$(PROJECT_NAME)_test"
endif
# Documentation
@ -170,8 +190,8 @@ endif
# Dependencies
.PHONY: deps
deps:
@$(foreach lib,$(PROJECT_DEPEND_LOCAL), cd "$(ROOT_PROJECT_DEPENDENCY_PATH)/lib$(lib)" && $(MAKE) static_lib && cd "$(PROJECT_PATH)";)
@$(foreach lib,$(PROJECT_DEPEND_LOCAL_DIR), cd "$(ROOT_PROJECT_DEPENDENCY_PATH)/$(lib)" && $(MAKE) static_lib && cd "$(PROJECT_PATH)";)
.PHONY: clean_deps
clean_deps:
@$(foreach lib,$(PROJECT_DEPEND_LOCAL), cd "$(ROOT_PROJECT_DEPENDENCY_PATH)/lib$(lib)" && $(MAKE) clean && cd "$(PROJECT_PATH)";)
@$(foreach lib,$(PROJECT_DEPEND_LOCAL_DIR), cd "$(ROOT_PROJECT_DEPENDENCY_PATH)/$(lib)" && $(MAKE) clean && cd "$(PROJECT_PATH)";)

View file

@ -1,115 +1,108 @@
#include <iostream>
#include <iomanip>
#include <fnd/SimpleFile.h>
#include <fnd/OffsetAdjustedIFile.h>
#include <fnd/Vec.h>
#include "AssetProcess.h"
#include "util.h"
AssetProcess::AssetProcess() :
nstool::AssetProcess::AssetProcess() :
mModuleName("nstool::AssetProcess"),
mFile(),
mCliOutputMode(_BIT(OUTPUT_BASIC)),
mCliOutputMode(true, false, false, false),
mVerify(false)
{
}
void AssetProcess::process()
void nstool::AssetProcess::process()
{
importHeader();
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
if (mCliOutputMode.show_basic_info)
displayHeader();
processSections();
}
void AssetProcess::setInputFile(const fnd::SharedPtr<fnd::IFile>& file)
void nstool::AssetProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
{
mFile = file;
}
void AssetProcess::setCliOutputMode(CliOutputMode type)
void nstool::AssetProcess::setCliOutputMode(CliOutputMode type)
{
mCliOutputMode = type;
}
void AssetProcess::setVerifyMode(bool verify)
void nstool::AssetProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void AssetProcess::setListFs(bool list)
{
mRomfs.setListFs(list);
}
void AssetProcess::setIconExtractPath(const std::string& path)
void nstool::AssetProcess::setIconExtractPath(const tc::io::Path& path)
{
mIconExtractPath = path;
}
void AssetProcess::setNacpExtractPath(const std::string& path)
void nstool::AssetProcess::setNacpExtractPath(const tc::io::Path& path)
{
mNacpExtractPath = path;
}
void AssetProcess::setRomfsExtractPath(const std::string& path)
void nstool::AssetProcess::setRomfsShowFsTree(bool show_fs_tree)
{
mRomfs.setExtractPath(path);
mRomfs.setShowFsTree(show_fs_tree);
}
void AssetProcess::importHeader()
void nstool::AssetProcess::setRomfsExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs)
{
fnd::Vec<byte_t> scratch;
mRomfs.setExtractJobs(extract_jobs);
}
if (*mFile == nullptr)
void nstool::AssetProcess::importHeader()
{
if (mFile == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
throw tc::Exception(mModuleName, "No file reader set.");
}
if (mFile->canRead() == false || mFile->canSeek() == false)
{
throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions.");
}
if ((*mFile)->size() < sizeof(nn::hac::sAssetHeader))
if (mFile->length() < tc::io::IOUtil::castSizeToInt64(sizeof(nn::hac::sAssetHeader)))
{
throw fnd::Exception(kModuleName, "Corrupt ASET: file too small");
throw tc::Exception(mModuleName, "Corrupt ASET: file too small");
}
scratch.alloc(sizeof(nn::hac::sAssetHeader));
(*mFile)->read(scratch.data(), 0, scratch.size());
tc::ByteData scratch = tc::ByteData(sizeof(nn::hac::sAssetHeader));
mFile->seek(0, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
mHdr.fromBytes(scratch.data(), scratch.size());
}
void AssetProcess::processSections()
void nstool::AssetProcess::processSections()
{
if (mHdr.getIconInfo().size > 0 && mIconExtractPath.isSet)
int64_t file_size = mFile->length();
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");
if ((mHdr.getIconInfo().size + mHdr.getIconInfo().offset) > file_size)
throw tc::Exception(mModuleName, "ASET geometry for icon beyond file size");
fnd::SimpleFile outfile(mIconExtractPath.var, fnd::SimpleFile::Create);
fnd::Vec<byte_t> cache;
std::string icon_extract_path_str;
tc::io::PathUtil::pathToUnixUTF8(mIconExtractPath.get(), icon_extract_path_str);
cache.alloc(mHdr.getIconInfo().size);
(*mFile)->read(cache.data(), mHdr.getIconInfo().offset, cache.size());
outfile.write(cache.data(), cache.size());
outfile.close();
fmt::print("Saving {:s}...", icon_extract_path_str);
writeSubStreamToFile(mFile, mHdr.getIconInfo().offset, mHdr.getIconInfo().size, mIconExtractPath.get());
}
if (mHdr.getNacpInfo().size > 0)
{
if ((mHdr.getNacpInfo().size + mHdr.getNacpInfo().offset) > (*mFile)->size())
throw fnd::Exception(kModuleName, "ASET geometry for nacp beyond file size");
if ((mHdr.getNacpInfo().size + mHdr.getNacpInfo().offset) > file_size)
throw tc::Exception(mModuleName, "ASET geometry for nacp beyond file size");
if (mNacpExtractPath.isSet)
if (mNacpExtractPath.isSet())
{
fnd::SimpleFile outfile(mNacpExtractPath.var, fnd::SimpleFile::Create);
fnd::Vec<byte_t> cache;
cache.alloc(mHdr.getNacpInfo().size);
(*mFile)->read(cache.data(), mHdr.getNacpInfo().offset, cache.size());
outfile.write(cache.data(), cache.size());
outfile.close();
writeSubStreamToFile(mFile, mHdr.getNacpInfo().offset, mHdr.getNacpInfo().size, mNacpExtractPath.get());
}
mNacp.setInputFile(new fnd::OffsetAdjustedIFile(mFile, mHdr.getNacpInfo().offset, mHdr.getNacpInfo().size));
mNacp.setInputFile(std::make_shared<tc::io::SubStream>(mFile, mHdr.getNacpInfo().offset, mHdr.getNacpInfo().size));
mNacp.setCliOutputMode(mCliOutputMode);
mNacp.setVerifyMode(mVerify);
@ -118,10 +111,10 @@ void AssetProcess::processSections()
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");
if ((mHdr.getRomfsInfo().size + mHdr.getRomfsInfo().offset) > file_size)
throw tc::Exception(mModuleName, "ASET geometry for romfs beyond file size");
mRomfs.setInputFile(new fnd::OffsetAdjustedIFile(mFile, mHdr.getRomfsInfo().offset, mHdr.getRomfsInfo().size));
mRomfs.setInputFile(std::make_shared<tc::io::SubStream>(mFile, mHdr.getRomfsInfo().offset, mHdr.getRomfsInfo().size));
mRomfs.setCliOutputMode(mCliOutputMode);
mRomfs.setVerifyMode(mVerify);
@ -129,20 +122,20 @@ void AssetProcess::processSections()
}
}
void AssetProcess::displayHeader()
void nstool::AssetProcess::displayHeader()
{
if (_HAS_BIT(mCliOutputMode, OUTPUT_LAYOUT))
if (mCliOutputMode.show_layout)
{
std::cout << "[ASET Header]" << std::endl;
std::cout << " Icon:" << std::endl;
std::cout << " Offset: 0x" << std::hex << mHdr.getIconInfo().offset << std::endl;
std::cout << " Size: 0x" << std::hex << mHdr.getIconInfo().size << std::endl;
std::cout << " NACP:" << std::endl;
std::cout << " Offset: 0x" << std::hex << mHdr.getNacpInfo().offset << std::endl;
std::cout << " Size: 0x" << std::hex << mHdr.getNacpInfo().size << std::endl;
std::cout << " RomFS:" << std::endl;
std::cout << " Offset: 0x" << std::hex << mHdr.getRomfsInfo().offset << std::endl;
std::cout << " Size: 0x" << std::hex << mHdr.getRomfsInfo().size << std::endl;
fmt::print("[ASET Header]\n");
fmt::print(" Icon:\n");
fmt::print(" Offset: 0x{:x}\n", mHdr.getIconInfo().offset);
fmt::print(" Size: 0x{:x}\n", mHdr.getIconInfo().size);
fmt::print(" NACP:\n");
fmt::print(" Offset: 0x{:x}\n", mHdr.getNacpInfo().offset);
fmt::print(" Size: 0x{:x}\n", mHdr.getNacpInfo().size);
fmt::print(" RomFs:\n");
fmt::print(" Offset: 0x{:x}\n", mHdr.getRomfsInfo().offset);
fmt::print(" Size: 0x{:x}\n", mHdr.getRomfsInfo().size);
}
}

View file

@ -1,13 +1,11 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/IFile.h>
#include <fnd/SharedPtr.h>
#include <nn/hac/AssetHeader.h>
#include "types.h"
#include "NacpProcess.h"
#include "RomfsProcess.h"
#include "common.h"
#include <nn/hac/AssetHeader.h>
namespace nstool {
class AssetProcess
{
@ -16,26 +14,24 @@ public:
void process();
void setInputFile(const fnd::SharedPtr<fnd::IFile>& file);
void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setCliOutputMode(CliOutputMode 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);
void setIconExtractPath(const tc::io::Path& path);
void setNacpExtractPath(const tc::io::Path& path);
void setRomfsShowFsTree(bool show_fs_tree);
void setRomfsExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs);
private:
const std::string kModuleName = "AssetProcess";
std::string mModuleName;
fnd::SharedPtr<fnd::IFile> mFile;
std::shared_ptr<tc::io::IStream> mFile;
CliOutputMode mCliOutputMode;
bool mVerify;
sOptional<std::string> mIconExtractPath;
sOptional<std::string> mNacpExtractPath;
tc::Optional<tc::io::Path> mIconExtractPath;
tc::Optional<tc::io::Path> mNacpExtractPath;
nn::hac::AssetHeader mHdr;
NacpProcess mNacp;
@ -45,3 +41,5 @@ private:
void processSections();
void displayHeader();
};
}

View file

@ -1,140 +1,144 @@
#include "CnmtProcess.h"
#include <iostream>
#include <iomanip>
#include <fnd/SimpleTextOutput.h>
#include <fnd/OffsetAdjustedIFile.h>
#include <nn/hac/ContentMetaUtil.h>
CnmtProcess::CnmtProcess() :
nstool::CnmtProcess::CnmtProcess() :
mModuleName("nstool::CnmtProcess"),
mFile(),
mCliOutputMode(_BIT(OUTPUT_BASIC)),
mCliOutputMode(true, false, false, false),
mVerify(false)
{
}
void CnmtProcess::process()
void nstool::CnmtProcess::process()
{
importCnmt();
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
if (mCliOutputMode.show_basic_info)
displayCnmt();
}
void CnmtProcess::setInputFile(const fnd::SharedPtr<fnd::IFile>& file)
void nstool::CnmtProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
{
mFile = file;
}
void CnmtProcess::setCliOutputMode(CliOutputMode type)
void nstool::CnmtProcess::setCliOutputMode(CliOutputMode type)
{
mCliOutputMode = type;
}
void CnmtProcess::setVerifyMode(bool verify)
void nstool::CnmtProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
const nn::hac::ContentMeta& CnmtProcess::getContentMeta() const
const nn::hac::ContentMeta& nstool::CnmtProcess::getContentMeta() const
{
return mCnmt;
}
void CnmtProcess::importCnmt()
void nstool::CnmtProcess::importCnmt()
{
fnd::Vec<byte_t> scratch;
if (*mFile == nullptr)
if (mFile == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
throw tc::Exception(mModuleName, "No file reader set.");
}
if (mFile->canRead() == false || mFile->canSeek() == false)
{
throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions.");
}
scratch.alloc((*mFile)->size());
(*mFile)->read(scratch.data(), 0, scratch.size());
// check if file_size is greater than 20MB, don't import.
size_t cnmt_file_size = tc::io::IOUtil::castInt64ToSize(mFile->length());
if (cnmt_file_size > (0x100000 * 20))
{
throw tc::Exception(mModuleName, "File too large.");
}
// read cnmt
tc::ByteData scratch = tc::ByteData(cnmt_file_size);
mFile->seek(0, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
// parse cnmt
mCnmt.fromBytes(scratch.data(), scratch.size());
}
void CnmtProcess::displayCnmt()
void nstool::CnmtProcess::displayCnmt()
{
std::cout << "[ContentMeta]" << std::endl;
std::cout << " TitleId: 0x" << std::hex << std::setw(16) << std::setfill('0') << mCnmt.getTitleId() << std::endl;
std::cout << " Version: " << nn::hac::ContentMetaUtil::getVersionAsString(mCnmt.getTitleVersion()) << " (v" << std::dec << mCnmt.getTitleVersion() << ")"<< std::endl;
std::cout << " Type: " << nn::hac::ContentMetaUtil::getContentMetaTypeAsString(mCnmt.getContentMetaType()) << " (" << std::dec << (uint32_t)mCnmt.getContentMetaType() << ")" << std::endl;
std::cout << " Attributes: 0x" << std::hex << mCnmt.getAttribute().to_ullong();
if (mCnmt.getAttribute().any())
const nn::hac::sContentMetaHeader* cnmt_hdr = (const nn::hac::sContentMetaHeader*)mCnmt.getBytes().data();
fmt::print("[ContentMeta]\n");
fmt::print(" TitleId: 0x{:016x}\n", mCnmt.getTitleId());
fmt::print(" Version: {:s} (v{:d})\n", nn::hac::ContentMetaUtil::getVersionAsString(mCnmt.getTitleVersion()), mCnmt.getTitleVersion());
fmt::print(" Type: {:s} ({:d})\n", nn::hac::ContentMetaUtil::getContentMetaTypeAsString(mCnmt.getContentMetaType()), (uint32_t)mCnmt.getContentMetaType());
fmt::print(" Attributes: 0x{:x}", *((byte_t*)&cnmt_hdr->attributes));
if (mCnmt.getAttribute().size())
{
std::vector<std::string> attribute_list;
for (size_t flag = 0; flag < mCnmt.getAttribute().size(); flag++)
for (auto itr = mCnmt.getAttribute().begin(); itr != mCnmt.getAttribute().end(); itr++)
{
if (mCnmt.getAttribute().test(flag))
{
attribute_list.push_back(nn::hac::ContentMetaUtil::getContentMetaAttributeFlagAsString(nn::hac::cnmt::ContentMetaAttributeFlag(flag)));
}
attribute_list.push_back(nn::hac::ContentMetaUtil::getContentMetaAttributeFlagAsString(nn::hac::cnmt::ContentMetaAttributeFlag(*itr)));
}
std::cout << " [";
fmt::print(" [");
for (auto itr = attribute_list.begin(); itr != attribute_list.end(); itr++)
{
std::cout << *itr;
fmt::print("{:s}",*itr);
if ((itr + 1) != attribute_list.end())
{
std::cout << ", ";
fmt::print(", ");
}
}
std::cout << "]";
fmt::print("]");
}
std::cout << std::endl;
fmt::print("\n");
std::cout << " StorageId: " << nn::hac::ContentMetaUtil::getStorageIdAsString(mCnmt.getStorageId()) << " (" << std::dec << (uint32_t)mCnmt.getStorageId() << ")" << std::endl;
std::cout << " ContentInstallType: " << nn::hac::ContentMetaUtil::getContentInstallTypeAsString(mCnmt.getContentInstallType()) << " (" << std::dec << (uint32_t)mCnmt.getContentInstallType() << ")" << std::endl;
std::cout << " RequiredDownloadSystemVersion: " << nn::hac::ContentMetaUtil::getVersionAsString(mCnmt.getRequiredDownloadSystemVersion()) << " (v" << mCnmt.getRequiredDownloadSystemVersion() << ")"<< std::endl;
fmt::print(" StorageId: {:s} ({:d})\n", nn::hac::ContentMetaUtil::getStorageIdAsString(mCnmt.getStorageId()), (uint32_t)mCnmt.getStorageId());
fmt::print(" ContentInstallType: {:s} ({:d})\n", nn::hac::ContentMetaUtil::getContentInstallTypeAsString(mCnmt.getContentInstallType()),(uint32_t)mCnmt.getContentInstallType());
fmt::print(" RequiredDownloadSystemVersion: {:s} (v{:d})\n", nn::hac::ContentMetaUtil::getVersionAsString(mCnmt.getRequiredDownloadSystemVersion()), mCnmt.getRequiredDownloadSystemVersion());
switch(mCnmt.getContentMetaType())
{
case (nn::hac::cnmt::ContentMetaType::Application):
std::cout << " ApplicationExtendedHeader:" << std::endl;
std::cout << " RequiredApplicationVersion: " << nn::hac::ContentMetaUtil::getVersionAsString(mCnmt.getApplicationMetaExtendedHeader().getRequiredApplicationVersion()) << " (v" << std::dec << mCnmt.getApplicationMetaExtendedHeader().getRequiredApplicationVersion() << ")"<< std::endl;
std::cout << " RequiredSystemVersion: " << nn::hac::ContentMetaUtil::getVersionAsString(mCnmt.getApplicationMetaExtendedHeader().getRequiredSystemVersion()) << " (v" << std::dec << mCnmt.getApplicationMetaExtendedHeader().getRequiredSystemVersion() << ")"<< std::endl;
std::cout << " PatchId: 0x" << std::hex << std::setw(16) << std::setfill('0') << mCnmt.getApplicationMetaExtendedHeader().getPatchId() << std::endl;
fmt::print(" ApplicationExtendedHeader:\n");
fmt::print(" RequiredApplicationVersion: {:s} (v{:d})\n", nn::hac::ContentMetaUtil::getVersionAsString(mCnmt.getApplicationMetaExtendedHeader().getRequiredApplicationVersion()), mCnmt.getApplicationMetaExtendedHeader().getRequiredApplicationVersion());
fmt::print(" RequiredSystemVersion: {:s} (v{:d})\n", nn::hac::ContentMetaUtil::getVersionAsString(mCnmt.getApplicationMetaExtendedHeader().getRequiredSystemVersion()), mCnmt.getApplicationMetaExtendedHeader().getRequiredSystemVersion());
fmt::print(" PatchId: 0x{:016x}\n", mCnmt.getApplicationMetaExtendedHeader().getPatchId());
break;
case (nn::hac::cnmt::ContentMetaType::Patch):
std::cout << " PatchMetaExtendedHeader:" << std::endl;
std::cout << " RequiredSystemVersion: " << nn::hac::ContentMetaUtil::getVersionAsString(mCnmt.getPatchMetaExtendedHeader().getRequiredSystemVersion()) << " (v" << std::dec << mCnmt.getPatchMetaExtendedHeader().getRequiredSystemVersion() << ")"<< std::endl;
std::cout << " ApplicationId: 0x" << std::hex << std::setw(16) << std::setfill('0') << mCnmt.getPatchMetaExtendedHeader().getApplicationId() << std::endl;
fmt::print(" PatchMetaExtendedHeader:\n");
fmt::print(" RequiredSystemVersion: {:s} (v{:d})\n", nn::hac::ContentMetaUtil::getVersionAsString(mCnmt.getPatchMetaExtendedHeader().getRequiredSystemVersion()), mCnmt.getPatchMetaExtendedHeader().getRequiredSystemVersion());
fmt::print(" ApplicationId: 0x{:016x}\n", mCnmt.getPatchMetaExtendedHeader().getApplicationId());
break;
case (nn::hac::cnmt::ContentMetaType::AddOnContent):
std::cout << " AddOnContentMetaExtendedHeader:" << std::endl;
std::cout << " RequiredApplicationVersion: " << nn::hac::ContentMetaUtil::getVersionAsString(mCnmt.getAddOnContentMetaExtendedHeader().getRequiredApplicationVersion()) << " (v" << std::dec << mCnmt.getAddOnContentMetaExtendedHeader().getRequiredApplicationVersion() << ")" << std::endl;
std::cout << " ApplicationId: 0x" << std::hex << std::setw(16) << std::setfill('0') << mCnmt.getAddOnContentMetaExtendedHeader().getApplicationId() << std::endl;
fmt::print(" AddOnContentMetaExtendedHeader:\n");
fmt::print(" RequiredApplicationVersion: {:s} (v{:d})\n", nn::hac::ContentMetaUtil::getVersionAsString(mCnmt.getAddOnContentMetaExtendedHeader().getRequiredApplicationVersion()), mCnmt.getAddOnContentMetaExtendedHeader().getRequiredApplicationVersion());
fmt::print(" ApplicationId: 0x{:016x}\n", mCnmt.getAddOnContentMetaExtendedHeader().getApplicationId());
break;
case (nn::hac::cnmt::ContentMetaType::Delta):
std::cout << " DeltaMetaExtendedHeader:" << std::endl;
std::cout << " ApplicationId: 0x" << std::hex << std::setw(16) << std::setfill('0') << mCnmt.getDeltaMetaExtendedHeader().getApplicationId() << std::endl;
fmt::print(" DeltaMetaExtendedHeader:\n");
fmt::print(" ApplicationId: 0x{:016x}\n", mCnmt.getDeltaMetaExtendedHeader().getApplicationId());
break;
default:
break;
}
if (mCnmt.getContentInfo().size() > 0)
{
printf(" ContentInfo:\n");
fmt::print(" ContentInfo:\n");
for (size_t i = 0; i < mCnmt.getContentInfo().size(); i++)
{
const nn::hac::ContentInfo& info = mCnmt.getContentInfo()[i];
std::cout << " " << std::dec << i << std::endl;
std::cout << " Type: " << nn::hac::ContentMetaUtil::getContentTypeAsString(info.getContentType()) << " (" << std::dec << (uint32_t)info.getContentType() << ")" << std::endl;
std::cout << " Id: " << fnd::SimpleTextOutput::arrayToString(info.getContentId().data(), info.getContentId().size(), false, "") << std::endl;
std::cout << " Size: 0x" << std::hex << info.getContentSize() << std::endl;
std::cout << " Hash: " << fnd::SimpleTextOutput::arrayToString(info.getContentHash().bytes, sizeof(info.getContentHash()), false, "") << std::endl;
fmt::print(" {:d}\n", i);
fmt::print(" Type: {:s} ({:d})\n", nn::hac::ContentMetaUtil::getContentTypeAsString(info.getContentType()), (uint32_t)info.getContentType());
fmt::print(" Id: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(info.getContentId().data(), info.getContentId().size(), false, ""));
fmt::print(" Size: 0x{:x}\n", info.getContentSize());
fmt::print(" Hash: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(info.getContentHash().data(), info.getContentHash().size(), false, ""));
}
}
if (mCnmt.getContentMetaInfo().size() > 0)
{
std::cout << " ContentMetaInfo:" << std::endl;
fmt::print(" ContentMetaInfo:\n");
displayContentMetaInfoList(mCnmt.getContentMetaInfo(), " ");
}
@ -142,81 +146,76 @@ void CnmtProcess::displayCnmt()
if (mCnmt.getContentMetaType() == nn::hac::cnmt::ContentMetaType::Patch && mCnmt.getPatchMetaExtendedHeader().getExtendedDataSize() != 0)
{
// this is stubbed as the raw output is for development purposes
//std::cout << " PatchMetaExtendedData:" << std::endl;
//fnd::SimpleTextOutput::hxdStyleDump(mCnmt.getPatchMetaExtendedData().data(), mCnmt.getPatchMetaExtendedData().size());
//fmt::print(" PatchMetaExtendedData:\n");
//tc::cli::FormatUtil::formatBytesAsHxdHexString(mCnmt.getPatchMetaExtendedData().data(), mCnmt.getPatchMetaExtendedData().size());
}
else if (mCnmt.getContentMetaType() == nn::hac::cnmt::ContentMetaType::Delta && mCnmt.getDeltaMetaExtendedHeader().getExtendedDataSize() != 0)
{
// this is stubbed as the raw output is for development purposes
//std::cout << " DeltaMetaExtendedData:" << std::endl;
//fnd::SimpleTextOutput::hxdStyleDump(mCnmt.getDeltaMetaExtendedData().data(), mCnmt.getDeltaMetaExtendedData().size());
//fmt::print(" DeltaMetaExtendedData:\n");
//tc::cli::FormatUtil::formatBytesAsHxdHexString(mCnmt.getDeltaMetaExtendedData().data(), mCnmt.getDeltaMetaExtendedData().size());
}
else if (mCnmt.getContentMetaType() == nn::hac::cnmt::ContentMetaType::SystemUpdate && mCnmt.getSystemUpdateMetaExtendedHeader().getExtendedDataSize() != 0)
{
std::cout << " SystemUpdateMetaExtendedData:" << std::endl;
std::cout << " FormatVersion: " << std::dec << mCnmt.getSystemUpdateMetaExtendedData().getFormatVersion() << std::endl;
std::cout << " FirmwareVariation:" << std::endl;
fmt::print(" SystemUpdateMetaExtendedData:\n");
fmt::print(" FormatVersion: {:d}\n", mCnmt.getSystemUpdateMetaExtendedData().getFormatVersion());
fmt::print(" FirmwareVariation:\n");
auto variation_info = mCnmt.getSystemUpdateMetaExtendedData().getFirmwareVariationInfo();
for (size_t i = 0; i < mCnmt.getSystemUpdateMetaExtendedData().getFirmwareVariationInfo().size(); i++)
{
std::cout << " " << std::dec << i << std::endl;
std::cout << " FirmwareVariationId: 0x" << std::hex << variation_info[i].variation_id << std::endl;
fmt::print(" {:d}\n", i);
fmt::print(" FirmwareVariationId: 0x{:x}\n", variation_info[i].variation_id);
if (mCnmt.getSystemUpdateMetaExtendedData().getFormatVersion() == 2)
{
std::cout << " ReferToBase: " << std::boolalpha << variation_info[i].meta.empty() << std::endl;
fmt::print(" ReferToBase: {}\n", variation_info[i].meta.empty());
if (variation_info[i].meta.empty() == false)
{
std::cout << " ContentMeta:" << std::endl;
fmt::print(" ContentMeta:\n");
displayContentMetaInfoList(variation_info[i].meta, " ");
}
}
}
}
std::cout << " Digest: " << fnd::SimpleTextOutput::arrayToString(mCnmt.getDigest().data(), mCnmt.getDigest().size(), false, "") << std::endl;
fmt::print(" Digest: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mCnmt.getDigest().data(), mCnmt.getDigest().size(), false, ""));
}
void CnmtProcess::displayContentMetaInfo(const nn::hac::ContentMetaInfo& content_meta_info, const std::string& prefix)
void nstool::CnmtProcess::displayContentMetaInfo(const nn::hac::ContentMetaInfo& content_meta_info, const std::string& prefix)
{
std::cout << prefix << "Id: 0x" << std::hex << std::setw(16) << std::setfill('0') << content_meta_info.getTitleId() << std::endl;
std::cout << prefix << "Version: " << nn::hac::ContentMetaUtil::getVersionAsString(content_meta_info.getTitleVersion()) << " (v" << std::dec << content_meta_info.getTitleVersion() << ")"<< std::endl;
std::cout << prefix << "Type: " << nn::hac::ContentMetaUtil::getContentMetaTypeAsString(content_meta_info.getContentMetaType()) << " (" << std::dec << (uint32_t)content_meta_info.getContentMetaType() << ")" << std::endl;
std::cout << prefix << "Attributes: 0x" << std::hex << content_meta_info.getAttribute().to_ullong();
if (content_meta_info.getAttribute().any())
const nn::hac::sContentMetaInfo* content_meta_info_raw = (const nn::hac::sContentMetaInfo*)content_meta_info.getBytes().data();
fmt::print("{:s}Id: 0x{:016x}\n", prefix, content_meta_info.getTitleId());
fmt::print("{:s}Version: {:s} (v{:d})\n", prefix, nn::hac::ContentMetaUtil::getVersionAsString(content_meta_info.getTitleVersion()), content_meta_info.getTitleVersion());
fmt::print("{:s}Type: {:s} ({:d})\n", prefix, nn::hac::ContentMetaUtil::getContentMetaTypeAsString(content_meta_info.getContentMetaType()), (uint32_t)content_meta_info.getContentMetaType());
fmt::print("{:s}Attributes: 0x{:x}", prefix, *((byte_t*)&content_meta_info_raw->attributes) );
if (content_meta_info.getAttribute().size())
{
std::vector<std::string> attribute_list;
for (size_t flag = 0; flag < content_meta_info.getAttribute().size(); flag++)
for (auto itr = content_meta_info.getAttribute().begin(); itr != content_meta_info.getAttribute().end(); itr++)
{
if (content_meta_info.getAttribute().test(flag))
{
attribute_list.push_back(nn::hac::ContentMetaUtil::getContentMetaAttributeFlagAsString(nn::hac::cnmt::ContentMetaAttributeFlag(flag)));
}
attribute_list.push_back(nn::hac::ContentMetaUtil::getContentMetaAttributeFlagAsString(nn::hac::cnmt::ContentMetaAttributeFlag(*itr)));
}
std::cout << " [";
fmt::print(" [");
for (auto itr = attribute_list.begin(); itr != attribute_list.end(); itr++)
{
std::cout << *itr;
fmt::print("{:s}",*itr);
if ((itr + 1) != attribute_list.end())
{
std::cout << ", ";
fmt::print(", ");
}
}
std::cout << "]";
fmt::print("]");
}
fmt::print("\n");
}
void CnmtProcess::displayContentMetaInfoList(const std::vector<nn::hac::ContentMetaInfo>& content_meta_info_list, const std::string& prefix)
void nstool::CnmtProcess::displayContentMetaInfoList(const std::vector<nn::hac::ContentMetaInfo>& content_meta_info_list, const std::string& prefix)
{
for (size_t i = 0; i < content_meta_info_list.size(); i++)
{
const nn::hac::ContentMetaInfo& info = mCnmt.getContentMetaInfo()[i];
std::cout << prefix << std::dec << i << std::endl;
displayContentMetaInfo(info, prefix + " ");
std::cout << std::endl;
}
{
const nn::hac::ContentMetaInfo& info = mCnmt.getContentMetaInfo()[i];
fmt::print("{:s}{:d}\n", i);
displayContentMetaInfo(info, prefix + " ");
}
}

View file

@ -1,11 +1,9 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/IFile.h>
#include <fnd/SharedPtr.h>
#include "types.h"
#include <nn/hac/ContentMeta.h>
#include "common.h"
namespace nstool {
class CnmtProcess
{
@ -14,16 +12,15 @@ public:
void process();
void setInputFile(const fnd::SharedPtr<fnd::IFile>& file);
void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify);
const nn::hac::ContentMeta& getContentMeta() const;
private:
const std::string kModuleName = "CnmtProcess";
std::string mModuleName;
fnd::SharedPtr<fnd::IFile> mFile;
std::shared_ptr<tc::io::IStream> mFile;
CliOutputMode mCliOutputMode;
bool mVerify;
@ -35,3 +32,5 @@ private:
void displayContentMetaInfo(const nn::hac::ContentMetaInfo& content_meta_info, const std::string& prefix);
void displayContentMetaInfoList(const std::vector<nn::hac::ContentMetaInfo>& content_meta_info_list, const std::string& prefix);
};
}

View file

@ -1,195 +0,0 @@
#include "CompressedArchiveIFile.h"
#include <fnd/lz4.h>
#include <iostream>
CompressedArchiveIFile::CompressedArchiveIFile(const fnd::SharedPtr<fnd::IFile>& base_file, size_t compression_meta_offset) :
mFile(base_file),
mCompEntries(),
mLogicalFileSize(0),
mCacheCapacity(nn::hac::compression::kRomfsBlockSize),
mCurrentCacheDataSize(0),
mCache(std::shared_ptr<byte_t>(new byte_t[mCacheCapacity])),
mScratch(std::shared_ptr<byte_t>(new byte_t[mCacheCapacity]))
{
// determine and check the compression metadata size
size_t compression_meta_size = (*mFile)->size() - compression_meta_offset;
if (compression_meta_size % sizeof(nn::hac::sCompressionEntry))
{
fnd::Exception(kModuleName, "Invalid compression meta size");
}
// import raw metadata
std::shared_ptr<byte_t> entries_raw = std::shared_ptr<byte_t>(new byte_t[compression_meta_size]);
(*mFile)->read(entries_raw.get(), compression_meta_offset, compression_meta_size);
// process metadata entries
nn::hac::sCompressionEntry* entries = (nn::hac::sCompressionEntry*)entries_raw.get();
for (size_t idx = 0, num = compression_meta_size / sizeof(nn::hac::sCompressionEntry); idx < num; idx++)
{
if (idx == 0)
{
if (entries[idx].physical_offset.get() != 0x0)
throw fnd::Exception(kModuleName, "Entry 0 had a non-zero physical offset");
if (entries[idx].virtual_offset.get() != 0x0)
throw fnd::Exception(kModuleName, "Entry 0 had a non-zero virtual offset");
}
else
{
if (entries[idx].physical_offset.get() != align(entries[idx - 1].physical_offset.get() + entries[idx - 1].physical_size.get(), nn::hac::compression::kRomfsBlockAlign))
throw fnd::Exception(kModuleName, "Entry was not physically aligned with previous entry");
if (entries[idx].virtual_offset.get() <= entries[idx - 1].virtual_offset.get())
throw fnd::Exception(kModuleName, "Entry was not virtually aligned with previous entry");
// set previous entry virtual_size = this->virtual_offset - prev->virtual_offset;
mCompEntries[mCompEntries.size() - 1].virtual_size = uint32_t(entries[idx].virtual_offset.get() - mCompEntries[mCompEntries.size() - 1].virtual_offset);
}
if (entries[idx].physical_size.get() > nn::hac::compression::kRomfsBlockSize)
throw fnd::Exception(kModuleName, "Entry physical size was too large");
switch ((nn::hac::compression::CompressionType)entries[idx].compression_type)
{
case (nn::hac::compression::CompressionType::None):
case (nn::hac::compression::CompressionType::Lz4):
break;
default:
throw fnd::Exception(kModuleName, "Unsupported CompressionType");
}
mCompEntries.push_back({(nn::hac::compression::CompressionType)entries[idx].compression_type, entries[idx].virtual_offset.get(), 0, entries[idx].physical_offset.get(), entries[idx].physical_size.get()});
}
// determine logical file size and final entry size
importEntryDataToCache(mCompEntries.size() - 1);
mCompEntries[mCurrentEntryIndex].virtual_size = mCurrentCacheDataSize;
mLogicalFileSize = mCompEntries[mCurrentEntryIndex].virtual_offset + mCompEntries[mCurrentEntryIndex].virtual_size;
/*
for (auto itr = mCompEntries.begin(); itr != mCompEntries.end(); itr++)
{
std::cout << "entry " << std::endl;
std::cout << " type: " << (uint32_t)itr->compression_type << std::endl;
std::cout << " phys_addr: 0x" << std::hex << itr->physical_offset << std::endl;
std::cout << " phys_size: 0x" << std::hex << itr->physical_size << std::endl;
std::cout << " virt_addr: 0x" << std::hex << itr->virtual_offset << std::endl;
std::cout << " virt_size: 0x" << std::hex << itr->virtual_size << std::endl;
}
std::cout << "logical size: 0x" << std::hex << mLogicalFileSize << std::endl;
*/
}
size_t CompressedArchiveIFile::size()
{
return mLogicalFileSize;
}
void CompressedArchiveIFile::seek(size_t offset)
{
mLogicalOffset = std::min<size_t>(offset, mLogicalFileSize);
}
void CompressedArchiveIFile::read(byte_t* out, size_t len)
{
// limit len to the end of the logical file
len = std::min<size_t>(len, mLogicalFileSize - mLogicalOffset);
for (size_t pos = 0, entry_index = getEntryIndexForLogicalOffset(mLogicalOffset); pos < len; entry_index++)
{
// importing entry into cache (this does nothing if the entry is already imported)
importEntryDataToCache(entry_index);
// write padding if required
if (mCompEntries[entry_index].virtual_size > mCurrentCacheDataSize)
{
memset(mCache.get() + mCurrentCacheDataSize, 0, mCompEntries[entry_index].virtual_size - mCurrentCacheDataSize);
}
// determine subset of cache to copy out
size_t read_offset = mLogicalOffset - (size_t)mCompEntries[entry_index].virtual_offset;
size_t read_size = std::min<size_t>(len, (size_t)mCompEntries[entry_index].virtual_size - read_offset);
memcpy(out + pos, mCache.get() + read_offset, read_size);
// update position/logical offset
pos += read_size;
mLogicalOffset += read_size;
}
}
void CompressedArchiveIFile::read(byte_t* out, size_t offset, size_t len)
{
seek(offset);
read(out, len);
}
void CompressedArchiveIFile::write(const byte_t* out, size_t len)
{
throw fnd::Exception(kModuleName, "write() not supported");
}
void CompressedArchiveIFile::write(const byte_t* out, size_t offset, size_t len)
{
throw fnd::Exception(kModuleName, "write() not supported");
}
void CompressedArchiveIFile::importEntryDataToCache(size_t entry_index)
{
// return if entry already imported
if (mCurrentEntryIndex == entry_index && mCurrentCacheDataSize != 0)
return;
// save index
mCurrentEntryIndex = entry_index;
// reference entry
CompressionEntry& entry = mCompEntries[mCurrentEntryIndex];
if (entry.compression_type == nn::hac::compression::CompressionType::None)
{
(*mFile)->read(mCache.get(), entry.physical_offset, entry.physical_size);
mCurrentCacheDataSize = entry.physical_size;
}
else if (entry.compression_type == nn::hac::compression::CompressionType::Lz4)
{
(*mFile)->read(mScratch.get(), entry.physical_offset, entry.physical_size);
mCurrentCacheDataSize = 0;
fnd::lz4::decompressData(mScratch.get(), entry.physical_size, mCache.get(), uint32_t(mCacheCapacity), mCurrentCacheDataSize);
if (mCurrentCacheDataSize == 0)
{
throw fnd::Exception(kModuleName, "Decompression of final block failed");
}
}
}
size_t CompressedArchiveIFile::getEntryIndexForLogicalOffset(size_t logical_offset)
{
// rule out bad offset
if (logical_offset > mLogicalFileSize)
throw fnd::Exception(kModuleName, "illegal logical offset");
size_t entry_index = 0;
// try the current comp entry
if (mCompEntries[mCurrentEntryIndex].virtual_offset <= logical_offset && \
mCompEntries[mCurrentEntryIndex].virtual_offset + mCompEntries[mCurrentEntryIndex].virtual_size >= logical_offset)
{
entry_index = mCurrentEntryIndex;
}
else
{
for (size_t index = 0; index < mCompEntries.size(); index++)
{
if (mCompEntries[index].virtual_offset <= logical_offset && \
mCompEntries[index].virtual_offset + mCompEntries[index].virtual_size >= logical_offset)
{
entry_index = index;
}
}
}
return entry_index;
}

View file

@ -1,51 +0,0 @@
#pragma once
#include <sstream>
#include <fnd/IFile.h>
#include <fnd/SharedPtr.h>
#include <memory>
#include <vector>
#include <nn/hac/define/compression.h>
class CompressedArchiveIFile : public fnd::IFile
{
public:
CompressedArchiveIFile(const fnd::SharedPtr<fnd::IFile>& file, size_t compression_meta_offset);
size_t size();
void seek(size_t offset);
void read(byte_t* out, size_t len);
void read(byte_t* out, size_t offset, size_t len);
void write(const byte_t* out, size_t len);
void write(const byte_t* out, size_t offset, size_t len);
private:
const std::string kModuleName = "CompressedArchiveIFile";
std::stringstream mErrorSs;
struct CompressionEntry
{
nn::hac::compression::CompressionType compression_type;
uint64_t virtual_offset;
uint32_t virtual_size;
uint64_t physical_offset;
uint32_t physical_size;
};
// raw data
fnd::SharedPtr<fnd::IFile> mFile;
// compression metadata
std::vector<CompressionEntry> mCompEntries;
size_t mLogicalFileSize;
size_t mLogicalOffset;
// cached decompressed entry
size_t mCacheCapacity; // capacity
size_t mCurrentEntryIndex; // index of entry currently associated with the cache
uint32_t mCurrentCacheDataSize; // size of data currently in cache
std::shared_ptr<byte_t> mCache; // where decompressed data resides
std::shared_ptr<byte_t> mScratch; // same size as cache, but is used for storing data pre-compression
// this will import entry to cache
void importEntryDataToCache(size_t entry_index);
size_t getEntryIndexForLogicalOffset(size_t logical_offset);
};

View file

@ -1,28 +1,29 @@
#include "ElfSymbolParser.h"
ElfSymbolParser::ElfSymbolParser()
nstool::ElfSymbolParser::ElfSymbolParser() :
mModuleName("nstool::ElfSymbolParser"),
mSymbolList()
{
mSymbolList.clear();
}
void ElfSymbolParser::operator=(const ElfSymbolParser& other)
void nstool::ElfSymbolParser::operator=(const ElfSymbolParser& other)
{
mSymbolList = other.mSymbolList;
}
bool ElfSymbolParser::operator==(const ElfSymbolParser& other) const
bool nstool::ElfSymbolParser::operator==(const ElfSymbolParser& other) const
{
return mSymbolList == other.mSymbolList;
}
bool ElfSymbolParser::operator!=(const ElfSymbolParser& other) const
bool nstool::ElfSymbolParser::operator!=(const ElfSymbolParser& other) const
{
return !(*this == other);
}
void ElfSymbolParser::parseData(const byte_t *dyn_sym, size_t dyn_sym_size, const byte_t *dyn_str, size_t dyn_str_size, bool is64Bit)
void nstool::ElfSymbolParser::parseData(const byte_t *dyn_sym, size_t dyn_sym_size, const byte_t *dyn_str, size_t dyn_str_size, bool is64Bit)
{
size_t dynSymSize = is64Bit ? sizeof(fnd::Elf64_Sym) : sizeof(fnd::Elf32_Sym);
size_t dynSymSize = is64Bit ? sizeof(Elf64_Sym) : sizeof(Elf32_Sym);
sElfSymbol symbol;
for (size_t i = 0; i < dyn_sym_size; i += dynSymSize)
@ -31,32 +32,32 @@ void ElfSymbolParser::parseData(const byte_t *dyn_sym, size_t dyn_sym_size, cons
if (is64Bit)
{
name_pos = le_word(((fnd::Elf64_Sym*)(dyn_sym + i))->st_name);
symbol.shn_index = le_hword(((fnd::Elf64_Sym*)(dyn_sym + i))->st_shndx);
symbol.symbol_type = fnd::elf::get_elf_st_type(((fnd::Elf64_Sym*)(dyn_sym + i))->st_info);
symbol.symbol_binding = fnd::elf::get_elf_st_bind(((fnd::Elf64_Sym*)(dyn_sym + i))->st_info);
name_pos = tc::bn::detail::__le_uint32(((Elf64_Sym*)(dyn_sym + i))->st_name);
symbol.shn_index = tc::bn::detail::__le_uint16(((Elf64_Sym*)(dyn_sym + i))->st_shndx);
symbol.symbol_type = elf::get_elf_st_type(((Elf64_Sym*)(dyn_sym + i))->st_info);
symbol.symbol_binding = elf::get_elf_st_bind(((Elf64_Sym*)(dyn_sym + i))->st_info);
}
else
{
name_pos = le_word(((fnd::Elf32_Sym*)(dyn_sym + i))->st_name);
symbol.shn_index = le_hword(((fnd::Elf32_Sym*)(dyn_sym + i))->st_shndx);
symbol.symbol_type = fnd::elf::get_elf_st_type(((fnd::Elf32_Sym*)(dyn_sym + i))->st_info);
symbol.symbol_binding = fnd::elf::get_elf_st_bind(((fnd::Elf32_Sym*)(dyn_sym + i))->st_info);
name_pos = tc::bn::detail::__le_uint32(((Elf32_Sym*)(dyn_sym + i))->st_name);
symbol.shn_index = tc::bn::detail::__le_uint16(((Elf32_Sym*)(dyn_sym + i))->st_shndx);
symbol.symbol_type = elf::get_elf_st_type(((Elf32_Sym*)(dyn_sym + i))->st_info);
symbol.symbol_binding = elf::get_elf_st_bind(((Elf32_Sym*)(dyn_sym + i))->st_info);
}
if (name_pos >= dyn_str_size)
{
throw fnd::Exception(kModuleName, "Out of bounds symbol name offset");
throw tc::Exception(mModuleName, "Out of bounds symbol name offset");
}
//for (; dyn_str[name_pos] == 0x00 && name_pos < dyn_str_size; name_pos++);
symbol.name = std::string((char*)&dyn_str[name_pos]);
mSymbolList.addElement(symbol);
mSymbolList.push_back(symbol);
}
}
const fnd::List<ElfSymbolParser::sElfSymbol>& ElfSymbolParser::getSymbolList() const
const std::vector<nstool::ElfSymbolParser::sElfSymbol>& nstool::ElfSymbolParser::getSymbolList() const
{
return mSymbolList;
}

View file

@ -1,7 +1,8 @@
#pragma once
#include <string>
#include <fnd/List.h>
#include <fnd/elf.h>
#include "types.h"
#include "elf.h"
namespace nstool {
class ElfSymbolParser
{
@ -40,10 +41,12 @@ public:
void parseData(const byte_t *dyn_sym, size_t dyn_sym_size, const byte_t *dyn_str, size_t dyn_str_size, bool is64Bit);
const fnd::List<sElfSymbol>& getSymbolList() const;
const std::vector<sElfSymbol>& getSymbolList() const;
private:
const std::string kModuleName = "ElfSymbolParser";
std::string mModuleName;
// data
fnd::List<sElfSymbol> mSymbolList;
std::vector<sElfSymbol> mSymbolList;
};
}

228
src/EsCertProcess.cpp Normal file
View file

@ -0,0 +1,228 @@
#include "EsCertProcess.h"
#include "PkiValidator.h"
#include "util.h"
#include <nn/pki/SignUtils.h>
nstool::EsCertProcess::EsCertProcess() :
mModuleName("nstool::EsCertProcess"),
mFile(),
mCliOutputMode(true, false, false, false),
mVerify(false)
{
}
void nstool::EsCertProcess::process()
{
importCerts();
if (mVerify)
validateCerts();
if (mCliOutputMode.show_basic_info)
displayCerts();
}
void nstool::EsCertProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
{
mFile = file;
}
void nstool::EsCertProcess::setKeyCfg(const KeyBag& keycfg)
{
mKeyCfg = keycfg;
}
void nstool::EsCertProcess::setCliOutputMode(CliOutputMode mode)
{
mCliOutputMode = mode;
}
void nstool::EsCertProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void nstool::EsCertProcess::importCerts()
{
if (mFile == nullptr)
{
throw tc::Exception(mModuleName, "No file reader set.");
}
if (mFile->canRead() == false || mFile->canSeek() == false)
{
throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions.");
}
// check if file_size is greater than 20MB, don't import.
size_t file_size = tc::io::IOUtil::castInt64ToSize(mFile->length());
if (file_size > (0x100000 * 20))
{
throw tc::Exception(mModuleName, "File too large.");
}
// import certs
tc::ByteData scratch = tc::ByteData(file_size);
mFile->seek(0, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
nn::pki::SignedData<nn::pki::CertificateBody> cert;
for (size_t f_pos = 0; f_pos < scratch.size(); f_pos += cert.getBytes().size())
{
cert.fromBytes(scratch.data() + f_pos, scratch.size() - f_pos);
mCert.push_back(cert);
}
}
void nstool::EsCertProcess::validateCerts()
{
PkiValidator pki;
try
{
pki.setKeyCfg(mKeyCfg);
pki.addCertificates(mCert);
}
catch (const tc::Exception& e)
{
fmt::print("[WARNING] {}\n", e.error());
return;
}
}
void nstool::EsCertProcess::displayCerts()
{
for (size_t i = 0; i < mCert.size(); i++)
{
displayCert(mCert[i]);
}
}
void nstool::EsCertProcess::displayCert(const nn::pki::SignedData<nn::pki::CertificateBody>& cert)
{
fmt::print("[ES Certificate]\n");
fmt::print(" SignType {:s}", getSignTypeStr(cert.getSignature().getSignType()));
if (mCliOutputMode.show_extended_info)
fmt::print(" (0x{:x}) ({:s})", cert.getSignature().getSignType(), getEndiannessStr(cert.getSignature().isLittleEndian()));
fmt::print("\n");
fmt::print(" Issuer: {:s}\n", cert.getBody().getIssuer());
fmt::print(" Subject: {:s}\n", cert.getBody().getSubject());
fmt::print(" PublicKeyType: {:s}", getPublicKeyTypeStr(cert.getBody().getPublicKeyType()));
if (mCliOutputMode.show_extended_info)
fmt::print(" ({:d})", cert.getBody().getPublicKeyType());
fmt::print("\n");
fmt::print(" CertID: 0x{:x}\n", cert.getBody().getCertId());
if (cert.getBody().getPublicKeyType() == nn::pki::cert::RSA4096)
{
fmt::print(" PublicKey:\n");
if (mCliOutputMode.show_extended_info)
{
fmt::print(" Modulus:\n");
fmt::print(" {:s}", tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(cert.getBody().getRsa4096PublicKey().n.data(), cert.getBody().getRsa4096PublicKey().n.size(), true, "", 0x10, 6, false));
fmt::print(" Public Exponent:\n");
fmt::print(" {:s}", tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(cert.getBody().getRsa4096PublicKey().e.data(), cert.getBody().getRsa4096PublicKey().e.size(), true, "", 0x10, 6, false));
}
else
{
fmt::print(" Modulus:\n");
fmt::print(" {:s}\n", getTruncatedBytesString(cert.getBody().getRsa4096PublicKey().n.data(), cert.getBody().getRsa4096PublicKey().n.size()));
fmt::print(" Public Exponent:\n");
fmt::print(" {:s}\n", getTruncatedBytesString(cert.getBody().getRsa4096PublicKey().e.data(), cert.getBody().getRsa4096PublicKey().e.size()));
}
}
else if (cert.getBody().getPublicKeyType() == nn::pki::cert::RSA2048)
{
fmt::print(" PublicKey:\n");
if (mCliOutputMode.show_extended_info)
{
fmt::print(" Modulus:\n");
fmt::print(" {:s}", tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(cert.getBody().getRsa2048PublicKey().n.data(), cert.getBody().getRsa2048PublicKey().n.size(), true, "", 0x10, 6, false));
fmt::print(" Public Exponent:\n");
fmt::print(" {:s}", tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(cert.getBody().getRsa2048PublicKey().e.data(), cert.getBody().getRsa2048PublicKey().e.size(), true, "", 0x10, 6, false));
}
else
{
fmt::print(" Modulus:\n");
fmt::print(" {:s}\n", getTruncatedBytesString(cert.getBody().getRsa2048PublicKey().n.data(), cert.getBody().getRsa2048PublicKey().n.size()));
fmt::print(" Public Exponent:\n");
fmt::print(" {:s}\n", getTruncatedBytesString(cert.getBody().getRsa2048PublicKey().e.data(), cert.getBody().getRsa2048PublicKey().e.size()));
}
}
else if (cert.getBody().getPublicKeyType() == nn::pki::cert::ECDSA240)
{
fmt::print(" PublicKey:\n");
if (mCliOutputMode.show_extended_info)
{
fmt::print(" Modulus:\n");
fmt::print(" {:s}", tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(cert.getBody().getEcdsa240PublicKey().r.data(), cert.getBody().getEcdsa240PublicKey().r.size(), true, "", 0x10, 6, false));
fmt::print(" Public Exponent:\n");
fmt::print(" {:s}", tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(cert.getBody().getEcdsa240PublicKey().s.data(), cert.getBody().getEcdsa240PublicKey().s.size(), true, "", 0x10, 6, false));
}
else
{
fmt::print(" Modulus:\n");
fmt::print(" {:s}\n", getTruncatedBytesString(cert.getBody().getEcdsa240PublicKey().r.data(), cert.getBody().getEcdsa240PublicKey().r.size()));
fmt::print(" Public Exponent:\n");
fmt::print(" {:s}\n", getTruncatedBytesString(cert.getBody().getEcdsa240PublicKey().s.data(), cert.getBody().getEcdsa240PublicKey().s.size()));
}
}
}
std::string nstool::EsCertProcess::getSignTypeStr(nn::pki::sign::SignatureId type) const
{
std::string str;
switch (type)
{
case (nn::pki::sign::SIGN_ID_RSA4096_SHA1):
str = "RSA4096-SHA1";
break;
case (nn::pki::sign::SIGN_ID_RSA2048_SHA1):
str = "RSA2048-SHA1";
break;
case (nn::pki::sign::SIGN_ID_ECDSA240_SHA1):
str = "ECDSA240-SHA1";
break;
case (nn::pki::sign::SIGN_ID_RSA4096_SHA256):
str = "RSA4096-SHA256";
break;
case (nn::pki::sign::SIGN_ID_RSA2048_SHA256):
str = "RSA2048-SHA256";
break;
case (nn::pki::sign::SIGN_ID_ECDSA240_SHA256):
str = "ECDSA240-SHA256";
break;
default:
str = "Unknown";
break;
}
return str;
}
std::string nstool::EsCertProcess::getEndiannessStr(bool isLittleEndian) const
{
return isLittleEndian ? "LittleEndian" : "BigEndian";
}
std::string nstool::EsCertProcess::getPublicKeyTypeStr(nn::pki::cert::PublicKeyType type) const
{
std::string str;
switch (type)
{
case (nn::pki::cert::RSA4096):
str = "RSA4096";
break;
case (nn::pki::cert::RSA2048):
str = "RSA2048";
break;
case (nn::pki::cert::ECDSA240):
str = "ECDSA240";
break;
default:
str = "Unknown";
break;
}
return str;
}

42
src/EsCertProcess.h Normal file
View file

@ -0,0 +1,42 @@
#pragma once
#include "types.h"
#include "KeyBag.h"
#include <nn/pki/SignedData.h>
#include <nn/pki/CertificateBody.h>
namespace nstool {
class EsCertProcess
{
public:
EsCertProcess();
void process();
void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setKeyCfg(const KeyBag& keycfg);
void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify);
private:
std::string mModuleName;
std::shared_ptr<tc::io::IStream> mFile;
KeyBag mKeyCfg;
CliOutputMode mCliOutputMode;
bool mVerify;
std::vector<nn::pki::SignedData<nn::pki::CertificateBody>> mCert;
void importCerts();
void validateCerts();
void displayCerts();
void displayCert(const nn::pki::SignedData<nn::pki::CertificateBody>& cert);
std::string getSignTypeStr(nn::pki::sign::SignatureId type) const;
std::string getEndiannessStr(bool isLittleEndian) const;
std::string getPublicKeyTypeStr(nn::pki::cert::PublicKeyType type) const;
};
}

View file

@ -1,85 +1,92 @@
#include <iostream>
#include <iomanip>
#include <fnd/SimpleTextOutput.h>
#include <fnd/OffsetAdjustedIFile.h>
#include <nn/pki/SignUtils.h>
#include "EsTikProcess.h"
#include "PkiValidator.h"
#include <nn/pki/SignUtils.h>
EsTikProcess::EsTikProcess() :
nstool::EsTikProcess::EsTikProcess() :
mModuleName("nstool::EsTikProcess"),
mFile(),
mCliOutputMode(_BIT(OUTPUT_BASIC)),
mCliOutputMode(true, false, false, false),
mVerify(false)
{
}
void EsTikProcess::process()
void nstool::EsTikProcess::process()
{
importTicket();
if (mVerify)
verifyTicket();
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
if (mCliOutputMode.show_basic_info)
displayTicket();
}
void EsTikProcess::setInputFile(const fnd::SharedPtr<fnd::IFile>& file)
void nstool::EsTikProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
{
mFile = file;
}
void EsTikProcess::setKeyCfg(const KeyConfiguration& keycfg)
void nstool::EsTikProcess::setKeyCfg(const KeyBag& keycfg)
{
mKeyCfg = keycfg;
}
void EsTikProcess::setCertificateChain(const fnd::List<nn::pki::SignedData<nn::pki::CertificateBody>>& certs)
void nstool::EsTikProcess::setCertificateChain(const std::vector<nn::pki::SignedData<nn::pki::CertificateBody>>& certs)
{
mCerts = certs;
}
void EsTikProcess::setCliOutputMode(CliOutputMode mode)
void nstool::EsTikProcess::setCliOutputMode(CliOutputMode mode)
{
mCliOutputMode = mode;
}
void EsTikProcess::setVerifyMode(bool verify)
void nstool::EsTikProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void EsTikProcess::importTicket()
void nstool::EsTikProcess::importTicket()
{
fnd::Vec<byte_t> scratch;
if (*mFile == nullptr)
if (mFile == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
throw tc::Exception(mModuleName, "No file reader set.");
}
if (mFile->canRead() == false || mFile->canSeek() == false)
{
throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions.");
}
scratch.alloc((*mFile)->size());
(*mFile)->read(scratch.data(), 0, scratch.size());
// check if file_size is greater than 20MB, don't import.
size_t file_size = tc::io::IOUtil::castInt64ToSize(mFile->length());
if (file_size > (0x100000 * 20))
{
throw tc::Exception(mModuleName, "File too large.");
}
// read ticket
tc::ByteData scratch = tc::ByteData(file_size);
mFile->seek(0, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
mTik.fromBytes(scratch.data(), scratch.size());
}
void EsTikProcess::verifyTicket()
void nstool::EsTikProcess::verifyTicket()
{
PkiValidator pki_validator;
fnd::Vec<byte_t> tik_hash;
tc::ByteData tik_hash;
switch (nn::pki::sign::getHashAlgo(mTik.getSignature().getSignType()))
{
case (nn::pki::sign::HASH_ALGO_SHA1):
tik_hash.alloc(fnd::sha::kSha1HashLen);
fnd::sha::Sha1(mTik.getBody().getBytes().data(), mTik.getBody().getBytes().size(), tik_hash.data());
tik_hash = tc::ByteData(tc::crypto::Sha1Generator::kHashSize);
tc::crypto::GenerateSha1Hash(tik_hash.data(), mTik.getBody().getBytes().data(), mTik.getBody().getBytes().size());
break;
case (nn::pki::sign::HASH_ALGO_SHA256):
tik_hash.alloc(fnd::sha::kSha256HashLen);
fnd::sha::Sha256(mTik.getBody().getBytes().data(), mTik.getBody().getBytes().size(), tik_hash.data());
tik_hash = tc::ByteData(tc::crypto::Sha256Generator::kHashSize);
tc::crypto::GenerateSha256Hash(tik_hash.data(), mTik.getBody().getBytes().data(), mTik.getBody().getBytes().size());
break;
}
@ -89,87 +96,75 @@ void EsTikProcess::verifyTicket()
pki_validator.addCertificates(mCerts);
pki_validator.validateSignature(mTik.getBody().getIssuer(), mTik.getSignature().getSignType(), mTik.getSignature().getSignature(), tik_hash);
}
catch (const fnd::Exception& e)
catch (const tc::Exception& e)
{
std::cout << "[WARNING] Ticket signature could not be validated (" << e.error() << ")" << std::endl;
fmt::print("[WARNING] Ticket signature could not be validated ({:s})\n", e.error());
}
}
void EsTikProcess::displayTicket()
void nstool::EsTikProcess::displayTicket()
{
#define _SPLIT_VER(ver) (uint32_t)((ver>>10) & 0x3f) << "." << (uint32_t)((ver>>4) & 0x3f) << "." << (uint32_t)((ver>>0) & 0xf)
const nn::es::TicketBody_V2& body = mTik.getBody();
std::cout << "[ES Ticket]" << std::endl;
fmt::print("[ES Ticket]\n");
fmt::print(" SignType: {:s}", getSignTypeStr(mTik.getSignature().getSignType()));
if (mCliOutputMode.show_extended_info)
fmt::print(" (0x{:x})", mTik.getSignature().getSignType());
fmt::print("\n");
std::cout << " SignType: " << getSignTypeStr(mTik.getSignature().getSignType());
if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
std::cout << " (0x" << std::hex << mTik.getSignature().getSignType() << ")";
std::cout << std::endl;
std::cout << " Issuer: " << body.getIssuer() << std::endl;
std::cout << " Title Key:" << std::endl;
std::cout << " EncMode: " << getTitleKeyPersonalisationStr(body.getTitleKeyEncType()) << std::endl;
std::cout << " KeyGeneration: " << std::dec << (uint32_t)body.getCommonKeyId() << std::endl;
fmt::print(" Issuer: {:s}\n", body.getIssuer());
fmt::print(" Title Key:\n");
fmt::print(" EncMode: {:s}\n", getTitleKeyPersonalisationStr(body.getTitleKeyEncType()));
fmt::print(" KeyGeneration: {:d}\n", (uint32_t)body.getCommonKeyId());
if (body.getTitleKeyEncType() == nn::es::ticket::RSA2048)
{
std::cout << " Data:" << std::endl;
for (size_t i = 0; i < 0x10; i++)
std::cout << " " << fnd::SimpleTextOutput::arrayToString(body.getEncTitleKey() + 0x10*i, 0x10, true, ":") << std::endl;
fmt::print(" Data:\n");
fmt::print(" {:s}", tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(body.getEncTitleKey(), 0x100, true, "", 0x10, 6, false));
}
else if (body.getTitleKeyEncType() == nn::es::ticket::AES128_CBC)
{
std::cout << " Data:" << std::endl;
std::cout << " " << fnd::SimpleTextOutput::arrayToString(body.getEncTitleKey(), 0x10, true, ":") << std::endl;
fmt::print(" Data:\n");
fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(body.getEncTitleKey(), 0x10, true, ""));
}
else
{
std::cout << " Data: <cannot display>" << std::endl;
fmt::print(" Data: <cannot display>\n");
}
std::cout << " Version: v" << _SPLIT_VER(body.getTicketVersion());
if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
std::cout << " (" << (uint32_t)body.getTicketVersion() << ")";
std::cout << std::endl;
std::cout << " License Type: " << getLicenseTypeStr(body.getLicenseType()) << std::endl;
if (body.getPropertyFlags().size() > 0)
fmt::print(" Version: {:s} (v{:d})\n", getTitleVersionStr(body.getTicketVersion()), body.getTicketVersion());
fmt::print(" License Type: {:s}\n", getLicenseTypeStr(body.getLicenseType()));
if (body.getPropertyFlags().size() > 0 || mCliOutputMode.show_extended_info)
{
std::cout << " Flags:" << std::endl;
nn::es::sTicketBody_v2* raw_body = (nn::es::sTicketBody_v2*)body.getBytes().data();
fmt::print(" PropertyMask: 0x{:04x}\n", ((tc::bn::le16<uint16_t>*)&raw_body->property_mask)->unwrap());
for (size_t i = 0; i < body.getPropertyFlags().size(); i++)
{
std::cout << " " << getPropertyFlagStr(body.getPropertyFlags()[i]) << std::endl;
fmt::print(" {:s}\n", getPropertyFlagStr(body.getPropertyFlags()[i]));
}
}
if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mCliOutputMode.show_extended_info)
{
std::cout << " Reserved Region:" << std::endl;
fnd::SimpleTextOutput::hexDump(body.getReservedRegion(), 8, 0x10, 4);
fmt::print(" Reserved Region:\n");
fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(body.getReservedRegion(), 8, true, ""));
}
if (body.getTicketId() != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
std::cout << " TicketId: 0x" << std::hex << std::setw(16) << std::setfill('0') << body.getTicketId() << std::endl;
if (body.getTicketId() != 0 || mCliOutputMode.show_extended_info)
fmt::print(" TicketId: 0x{:016x}\n", body.getTicketId());
if (body.getDeviceId() != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
std::cout << " DeviceId: 0x" << std::hex << std::setw(16) << std::setfill('0') << body.getDeviceId() << std::endl;
if (body.getDeviceId() != 0 || mCliOutputMode.show_extended_info)
fmt::print(" DeviceId: 0x{:016x}\n", body.getDeviceId());
std::cout << " RightsId: " << std::endl << " ";
fnd::SimpleTextOutput::hexDump(body.getRightsId(), 16);
fmt::print(" RightsId: \n");
fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(body.getRightsId(), 16, true, ""));
std::cout << " SectionTotalSize: 0x" << std::hex << body.getSectionTotalSize() << std::endl;
std::cout << " SectionHeaderOffset: 0x" << std::hex << body.getSectionHeaderOffset() << std::endl;
std::cout << " SectionNum: 0x" << std::hex << body.getSectionNum() << std::endl;
std::cout << " SectionEntrySize: 0x" << std::hex << body.getSectionEntrySize() << std::endl;
#undef _SPLIT_VER
fmt::print(" SectionTotalSize: 0x{:x}\n", body.getSectionTotalSize());
fmt::print(" SectionHeaderOffset: 0x{:x}\n", body.getSectionHeaderOffset());
fmt::print(" SectionNum: 0x{:x}\n", body.getSectionNum());
fmt::print(" SectionEntrySize: 0x{:x}\n", body.getSectionEntrySize());
}
const char* EsTikProcess::getSignTypeStr(uint32_t type) const
std::string nstool::EsTikProcess::getSignTypeStr(uint32_t type) const
{
const char* str = nullptr;
std::string str;
switch(type)
{
case (nn::pki::sign::SIGN_ID_RSA4096_SHA1):
@ -197,9 +192,9 @@ const char* EsTikProcess::getSignTypeStr(uint32_t type) const
return str;
}
const char* EsTikProcess::getTitleKeyPersonalisationStr(byte_t flag) const
std::string nstool::EsTikProcess::getTitleKeyPersonalisationStr(byte_t flag) const
{
const char* str = nullptr;
std::string str;
switch(flag)
{
case (nn::es::ticket::AES128_CBC):
@ -209,15 +204,15 @@ const char* EsTikProcess::getTitleKeyPersonalisationStr(byte_t flag) const
str = "Personalised (RSA2048)";
break;
default:
str = "Unknown";
str = fmt::format("Unknown ({:d})", flag);
break;
}
return str;
}
const char* EsTikProcess::getLicenseTypeStr(byte_t flag) const
std::string nstool::EsTikProcess::getLicenseTypeStr(byte_t flag) const
{
const char* str = nullptr;
std::string str;
switch(flag)
{
case (nn::es::ticket::LICENSE_PERMANENT):
@ -239,15 +234,15 @@ const char* EsTikProcess::getLicenseTypeStr(byte_t flag) const
str = "Service";
break;
default:
str = "Unknown";
str = fmt::format("Unknown ({:d})", flag);
break;
}
return str;
}
const char* EsTikProcess::getPropertyFlagStr(byte_t flag) const
std::string nstool::EsTikProcess::getPropertyFlagStr(byte_t flag) const
{
const char* str = nullptr;
std::string str;
switch(flag)
{
case (nn::es::ticket::FLAG_PRE_INSTALL):
@ -259,9 +254,23 @@ const char* EsTikProcess::getPropertyFlagStr(byte_t flag) const
case (nn::es::ticket::FLAG_ALLOW_ALL_CONTENT):
str = "AllContent";
break;
case (nn::es::ticket::FLAG_DEVICE_LINK_INDEPENDENT):
str = "DeviceLinkIndependent";
break;
case (nn::es::ticket::FLAG_VOLATILE):
str = "Volatile";
break;
case (nn::es::ticket::FLAG_ELICENSE_REQUIRED):
str = "ELicenseRequired";
break;
default:
str = "Unknown";
str = fmt::format("Unknown ({:d})", flag);
break;
}
return str;
}
std::string nstool::EsTikProcess::getTitleVersionStr(uint16_t version) const
{
return fmt::format("{:d}.{:d}.{:d}", ((version>>10) & 0x3f), ((version>>4) & 0x3f), ((version>>0) & 0xf));
}

View file

@ -1,14 +1,12 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/IFile.h>
#include <fnd/SharedPtr.h>
#include <fnd/Vec.h>
#include "types.h"
#include "KeyBag.h"
#include <nn/pki/SignedData.h>
#include <nn/pki/CertificateBody.h>
#include <nn/es/TicketBody_V2.h>
#include "KeyConfiguration.h"
#include "common.h"
namespace nstool {
class EsTikProcess
{
@ -17,29 +15,31 @@ public:
void process();
void setInputFile(const fnd::SharedPtr<fnd::IFile>& file);
void setKeyCfg(const KeyConfiguration& keycfg);
void setCertificateChain(const fnd::List<nn::pki::SignedData<nn::pki::CertificateBody>>& certs);
void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setKeyCfg(const KeyBag& keycfg);
void setCertificateChain(const std::vector<nn::pki::SignedData<nn::pki::CertificateBody>>& certs);
void setCliOutputMode(CliOutputMode mode);
void setVerifyMode(bool verify);
private:
const std::string kModuleName = "EsTikProcess";
std::string mModuleName;
fnd::SharedPtr<fnd::IFile> mFile;
KeyConfiguration mKeyCfg;
std::shared_ptr<tc::io::IStream> mFile;
KeyBag mKeyCfg;
CliOutputMode mCliOutputMode;
bool mVerify;
fnd::List<nn::pki::SignedData<nn::pki::CertificateBody>> mCerts;
std::vector<nn::pki::SignedData<nn::pki::CertificateBody>> mCerts;
nn::pki::SignedData<nn::es::TicketBody_V2> mTik;
void importTicket();
void verifyTicket();
void displayTicket();
const char* getSignTypeStr(uint32_t type) const;
const char* getTitleKeyPersonalisationStr(byte_t flag) const;
const char* getLicenseTypeStr(byte_t flag) const;
const char* getPropertyFlagStr(byte_t flag) const;
std::string getSignTypeStr(uint32_t type) const;
std::string getTitleKeyPersonalisationStr(byte_t flag) const;
std::string getLicenseTypeStr(byte_t flag) const;
std::string getPropertyFlagStr(byte_t flag) const;
std::string getTitleVersionStr(uint16_t version) const;
};
}

275
src/FsProcess.cpp Normal file
View file

@ -0,0 +1,275 @@
#include "FsProcess.h"
#include "util.h"
#include <memory>
#include <tc/io/FileNotFoundException.h>
#include <tc/io/DirectoryNotFoundException.h>
nstool::FsProcess::FsProcess() :
mModuleLabel("nstool::FsProcess"),
mInputFs(),
mFsFormatName(),
mShowFsInfo(false),
mProperties(),
mShowFsTree(false),
mFsRootLabel(),
mExtractJobs(),
mDataCache(0x10000)
{
}
void nstool::FsProcess::process()
{
if (mInputFs == nullptr)
{
throw tc::InvalidOperationException(mModuleLabel, "No input filesystem");
}
if (mShowFsInfo)
{
fmt::print("[{:s}]\n", mFsFormatName.isSet() ? mFsFormatName.get() : "FileSystem/Info");
for (auto itr = mProperties.begin(); itr != mProperties.end(); itr++)
{
fmt::print(" {:s}\n", *itr);
}
}
if (mShowFsTree)
{
printFs();
}
if (mExtractJobs.empty() == false)
{
extractFs();
}
}
void nstool::FsProcess::setInputFileSystem(const std::shared_ptr<tc::io::IStorage>& input_fs)
{
mInputFs = input_fs;
}
void nstool::FsProcess::setFsFormatName(const std::string& fs_format_name)
{
mFsFormatName = fs_format_name;
}
void nstool::FsProcess::setShowFsInfo(bool show_fs_info)
{
mShowFsInfo = show_fs_info;
}
void nstool::FsProcess::setFsProperties(const std::vector<std::string>& properties)
{
mProperties = properties;
}
void nstool::FsProcess::setShowFsTree(bool show_fs_tree)
{
mShowFsTree = show_fs_tree;
}
void nstool::FsProcess::setFsRootLabel(const std::string& root_label)
{
mFsRootLabel = root_label;
}
void nstool::FsProcess::setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs)
{
mExtractJobs = extract_jobs;
}
void nstool::FsProcess::printFs()
{
fmt::print("[{:s}/Tree]\n", (mFsFormatName.isSet() ? mFsFormatName.get() : "FileSystem"));
visitDir(tc::io::Path("/"), tc::io::Path("/"), false, true);
}
void nstool::FsProcess::extractFs()
{
fmt::print("[{:s}/Extract]\n", (mFsFormatName.isSet() ? mFsFormatName.get() : "FileSystem"));
for (auto itr = mExtractJobs.begin(); itr != mExtractJobs.end(); itr++)
{
std::string path_str;
tc::io::PathUtil::pathToUnixUTF8(itr->virtual_path, path_str);
// check if root path (legacy case)
if (itr->virtual_path == tc::io::Path("/"))
{
visitDir(tc::io::Path("/"), itr->extract_path, true, false);
//fmt::print("Root Dir Virtual Path: \"{:s}\"\n", path_str);
// root directory extract successful, continue to next job
continue;
}
// otherwise determine if this is a file or subdirectory
try {
std::shared_ptr<tc::io::IStream> file_stream;
mInputFs->openFile(itr->virtual_path, tc::io::FileMode::Open, tc::io::FileAccess::Read, file_stream);
//fmt::print("Valid File Path: \"{:s}\"\n", path_str);
// the output path for this file will depend on the user specified extract path
std::shared_ptr<tc::io::IStorage> local_fs = std::make_shared<tc::io::LocalStorage>(tc::io::LocalStorage());
// case: the extract_path is a valid path to an existing directory
// behaviour: extract the file, preserving the original filename, to the specified directory
// method: try getDirectoryListing(itr->extract_path), if this is does not throw, then we can be sure this is a valid path to a directory, file_extract_path = itr->extract_path + itr->virtual_path.back()
try {
tc::io::sDirectoryListing dir_listing;
local_fs->getDirectoryListing(itr->extract_path, dir_listing);
tc::io::Path file_extract_path = itr->extract_path + itr->virtual_path.back();
std::string file_extract_path_str;
tc::io::PathUtil::pathToUnixUTF8(file_extract_path, file_extract_path_str);
fmt::print("Saving {:s}...\n", file_extract_path_str);
writeStreamToFile(file_stream, itr->extract_path + itr->virtual_path.back(), mDataCache);
continue;
} catch (tc::io::DirectoryNotFoundException&) {
// acceptable exception, just means directory didn't exist
}
// case: the extract_path up until the last element is a valid path to an existing directory, but the full path specifies neither a directory or a file
// behaviour: treat extract_path as the intended location to write the extracted file (the original filename is not preserved, instead specified by the user in the final element of the extract path)
// method: since this checks n-1 elements, it implies a path with more than one element, so that must be accounted for, as relative paths are valid and single element paths aren't always root
try {
std::string test_path_str;
// get path to parent directory
tc::io::Path parent_dir_path = itr->extract_path;
// replace final path element with the current directory alias
parent_dir_path.pop_back(); // remove filename
parent_dir_path.push_back("."); // replace with the current dir name alias
tc::io::PathUtil::pathToUnixUTF8(parent_dir_path, test_path_str);
// test parent directory exists
tc::io::sDirectoryListing dir_listing;
local_fs->getDirectoryListing(parent_dir_path, dir_listing);
std::string file_extract_path_str;
tc::io::PathUtil::pathToUnixUTF8(itr->extract_path, file_extract_path_str);
fmt::print("Saving {:s} as {:s}...\n", path_str, file_extract_path_str);
writeStreamToFile(file_stream, itr->extract_path, mDataCache);
continue;
} catch (tc::io::DirectoryNotFoundException&) {
// acceptable exception, just means the parent directory didn't exist
}
// extract path could not be determined, inform the user and skip this job
std::string literal_extract_path_str;
tc::io::PathUtil::pathToUnixUTF8(itr->extract_path, literal_extract_path_str);
fmt::print("[WARNING] Extract path was invalid, and was skipped: {:s}\n", literal_extract_path_str);
continue;
} catch (tc::io::FileNotFoundException&) {
// acceptable exception, just means file didn't exist
}
// not a file, attempt to process this as a directory
try {
tc::io::sDirectoryListing dir_listing;
mInputFs->getDirectoryListing(itr->virtual_path, dir_listing);
visitDir(itr->virtual_path, itr->extract_path, true, false);
//fmt::print("Valid Directory Path: \"{:s}\"\n", path_str);
// directory extract successful, continue to next job
continue;
} catch (tc::io::DirectoryNotFoundException&) {
// acceptable exception, just means directory didn't exist
}
fmt::print("[WARNING] Failed to extract virtual path: \"{:s}\"\n", path_str);
}
}
void nstool::FsProcess::visitDir(const tc::io::Path& v_path, const tc::io::Path& l_path, bool extract_fs, bool print_fs)
{
tc::io::LocalStorage local_fs;
// get listing for directory
tc::io::sDirectoryListing info;
mInputFs->getDirectoryListing(v_path, info);
if (print_fs)
{
for (size_t i = 0; i < v_path.size(); i++)
fmt::print(" ");
fmt::print("{:s}/\n", ((v_path.size() == 1) ? (mFsRootLabel.isSet() ? (mFsRootLabel.get() + ":") : "Root:") : v_path.back()));
}
if (extract_fs)
{
// create local dir
local_fs.createDirectory(l_path);
}
// iterate thru child files
size_t cache_read_len;
tc::io::Path out_path;
std::string out_path_str;
std::shared_ptr<tc::io::IStream> in_stream;
std::shared_ptr<tc::io::IStream> out_stream;
for (auto itr = info.file_list.begin(); itr != info.file_list.end(); itr++)
{
if (print_fs)
{
for (size_t i = 0; i < v_path.size(); i++)
fmt::print(" ");
fmt::print(" {:s}\n", *itr);
}
if (extract_fs)
{
// build out path
out_path = l_path + *itr;
tc::io::PathUtil::pathToUnixUTF8(out_path, out_path_str);
fmt::print("Saving {:s}...\n", out_path_str);
// begin export
mInputFs->openFile(v_path + *itr, tc::io::FileMode::Open, tc::io::FileAccess::Read, in_stream);
local_fs.openFile(out_path, tc::io::FileMode::OpenOrCreate, tc::io::FileAccess::Write, out_stream);
in_stream->seek(0, tc::io::SeekOrigin::Begin);
out_stream->seek(0, tc::io::SeekOrigin::Begin);
for (int64_t remaining_data = in_stream->length(); remaining_data > 0;)
{
cache_read_len = in_stream->read(mDataCache.data(), mDataCache.size());
if (cache_read_len == 0)
{
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);
remaining_data -= int64_t(cache_read_len);
}
}
}
// iterate thru child dirs
for (auto itr = info.dir_list.begin(); itr != info.dir_list.end(); itr++)
{
visitDir(v_path + *itr, l_path + *itr, extract_fs, print_fs);
}
}

50
src/FsProcess.h Normal file
View file

@ -0,0 +1,50 @@
#pragma once
#include <tc/Optional.h>
#include <tc/io.h>
#include "types.h"
namespace nstool
{
class FsProcess
{
public:
FsProcess();
void process();
void setInputFileSystem(const std::shared_ptr<tc::io::IStorage>& input_fs);
void setFsFormatName(const std::string& fs_format_name);
void setFsProperties(const std::vector<std::string>& properties);
void setShowFsInfo(bool show_fs_info);
void setShowFsTree(bool show_fs_tree);
void setFsRootLabel(const std::string& root_label);
void setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs);
private:
std::string mModuleLabel;
std::shared_ptr<tc::io::IStorage> mInputFs;
// fs info
tc::Optional<std::string> mFsFormatName;
bool mShowFsInfo;
std::vector<std::string> mProperties;
// fs tree
bool mShowFsTree;
tc::Optional<std::string> mFsRootLabel;
// extract jobs
std::vector<nstool::ExtractJob> mExtractJobs;
// cache for file extract
tc::ByteData mDataCache;
void printFs();
void extractFs();
void visitDir(const tc::io::Path& v_path, const tc::io::Path& l_path, bool extract_fs, bool print_fs);
};
}

View file

@ -1,24 +1,29 @@
#include <iostream>
#include <iomanip>
#include <fnd/SimpleTextOutput.h>
#include <fnd/OffsetAdjustedIFile.h>
#include "GameCardProcess.h"
#include <tc/crypto.h>
#include <tc/io/IOUtil.h>
#include <nn/hac/GameCardUtil.h>
#include <nn/hac/ContentMetaUtil.h>
#include <nn/hac/ContentArchiveUtil.h>
#include "GameCardProcess.h"
GameCardProcess::GameCardProcess() :
#include <nn/hac/GameCardFsMetaGenerator.h>
#include "FsProcess.h"
nstool::GameCardProcess::GameCardProcess() :
mModuleName("nstool::GameCardProcess"),
mFile(),
mCliOutputMode(_BIT(OUTPUT_BASIC)),
mCliOutputMode(true, false, false, false),
mVerify(false),
mListFs(false),
mProccessExtendedHeader(false),
mRootPfs(),
mExtractInfo()
mExtractJobs()
{
}
void GameCardProcess::process()
void nstool::GameCardProcess::process()
{
importHeader();
@ -27,90 +32,96 @@ void GameCardProcess::process()
validateXciSignature();
// display header
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
if (mCliOutputMode.show_basic_info)
displayHeader();
// process root partition
// process nested HFS0
processRootPfs();
// process partitions
processPartitionPfs();
}
void GameCardProcess::setInputFile(const fnd::SharedPtr<fnd::IFile>& file)
void nstool::GameCardProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
{
mFile = file;
}
void GameCardProcess::setKeyCfg(const KeyConfiguration& keycfg)
void nstool::GameCardProcess::setKeyCfg(const KeyBag& keycfg)
{
mKeyCfg = keycfg;
}
void GameCardProcess::setCliOutputMode(CliOutputMode type)
void nstool::GameCardProcess::setCliOutputMode(CliOutputMode type)
{
mCliOutputMode = type;
}
void GameCardProcess::setVerifyMode(bool verify)
void nstool::GameCardProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void GameCardProcess::setPartitionForExtract(const std::string& partition_name, const std::string& extract_path)
void nstool::GameCardProcess::setExtractJobs(const std::vector<nstool::ExtractJob> extract_jobs)
{
mExtractInfo.addElement({partition_name, extract_path});
mExtractJobs = extract_jobs;
}
void GameCardProcess::setListFs(bool list_fs)
void nstool::GameCardProcess::setShowFsTree(bool show_fs_tree)
{
mListFs = list_fs;
mListFs = show_fs_tree;
}
void GameCardProcess::importHeader()
void nstool::GameCardProcess::importHeader()
{
fnd::Vec<byte_t> scratch;
if (*mFile == nullptr)
if (mFile == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
throw tc::Exception(mModuleName, "No file reader set.");
}
if (mFile->canRead() == false || mFile->canSeek() == false)
{
throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions.");
}
// check stream is large enough for header
if (mFile->length() < tc::io::IOUtil::castSizeToInt64(sizeof(nn::hac::sSdkGcHeader)))
{
throw tc::Exception(mModuleName, "Corrupt GameCard Image: File too small.");
}
// allocate memory for header
scratch.alloc(sizeof(nn::hac::sSdkGcHeader));
tc::ByteData scratch = tc::ByteData(sizeof(nn::hac::sSdkGcHeader));
// read header region
(*mFile)->read((byte_t*)scratch.data(), 0, sizeof(nn::hac::sSdkGcHeader));
mFile->seek(0, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
// determine if this is a SDK XCI or a "Community" XCI
if (((nn::hac::sSdkGcHeader*)scratch.data())->signed_header.header.st_magic.get() == nn::hac::gc::kGcHeaderStructMagic)
if (((nn::hac::sSdkGcHeader*)scratch.data())->signed_header.header.st_magic.unwrap() == nn::hac::gc::kGcHeaderStructMagic)
{
mIsTrueSdkXci = true;
mGcHeaderOffset = sizeof(nn::hac::sGcKeyDataRegion);
}
else if (((nn::hac::sGcHeader_Rsa2048Signed*)scratch.data())->header.st_magic.get() == nn::hac::gc::kGcHeaderStructMagic)
else if (((nn::hac::sGcHeader_Rsa2048Signed*)scratch.data())->header.st_magic.unwrap() == nn::hac::gc::kGcHeaderStructMagic)
{
mIsTrueSdkXci = false;
mGcHeaderOffset = 0;
}
else
{
throw fnd::Exception(kModuleName, "GameCard image did not have expected magic bytes");
throw tc::Exception(mModuleName, "Corrupt GameCard Image: Unexpected magic bytes.");
}
nn::hac::sGcHeader_Rsa2048Signed* hdr_ptr = (nn::hac::sGcHeader_Rsa2048Signed*)(scratch.data() + mGcHeaderOffset);
// generate hash of raw header
fnd::sha::Sha256((byte_t*)&hdr_ptr->header, sizeof(nn::hac::sGcHeader), mHdrHash.bytes);
tc::crypto::GenerateSha256Hash(mHdrHash.data(), (byte_t*)&hdr_ptr->header, sizeof(nn::hac::sGcHeader));
// save the signature
memcpy(mHdrSignature, hdr_ptr->signature, fnd::rsa::kRsa2048Size);
memcpy(mHdrSignature.data(), hdr_ptr->signature.data(), mHdrSignature.size());
// decrypt extended header
fnd::aes::sAes128Key header_key;
if (mKeyCfg.getXciHeaderKey(header_key))
byte_t xci_header_key_index = hdr_ptr->header.key_flag & 7;
if (mKeyCfg.xci_header_key.find(xci_header_key_index) != mKeyCfg.xci_header_key.end())
{
nn::hac::GameCardUtil::decryptXciHeader(&hdr_ptr->header, header_key.key);
nn::hac::GameCardUtil::decryptXciHeader(&hdr_ptr->header, mKeyCfg.xci_header_key[xci_header_key_index].data());
mProccessExtendedHeader = true;
}
@ -118,166 +129,158 @@ void GameCardProcess::importHeader()
mHdr.fromBytes((byte_t*)&hdr_ptr->header, sizeof(nn::hac::sGcHeader));
}
void GameCardProcess::displayHeader()
void nstool::GameCardProcess::displayHeader()
{
std::cout << "[GameCard Header]" << std::endl;
std::cout << " CardHeaderVersion: " << std::dec << (uint32_t)mHdr.getCardHeaderVersion() << std::endl;
std::cout << " RomSize: " << nn::hac::GameCardUtil::getRomSizeAsString((nn::hac::gc::RomSize)mHdr.getRomSizeType());
if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
std::cout << " (0x" << std::hex << (uint32_t)mHdr.getRomSizeType() << ")";
std::cout << std::endl;
std::cout << " PackageId: 0x" << std::hex << std::setw(16) << std::setfill('0') << mHdr.getPackageId() << std::endl;
std::cout << " Flags: 0x" << std::dec << (uint32_t)mHdr.getFlags() << std::endl;
if (mHdr.getFlags() != 0)
const nn::hac::sGcHeader* raw_hdr = (const nn::hac::sGcHeader*)mHdr.getBytes().data();
fmt::print("[GameCard/Header]\n");
fmt::print(" CardHeaderVersion: {:d}\n", mHdr.getCardHeaderVersion());
fmt::print(" RomSize: {:s}", nn::hac::GameCardUtil::getRomSizeAsString((nn::hac::gc::RomSize)mHdr.getRomSizeType()));
if (mCliOutputMode.show_extended_info)
fmt::print(" (0x{:x})", mHdr.getRomSizeType());
fmt::print("\n");
fmt::print(" PackageId: 0x{:016x}\n", mHdr.getPackageId());
fmt::print(" Flags: 0x{:02x}\n", *((byte_t*)&raw_hdr->flags));
for (auto itr = mHdr.getFlags().begin(); itr != mHdr.getFlags().end(); itr++)
{
for (uint32_t i = 0; i < 8; i++)
{
if (_HAS_BIT(mHdr.getFlags(), i))
{
std::cout << " " << nn::hac::GameCardUtil::getHeaderFlagsAsString((nn::hac::gc::HeaderFlags)i) << std::endl;
}
}
fmt::print(" {:s}\n", nn::hac::GameCardUtil::getHeaderFlagsAsString((nn::hac::gc::HeaderFlags)*itr));
}
if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mCliOutputMode.show_extended_info)
{
std::cout << " KekIndex: " << nn::hac::GameCardUtil::getKekIndexAsString((nn::hac::gc::KekIndex)mHdr.getKekIndex()) << " (" << std::dec << (uint32_t)mHdr.getKekIndex() << ")" << std::endl;
std::cout << " TitleKeyDecIndex: " << std::dec << (uint32_t)mHdr.getTitleKeyDecIndex() << std::endl;
std::cout << " InitialData:" << std::endl;
std::cout << " Hash:" << std::endl;
std::cout << " " << fnd::SimpleTextOutput::arrayToString(mHdr.getInitialDataHash().bytes, 0x10, true, ":") << std::endl;
std::cout << " " << fnd::SimpleTextOutput::arrayToString(mHdr.getInitialDataHash().bytes+0x10, 0x10, true, ":") << std::endl;
fmt::print(" KekIndex: {:s} ({:d})\n", nn::hac::GameCardUtil::getKekIndexAsString((nn::hac::gc::KekIndex)mHdr.getKekIndex()), mHdr.getKekIndex());
fmt::print(" TitleKeyDecIndex: {:d}\n", mHdr.getTitleKeyDecIndex());
fmt::print(" InitialData:\n");
fmt::print(" Hash:\n");
fmt::print(" {:s}", tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(mHdr.getInitialDataHash().data(), mHdr.getInitialDataHash().size(), true, "", 0x10, 6, false));
}
if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mCliOutputMode.show_extended_info)
{
std::cout << " Extended Header AesCbc IV:" << std::endl;
std::cout << " " << fnd::SimpleTextOutput::arrayToString(mHdr.getAesCbcIv().iv, sizeof(mHdr.getAesCbcIv().iv), true, ":") << std::endl;
fmt::print(" Extended Header AesCbc IV:\n");
fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mHdr.getAesCbcIv().data(), mHdr.getAesCbcIv().size(), true, ""));
}
std::cout << " SelSec: 0x" << std::hex << mHdr.getSelSec() << std::endl;
std::cout << " SelT1Key: 0x" << std::hex << mHdr.getSelT1Key() << std::endl;
std::cout << " SelKey: 0x" << std::hex << mHdr.getSelKey() << std::endl;
if (_HAS_BIT(mCliOutputMode, OUTPUT_LAYOUT))
fmt::print(" SelSec: 0x{:x}\n", mHdr.getSelSec());
fmt::print(" SelT1Key: 0x{:x}\n", mHdr.getSelT1Key());
fmt::print(" SelKey: 0x{:x}\n", mHdr.getSelKey());
if (mCliOutputMode.show_layout)
{
std::cout << " RomAreaStartPage: 0x" << std::hex << mHdr.getRomAreaStartPage();
fmt::print(" RomAreaStartPage: 0x{:x}", mHdr.getRomAreaStartPage());
if (mHdr.getRomAreaStartPage() != (uint32_t)(-1))
std::cout << " (0x" << std::hex << nn::hac::GameCardUtil::blockToAddr(mHdr.getRomAreaStartPage()) << ")";
std::cout << std::endl;
fmt::print(" (0x{:x})", nn::hac::GameCardUtil::blockToAddr(mHdr.getRomAreaStartPage()));
fmt::print("\n");
std::cout << " BackupAreaStartPage: 0x" << std::hex << mHdr.getBackupAreaStartPage();
fmt::print(" BackupAreaStartPage: 0x{:x}", mHdr.getBackupAreaStartPage());
if (mHdr.getBackupAreaStartPage() != (uint32_t)(-1))
std::cout << " (0x" << std::hex << nn::hac::GameCardUtil::blockToAddr(mHdr.getBackupAreaStartPage()) << ")";
std::cout << std::endl;
fmt::print(" (0x{:x})", nn::hac::GameCardUtil::blockToAddr(mHdr.getBackupAreaStartPage()));
fmt::print("\n");
std::cout << " ValidDataEndPage: 0x" << std::hex << mHdr.getValidDataEndPage();
fmt::print(" ValidDataEndPage: 0x{:x}", mHdr.getValidDataEndPage());
if (mHdr.getValidDataEndPage() != (uint32_t)(-1))
std::cout << " (0x" << std::hex << nn::hac::GameCardUtil::blockToAddr(mHdr.getValidDataEndPage()) << ")";
std::cout << std::endl;
fmt::print(" (0x{:x})", nn::hac::GameCardUtil::blockToAddr(mHdr.getValidDataEndPage()));
fmt::print("\n");
std::cout << " LimArea: 0x" << std::hex << mHdr.getLimAreaPage();
fmt::print(" LimArea: 0x{:x}", mHdr.getLimAreaPage());
if (mHdr.getLimAreaPage() != (uint32_t)(-1))
std::cout << " (0x" << std::hex << nn::hac::GameCardUtil::blockToAddr(mHdr.getLimAreaPage()) << ")";
std::cout << std::endl;
fmt::print(" (0x{:x})", nn::hac::GameCardUtil::blockToAddr(mHdr.getLimAreaPage()));
fmt::print("\n");
std::cout << " PartitionFs Header:" << std::endl;
std::cout << " Offset: 0x" << std::hex << mHdr.getPartitionFsAddress() << std::endl;
std::cout << " Size: 0x" << std::hex << mHdr.getPartitionFsSize() << std::endl;
if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
fmt::print(" PartitionFs Header:\n");
fmt::print(" Offset: 0x{:x}\n", mHdr.getPartitionFsAddress());
fmt::print(" Size: 0x{:x}\n", mHdr.getPartitionFsSize());
if (mCliOutputMode.show_extended_info)
{
std::cout << " Hash:" << std::endl;
std::cout << " " << fnd::SimpleTextOutput::arrayToString(mHdr.getPartitionFsHash().bytes, 0x10, true, ":") << std::endl;
std::cout << " " << fnd::SimpleTextOutput::arrayToString(mHdr.getPartitionFsHash().bytes+0x10, 0x10, true, ":") << std::endl;
fmt::print(" Hash:\n");
fmt::print(" {:s}", tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(mHdr.getPartitionFsHash().data(), mHdr.getPartitionFsHash().size(), true, "", 0x10, 6, false));
}
}
if (mProccessExtendedHeader)
{
std::cout << "[GameCard Extended Header]" << std::endl;
std::cout << " FwVersion: v" << std::dec << mHdr.getFwVersion() << " (" << nn::hac::GameCardUtil::getCardFwVersionDescriptionAsString((nn::hac::gc::FwVersion)mHdr.getFwVersion()) << ")" << std::endl;
std::cout << " AccCtrl1: 0x" << std::hex << mHdr.getAccCtrl1() << std::endl;
std::cout << " CardClockRate: " << nn::hac::GameCardUtil::getCardClockRateAsString((nn::hac::gc::CardClockRate)mHdr.getAccCtrl1()) << std::endl;
std::cout << " Wait1TimeRead: 0x" << std::hex << mHdr.getWait1TimeRead() << std::endl;
std::cout << " Wait2TimeRead: 0x" << std::hex << mHdr.getWait2TimeRead() << std::endl;
std::cout << " Wait1TimeWrite: 0x" << std::hex << mHdr.getWait1TimeWrite() << std::endl;
std::cout << " Wait2TimeWrite: 0x" << std::hex << mHdr.getWait2TimeWrite() << std::endl;
std::cout << " SdkAddon Version: " << nn::hac::ContentArchiveUtil::getSdkAddonVersionAsString(mHdr.getFwMode()) << " (v" << std::dec << mHdr.getFwMode() << ")" << std::endl;
std::cout << " CompatibilityType: " << nn::hac::GameCardUtil::getCompatibilityTypeAsString((nn::hac::gc::CompatibilityType)mHdr.getCompatibilityType()) << " (" << std::dec << (uint32_t) mHdr.getCompatibilityType() << ")" << std::endl;
std::cout << " Update Partition Info:" << std::endl;
std::cout << " CUP Version: " << nn::hac::ContentMetaUtil::getVersionAsString(mHdr.getUppVersion()) << " (v" << std::dec << mHdr.getUppVersion() << ")" << std::endl;
std::cout << " CUP TitleId: 0x" << std::hex << std::setw(16) << std::setfill('0') << mHdr.getUppId() << std::endl;
std::cout << " CUP Digest: " << fnd::SimpleTextOutput::arrayToString(mHdr.getUppHash(), 8, true, ":") << std::endl;
fmt::print("[GameCard/ExtendedHeader]\n");
fmt::print(" FwVersion: v{:d} ({:s})\n", mHdr.getFwVersion(), nn::hac::GameCardUtil::getCardFwVersionDescriptionAsString((nn::hac::gc::FwVersion)mHdr.getFwVersion()));
fmt::print(" AccCtrl1: 0x{:x}\n", mHdr.getAccCtrl1());
fmt::print(" CardClockRate: {:s}\n", nn::hac::GameCardUtil::getCardClockRateAsString((nn::hac::gc::CardClockRate)mHdr.getAccCtrl1()));
fmt::print(" Wait1TimeRead: 0x{:x}\n", mHdr.getWait1TimeRead());
fmt::print(" Wait2TimeRead: 0x{:x}\n", mHdr.getWait2TimeRead());
fmt::print(" Wait1TimeWrite: 0x{:x}\n", mHdr.getWait1TimeWrite());
fmt::print(" Wait2TimeWrite: 0x{:x}\n", mHdr.getWait2TimeWrite());
fmt::print(" SdkAddon Version: {:s} (v{:d})\n", nn::hac::ContentArchiveUtil::getSdkAddonVersionAsString(mHdr.getFwMode()), mHdr.getFwMode());
fmt::print(" CompatibilityType: {:s} ({:d})\n", nn::hac::GameCardUtil::getCompatibilityTypeAsString((nn::hac::gc::CompatibilityType)mHdr.getCompatibilityType()), mHdr.getCompatibilityType());
fmt::print(" Update Partition Info:\n");
fmt::print(" CUP Version: {:s} (v{:d})\n", nn::hac::ContentMetaUtil::getVersionAsString(mHdr.getUppVersion()), mHdr.getUppVersion());
fmt::print(" CUP TitleId: 0x{:016x}\n", mHdr.getUppId());
fmt::print(" CUP Digest: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mHdr.getUppHash().data(), mHdr.getUppHash().size(), true, ""));
}
}
bool GameCardProcess::validateRegionOfFile(size_t offset, size_t len, const byte_t* test_hash, bool use_salt, byte_t salt)
bool nstool::GameCardProcess::validateRegionOfFile(int64_t offset, int64_t len, const byte_t* test_hash, bool use_salt, byte_t salt)
{
fnd::Vec<byte_t> scratch;
fnd::sha::sSha256Hash calc_hash;
// read region into memory
tc::ByteData scratch = tc::ByteData(tc::io::IOUtil::castInt64ToSize(len));
mFile->seek(offset, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
// update hash
tc::crypto::Sha256Generator sha256_gen;
sha256_gen.initialize();
sha256_gen.update(scratch.data(), scratch.size());
if (use_salt)
{
scratch.alloc(len + 1);
scratch.data()[len] = salt;
}
else
{
scratch.alloc(len);
}
sha256_gen.update(&salt, sizeof(salt));
(*mFile)->read(scratch.data(), offset, len);
fnd::sha::Sha256(scratch.data(), scratch.size(), calc_hash.bytes);
// calculate hash
nn::hac::detail::sha256_hash_t calc_hash;
sha256_gen.getHash(calc_hash.data());
return calc_hash.compare(test_hash);
return memcmp(calc_hash.data(), test_hash, calc_hash.size()) == 0;
}
bool GameCardProcess::validateRegionOfFile(size_t offset, size_t len, const byte_t* test_hash)
bool nstool::GameCardProcess::validateRegionOfFile(int64_t offset, int64_t len, const byte_t* test_hash)
{
return validateRegionOfFile(offset, len, test_hash, false, 0);
}
void GameCardProcess::validateXciSignature()
void nstool::GameCardProcess::validateXciSignature()
{
fnd::rsa::sRsa2048Key header_sign_key;
mKeyCfg.getXciHeaderSignKey(header_sign_key);
if (fnd::rsa::pkcs::rsaVerify(header_sign_key, fnd::sha::HASH_SHA256, mHdrHash.bytes, mHdrSignature) != 0)
if (mKeyCfg.xci_header_sign_key.isSet())
{
std::cout << "[WARNING] GameCard Header Signature: FAIL" << std::endl;
}
}
void GameCardProcess::processRootPfs()
{
if (mVerify && validateRegionOfFile(mHdr.getPartitionFsAddress(), mHdr.getPartitionFsSize(), mHdr.getPartitionFsHash().bytes, mHdr.getCompatibilityType() != nn::hac::gc::COMPAT_GLOBAL, mHdr.getCompatibilityType()) == false)
{
std::cout << "[WARNING] GameCard Root HFS0: FAIL (bad hash)" << std::endl;
}
mRootPfs.setInputFile(new fnd::OffsetAdjustedIFile(mFile, mHdr.getPartitionFsAddress(), mHdr.getPartitionFsSize()));
mRootPfs.setListFs(mListFs);
mRootPfs.setVerifyMode(false);
mRootPfs.setCliOutputMode(mCliOutputMode);
mRootPfs.setMountPointName(kXciMountPointName);
mRootPfs.process();
}
void GameCardProcess::processPartitionPfs()
{
const fnd::List<nn::hac::PartitionFsHeader::sFile>& rootPartitions = mRootPfs.getPfsHeader().getFileList();
for (size_t i = 0; i < rootPartitions.size(); i++)
{
// this must be validated here because only the size of the root partiton header is known at verification time
if (mVerify && validateRegionOfFile(mHdr.getPartitionFsAddress() + rootPartitions[i].offset, rootPartitions[i].hash_protected_size, rootPartitions[i].hash.bytes) == false)
if (tc::crypto::VerifyRsa2048Pkcs1Sha256(mHdrSignature.data(), mHdrHash.data(), mKeyCfg.xci_header_sign_key.get()) == false)
{
std::cout << "[WARNING] GameCard " << rootPartitions[i].name << " Partition HFS0: FAIL (bad hash)" << std::endl;
fmt::print("[WARNING] GameCard Header Signature: FAIL\n");
}
PfsProcess tmp;
tmp.setInputFile(new fnd::OffsetAdjustedIFile(mFile, mHdr.getPartitionFsAddress() + rootPartitions[i].offset, rootPartitions[i].size));
tmp.setListFs(mListFs);
tmp.setVerifyMode(mVerify);
tmp.setCliOutputMode(mCliOutputMode);
tmp.setMountPointName(kXciMountPointName + rootPartitions[i].name);
if (mExtractInfo.hasElement<std::string>(rootPartitions[i].name))
tmp.setExtractPath(mExtractInfo.getElement<std::string>(rootPartitions[i].name).extract_path);
tmp.process();
}
else
{
fmt::print("[WARNING] GameCard Header Signature: FAIL (Failed to load rsa public key.)\n");
}
}
void nstool::GameCardProcess::processRootPfs()
{
if (mVerify && validateRegionOfFile(mHdr.getPartitionFsAddress(), mHdr.getPartitionFsSize(), mHdr.getPartitionFsHash().data(), mHdr.getCompatibilityType() != nn::hac::gc::COMPAT_GLOBAL, mHdr.getCompatibilityType()) == false)
{
fmt::print("[WARNING] GameCard Root HFS0: FAIL (bad hash)\n");
}
std::shared_ptr<tc::io::IStream> gc_fs_raw = std::make_shared<tc::io::SubStream>(tc::io::SubStream(mFile, mHdr.getPartitionFsAddress(), nn::hac::GameCardUtil::blockToAddr(mHdr.getValidDataEndPage()+1) - mHdr.getPartitionFsAddress()));
auto gc_vfs_meta = nn::hac::GameCardFsMetaGenerator(gc_fs_raw, mHdr.getPartitionFsSize(), mVerify ? nn::hac::GameCardFsMetaGenerator::ValidationMode_Warn : nn::hac::GameCardFsMetaGenerator::ValidationMode_None);
std::shared_ptr<tc::io::IStorage> gc_vfs = std::make_shared<tc::io::VirtualFileSystem>(tc::io::VirtualFileSystem(gc_vfs_meta) );
FsProcess fs_proc;
fs_proc.setInputFileSystem(gc_vfs);
fs_proc.setFsFormatName("PartitionFs");
fs_proc.setFsProperties({
fmt::format("Type: Nested HFS0"),
fmt::format("DirNum: {:d}", gc_vfs_meta.dir_entries.empty() ? 0 : gc_vfs_meta.dir_entries.size() - 1), // -1 to not include root directory
fmt::format("FileNum: {:d}", gc_vfs_meta.file_entries.size())
});
fs_proc.setShowFsInfo(mCliOutputMode.show_basic_info);
fs_proc.setShowFsTree(mListFs);
fs_proc.setFsRootLabel(kXciMountPointName);
fs_proc.setExtractJobs(mExtractJobs);
fs_proc.process();
}

View file

@ -1,14 +1,11 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/IFile.h>
#include <fnd/SharedPtr.h>
#include <fnd/List.h>
#include <nn/hac/GameCardHeader.h>
#include "KeyConfiguration.h"
#include "types.h"
#include "KeyBag.h"
#include "PfsProcess.h"
#include "common.h"
#include <nn/hac/GameCardHeader.h>
namespace nstool {
class GameCardProcess
{
@ -18,60 +15,42 @@ public:
void process();
// generic
void setInputFile(const fnd::SharedPtr<fnd::IFile>& file);
void setKeyCfg(const KeyConfiguration& keycfg);
void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setKeyCfg(const KeyBag& keycfg);
void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify);
// xci specific
void setPartitionForExtract(const std::string& partition_name, const std::string& extract_path);
void setListFs(bool list_fs);
// fs specific
void setShowFsTree(bool show_fs_tree);
void setExtractJobs(const std::vector<nstool::ExtractJob> extract_jobs);
private:
const std::string kModuleName = "GameCardProcess";
const std::string kXciMountPointName = "gamecard:/";
const std::string kXciMountPointName = "gamecard";
fnd::SharedPtr<fnd::IFile> mFile;
KeyConfiguration mKeyCfg;
std::string mModuleName;
std::shared_ptr<tc::io::IStream> mFile;
KeyBag mKeyCfg;
CliOutputMode mCliOutputMode;
bool mVerify;
bool mListFs;
struct sExtractInfo
{
std::string partition_name;
std::string extract_path;
void operator=(const sExtractInfo& other)
{
partition_name = other.partition_name;
extract_path = other.extract_path;
}
bool operator==(const std::string& name) const
{
return name == partition_name;
}
};
bool mIsTrueSdkXci;
bool mIsSdkXciEncrypted;
size_t mGcHeaderOffset;
bool mProccessExtendedHeader;
byte_t mHdrSignature[fnd::rsa::kRsa2048Size];
fnd::sha::sSha256Hash mHdrHash;
nn::hac::detail::rsa2048_signature_t mHdrSignature;
nn::hac::detail::sha256_hash_t mHdrHash;
nn::hac::GameCardHeader mHdr;
PfsProcess mRootPfs;
fnd::List<sExtractInfo> mExtractInfo;
std::vector<nstool::ExtractJob> mExtractJobs;
void importHeader();
void displayHeader();
bool validateRegionOfFile(size_t offset, size_t len, const byte_t* test_hash, bool use_salt, byte_t salt);
bool validateRegionOfFile(size_t offset, size_t len, const byte_t* test_hash);
bool validateRegionOfFile(int64_t offset, int64_t len, const byte_t* test_hash, bool use_salt, byte_t salt);
bool validateRegionOfFile(int64_t offset, int64_t len, const byte_t* test_hash);
void validateXciSignature();
void processRootPfs();
void processPartitionPfs();
};
}

View file

@ -1,114 +1,113 @@
#include <iostream>
#include <iomanip>
#include <fnd/io.h>
#include <fnd/SimpleFile.h>
#include <fnd/SimpleTextOutput.h>
#include <fnd/OffsetAdjustedIFile.h>
#include <fnd/Vec.h>
#include "IniProcess.h"
#include "util.h"
#include "KipProcess.h"
IniProcess::IniProcess() :
nstool::IniProcess::IniProcess() :
mModuleName("nstool::IniProcess"),
mFile(),
mCliOutputMode(_BIT(OUTPUT_BASIC)),
mCliOutputMode(true, false, false, false),
mVerify(false),
mDoExtractKip(false),
mKipExtractPath()
{
}
void IniProcess::process()
void nstool::IniProcess::process()
{
importHeader();
importKipList();
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
if (mCliOutputMode.show_basic_info)
{
displayHeader();
displayKipList();
}
if (mDoExtractKip)
if (mKipExtractPath.isSet())
{
extractKipList();
}
}
void IniProcess::setInputFile(const fnd::SharedPtr<fnd::IFile>& file)
void nstool::IniProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
{
mFile = file;
}
void IniProcess::setCliOutputMode(CliOutputMode type)
void nstool::IniProcess::setCliOutputMode(CliOutputMode type)
{
mCliOutputMode = type;
}
void IniProcess::setVerifyMode(bool verify)
void nstool::IniProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void IniProcess::setKipExtractPath(const std::string& path)
void nstool::IniProcess::setKipExtractPath(const tc::io::Path& path)
{
mDoExtractKip = true;
mKipExtractPath = path;
}
void IniProcess::importHeader()
void nstool::IniProcess::importHeader()
{
fnd::Vec<byte_t> scratch;
if (*mFile == nullptr)
if (mFile == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
throw tc::Exception(mModuleName, "No file reader set.");
}
if (mFile->canRead() == false || mFile->canSeek() == false)
{
throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions.");
}
if ((*mFile)->size() < sizeof(nn::hac::sIniHeader))
// check if file_size is smaller than INI header size
if (tc::io::IOUtil::castInt64ToSize(mFile->length()) < sizeof(nn::hac::sIniHeader))
{
throw fnd::Exception(kModuleName, "Corrupt INI: file too small");
throw tc::Exception(mModuleName, "Corrupt INI: file too small.");
}
scratch.alloc(sizeof(nn::hac::sIniHeader));
(*mFile)->read(scratch.data(), 0, scratch.size());
// read ini
tc::ByteData scratch = tc::ByteData(sizeof(nn::hac::sIniHeader));
mFile->seek(0, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
// parse ini header
mHdr.fromBytes(scratch.data(), scratch.size());
}
void IniProcess::importKipList()
void nstool::IniProcess::importKipList()
{
// kip pos info
size_t kip_pos = sizeof(nn::hac::sIniHeader);
size_t kip_size = 0;
int64_t kip_pos = tc::io::IOUtil::castSizeToInt64(sizeof(nn::hac::sIniHeader));
int64_t kip_size = 0;
// tmp data to determine size
fnd::Vec<byte_t> hdr_raw;
nn::hac::sKipHeader hdr_raw;
nn::hac::KernelInitialProcessHeader hdr;
hdr_raw.alloc(sizeof(nn::hac::sKipHeader));
for (size_t i = 0; i < mHdr.getKipNum(); i++)
{
(*mFile)->read(hdr_raw.data(), kip_pos, hdr_raw.size());
hdr.fromBytes(hdr_raw.data(), hdr_raw.size());
mFile->seek(kip_pos, tc::io::SeekOrigin::Begin);
mFile->read((byte_t*)&hdr_raw, sizeof(hdr_raw));
hdr.fromBytes((byte_t*)&hdr_raw, sizeof(hdr_raw));
kip_size = getKipSizeFromHeader(hdr);
mKipList.addElement(new fnd::OffsetAdjustedIFile(mFile, kip_pos, kip_size));
mKipList.push_back({hdr, std::make_shared<tc::io::SubStream>(tc::io::SubStream(mFile, kip_pos, kip_size))});
kip_pos += kip_size;
}
}
void IniProcess::displayHeader()
void nstool::IniProcess::displayHeader()
{
std::cout << "[INI Header]" << std::endl;
std::cout << " Size: 0x" << std::hex << mHdr.getSize() << std::endl;
std::cout << " KIP Num: " << std::dec << (uint32_t)mHdr.getKipNum() << std::endl;
fmt::print("[INI Header]\n");
fmt::print(" Size: 0x{:x}\n", mHdr.getSize());
fmt::print(" KIP Num: {:d}\n", mHdr.getKipNum());
}
void IniProcess::displayKipList()
void nstool::IniProcess::displayKipList()
{
for (size_t i = 0; i < mKipList.size(); i++)
for (auto itr = mKipList.begin(); itr != mKipList.end(); itr++)
{
KipProcess obj;
obj.setInputFile(mKipList[i]);
obj.setInputFile(itr->stream);
obj.setCliOutputMode(mCliOutputMode);
obj.setVerifyMode(mVerify);
@ -116,55 +115,36 @@ void IniProcess::displayKipList()
}
}
void IniProcess::extractKipList()
void nstool::IniProcess::extractKipList()
{
fnd::Vec<byte_t> cache;
nn::hac::KernelInitialProcessHeader hdr;
// allocate cache memory
cache.alloc(kCacheSize);
tc::ByteData cache = tc::ByteData(kCacheSize);
// make extract dir
fnd::io::makeDirectory(mKipExtractPath);
tc::io::LocalStorage local_fs;
local_fs.createDirectory(mKipExtractPath.get());
// out path for extracted KIP
tc::io::Path out_path;
std::string out_path_str;
// outfile object for writing KIP
fnd::SimpleFile out_file;
std::string out_path;
size_t out_size;
for (size_t i = 0; i < mKipList.size(); i++)
// extract KIPs
for (auto itr = mKipList.begin(); itr != mKipList.end(); itr++)
{
// read header
(*mKipList[i])->read(cache.data(), 0, cache.size());
hdr.fromBytes(cache.data(), cache.size());
out_path = mKipExtractPath.get();
out_path += fmt::format("{:s}.kip", itr->hdr.getName());
// generate path
out_path.clear();
fnd::io::appendToPath(out_path, mKipExtractPath);
fnd::io::appendToPath(out_path, hdr.getName() + kKipExtention);
tc::io::PathUtil::pathToUnixUTF8(out_path, out_path_str);
// open file
out_file.open(out_path, fnd::SimpleFile::Create);
if (mCliOutputMode.show_basic_info)
fmt::print("Saving {:s}...\n", out_path_str);
// get kip file size
out_size = (*mKipList[i])->size();
// extract kip
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
printf("extract=[%s]\n", out_path.c_str());
(*mKipList[i])->seek(0);
for (size_t j = 0; j < ((out_size / kCacheSize) + ((out_size % kCacheSize) != 0)); j++)
{
(*mKipList[i])->read(cache.data(), _MIN(out_size - (kCacheSize * j), kCacheSize));
out_file.write(cache.data(), _MIN(out_size - (kCacheSize * j), kCacheSize));
}
out_file.close();
writeStreamToFile(itr->stream, out_path, cache);
}
}
size_t IniProcess::getKipSizeFromHeader(const nn::hac::KernelInitialProcessHeader& hdr) const
int64_t nstool::IniProcess::getKipSizeFromHeader(const nn::hac::KernelInitialProcessHeader& hdr) const
{
return sizeof(nn::hac::sKipHeader) + hdr.getTextSegmentInfo().file_layout.size + hdr.getRoSegmentInfo().file_layout.size + hdr.getDataSegmentInfo().file_layout.size;
// the order of elements in a KIP are sequential, there are no file offsets
return int64_t(sizeof(nn::hac::sKipHeader)) + int64_t(hdr.getTextSegmentInfo().file_layout.size + hdr.getRoSegmentInfo().file_layout.size + hdr.getDataSegmentInfo().file_layout.size);
}

View file

@ -1,14 +1,10 @@
#pragma once
#include <vector>
#include <string>
#include <fnd/types.h>
#include <fnd/IFile.h>
#include <fnd/List.h>
#include <fnd/SharedPtr.h>
#include "types.h"
#include <nn/hac/IniHeader.h>
#include <nn/hac/KernelInitialProcessHeader.h>
#include "common.h"
namespace nstool {
class IniProcess
{
@ -17,25 +13,29 @@ public:
void process();
void setInputFile(const fnd::SharedPtr<fnd::IFile>& file);
void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify);
void setKipExtractPath(const std::string& path);
void setKipExtractPath(const tc::io::Path& path);
private:
const std::string kModuleName = "IniProcess";
const std::string kKipExtention = ".kip";
const size_t kCacheSize = 0x10000;
fnd::SharedPtr<fnd::IFile> mFile;
std::string mModuleName;
std::shared_ptr<tc::io::IStream> mFile;
CliOutputMode mCliOutputMode;
bool mVerify;
bool mDoExtractKip;
std::string mKipExtractPath;
tc::Optional<tc::io::Path> mKipExtractPath;
nn::hac::IniHeader mHdr;
fnd::List<fnd::SharedPtr<fnd::IFile>> mKipList;
struct InnerKipInfo
{
nn::hac::KernelInitialProcessHeader hdr;
std::shared_ptr<tc::io::IStream> stream;
};
std::vector<InnerKipInfo> mKipList;
void importHeader();
void importKipList();
@ -43,5 +43,7 @@ private:
void displayKipList();
void extractKipList();
size_t getKipSizeFromHeader(const nn::hac::KernelInitialProcessHeader& hdr) const;
int64_t getKipSizeFromHeader(const nn::hac::KernelInitialProcessHeader& hdr) const;
};
}

1014
src/KeyBag.cpp Normal file

File diff suppressed because it is too large Load diff

85
src/KeyBag.h Normal file
View file

@ -0,0 +1,85 @@
#pragma once
#include <string>
#include <vector>
#include <array>
#include <map>
#include <tc/Optional.h>
#include <tc/io.h>
#include <nn/pki/SignUtils.h>
#include <nn/hac/define/types.h>
#include <nn/hac/define/nca.h>
namespace nstool {
struct KeyBag
{
using aes128_key_t = nn::hac::detail::aes128_key_t;
using aes128_xtskey_t = nn::hac::detail::aes128_xtskey_t;
using rsa_key_t = tc::crypto::RsaKey;
//using ecc_key_t = tc::crypto::EccKey;
using rights_id_t = nn::hac::detail::rights_id_t;
using key_generation_t = byte_t;
using broadon_issuer_t = std::string;
static const size_t kNcaKeakNum = nn::hac::nca::kKeyAreaEncryptionKeyNum;
// acid
std::map<key_generation_t, rsa_key_t> acid_sign_key;
// pkg1 and pkg2
std::map<key_generation_t, aes128_key_t> pkg1_key;
std::map<key_generation_t, aes128_key_t> pkg2_key;
tc::Optional<rsa_key_t> pkg2_sign_key;
// nca
tc::Optional<aes128_xtskey_t> nca_header_key;
std::map<key_generation_t, rsa_key_t> nca_header_sign0_key;
std::array<std::map<key_generation_t, aes128_key_t>, kNcaKeakNum> nca_key_area_encryption_key;
std::array<std::map<key_generation_t, aes128_key_t>, kNcaKeakNum> nca_key_area_encryption_key_hw;
// external content keys (nca<->ticket)
std::map<rights_id_t, aes128_key_t> external_content_keys;
tc::Optional<aes128_key_t> fallback_enc_content_key; // encrypted content key to be used when external_content_keys does not have the required content key (usually taken raw from ticket)
tc::Optional<aes128_key_t> fallback_content_key; // content key to be used when external_content_keys does not have the required content key (usually already decrypted from ticket)
// nrr
std::map<key_generation_t, rsa_key_t> nrr_certificate_sign_key;
// xci
tc::Optional<rsa_key_t> xci_header_sign_key;
std::map<byte_t, aes128_key_t> xci_header_key;
std::map<byte_t, aes128_key_t> xci_initial_data_kek;
tc::Optional<rsa_key_t> xci_cert_sign_key;
// ticket
std::map<key_generation_t, aes128_key_t> etik_common_key;
// BroadOn signer profiles (for es cert and es tik)
// BroadOn Keys
struct BroadOnSignerProfile
{
tc::ByteData certificate;
nn::pki::sign::SignatureAlgo key_type;
rsa_key_t rsa_key;
// ecc_key_t ecc_key;
};
std::map<broadon_issuer_t, BroadOnSignerProfile> broadon_signer;
};
class KeyBagInitializer : public KeyBag
{
public:
KeyBagInitializer(bool isDev, const tc::Optional<tc::io::Path>& keyfile_path, const tc::Optional<tc::io::Path>& tik_path, const tc::Optional<tc::io::Path>& cert_path);
private:
KeyBagInitializer();
void importBaseKeyFile(const tc::io::Path& keyfile_path, bool isDev);
void importTitleKeyFile(const tc::io::Path& keyfile_path);
void importCertificateChain(const tc::io::Path& cert_path);
void importTicket(const tc::io::Path& tik_path);
void importKnownKeys(bool isDev);
};
}

View file

@ -1,413 +0,0 @@
#include "KeyConfiguration.h"
#include <fnd/ResourceFileReader.h>
#include <fnd/SimpleTextOutput.h>
#include <nn/hac/AesKeygen.h>
KeyConfiguration::KeyConfiguration()
{
clearGeneralKeyConfiguration();
clearNcaExternalKeys();
}
KeyConfiguration::KeyConfiguration(const KeyConfiguration& other)
{
*this = other;
}
void KeyConfiguration::operator=(const KeyConfiguration& other)
{
mPkg2SignKey = other.mPkg2SignKey;
mXciHeaderSignKey = other.mXciHeaderSignKey;
mContentArchiveHeaderKey = other.mContentArchiveHeaderKey;
mXciHeaderKey = other.mXciHeaderKey;
for (size_t i = 0; i < kMasterKeyNum; i++)
{
mAcidSignKey[i] = other.mAcidSignKey[i];
mContentArchiveHeader0SignKey[i] = other.mContentArchiveHeader0SignKey[i];
mNrrCertificateSignKey[i] = other.mNrrCertificateSignKey[i];
mPkg2Key[i] = other.mPkg2Key[i];
mPkg1Key[i] = other.mPkg1Key[i];
mNcaKeyAreaEncryptionKey[0][i] = other.mNcaKeyAreaEncryptionKey[0][i];
mNcaKeyAreaEncryptionKey[1][i] = other.mNcaKeyAreaEncryptionKey[1][i];
mNcaKeyAreaEncryptionKey[2][i] = other.mNcaKeyAreaEncryptionKey[2][i];
mNcaKeyAreaEncryptionKeyHw[0][i] = other.mNcaKeyAreaEncryptionKeyHw[0][i];
mNcaKeyAreaEncryptionKeyHw[1][i] = other.mNcaKeyAreaEncryptionKeyHw[1][i];
mNcaKeyAreaEncryptionKeyHw[2][i] = other.mNcaKeyAreaEncryptionKeyHw[2][i];
mETicketCommonKey[i] = other.mETicketCommonKey[i];
}
mPkiRootKeyList = other.mPkiRootKeyList;
mNcaExternalContentKeyList = other.mNcaExternalContentKeyList;
}
void KeyConfiguration::importHactoolGenericKeyfile(const std::string& path)
{
clearGeneralKeyConfiguration();
fnd::ResourceFileReader res;
try
{
res.processFile(path);
}
catch (const fnd::Exception&)
{
throw fnd::Exception(kModuleName, "Failed to open key file: " + path);
}
// internally used sources
fnd::aes::sAes128Key master_key[kMasterKeyNum] = { kNullAesKey };
fnd::aes::sAes128Key package2_key_source = kNullAesKey;
fnd::aes::sAes128Key ticket_titlekek_source = kNullAesKey;
fnd::aes::sAes128Key key_area_key_source[kNcaKeakNum] = { kNullAesKey, kNullAesKey, kNullAesKey };
fnd::aes::sAes128Key aes_kek_generation_source = kNullAesKey;
fnd::aes::sAes128Key aes_key_generation_source = kNullAesKey;
fnd::aes::sAes128Key nca_header_kek_source = kNullAesKey;
fnd::aes::sAesXts128Key nca_header_key_source = kNullAesXtsKey;
fnd::rsa::sRsa4096Key pki_root_sign_key = kNullRsa4096Key;
#define _CONCAT_2_STRINGS(str1, str2) ((str1) + "_" + (str2))
#define _CONCAT_3_STRINGS(str1, str2, str3) _CONCAT_2_STRINGS(_CONCAT_2_STRINGS(str1, str2), str3)
#define _CONCAT_4_STRINGS(str1, str2, str3, str4) _CONCAT_2_STRINGS(_CONCAT_2_STRINGS(_CONCAT_2_STRINGS(str1, str2), str3), str4)
std::string key,val;
fnd::Vec<byte_t> dec_array;
#define _SAVE_KEYDATA(key_name, array, len) \
key = (key_name); \
val = res[key]; \
if (val.empty() == false) { \
fnd::SimpleTextOutput::stringToArray(val, dec_array); \
if (dec_array.size() != len) \
throw fnd::Exception(kModuleName, "Key: \"" + key_name + "\" has incorrect length"); \
memcpy(array, dec_array.data(), len); \
}
for (size_t nameidx = 0; nameidx < kNameVariantNum; nameidx++)
{
// import sources
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kPkg2Base[nameidx], kKeyStr, kSourceStr), package2_key_source.key, 0x10);
_SAVE_KEYDATA(_CONCAT_2_STRINGS(kTicketCommonKeyBase[nameidx], kSourceStr), ticket_titlekek_source.key, 0x10);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaKeyAreaEncKeyBase[nameidx], kNcaKeyAreaKeyIndexStr[0], kSourceStr), key_area_key_source[0].key, 0x10);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaKeyAreaEncKeyBase[nameidx], kNcaKeyAreaKeyIndexStr[1], kSourceStr), key_area_key_source[1].key, 0x10);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaKeyAreaEncKeyBase[nameidx], kNcaKeyAreaKeyIndexStr[2], kSourceStr), key_area_key_source[2].key, 0x10);
_SAVE_KEYDATA(_CONCAT_2_STRINGS(kKekGenBase[nameidx], kSourceStr), aes_kek_generation_source.key, 0x10);
_SAVE_KEYDATA(_CONCAT_2_STRINGS(kKeyGenBase[nameidx], kSourceStr), aes_key_generation_source.key, 0x10);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kContentArchiveHeaderBase[nameidx], kKekStr, kSourceStr), nca_header_kek_source.key, 0x10);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kContentArchiveHeaderBase[nameidx], kKeyStr, kSourceStr), nca_header_key_source.key, 0x20);
// Store Key Variants/Derivatives
for (size_t mkeyidx = 0; mkeyidx < kMasterKeyNum; mkeyidx++)
{
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kMasterBase[nameidx], kKeyStr, kKeyIndex[mkeyidx]), master_key[mkeyidx].key, 0x10);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kPkg1Base[nameidx], kKeyStr, kKeyIndex[mkeyidx]), mPkg1Key[mkeyidx].key, 0x10);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kPkg2Base[nameidx], kKeyStr, kKeyIndex[mkeyidx]), mPkg2Key[mkeyidx].key, 0x10);
_SAVE_KEYDATA(_CONCAT_2_STRINGS(kTicketCommonKeyBase[nameidx], kKeyIndex[mkeyidx]), mETicketCommonKey[mkeyidx].key, 0x10);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaKeyAreaEncKeyBase[nameidx], kNcaKeyAreaKeyIndexStr[0], kKeyIndex[mkeyidx]), mNcaKeyAreaEncryptionKey[0][mkeyidx].key, 0x10);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaKeyAreaEncKeyBase[nameidx], kNcaKeyAreaKeyIndexStr[1], kKeyIndex[mkeyidx]), mNcaKeyAreaEncryptionKey[1][mkeyidx].key, 0x10);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaKeyAreaEncKeyBase[nameidx], kNcaKeyAreaKeyIndexStr[2], kKeyIndex[mkeyidx]), mNcaKeyAreaEncryptionKey[2][mkeyidx].key, 0x10);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaKeyAreaEncKeyHwBase[nameidx], kNcaKeyAreaKeyIndexStr[0], kKeyIndex[mkeyidx]), mNcaKeyAreaEncryptionKeyHw[0][mkeyidx].key, 0x10);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaKeyAreaEncKeyHwBase[nameidx], kNcaKeyAreaKeyIndexStr[1], kKeyIndex[mkeyidx]), mNcaKeyAreaEncryptionKeyHw[1][mkeyidx].key, 0x10);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaKeyAreaEncKeyHwBase[nameidx], kNcaKeyAreaKeyIndexStr[2], kKeyIndex[mkeyidx]), mNcaKeyAreaEncryptionKeyHw[2][mkeyidx].key, 0x10);
_SAVE_KEYDATA(_CONCAT_4_STRINGS(kContentArchiveHeaderBase[nameidx], kSignKey, kKeyIndex[mkeyidx], kPrivateStr), mContentArchiveHeader0SignKey[mkeyidx].priv_exponent, fnd::rsa::kRsa2048Size);
_SAVE_KEYDATA(_CONCAT_4_STRINGS(kContentArchiveHeaderBase[nameidx], kSignKey, kKeyIndex[mkeyidx], kModulusStr), mContentArchiveHeader0SignKey[mkeyidx].modulus, fnd::rsa::kRsa2048Size);
_SAVE_KEYDATA(_CONCAT_4_STRINGS(kAcidBase[nameidx], kSignKey, kKeyIndex[mkeyidx], kPrivateStr), mAcidSignKey[mkeyidx].priv_exponent, fnd::rsa::kRsa2048Size);
_SAVE_KEYDATA(_CONCAT_4_STRINGS(kAcidBase[nameidx], kSignKey, kKeyIndex[mkeyidx], kModulusStr), mAcidSignKey[mkeyidx].modulus, fnd::rsa::kRsa2048Size);
_SAVE_KEYDATA(_CONCAT_4_STRINGS(kNrrCertBase[nameidx], kSignKey, kKeyIndex[mkeyidx], kPrivateStr), mNrrCertificateSignKey[mkeyidx].priv_exponent, fnd::rsa::kRsa2048Size);
_SAVE_KEYDATA(_CONCAT_4_STRINGS(kNrrCertBase[nameidx], kSignKey, kKeyIndex[mkeyidx], kModulusStr), mNrrCertificateSignKey[mkeyidx].modulus, fnd::rsa::kRsa2048Size);
}
// store nca header key
_SAVE_KEYDATA(_CONCAT_2_STRINGS(kContentArchiveHeaderBase[nameidx], kKeyStr), mContentArchiveHeaderKey.key[0], 0x20);
// store xci header key
_SAVE_KEYDATA(_CONCAT_2_STRINGS(kXciHeaderBase[nameidx], kKeyStr), mXciHeaderKey.key, 0x10);
// store rsa keys
// legacy header nca key name
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kContentArchiveHeaderBase[nameidx], kSignKey, kPrivateStr), mContentArchiveHeader0SignKey[0].priv_exponent, fnd::rsa::kRsa2048Size);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kContentArchiveHeaderBase[nameidx], kSignKey, kModulusStr), mContentArchiveHeader0SignKey[0].modulus, fnd::rsa::kRsa2048Size);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kXciHeaderBase[nameidx], kSignKey, kPrivateStr), mXciHeaderSignKey.priv_exponent, fnd::rsa::kRsa2048Size);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kXciHeaderBase[nameidx], kSignKey, kModulusStr), mXciHeaderSignKey.modulus, fnd::rsa::kRsa2048Size);
// legacy acid header key name
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kAcidBase[nameidx], kSignKey, kPrivateStr), mAcidSignKey[0].priv_exponent, fnd::rsa::kRsa2048Size);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kAcidBase[nameidx], kSignKey, kModulusStr), mAcidSignKey[0].modulus, fnd::rsa::kRsa2048Size);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kPkg2Base[nameidx], kSignKey, kPrivateStr), mPkg2SignKey.priv_exponent, fnd::rsa::kRsa2048Size);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kPkg2Base[nameidx], kSignKey, kModulusStr), mPkg2SignKey.modulus, fnd::rsa::kRsa2048Size);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kPkiRootBase[nameidx], kSignKey, kPrivateStr), pki_root_sign_key.priv_exponent, fnd::rsa::kRsa4096Size);
_SAVE_KEYDATA(_CONCAT_3_STRINGS(kPkiRootBase[nameidx], kSignKey, kModulusStr), pki_root_sign_key.modulus, fnd::rsa::kRsa4096Size);
}
#undef _SAVE_KEYDATA
#undef _CONCAT_3_STRINGS
#undef _CONCAT_2_STRINGS
// Derive keys
for (size_t i = 0; i < kMasterKeyNum; i++)
{
if (master_key[i] != kNullAesKey)
{
if (aes_kek_generation_source != kNullAesKey && aes_key_generation_source != kNullAesKey)
{
if (i == 0 && nca_header_kek_source != kNullAesKey && nca_header_key_source != kNullAesXtsKey)
{
if (mContentArchiveHeaderKey == kNullAesXtsKey)
{
fnd::aes::sAes128Key nca_header_kek;
nn::hac::AesKeygen::generateKey(nca_header_kek.key, aes_kek_generation_source.key, nca_header_kek_source.key, aes_key_generation_source.key, master_key[i].key);
nn::hac::AesKeygen::generateKey(mContentArchiveHeaderKey.key[0], nca_header_key_source.key[0], nca_header_kek.key);
nn::hac::AesKeygen::generateKey(mContentArchiveHeaderKey.key[1], nca_header_key_source.key[1], nca_header_kek.key);
}
}
for (size_t j = 0; j < nn::hac::nca::kKeyAreaEncryptionKeyNum; j++)
{
if (key_area_key_source[j] != kNullAesKey && mNcaKeyAreaEncryptionKey[j][i] == kNullAesKey)
{
nn::hac::AesKeygen::generateKey(mNcaKeyAreaEncryptionKey[j][i].key, aes_kek_generation_source.key, key_area_key_source[j].key, aes_key_generation_source.key, master_key[i].key);
}
}
}
if (ticket_titlekek_source != kNullAesKey && mETicketCommonKey[i] == kNullAesKey)
{
nn::hac::AesKeygen::generateKey(mETicketCommonKey[i].key, ticket_titlekek_source.key, master_key[i].key);
}
if (package2_key_source != kNullAesKey && mPkg2Key[i] == kNullAesKey)
{
nn::hac::AesKeygen::generateKey(mPkg2Key[i].key, package2_key_source.key, master_key[i].key);
}
}
}
// populate pki root keys
if (pki_root_sign_key != kNullRsa4096Key)
{
sPkiRootKey tmp;
tmp.name = nn::pki::sign::kRootIssuerStr;
tmp.key_type = nn::pki::sign::SIGN_ALGO_RSA4096;
tmp.rsa4096_key = pki_root_sign_key;
mPkiRootKeyList.addElement(tmp);
}
}
void KeyConfiguration::clearGeneralKeyConfiguration()
{
mPkg2SignKey = kNullRsa2048Key;
mXciHeaderSignKey = kNullRsa2048Key;
mPkiRootKeyList.clear();
mContentArchiveHeaderKey = kNullAesXtsKey;
mXciHeaderKey = kNullAesKey;
for (size_t i = 0; i < kMasterKeyNum; i++)
{
mAcidSignKey[i] = kNullRsa2048Key;
mContentArchiveHeader0SignKey[i] = kNullRsa2048Key;
mNrrCertificateSignKey[i] = kNullRsa2048Key;
mPkg1Key[i] = kNullAesKey;
mPkg2Key[i] = kNullAesKey;
mETicketCommonKey[i] = kNullAesKey;
for (size_t j = 0; j < kNcaKeakNum; j++)
{
mNcaKeyAreaEncryptionKey[j][i] = kNullAesKey;
mNcaKeyAreaEncryptionKeyHw[j][i] = kNullAesKey;
}
}
}
void KeyConfiguration::clearNcaExternalKeys()
{
mNcaExternalContentKeyList.clear();
}
bool KeyConfiguration::getContentArchiveHeaderKey(fnd::aes::sAesXts128Key& key) const
{
return copyOutKeyResourceIfExists(mContentArchiveHeaderKey, key, kNullAesXtsKey);
}
bool KeyConfiguration::getContentArchiveHeader0SignKey(fnd::rsa::sRsa2048Key& key, byte_t key_generation) const
{
// TODO: This needs to be changed to support multiple keys
if (key_generation >= kMasterKeyNum)
{
return false;
}
return copyOutKeyResourceIfExists(mContentArchiveHeader0SignKey[key_generation], key, kNullRsa2048Key);
}
bool KeyConfiguration::getAcidSignKey(fnd::rsa::sRsa2048Key& key, byte_t key_generation) const
{
// TODO: This needs to be changed to support multiple keys
if (key_generation >= kMasterKeyNum)
{
return false;
}
return copyOutKeyResourceIfExists(mAcidSignKey[key_generation], key, kNullRsa2048Key);
}
bool KeyConfiguration::getNcaKeyAreaEncryptionKey(byte_t masterkey_index, byte_t keak_type, fnd::aes::sAes128Key& key) const
{
if (keak_type >= kNcaKeakNum || masterkey_index >= kMasterKeyNum)
{
return false;
}
return copyOutKeyResourceIfExists(mNcaKeyAreaEncryptionKey[keak_type][masterkey_index], key, kNullAesKey);
}
bool KeyConfiguration::getNcaKeyAreaEncryptionKeyHw(byte_t masterkey_index, byte_t keak_type, fnd::aes::sAes128Key& key) const
{
if (keak_type >= kNcaKeakNum || masterkey_index >= kMasterKeyNum)
{
return false;
}
return copyOutKeyResourceIfExists(mNcaKeyAreaEncryptionKeyHw[keak_type][masterkey_index], key, kNullAesKey);
}
void KeyConfiguration::addNcaExternalContentKey(const byte_t rights_id[nn::hac::nca::kRightsIdLen], const fnd::aes::sAes128Key& key)
{
sNcaExternalContentKey tmp;
memcpy(tmp.rights_id.data, rights_id, nn::hac::nca::kRightsIdLen);
tmp.key = key;
if (mNcaExternalContentKeyList.hasElement(tmp))
return;
mNcaExternalContentKeyList.addElement(tmp);
}
bool KeyConfiguration::getNcaExternalContentKey(const byte_t rights_id[nn::hac::nca::kRightsIdLen], fnd::aes::sAes128Key& key) const
{
sRightsId id;
bool res_exists = false;
memcpy(id.data, rights_id, nn::hac::nca::kRightsIdLen);
for (size_t i = 0; i < mNcaExternalContentKeyList.size(); i++)
{
if (mNcaExternalContentKeyList[i].rights_id == id)
{
res_exists = true;
key = mNcaExternalContentKeyList[i].key;
break;
}
}
return res_exists;
}
bool KeyConfiguration::getNrrCertificateSignKey(fnd::rsa::sRsa2048Key& key, byte_t key_generation) const
{
// TODO: This needs to be changed to support multiple keys
if (key_generation >= kMasterKeyNum)
{
return false;
}
return copyOutKeyResourceIfExists(mNrrCertificateSignKey[key_generation], key, kNullRsa2048Key);
}
bool KeyConfiguration::getPkg1Key(byte_t masterkey_index, fnd::aes::sAes128Key& key) const
{
if (masterkey_index >= kMasterKeyNum)
{
return false;
}
return copyOutKeyResourceIfExists(mPkg1Key[masterkey_index], key, kNullAesKey);
}
bool KeyConfiguration::getPkg2Key(byte_t masterkey_index, fnd::aes::sAes128Key& key) const
{
if (masterkey_index >= kMasterKeyNum)
{
return false;
}
return copyOutKeyResourceIfExists(mPkg2Key[masterkey_index], key, kNullAesKey);
}
bool KeyConfiguration::getPkg2SignKey(fnd::rsa::sRsa2048Key& key) const
{
return copyOutKeyResourceIfExists(mPkg2SignKey, key, kNullRsa2048Key);
}
bool KeyConfiguration::getXciHeaderSignKey(fnd::rsa::sRsa2048Key& key) const
{
return copyOutKeyResourceIfExists(mXciHeaderSignKey, key, kNullRsa2048Key);
}
bool KeyConfiguration::getXciHeaderKey(fnd::aes::sAes128Key& key) const
{
return copyOutKeyResourceIfExists(mXciHeaderKey, key, kNullAesKey);
}
bool KeyConfiguration::getETicketCommonKey(byte_t masterkey_index, fnd::aes::sAes128Key& key) const
{
if (masterkey_index >= kMasterKeyNum)
{
return false;
}
return copyOutKeyResourceIfExists(mETicketCommonKey[masterkey_index], key, kNullAesKey);
}
bool KeyConfiguration::getPkiRootSignKey(const std::string& root_name, fnd::rsa::sRsa4096Key& key) const
{
bool res_exists = false;
for (size_t i = 0; i < mPkiRootKeyList.size(); i++)
{
if (root_name == mPkiRootKeyList[i].name && mPkiRootKeyList[i].key_type == nn::pki::sign::SIGN_ALGO_RSA4096)
{
res_exists = true;
key = mPkiRootKeyList[i].rsa4096_key;
break;
}
}
return res_exists;
}
bool KeyConfiguration::getPkiRootSignKey(const std::string& root_name, fnd::rsa::sRsa2048Key& key) const
{
bool res_exists = false;
for (size_t i = 0; i < mPkiRootKeyList.size(); i++)
{
if (root_name == mPkiRootKeyList[i].name && mPkiRootKeyList[i].key_type == nn::pki::sign::SIGN_ALGO_RSA2048)
{
res_exists = true;
key = mPkiRootKeyList[i].rsa2048_key;
break;
}
}
return res_exists;
}
bool KeyConfiguration::getPkiRootSignKey(const std::string& root_name, fnd::ecdsa::sEcdsa240Key& key) const
{
bool res_exists = false;
for (size_t i = 0; i < mPkiRootKeyList.size(); i++)
{
if (root_name == mPkiRootKeyList[i].name && mPkiRootKeyList[i].key_type == nn::pki::sign::SIGN_ALGO_ECDSA240)
{
res_exists = true;
key = mPkiRootKeyList[i].ecdsa240_key;
break;
}
}
return res_exists;
}

View file

@ -1,217 +0,0 @@
#pragma once
#include <string>
#include <cstring>
#include <fnd/types.h>
#include <fnd/aes.h>
#include <fnd/rsa.h>
#include <fnd/ecdsa.h>
#include <nn/hac/define/nca.h>
#include <nn/pki/SignedData.h>
#include <nn/es/TicketBody_V2.h>
class KeyConfiguration
{
public:
KeyConfiguration();
KeyConfiguration(const KeyConfiguration& other);
void operator=(const KeyConfiguration& other);
void importHactoolGenericKeyfile(const std::string& path);
//void importHactoolTitleKeyfile(const std::string& path);
void clearGeneralKeyConfiguration();
void clearNcaExternalKeys();
// nca keys
bool getContentArchiveHeaderKey(fnd::aes::sAesXts128Key& key) const;
bool getContentArchiveHeader0SignKey(fnd::rsa::sRsa2048Key& key, byte_t key_generation) const;
bool getAcidSignKey(fnd::rsa::sRsa2048Key& key, byte_t key_generation) const;
bool getNcaKeyAreaEncryptionKey(byte_t masterkey_index, byte_t keak_type, fnd::aes::sAes128Key& key) const;
bool getNcaKeyAreaEncryptionKeyHw(byte_t masterkey_index, byte_t keak_type, fnd::aes::sAes128Key& key) const;
// external content keys
void addNcaExternalContentKey(const byte_t rights_id[nn::hac::nca::kRightsIdLen], const fnd::aes::sAes128Key& key);
bool getNcaExternalContentKey(const byte_t rights_id[nn::hac::nca::kRightsIdLen], fnd::aes::sAes128Key& key) const;
// nrr key
bool getNrrCertificateSignKey(fnd::rsa::sRsa2048Key& key, byte_t key_generation) const;
// pkg1/pkg2
bool getPkg1Key(byte_t masterkey_index, fnd::aes::sAes128Key& key) const;
bool getPkg2Key(byte_t masterkey_index, fnd::aes::sAes128Key& key) const;
bool getPkg2SignKey(fnd::rsa::sRsa2048Key& key) const;
// xci keys
bool getXciHeaderSignKey(fnd::rsa::sRsa2048Key& key) const;
bool getXciHeaderKey(fnd::aes::sAes128Key& key) const;
// ticket
bool getETicketCommonKey(byte_t masterkey_index, fnd::aes::sAes128Key& key) const;
// pki
bool getPkiRootSignKey(const std::string& root_name, fnd::rsa::sRsa4096Key& key) const;
bool getPkiRootSignKey(const std::string& root_name, fnd::rsa::sRsa2048Key& key) const;
bool getPkiRootSignKey(const std::string& root_name, fnd::ecdsa::sEcdsa240Key& key) const;
private:
const std::string kModuleName = "KeyConfiguration";
const fnd::aes::sAes128Key kNullAesKey = {{0}};
const fnd::aes::sAesXts128Key kNullAesXtsKey = {{{0}}};
const fnd::rsa::sRsa4096Key kNullRsa4096Key = {{0}, {0}, {0}};
const fnd::rsa::sRsa2048Key kNullRsa2048Key = {{0}, {0}, {0}};
static const size_t kMasterKeyNum = 0x20;
static const size_t kNcaKeakNum = nn::hac::nca::kKeyAreaEncryptionKeyNum;
// keynames
enum NameVariantIndex
{
NNTOOLS,
LEGACY_HACTOOL,
LEGACY_0
};
static const size_t kNameVariantNum = 3;
const std::string kMasterBase[kNameVariantNum] = { "master", "master", "master" };
const std::string kPkg1Base[kNameVariantNum] = { "package1", "package1", "package1" };
const std::string kPkg2Base[kNameVariantNum] = { "package2", "package2", "package2" };
const std::string kXciHeaderBase[kNameVariantNum] = { "xci_header", "xci_header", "xci_header" };
const std::string kContentArchiveHeaderBase[kNameVariantNum] = { "nca_header", "header", "nca_header" };
const std::string kAcidBase[kNameVariantNum] = { "acid", "acid", "acid" };
const std::string kNrrCertBase[kNameVariantNum] = { "nrr_certificate", "nrr_certificate", "nrr_certificate" };
const std::string kPkiRootBase[kNameVariantNum] = { "pki_root", "pki_root", "pki_root" };
const std::string kTicketCommonKeyBase[kNameVariantNum] = { "ticket_commonkey", "titlekek", "ticket_commonkey" };
const std::string kNcaKeyAreaEncKeyBase[kNameVariantNum] = { "nca_key_area_key", "key_area_key", "nca_body_keak" };
const std::string kNcaKeyAreaEncKeyHwBase[kNameVariantNum] = { "nca_key_area_key_hw", "key_area_hw_key", "nca_key_area_key_hw" };
const std::string kKekGenBase[kNameVariantNum] = { "aes_kek_generation", "aes_kek_generation", "aes_kek_generation" };
const std::string kKeyGenBase[kNameVariantNum] = { "aes_key_generation", "aes_key_generation", "aes_key_generation" };
// misc str
const std::string kKeyStr = "key";
const std::string kKekStr = "kek";
const std::string kSourceStr = "source";
const std::string kSignKey = "sign_key";
const std::string kModulusStr = "modulus";
const std::string kPrivateStr = "private";
const std::string kNcaKeyAreaKeyIndexStr[kNcaKeakNum] = { "application", "ocean", "system" };
const std::string kKeyIndex[kMasterKeyNum] = {"00","01","02","03","04","05","06","07","08","09","0a","0b","0c","0d","0e","0f","10","11","12","13","14","15","16","17","18","19","1a","1b","1c","1d","1e","1f"};
struct sRightsId
{
byte_t data[nn::hac::nca::kRightsIdLen];
void operator=(const sRightsId& other)
{
memcpy(this->data, other.data, nn::hac::nca::kRightsIdLen);
}
bool operator==(const sRightsId& other) const
{
return memcmp(this->data, other.data, nn::hac::nca::kRightsIdLen) == 0;
}
bool operator!=(const sRightsId& other) const
{
return !(operator==(other));
}
};
struct sNcaExternalContentKey
{
sRightsId rights_id;
fnd::aes::sAes128Key key;
void operator=(const sNcaExternalContentKey& other)
{
rights_id = other.rights_id;
key = other.key;
}
bool operator==(const sNcaExternalContentKey& other) const
{
return (rights_id == other.rights_id) \
&& (key == other.key);
}
bool operator!=(const sNcaExternalContentKey& other) const
{
return !(operator==(other));
}
};
struct sPkiRootKey
{
std::string name;
nn::pki::sign::SignatureAlgo key_type;
fnd::rsa::sRsa4096Key rsa4096_key;
fnd::rsa::sRsa2048Key rsa2048_key;
fnd::ecdsa::sEcdsa240Key ecdsa240_key;
void operator=(const sPkiRootKey& other)
{
name = other.name;
key_type = other.key_type;
rsa4096_key = other.rsa4096_key;
rsa2048_key = other.rsa2048_key;
ecdsa240_key = other.ecdsa240_key;
}
bool operator==(const sPkiRootKey& other) const
{
return (name == other.name) \
&& (key_type == other.key_type) \
&& (rsa4096_key == other.rsa4096_key) \
&& (rsa2048_key == other.rsa2048_key) \
&& (ecdsa240_key == other.ecdsa240_key);
}
bool operator!=(const sPkiRootKey& other) const
{
return !(operator==(other));
}
};
/* general key config */
// acid
fnd::rsa::sRsa2048Key mAcidSignKey[kMasterKeyNum];
// pkg1 and pkg2
fnd::aes::sAes128Key mPkg1Key[kMasterKeyNum];
fnd::rsa::sRsa2048Key mPkg2SignKey;
fnd::aes::sAes128Key mPkg2Key[kMasterKeyNum];
// nca
fnd::rsa::sRsa2048Key mContentArchiveHeader0SignKey[kMasterKeyNum];
fnd::aes::sAesXts128Key mContentArchiveHeaderKey;
fnd::aes::sAes128Key mNcaKeyAreaEncryptionKey[kNcaKeakNum][kMasterKeyNum];
fnd::aes::sAes128Key mNcaKeyAreaEncryptionKeyHw[kNcaKeakNum][kMasterKeyNum];
// nrr
fnd::rsa::sRsa2048Key mNrrCertificateSignKey[kMasterKeyNum];
// xci
fnd::rsa::sRsa2048Key mXciHeaderSignKey;
fnd::aes::sAes128Key mXciHeaderKey;
// ticket
fnd::aes::sAes128Key mETicketCommonKey[kMasterKeyNum];
// pki
fnd::List<sPkiRootKey> mPkiRootKeyList;
/* Nca External Keys */
fnd::List<sNcaExternalContentKey> mNcaExternalContentKeyList;
template <class T>
bool copyOutKeyResourceIfExists(const T& src, T& dst, const T& null_sample) const
{
bool resource_exists = false;
if (src != null_sample)
{
resource_exists = true;
dst = src;
}
return resource_exists;
}
};

View file

@ -1,266 +1,273 @@
#include "KipProcess.h"
#include <iostream>
#include <iomanip>
#include <fnd/SimpleTextOutput.h>
#include <fnd/OffsetAdjustedIFile.h>
#include <fnd/Vec.h>
#include <nn/hac/KernelCapabilityUtil.h>
KipProcess::KipProcess():
#include <tc/NotImplementedException.h>
nstool::KipProcess::KipProcess() :
mModuleName("nstool::KipProcess"),
mFile(),
mCliOutputMode(_BIT(OUTPUT_BASIC)),
mCliOutputMode(true, false, false, false),
mVerify(false)
{
}
void KipProcess::process()
void nstool::KipProcess::process()
{
importHeader();
//importCodeSegments();
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
//importCodeSegments(); // code segments not imported because compression not supported yet
if (mCliOutputMode.show_basic_info)
{
displayHeader();
displayKernelCap(mHdr.getKernelCapabilities());
}
}
void KipProcess::setInputFile(const fnd::SharedPtr<fnd::IFile>& file)
void nstool::KipProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
{
mFile = file;
}
void KipProcess::setCliOutputMode(CliOutputMode type)
void nstool::KipProcess::setCliOutputMode(CliOutputMode type)
{
mCliOutputMode = type;
}
void KipProcess::setVerifyMode(bool verify)
void nstool::KipProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void KipProcess::importHeader()
void nstool::KipProcess::importHeader()
{
fnd::Vec<byte_t> scratch;
if (*mFile == nullptr)
if (mFile == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
throw tc::Exception(mModuleName, "No file reader set.");
}
if (mFile->canRead() == false || mFile->canSeek() == false)
{
throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions.");
}
if ((*mFile)->size() < sizeof(nn::hac::sKipHeader))
// check if file_size is smaller than KIP header size
if (tc::io::IOUtil::castInt64ToSize(mFile->length()) < sizeof(nn::hac::sKipHeader))
{
throw fnd::Exception(kModuleName, "Corrupt KIP: file too small");
throw tc::Exception(mModuleName, "Corrupt KIP: file too small.");
}
scratch.alloc(sizeof(nn::hac::sKipHeader));
(*mFile)->read(scratch.data(), 0, scratch.size());
// read kip
tc::ByteData scratch = tc::ByteData(sizeof(nn::hac::sKipHeader));
mFile->seek(0, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
// parse kip header
mHdr.fromBytes(scratch.data(), scratch.size());
}
void KipProcess::importCodeSegments()
void nstool::KipProcess::importCodeSegments()
{
#ifdef _KIP_COMPRESSION_IMPLEMENTED
fnd::Vec<byte_t> scratch;
uint32_t decompressed_len;
#endif
tc::ByteData scratch;
// process text segment
#ifdef _KIP_COMPRESSION_IMPLEMENTED
if (mHdr.getTextSegmentInfo().is_compressed)
{
scratch.alloc(mHdr.getTextSegmentInfo().file_layout.size);
(*mFile)->read(scratch.data(), mHdr.getTextSegmentInfo().file_layout.offset, scratch.size());
mTextBlob.alloc(mHdr.getTextSegmentInfo().memory_layout.size);
fnd::lz4::decompressData(scratch.data(), (uint32_t)scratch.size(), mTextBlob.data(), (uint32_t)mTextBlob.size(), decompressed_len);
if (decompressed_len != mTextBlob.size())
// allocate/read compressed text
scratch = tc::ByteData(mHdr.getTextSegmentInfo().file_layout.size);
mFile->seek(mHdr.getTextSegmentInfo().file_layout.offset, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
// allocate for decompressed text segment
mTextBlob = tc::ByteData(mHdr.getTextSegmentInfo().memory_layout.size);
// decompress text segment
if (decompressData(scratch.data(), scratch.size(), mTextBlob.data(), mTextBlob.size()) != mTextBlob.size())
{
throw fnd::Exception(kModuleName, "KIP text segment failed to decompress");
throw tc::Exception(mModuleName, "KIP text segment failed to decompress");
}
}
else
{
mTextBlob.alloc(mHdr.getTextSegmentInfo().file_layout.size);
(*mFile)->read(mTextBlob.data(), mHdr.getTextSegmentInfo().file_layout.offset, mTextBlob.size());
// read text segment directly (not compressed)
mTextBlob = tc::ByteData(mHdr.getTextSegmentInfo().file_layout.size);
mFile->seek(mHdr.getTextSegmentInfo().file_layout.offset, tc::io::SeekOrigin::Begin);
mFile->read(mTextBlob.data(), mTextBlob.size());
}
#else
mTextBlob.alloc(mHdr.getTextSegmentInfo().file_layout.size);
(*mFile)->read(mTextBlob.data(), mHdr.getTextSegmentInfo().file_layout.offset, mTextBlob.size());
#endif
// process ro segment
#ifdef _KIP_COMPRESSION_IMPLEMENTED
if (mHdr.getRoSegmentInfo().is_compressed)
{
scratch.alloc(mHdr.getRoSegmentInfo().file_layout.size);
(*mFile)->read(scratch.data(), mHdr.getRoSegmentInfo().file_layout.offset, scratch.size());
mRoBlob.alloc(mHdr.getRoSegmentInfo().memory_layout.size);
fnd::lz4::decompressData(scratch.data(), (uint32_t)scratch.size(), mRoBlob.data(), (uint32_t)mRoBlob.size(), decompressed_len);
if (decompressed_len != mRoBlob.size())
// allocate/read compressed ro segment
scratch = tc::ByteData(mHdr.getRoSegmentInfo().file_layout.size);
mFile->seek(mHdr.getRoSegmentInfo().file_layout.offset, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
// allocate for decompressed ro segment
mRoBlob = tc::ByteData(mHdr.getRoSegmentInfo().memory_layout.size);
// decompress ro segment
if (decompressData(scratch.data(), scratch.size(), mRoBlob.data(), mRoBlob.size()) != mRoBlob.size())
{
throw fnd::Exception(kModuleName, "KIP ro segment failed to decompress");
throw tc::Exception(mModuleName, "KIP ro segment failed to decompress");
}
}
else
{
mRoBlob.alloc(mHdr.getRoSegmentInfo().file_layout.size);
(*mFile)->read(mRoBlob.data(), mHdr.getRoSegmentInfo().file_layout.offset, mRoBlob.size());
// read ro segment directly (not compressed)
mRoBlob = tc::ByteData(mHdr.getRoSegmentInfo().file_layout.size);
mFile->seek(mHdr.getRoSegmentInfo().file_layout.offset, tc::io::SeekOrigin::Begin);
mFile->read(mRoBlob.data(), mRoBlob.size());
}
#else
mRoBlob.alloc(mHdr.getRoSegmentInfo().file_layout.size);
(*mFile)->read(mRoBlob.data(), mHdr.getRoSegmentInfo().file_layout.offset, mRoBlob.size());
#endif
// process data segment
#ifdef _KIP_COMPRESSION_IMPLEMENTED
// process ro segment
if (mHdr.getDataSegmentInfo().is_compressed)
{
scratch.alloc(mHdr.getDataSegmentInfo().file_layout.size);
(*mFile)->read(scratch.data(), mHdr.getDataSegmentInfo().file_layout.offset, scratch.size());
mDataBlob.alloc(mHdr.getDataSegmentInfo().memory_layout.size);
fnd::lz4::decompressData(scratch.data(), (uint32_t)scratch.size(), mDataBlob.data(), (uint32_t)mDataBlob.size(), decompressed_len);
if (decompressed_len != mDataBlob.size())
// allocate/read compressed ro segment
scratch = tc::ByteData(mHdr.getDataSegmentInfo().file_layout.size);
mFile->seek(mHdr.getDataSegmentInfo().file_layout.offset, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
// allocate for decompressed ro segment
mDataBlob = tc::ByteData(mHdr.getDataSegmentInfo().memory_layout.size);
// decompress ro segment
if (decompressData(scratch.data(), scratch.size(), mDataBlob.data(), mDataBlob.size()) != mDataBlob.size())
{
throw fnd::Exception(kModuleName, "KIP data segment failed to decompress");
throw tc::Exception(mModuleName, "KIP data segment failed to decompress");
}
}
else
{
mDataBlob.alloc(mHdr.getDataSegmentInfo().file_layout.size);
(*mFile)->read(mDataBlob.data(), mHdr.getDataSegmentInfo().file_layout.offset, mDataBlob.size());
// read ro segment directly (not compressed)
mDataBlob = tc::ByteData(mHdr.getDataSegmentInfo().file_layout.size);
mFile->seek(mHdr.getDataSegmentInfo().file_layout.offset, tc::io::SeekOrigin::Begin);
mFile->read(mDataBlob.data(), mDataBlob.size());
}
#else
mDataBlob.alloc(mHdr.getDataSegmentInfo().file_layout.size);
(*mFile)->read(mDataBlob.data(), mHdr.getDataSegmentInfo().file_layout.offset, mDataBlob.size());
#endif
}
void KipProcess::displayHeader()
size_t nstool::KipProcess::decompressData(const byte_t* src, size_t src_len, byte_t* dst, size_t dst_capacity)
{
std::cout << "[KIP Header]" << std::endl;
std::cout << " Meta:" << std::endl;
std::cout << " Name: " << mHdr.getName() << std::endl;
std::cout << " TitleId: 0x" << std::hex << std::setw(16) << std::setfill('0') << mHdr.getTitleId() << std::endl;
std::cout << " Version: v" << std::dec << mHdr.getVersion() << std::endl;
std::cout << " Is64BitInstruction: " << std::boolalpha << mHdr.getIs64BitInstructionFlag() << std::endl;
std::cout << " Is64BitAddressSpace: " << std::boolalpha << mHdr.getIs64BitAddressSpaceFlag() << std::endl;
std::cout << " UseSecureMemory: " << std::boolalpha << mHdr.getUseSecureMemoryFlag() << std::endl;
std::cout << " Program Sections:" << std::endl;
std::cout << " .text:" << std::endl;
if (_HAS_BIT(mCliOutputMode, OUTPUT_LAYOUT))
throw tc::NotImplementedException(mModuleName, "KIP decompression not implemented yet.");
}
void nstool::KipProcess::displayHeader()
{
fmt::print("[KIP Header]\n");
fmt::print(" Meta:\n");
fmt::print(" Name: {:s}\n", mHdr.getName());
fmt::print(" TitleId: 0x{:016x}\n", mHdr.getTitleId());
fmt::print(" Version: v{:d}\n", mHdr.getVersion());
fmt::print(" Is64BitInstruction: {}\n", mHdr.getIs64BitInstructionFlag());
fmt::print(" Is64BitAddressSpace: {}\n", mHdr.getIs64BitAddressSpaceFlag());
fmt::print(" UseSecureMemory: {}\n", mHdr.getUseSecureMemoryFlag());
fmt::print(" Program Sections:\n");
fmt::print(" .text:\n");
if (mCliOutputMode.show_layout)
{
std::cout << " FileOffset: 0x" << std::hex << mHdr.getTextSegmentInfo().file_layout.offset << std::endl;
std::cout << " FileSize: 0x" << std::hex << mHdr.getTextSegmentInfo().file_layout.size << (mHdr.getTextSegmentInfo().is_compressed? " (COMPRESSED)" : "") << std::endl;
fmt::print(" FileOffset: 0x{:x}\n", mHdr.getTextSegmentInfo().file_layout.offset);
fmt::print(" FileSize: 0x{:x}{:s}\n", mHdr.getTextSegmentInfo().file_layout.size, (mHdr.getTextSegmentInfo().is_compressed? " (COMPRESSED)" : ""));
}
std::cout << " MemoryOffset: 0x" << std::hex << mHdr.getTextSegmentInfo().memory_layout.offset << std::endl;
std::cout << " MemorySize: 0x" << std::hex << mHdr.getTextSegmentInfo().memory_layout.size << std::endl;
std::cout << " .ro:" << std::endl;
if (_HAS_BIT(mCliOutputMode, OUTPUT_LAYOUT))
fmt::print(" MemoryOffset: 0x{:x}\n", mHdr.getTextSegmentInfo().memory_layout.offset);
fmt::print(" MemorySize: 0x{:x}\n", mHdr.getTextSegmentInfo().memory_layout.size);
fmt::print(" .ro:\n");
if (mCliOutputMode.show_layout)
{
std::cout << " FileOffset: 0x" << std::hex << mHdr.getRoSegmentInfo().file_layout.offset << std::endl;
std::cout << " FileSize: 0x" << std::hex << mHdr.getRoSegmentInfo().file_layout.size << (mHdr.getRoSegmentInfo().is_compressed? " (COMPRESSED)" : "") << std::endl;
fmt::print(" FileOffset: 0x{:x}\n", mHdr.getRoSegmentInfo().file_layout.offset);
fmt::print(" FileSize: 0x{:x}{:s}\n", mHdr.getRoSegmentInfo().file_layout.size, (mHdr.getRoSegmentInfo().is_compressed? " (COMPRESSED)" : ""));
}
std::cout << " MemoryOffset: 0x" << std::hex << mHdr.getRoSegmentInfo().memory_layout.offset << std::endl;
std::cout << " MemorySize: 0x" << std::hex << mHdr.getRoSegmentInfo().memory_layout.size << std::endl;
std::cout << " .data:" << std::endl;
if (_HAS_BIT(mCliOutputMode, OUTPUT_LAYOUT))
fmt::print(" MemoryOffset: 0x{:x}\n", mHdr.getRoSegmentInfo().memory_layout.offset);
fmt::print(" MemorySize: 0x{:x}\n", mHdr.getRoSegmentInfo().memory_layout.size);
fmt::print(" .data:\n");
if (mCliOutputMode.show_layout)
{
std::cout << " FileOffset: 0x" << std::hex << mHdr.getDataSegmentInfo().file_layout.offset << std::endl;
std::cout << " FileSize: 0x" << std::hex << mHdr.getDataSegmentInfo().file_layout.size << (mHdr.getDataSegmentInfo().is_compressed? " (COMPRESSED)" : "") << std::endl;
fmt::print(" FileOffset: 0x{:x}\n", mHdr.getDataSegmentInfo().file_layout.offset);
fmt::print(" FileSize: 0x{:x}{:s}\n", mHdr.getDataSegmentInfo().file_layout.size, (mHdr.getDataSegmentInfo().is_compressed? " (COMPRESSED)" : ""));
}
std::cout << " MemoryOffset: 0x" << std::hex << mHdr.getDataSegmentInfo().memory_layout.offset << std::endl;
std::cout << " MemorySize: 0x" << std::hex << mHdr.getDataSegmentInfo().memory_layout.size << std::endl;
std::cout << " .bss:" << std::endl;
std::cout << " MemorySize: 0x" << std::hex << mHdr.getBssSize() << std::endl;
fmt::print(" MemoryOffset: 0x{:x}\n", mHdr.getDataSegmentInfo().memory_layout.offset);
fmt::print(" MemorySize: 0x{:x}\n", mHdr.getDataSegmentInfo().memory_layout.size);
fmt::print(" .bss:\n");
fmt::print(" MemorySize: 0x{:x}\n", mHdr.getBssSize());
}
void KipProcess::displayKernelCap(const nn::hac::KernelCapabilityControl& kern)
void nstool::KipProcess::displayKernelCap(const nn::hac::KernelCapabilityControl& kern)
{
std::cout << "[Kernel Capabilities]" << std::endl;
fmt::print("[Kernel Capabilities]\n");
if (kern.getThreadInfo().isSet())
{
nn::hac::ThreadInfoHandler threadInfo = kern.getThreadInfo();
std::cout << " Thread Priority:" << std::endl;
std::cout << " Min: " << std::dec << (uint32_t)threadInfo.getMinPriority() << std::endl;
std::cout << " Max: " << std::dec << (uint32_t)threadInfo.getMaxPriority() << std::endl;
std::cout << " CpuId:" << std::endl;
std::cout << " Min: " << std::dec << (uint32_t)threadInfo.getMinCpuId() << std::endl;
std::cout << " Max: " << std::dec << (uint32_t)threadInfo.getMaxCpuId() << std::endl;
fmt::print(" Thread Priority:\n");
fmt::print(" Min: {:d}\n", threadInfo.getMinPriority());
fmt::print(" Max: {:d}\n", threadInfo.getMaxPriority());
fmt::print(" CpuId:\n");
fmt::print(" Min: {:d}\n", threadInfo.getMinCpuId());
fmt::print(" Max: {:d}\n", threadInfo.getMaxCpuId());
}
if (kern.getSystemCalls().isSet())
{
auto syscall_ids = kern.getSystemCalls().getSystemCallIds();
std::cout << " SystemCalls:" << std::endl;
fmt::print(" SystemCalls:\n");
std::vector<std::string> syscall_names;
for (size_t syscall_id = 0; syscall_id < syscall_ids.size(); syscall_id++)
{
if (syscall_ids.test(syscall_id))
syscall_names.push_back(nn::hac::KernelCapabilityUtil::getSystemCallIdAsString(nn::hac::kc::SystemCallId(syscall_id)));
}
fnd::SimpleTextOutput::dumpStringList(syscall_names, 60, 4);
fmt::print("{:s}", tc::cli::FormatUtil::formatListWithLineLimit(syscall_names, 60, 4));
}
if (kern.getMemoryMaps().isSet())
{
fnd::List<nn::hac::MemoryMappingHandler::sMemoryMapping> maps = kern.getMemoryMaps().getMemoryMaps();
fnd::List<nn::hac::MemoryMappingHandler::sMemoryMapping> ioMaps = kern.getMemoryMaps().getIoMemoryMaps();
auto maps = kern.getMemoryMaps().getMemoryMaps();
auto ioMaps = kern.getMemoryMaps().getIoMemoryMaps();
std::cout << " MemoryMaps:" << std::endl;
fmt::print(" MemoryMaps:\n");
for (size_t i = 0; i < maps.size(); i++)
{
std::cout << " 0x" << std::hex << std::setw(16) << std::setfill('0') << ((uint64_t)maps[i].addr << 12) << " - 0x" << std::hex << std::setw(16) << std::setfill('0') << (((uint64_t)(maps[i].addr + maps[i].size) << 12) - 1) << " (perm=" << nn::hac::KernelCapabilityUtil::getMemoryPermissionAsString(maps[i].perm) << ") (type=" << nn::hac::KernelCapabilityUtil::getMappingTypeAsString(maps[i].type) << ")" << std::endl;
fmt::print(" {:s}\n", formatMappingAsString(maps[i]));
}
//std::cout << " IoMaps:" << std::endl;
//fmt::print(" IoMaps:\n");
for (size_t i = 0; i < ioMaps.size(); i++)
{
std::cout << " 0x" << std::hex << std::setw(16) << std::setfill('0') << ((uint64_t)ioMaps[i].addr << 12) << " - 0x" << std::hex << std::setw(16) << std::setfill('0') << (((uint64_t)(ioMaps[i].addr + ioMaps[i].size) << 12) - 1) << " (perm=" << nn::hac::KernelCapabilityUtil::getMemoryPermissionAsString(ioMaps[i].perm) << ") (type=" << nn::hac::KernelCapabilityUtil::getMappingTypeAsString(ioMaps[i].type) << ")" << std::endl;
fmt::print(" {:s}\n", formatMappingAsString(ioMaps[i]));
}
}
if (kern.getInterupts().isSet())
{
fnd::List<uint16_t> interupts = kern.getInterupts().getInteruptList();
std::cout << " Interupts Flags:" << std::endl;
for (uint32_t i = 0; i < interupts.size(); i++)
std::vector<std::string> interupts;
for (auto itr = kern.getInterupts().getInteruptList().begin(); itr != kern.getInterupts().getInteruptList().end(); itr++)
{
if (i % 10 == 0)
{
if (i != 0)
std::cout << std::endl;
std::cout << " ";
}
std::cout << "0x" << std::hex << (uint32_t)interupts[i];
if (interupts[i] != interupts.atBack())
std::cout << ", ";
interupts.push_back(fmt::format("0x{:x}", *itr));
}
std::cout << std::endl;
fmt::print(" Interupts Flags:\n");
fmt::print("{:s}", tc::cli::FormatUtil::formatListWithLineLimit(interupts, 60, 4));
}
if (kern.getMiscParams().isSet())
{
std::cout << " ProgramType: " << nn::hac::KernelCapabilityUtil::getProgramTypeAsString(kern.getMiscParams().getProgramType()) << " (" << std::dec << (uint32_t)kern.getMiscParams().getProgramType() << ")" << std::endl;
fmt::print(" ProgramType: {:s} ({:d})\n", nn::hac::KernelCapabilityUtil::getProgramTypeAsString(kern.getMiscParams().getProgramType()), kern.getMiscParams().getProgramType());
}
if (kern.getKernelVersion().isSet())
{
std::cout << " Kernel Version: " << std::dec << (uint32_t)kern.getKernelVersion().getVerMajor() << "." << (uint32_t)kern.getKernelVersion().getVerMinor() << std::endl;
fmt::print(" Kernel Version: {:d}.{:d}\n", kern.getKernelVersion().getVerMajor(), kern.getKernelVersion().getVerMinor());
}
if (kern.getHandleTableSize().isSet())
{
std::cout << " Handle Table Size: 0x" << std::hex << kern.getHandleTableSize().getHandleTableSize() << std::endl;
fmt::print(" Handle Table Size: 0x{:x}\n", kern.getHandleTableSize().getHandleTableSize());
}
if (kern.getMiscFlags().isSet())
{
auto misc_flags = kern.getMiscFlags().getMiscFlags();
std::cout << " Misc Flags:" << std::endl;
fmt::print(" Misc Flags:\n");
std::vector<std::string> misc_flags_names;
for (size_t misc_flags_bit = 0; misc_flags_bit < misc_flags.size(); misc_flags_bit++)
{
if (misc_flags.test(misc_flags_bit))
misc_flags_names.push_back(nn::hac::KernelCapabilityUtil::getMiscFlagsBitAsString(nn::hac::kc::MiscFlagsBit(misc_flags_bit)));
}
fnd::SimpleTextOutput::dumpStringList(misc_flags_names, 60, 4);
fmt::print("{:s}", tc::cli::FormatUtil::formatListWithLineLimit(misc_flags_names, 60, 4));
}
}
std::string nstool::KipProcess::formatMappingAsString(const nn::hac::MemoryMappingHandler::sMemoryMapping& map) const
{
return fmt::format("0x{:016x} - 0x{:016x} (perm={:s}) (type={:s})", ((uint64_t)map.addr << 12), (((uint64_t)(map.addr + map.size) << 12) - 1), nn::hac::KernelCapabilityUtil::getMemoryPermissionAsString(map.perm), nn::hac::KernelCapabilityUtil::getMappingTypeAsString(map.type));
}

View file

@ -1,12 +1,9 @@
#pragma once
#include <vector>
#include <string>
#include <fnd/types.h>
#include <fnd/IFile.h>
#include <fnd/SharedPtr.h>
#include "types.h"
#include <nn/hac/KernelInitialProcessHeader.h>
#include "common.h"
namespace nstool {
class KipProcess
{
@ -15,21 +12,26 @@ public:
void process();
void setInputFile(const fnd::SharedPtr<fnd::IFile>& file);
void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify);
private:
const std::string kModuleName = "KipProcess";
std::string mModuleName;
fnd::SharedPtr<fnd::IFile> mFile;
std::shared_ptr<tc::io::IStream> mFile;
CliOutputMode mCliOutputMode;
bool mVerify;
nn::hac::KernelInitialProcessHeader mHdr;
fnd::Vec<byte_t> mTextBlob, mRoBlob, mDataBlob;
tc::ByteData mTextBlob, mRoBlob, mDataBlob;
void importHeader();
void importCodeSegments();
size_t decompressData(const byte_t* src, size_t src_len, byte_t* dst, size_t dst_capacity);
void displayHeader();
void displayKernelCap(const nn::hac::KernelCapabilityControl& kern);
std::string formatMappingAsString(const nn::hac::MemoryMappingHandler::sMemoryMapping& map) const;
};
}

View file

@ -1,23 +1,19 @@
#include "MetaProcess.h"
#include <iostream>
#include <iomanip>
#include <nn/hac/AccessControlInfoUtil.h>
#include <nn/hac/FileSystemAccessUtil.h>
#include <nn/hac/KernelCapabilityUtil.h>
#include <nn/hac/MetaUtil.h>
#include <fnd/SimpleTextOutput.h>
MetaProcess::MetaProcess() :
nstool::MetaProcess::MetaProcess() :
mModuleName("nstool::MetaProcess"),
mFile(),
mCliOutputMode(_BIT(OUTPUT_BASIC)),
mCliOutputMode(true, false, false, false),
mVerify(false)
{
}
void MetaProcess::process()
void nstool::MetaProcess::process()
{
importMeta();
@ -27,7 +23,7 @@ void MetaProcess::process()
validateAciFromAcid(mMeta.getAccessControlInfo(), mMeta.getAccessControlInfoDesc());
}
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
if (mCliOutputMode.show_basic_info)
{
// npdm binary
displayMetaHeader(mMeta);
@ -39,7 +35,7 @@ void MetaProcess::process()
displayKernelCap(mMeta.getAccessControlInfo().getKernelCapabilities());
// acid binary
if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mCliOutputMode.show_extended_info)
{
displayAciDescHdr(mMeta.getAccessControlInfoDesc());
displayFac(mMeta.getAccessControlInfoDesc().getFileSystemAccessControl());
@ -49,80 +45,99 @@ void MetaProcess::process()
}
}
void MetaProcess::setInputFile(const fnd::SharedPtr<fnd::IFile>& file)
void nstool::MetaProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
{
mFile = file;
}
void MetaProcess::setKeyCfg(const KeyConfiguration& keycfg)
void nstool::MetaProcess::setKeyCfg(const KeyBag& keycfg)
{
mKeyCfg = keycfg;
}
void MetaProcess::setCliOutputMode(CliOutputMode type)
void nstool::MetaProcess::setCliOutputMode(CliOutputMode type)
{
mCliOutputMode = type;
}
void MetaProcess::setVerifyMode(bool verify)
void nstool::MetaProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
const nn::hac::Meta& MetaProcess::getMeta() const
const nn::hac::Meta& nstool::MetaProcess::getMeta() const
{
return mMeta;
}
void MetaProcess::importMeta()
void nstool::MetaProcess::importMeta()
{
fnd::Vec<byte_t> scratch;
if (*mFile == nullptr)
if (mFile == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
throw tc::Exception(mModuleName, "No file reader set.");
}
if (mFile->canRead() == false || mFile->canSeek() == false)
{
throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions.");
}
scratch.alloc((*mFile)->size());
(*mFile)->read(scratch.data(), 0, scratch.size());
// check if file_size is greater than 20MB, don't import.
size_t file_size = tc::io::IOUtil::castInt64ToSize(mFile->length());
if (file_size > (0x100000 * 20))
{
throw tc::Exception(mModuleName, "File too large.");
}
// read meta
tc::ByteData scratch = tc::ByteData(file_size);
mFile->seek(0, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
mMeta.fromBytes(scratch.data(), scratch.size());
}
void MetaProcess::validateAcidSignature(const nn::hac::AccessControlInfoDesc& acid, byte_t key_generation)
void nstool::MetaProcess::validateAcidSignature(const nn::hac::AccessControlInfoDesc& acid, byte_t key_generation)
{
try {
fnd::rsa::sRsa2048Key acid_sign_key;
if (mKeyCfg.getAcidSignKey(acid_sign_key, key_generation) != true)
throw fnd::Exception();
if (mKeyCfg.acid_sign_key.find(key_generation) == mKeyCfg.acid_sign_key.end())
{
throw tc::Exception("Failed to load rsa public key");
}
acid.validateSignature(acid_sign_key);
acid.validateSignature(mKeyCfg.acid_sign_key.at(key_generation));
}
catch (...) {
std::cout << "[WARNING] ACID Signature: FAIL" << std::endl;
catch (tc::Exception& e) {
fmt::print("[WARNING] ACID Signature: FAIL ({:s})\n", e.error());
}
}
void MetaProcess::validateAciFromAcid(const nn::hac::AccessControlInfo& aci, const nn::hac::AccessControlInfoDesc& acid)
void nstool::MetaProcess::validateAciFromAcid(const nn::hac::AccessControlInfo& aci, const nn::hac::AccessControlInfoDesc& acid)
{
// check Program ID
if (acid.getProgramIdRestrict().min > 0 && aci.getProgramId() < acid.getProgramIdRestrict().min)
{
std::cout << "[WARNING] ACI ProgramId: FAIL (Outside Legal Range)" << std::endl;
fmt::print("[WARNING] ACI ProgramId: FAIL (Outside Legal Range)\n");
}
else if (acid.getProgramIdRestrict().max > 0 && aci.getProgramId() > acid.getProgramIdRestrict().max)
{
std::cout << "[WARNING] ACI ProgramId: FAIL (Outside Legal Range)" << std::endl;
fmt::print("[WARNING] ACI ProgramId: FAIL (Outside Legal Range)\n");
}
auto fs_access = aci.getFileSystemAccessControl().getFsAccess();
auto desc_fs_access = acid.getFileSystemAccessControl().getFsAccess();
for (size_t i = 0; i < fs_access.size(); i++)
{
if (fs_access.test(i) && desc_fs_access.test(i) == false)
bool rightFound = false;
for (size_t j = 0; j < desc_fs_access.size() && rightFound == false; j++)
{
std::cout << "[WARNING] ACI/FAC FsaRights: FAIL (" << nn::hac::FileSystemAccessUtil::getFsAccessFlagAsString(nn::hac::fac::FsAccessFlag(i)) << " not permitted)" << std::endl;
if (fs_access[i] == desc_fs_access[j])
rightFound = true;
}
if (rightFound == false)
{
fmt::print("[WARNING] ACI/FAC FsaRights: FAIL ({:s} not permitted)\n", nn::hac::FileSystemAccessUtil::getFsAccessFlagAsString(fs_access[i]));
}
}
@ -138,7 +153,7 @@ void MetaProcess::validateAciFromAcid(const nn::hac::AccessControlInfo& aci, con
if (rightFound == false)
{
std::cout << "[WARNING] ACI/FAC ContentOwnerId: FAIL (" << std::hex << std::setw(16) << std::setfill('0') << aci.getFileSystemAccessControl().getContentOwnerIdList()[i] << " not permitted)" << std::endl;
fmt::print("[WARNING] ACI/FAC ContentOwnerId: FAIL (0x{:016x} not permitted)\n", aci.getFileSystemAccessControl().getContentOwnerIdList()[i]);
}
}
@ -154,7 +169,7 @@ void MetaProcess::validateAciFromAcid(const nn::hac::AccessControlInfo& aci, con
if (rightFound == false)
{
std::cout << "[WARNING] ACI/FAC SaveDataOwnerId: FAIL (" << std::hex << std::setw(16) << std::setfill('0') << aci.getFileSystemAccessControl().getSaveDataOwnerIdList()[i].id << "(" << std::dec << (uint32_t)aci.getFileSystemAccessControl().getSaveDataOwnerIdList()[i].access_type << ") not permitted)" << std::endl;
fmt::print("[WARNING] ACI/FAC SaveDataOwnerId: FAIL (0x{:016x} ({:d}) not permitted)\n", aci.getFileSystemAccessControl().getSaveDataOwnerIdList()[i].id, aci.getFileSystemAccessControl().getSaveDataOwnerIdList()[i].access_type);
}
}
@ -170,7 +185,7 @@ void MetaProcess::validateAciFromAcid(const nn::hac::AccessControlInfo& aci, con
if (rightFound == false)
{
std::cout << "[WARNING] ACI/SAC ServiceList: FAIL (" << aci.getServiceAccessControl().getServiceList()[i].getName() << (aci.getServiceAccessControl().getServiceList()[i].isServer()? " (Server)" : "") << " not permitted)" << std::endl;
fmt::print("[WARNING] ACI/SAC ServiceList: FAIL ({:s}{:s} not permitted)\n", aci.getServiceAccessControl().getServiceList()[i].getName(), (aci.getServiceAccessControl().getServiceList()[i].isServer()? " (Server)" : ""));
}
}
@ -178,19 +193,19 @@ void MetaProcess::validateAciFromAcid(const nn::hac::AccessControlInfo& aci, con
// check thread info
if (aci.getKernelCapabilities().getThreadInfo().getMaxCpuId() != acid.getKernelCapabilities().getThreadInfo().getMaxCpuId())
{
std::cout << "[WARNING] ACI/KC ThreadInfo/MaxCpuId: FAIL (" << std::dec << (uint32_t)aci.getKernelCapabilities().getThreadInfo().getMaxCpuId() << " not permitted)" << std::endl;
fmt::print("[WARNING] ACI/KC ThreadInfo/MaxCpuId: FAIL ({:d} not permitted)\n", aci.getKernelCapabilities().getThreadInfo().getMaxCpuId());
}
if (aci.getKernelCapabilities().getThreadInfo().getMinCpuId() != acid.getKernelCapabilities().getThreadInfo().getMinCpuId())
{
std::cout << "[WARNING] ACI/KC ThreadInfo/MinCpuId: FAIL (" << std::dec << (uint32_t)aci.getKernelCapabilities().getThreadInfo().getMinCpuId() << " not permitted)" << std::endl;
fmt::print("[WARNING] ACI/KC ThreadInfo/MinCpuId: FAIL ({:d} not permitted)\n", aci.getKernelCapabilities().getThreadInfo().getMinCpuId());
}
if (aci.getKernelCapabilities().getThreadInfo().getMaxPriority() != acid.getKernelCapabilities().getThreadInfo().getMaxPriority())
{
std::cout << "[WARNING] ACI/KC ThreadInfo/MaxPriority: FAIL (" << std::dec << (uint32_t)aci.getKernelCapabilities().getThreadInfo().getMaxPriority() << " not permitted)" << std::endl;
fmt::print("[WARNING] ACI/KC ThreadInfo/MaxPriority: FAIL ({:d} not permitted)\n", aci.getKernelCapabilities().getThreadInfo().getMaxPriority());
}
if (aci.getKernelCapabilities().getThreadInfo().getMinPriority() != acid.getKernelCapabilities().getThreadInfo().getMinPriority())
{
std::cout << "[WARNING] ACI/KC ThreadInfo/MinPriority: FAIL (" << std::dec << (uint32_t)aci.getKernelCapabilities().getThreadInfo().getMinPriority() << " not permitted)" << std::endl;
fmt::print("[WARNING] ACI/KC ThreadInfo/MinPriority: FAIL ({:d} not permitted)\n", aci.getKernelCapabilities().getThreadInfo().getMinPriority());
}
// check system calls
auto syscall_ids = aci.getKernelCapabilities().getSystemCalls().getSystemCallIds();
@ -199,7 +214,7 @@ void MetaProcess::validateAciFromAcid(const nn::hac::AccessControlInfo& aci, con
{
if (syscall_ids.test(i) && desc_syscall_ids.test(i) == false)
{
std::cout << "[WARNING] ACI/KC SystemCallList: FAIL (" << nn::hac::KernelCapabilityUtil::getSystemCallIdAsString(nn::hac::kc::SystemCallId(i)) << " not permitted)" << std::endl;
fmt::print("[WARNING] ACI/KC SystemCallList: FAIL ({:s} not permitted)\n", nn::hac::KernelCapabilityUtil::getSystemCallIdAsString(nn::hac::kc::SystemCallId(i)));
}
}
// check memory maps
@ -216,7 +231,7 @@ void MetaProcess::validateAciFromAcid(const nn::hac::AccessControlInfo& aci, con
{
auto map = aci.getKernelCapabilities().getMemoryMaps().getMemoryMaps()[i];
std::cout << "[WARNING] ACI/KC MemoryMap: FAIL (0x" << std::hex << std::setw(16) << std::setfill('0') << ((uint64_t)map.addr << 12) << " - 0x" << std::hex << std::setw(16) << std::setfill('0') << (((uint64_t)(map.addr + map.size) << 12) - 1) << " (perm=" << nn::hac::KernelCapabilityUtil::getMemoryPermissionAsString(map.perm) << ") (type=" << nn::hac::KernelCapabilityUtil::getMappingTypeAsString(map.type) << ") not permitted)" << std::endl;
fmt::print("[WARNING] ACI/KC MemoryMap: FAIL ({:s} not permitted)\n", formatMappingAsString(map));
}
}
for (size_t i = 0; i < aci.getKernelCapabilities().getMemoryMaps().getIoMemoryMaps().size(); i++)
@ -232,7 +247,7 @@ void MetaProcess::validateAciFromAcid(const nn::hac::AccessControlInfo& aci, con
{
auto map = aci.getKernelCapabilities().getMemoryMaps().getIoMemoryMaps()[i];
std::cout << "[WARNING] ACI/KC IoMemoryMap: FAIL (0x" << std::hex << std::setw(16) << std::setfill('0') << ((uint64_t)map.addr << 12) << " - 0x" << std::hex << std::setw(16) << std::setfill('0') << (((uint64_t)(map.addr + map.size) << 12) - 1) << " (perm=" << nn::hac::KernelCapabilityUtil::getMemoryPermissionAsString(map.perm) << ") (type=" << nn::hac::KernelCapabilityUtil::getMappingTypeAsString(map.type) << ") not permitted)" << std::endl;
fmt::print("[WARNING] ACI/KC IoMemoryMap: FAIL ({:s} not permitted)\n", formatMappingAsString(map));
}
}
// check interupts
@ -247,25 +262,25 @@ void MetaProcess::validateAciFromAcid(const nn::hac::AccessControlInfo& aci, con
if (rightFound == false)
{
std::cout << "[WARNING] ACI/KC InteruptsList: FAIL (0x" << std::hex << (uint32_t)aci.getKernelCapabilities().getInterupts().getInteruptList()[i] << " not permitted)" << std::endl;
fmt::print("[WARNING] ACI/KC InteruptsList: FAIL (0x{:x} not permitted)\n", aci.getKernelCapabilities().getInterupts().getInteruptList()[i]);
}
}
// check misc params
if (aci.getKernelCapabilities().getMiscParams().getProgramType() != acid.getKernelCapabilities().getMiscParams().getProgramType())
{
std::cout << "[WARNING] ACI/KC ProgramType: FAIL (" << std::dec << (uint32_t)aci.getKernelCapabilities().getMiscParams().getProgramType() << " not permitted)" << std::endl;
fmt::print("[WARNING] ACI/KC ProgramType: FAIL ({:d} not permitted)\n", aci.getKernelCapabilities().getMiscParams().getProgramType());
}
// check kernel version
uint32_t aciKernelVersion = (uint32_t)aci.getKernelCapabilities().getKernelVersion().getVerMajor() << 16 | (uint32_t)aci.getKernelCapabilities().getKernelVersion().getVerMinor();
uint32_t acidKernelVersion = (uint32_t)acid.getKernelCapabilities().getKernelVersion().getVerMajor() << 16 | (uint32_t)acid.getKernelCapabilities().getKernelVersion().getVerMinor();
if (aciKernelVersion < acidKernelVersion)
{
std::cout << "[WARNING] ACI/KC RequiredKernelVersion: FAIL (" << std::dec << aci.getKernelCapabilities().getKernelVersion().getVerMajor() << "." << aci.getKernelCapabilities().getKernelVersion().getVerMinor() << " not permitted)" << std::endl;
fmt::print("[WARNING] ACI/KC RequiredKernelVersion: FAIL ({:d}.{:d} not permitted)\n", aci.getKernelCapabilities().getKernelVersion().getVerMajor(), aci.getKernelCapabilities().getKernelVersion().getVerMinor());
}
// check handle table size
if (aci.getKernelCapabilities().getHandleTableSize().getHandleTableSize() > acid.getKernelCapabilities().getHandleTableSize().getHandleTableSize())
{
std::cout << "[WARNING] ACI/KC HandleTableSize: FAIL (0x" << std::hex << (uint32_t)aci.getKernelCapabilities().getHandleTableSize().getHandleTableSize() << " too large)" << std::endl;
fmt::print("[WARNING] ACI/KC HandleTableSize: FAIL (0x{:x} too large)\n", aci.getKernelCapabilities().getHandleTableSize().getHandleTableSize());
}
// check misc flags
auto misc_flags = aci.getKernelCapabilities().getMiscFlags().getMiscFlags();
@ -274,205 +289,187 @@ void MetaProcess::validateAciFromAcid(const nn::hac::AccessControlInfo& aci, con
{
if (misc_flags.test(i) && desc_misc_flags.test(i) == false)
{
std::cout << "[WARNING] ACI/KC MiscFlag: FAIL (" << nn::hac::KernelCapabilityUtil::getMiscFlagsBitAsString(nn::hac::kc::MiscFlagsBit(i)) << " not permitted)" << std::endl;
fmt::print("[WARNING] ACI/KC MiscFlag: FAIL ({:s} not permitted)\n", nn::hac::KernelCapabilityUtil::getMiscFlagsBitAsString(nn::hac::kc::MiscFlagsBit(i)));
}
}
}
void MetaProcess::displayMetaHeader(const nn::hac::Meta& hdr)
void nstool::MetaProcess::displayMetaHeader(const nn::hac::Meta& hdr)
{
std::cout << "[Meta Header]" << std::endl;
std::cout << " ACID KeyGeneration: " << std::dec << (uint32_t)hdr.getAccessControlInfoDescKeyGeneration() << std::endl;
std::cout << " Flags:" << std::endl;
std::cout << " Is64BitInstruction: " << std::boolalpha << hdr.getIs64BitInstructionFlag() << std::endl;
std::cout << " ProcessAddressSpace: " << nn::hac::MetaUtil::getProcessAddressSpaceAsString(hdr.getProcessAddressSpace()) << std::endl;
std::cout << " OptimizeMemoryAllocation: " << std::boolalpha << hdr.getOptimizeMemoryAllocationFlag() << std::endl;
std::cout << " SystemResourceSize: 0x" << std::hex << hdr.getSystemResourceSize() << std::endl;
std::cout << " Main Thread Params:" << std::endl;
std::cout << " Priority: " << std::dec << (uint32_t)hdr.getMainThreadPriority() << std::endl;
std::cout << " CpuId: " << std::dec << (uint32_t)hdr.getMainThreadCpuId() << std::endl;
std::cout << " StackSize: 0x" << std::hex << hdr.getMainThreadStackSize() << std::endl;
std::cout << " TitleInfo:" << std::endl;
std::cout << " Version: v" << std::dec << hdr.getVersion() << std::endl;
std::cout << " Name: " << hdr.getName() << std::endl;
fmt::print("[Meta Header]\n");
fmt::print(" ACID KeyGeneration: {:d}\n", hdr.getAccessControlInfoDescKeyGeneration());
fmt::print(" Flags:\n");
fmt::print(" Is64BitInstruction: {}\n", hdr.getIs64BitInstructionFlag());
fmt::print(" ProcessAddressSpace: {:s}\n", nn::hac::MetaUtil::getProcessAddressSpaceAsString(hdr.getProcessAddressSpace()));
fmt::print(" OptimizeMemoryAllocation: {}\n", hdr.getOptimizeMemoryAllocationFlag());
fmt::print(" SystemResourceSize: 0x{:x}\n", hdr.getSystemResourceSize());
fmt::print(" Main Thread Params:\n");
fmt::print(" Priority: {:d}\n", hdr.getMainThreadPriority());
fmt::print(" CpuId: {:d}\n", hdr.getMainThreadCpuId());
fmt::print(" StackSize: 0x{:x}\n", hdr.getMainThreadStackSize());
fmt::print(" TitleInfo:\n");
fmt::print(" Version: v{:d}\n", hdr.getVersion());
fmt::print(" Name: {:s}\n", hdr.getName());
if (hdr.getProductCode().length())
{
std::cout << " ProductCode: " << hdr.getProductCode() << std::endl;
fmt::print(" ProductCode: {:s}\n", hdr.getProductCode());
}
}
void MetaProcess::displayAciHdr(const nn::hac::AccessControlInfo& aci)
void nstool::MetaProcess::displayAciHdr(const nn::hac::AccessControlInfo& aci)
{
std::cout << "[Access Control Info]" << std::endl;
std::cout << " ProgramID: 0x" << std::hex << std::setw(16) << std::setfill('0') << aci.getProgramId() << std::endl;
fmt::print("[Access Control Info]\n");
fmt::print(" ProgramID: 0x{:016x}\n", aci.getProgramId());
}
void MetaProcess::displayAciDescHdr(const nn::hac::AccessControlInfoDesc& acid)
void nstool::MetaProcess::displayAciDescHdr(const nn::hac::AccessControlInfoDesc& acid)
{
std::cout << "[Access Control Info Desc]" << std::endl;
std::cout << " Flags: " << std::endl;
std::cout << " Production: " << std::boolalpha << acid.getProductionFlag() << std::endl;
std::cout << " Unqualified Approval: " << std::boolalpha << acid.getUnqualifiedApprovalFlag() << std::endl;
std::cout << " Memory Region: " << nn::hac::AccessControlInfoUtil::getMemoryRegionAsString(acid.getMemoryRegion()) << " (" << std::dec << (uint32_t)acid.getMemoryRegion() << ")" << std::endl;
std::cout << " ProgramID Restriction" << std::endl;
std::cout << " Min: 0x" << std::hex << std::setw(16) << std::setfill('0') << acid.getProgramIdRestrict().min << std::endl;
std::cout << " Max: 0x" << std::hex << std::setw(16) << std::setfill('0') << acid.getProgramIdRestrict().max << std::endl;
fmt::print("[Access Control Info Desc]\n");
fmt::print(" Flags: \n");
fmt::print(" Production: {}\n", acid.getProductionFlag());
fmt::print(" Unqualified Approval: {}\n", acid.getUnqualifiedApprovalFlag());
fmt::print(" Memory Region: {:s} ({:d})\n", nn::hac::AccessControlInfoUtil::getMemoryRegionAsString(acid.getMemoryRegion()), acid.getMemoryRegion());
fmt::print(" ProgramID Restriction\n");
fmt::print(" Min: 0x{:016x}\n", acid.getProgramIdRestrict().min);
fmt::print(" Max: 0x{:016x}\n", acid.getProgramIdRestrict().max);
}
void MetaProcess::displayFac(const nn::hac::FileSystemAccessControl& fac)
void nstool::MetaProcess::displayFac(const nn::hac::FileSystemAccessControl& fac)
{
std::cout << "[FS Access Control]" << std::endl;
std::cout << " Format Version: " << std::dec << (uint32_t)fac.getFormatVersion() << std::endl;
fmt::print("[FS Access Control]\n");
fmt::print(" Format Version: {:d}\n", fac.getFormatVersion());
auto fs_access = fac.getFsAccess();
if (fs_access.any())
if (fac.getFsAccess().size())
{
std::cout << " FsAccess:" << std::endl;
// TODO this formatting loop could be a utils function
for (size_t flag = 0, printed = 0; flag < fs_access.size(); flag++)
std::vector<std::string> fs_access_str_list;
for (auto itr = fac.getFsAccess().begin(); itr != fac.getFsAccess().end(); itr++)
{
// skip unset flags
if (fs_access.test(flag) == false)
continue;
// format the strings
// for each 10 printed we do a new line
if (printed % 10 == 0)
std::string flag_string = nn::hac::FileSystemAccessUtil::getFsAccessFlagAsString(nn::hac::fac::FsAccessFlag(*itr));
if (mCliOutputMode.show_extended_info)
{
// skip new line when we haven't printed anything
if (printed != 0)
std::cout << std::endl;
std::cout << " ";
fs_access_str_list.push_back(fmt::format("{:s} (bit {:d})", flag_string, *itr));
}
// within a line we want to separate the next string from the last one with a comma and a space
else
{
std::cout << ", ";
fs_access_str_list.push_back(flag_string);
}
printed++;
// output string info
std::cout << nn::hac::FileSystemAccessUtil::getFsAccessFlagAsString(nn::hac::fac::FsAccessFlag(flag));
if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
std::cout << " (bit " << std::dec << (uint32_t)flag << ")";
}
std::cout << std::endl;
fmt::print(" FsAccess:\n");
fmt::print("{:s}", tc::cli::FormatUtil::formatListWithLineLimit(fs_access_str_list, 60, 4));
}
if (fac.getContentOwnerIdList().size())
{
std::cout << " Content Owner IDs:" << std::endl;
fmt::print(" Content Owner IDs:\n");
for (size_t i = 0; i < fac.getContentOwnerIdList().size(); i++)
{
std::cout << " 0x" << std::hex << std::setw(16) << std::setfill('0') << fac.getContentOwnerIdList()[i] << std::endl;
fmt::print(" 0x{:016x}\n", fac.getContentOwnerIdList()[i]);
}
}
if (fac.getSaveDataOwnerIdList().size())
{
std::cout << " Save Data Owner IDs:" << std::endl;
fmt::print(" Save Data Owner IDs:\n");
for (size_t i = 0; i < fac.getSaveDataOwnerIdList().size(); i++)
{
std::cout << " 0x" << std::hex << std::setw(16) << std::setfill('0') << fac.getSaveDataOwnerIdList()[i].id << " (" << nn::hac::FileSystemAccessUtil::getSaveDataOwnerAccessModeAsString(fac.getSaveDataOwnerIdList()[i].access_type) << ")" << std::endl;
fmt::print(" 0x{:016x} ({:s})\n", fac.getSaveDataOwnerIdList()[i].id, nn::hac::FileSystemAccessUtil::getSaveDataOwnerAccessModeAsString(fac.getSaveDataOwnerIdList()[i].access_type));
}
}
}
void MetaProcess::displaySac(const nn::hac::ServiceAccessControl& sac)
void nstool::MetaProcess::displaySac(const nn::hac::ServiceAccessControl& sac)
{
std::cout << "[Service Access Control]" << std::endl;
std::cout << " Service List:" << std::endl;
fmt::print("[Service Access Control]\n");
fmt::print(" Service List:\n");
std::vector<std::string> service_name_list;
for (size_t i = 0; i < sac.getServiceList().size(); i++)
{
service_name_list.push_back(sac.getServiceList()[i].getName() + (sac.getServiceList()[i].isServer() ? "(isSrv)" : ""));
}
fnd::SimpleTextOutput::dumpStringList(service_name_list, 60, 4);
fmt::print("{:s}", tc::cli::FormatUtil::formatListWithLineLimit(service_name_list, 60, 4));
}
void MetaProcess::displayKernelCap(const nn::hac::KernelCapabilityControl& kern)
void nstool::MetaProcess::displayKernelCap(const nn::hac::KernelCapabilityControl& kern)
{
std::cout << "[Kernel Capabilities]" << std::endl;
fmt::print("[Kernel Capabilities]\n");
if (kern.getThreadInfo().isSet())
{
nn::hac::ThreadInfoHandler threadInfo = kern.getThreadInfo();
std::cout << " Thread Priority:" << std::endl;
std::cout << " Min: " << std::dec << (uint32_t)threadInfo.getMinPriority() << std::endl;
std::cout << " Max: " << std::dec << (uint32_t)threadInfo.getMaxPriority() << std::endl;
std::cout << " CpuId:" << std::endl;
std::cout << " Min: " << std::dec << (uint32_t)threadInfo.getMinCpuId() << std::endl;
std::cout << " Max: " << std::dec << (uint32_t)threadInfo.getMaxCpuId() << std::endl;
fmt::print(" Thread Priority:\n");
fmt::print(" Min: {:d}\n", threadInfo.getMinPriority());
fmt::print(" Max: {:d}\n", threadInfo.getMaxPriority());
fmt::print(" CpuId:\n");
fmt::print(" Min: {:d}\n", threadInfo.getMinCpuId());
fmt::print(" Max: {:d}\n", threadInfo.getMaxCpuId());
}
if (kern.getSystemCalls().isSet())
{
auto syscall_ids = kern.getSystemCalls().getSystemCallIds();
std::cout << " SystemCalls:" << std::endl;
fmt::print(" SystemCalls:\n");
std::vector<std::string> syscall_names;
for (size_t syscall_id = 0; syscall_id < syscall_ids.size(); syscall_id++)
{
if (syscall_ids.test(syscall_id))
syscall_names.push_back(nn::hac::KernelCapabilityUtil::getSystemCallIdAsString(nn::hac::kc::SystemCallId(syscall_id)));
}
fnd::SimpleTextOutput::dumpStringList(syscall_names, 60, 4);
fmt::print("{:s}", tc::cli::FormatUtil::formatListWithLineLimit(syscall_names, 60, 4));
}
if (kern.getMemoryMaps().isSet())
{
auto maps = kern.getMemoryMaps().getMemoryMaps();
auto ioMaps = kern.getMemoryMaps().getIoMemoryMaps();
std::cout << " MemoryMaps:" << std::endl;
fmt::print(" MemoryMaps:\n");
for (size_t i = 0; i < maps.size(); i++)
{
std::cout << " 0x" << std::hex << std::setw(16) << std::setfill('0') << ((uint64_t)maps[i].addr << 12) << " - 0x" << std::hex << std::setw(16) << std::setfill('0') << (((uint64_t)(maps[i].addr + maps[i].size) << 12) - 1) << " (perm=" << nn::hac::KernelCapabilityUtil::getMemoryPermissionAsString(maps[i].perm) << ") (type=" << nn::hac::KernelCapabilityUtil::getMappingTypeAsString(maps[i].type) << ")" << std::endl;
fmt::print(" {:s}\n", formatMappingAsString(maps[i]));
}
//std::cout << " IoMaps:" << std::endl;
//fmt::print(" IoMaps:\n");
for (size_t i = 0; i < ioMaps.size(); i++)
{
std::cout << " 0x" << std::hex << std::setw(16) << std::setfill('0') << ((uint64_t)ioMaps[i].addr << 12) << " - 0x" << std::hex << std::setw(16) << std::setfill('0') << (((uint64_t)(ioMaps[i].addr + ioMaps[i].size) << 12) - 1) << " (perm=" << nn::hac::KernelCapabilityUtil::getMemoryPermissionAsString(ioMaps[i].perm) << ") (type=" << nn::hac::KernelCapabilityUtil::getMappingTypeAsString(ioMaps[i].type) << ")" << std::endl;
fmt::print(" {:s}\n", formatMappingAsString(ioMaps[i]));
}
}
if (kern.getInterupts().isSet())
{
fnd::List<uint16_t> interupts = kern.getInterupts().getInteruptList();
std::cout << " Interupts Flags:" << std::endl;
for (uint32_t i = 0; i < interupts.size(); i++)
std::vector<std::string> interupts;
for (auto itr = kern.getInterupts().getInteruptList().begin(); itr != kern.getInterupts().getInteruptList().end(); itr++)
{
if (i % 10 == 0)
{
if (i != 0)
std::cout << std::endl;
std::cout << " ";
}
std::cout << "0x" << std::hex << (uint32_t)interupts[i];
if (interupts[i] != interupts.atBack())
std::cout << ", ";
interupts.push_back(fmt::format("0x{:x}", *itr));
}
std::cout << std::endl;
fmt::print(" Interupts Flags:\n");
fmt::print("{:s}", tc::cli::FormatUtil::formatListWithLineLimit(interupts, 60, 4));
}
if (kern.getMiscParams().isSet())
{
std::cout << " ProgramType: " << nn::hac::KernelCapabilityUtil::getProgramTypeAsString(kern.getMiscParams().getProgramType()) << " (" << std::dec << (uint32_t)kern.getMiscParams().getProgramType() << ")" << std::endl;
fmt::print(" ProgramType: {:s} ({:d})\n", nn::hac::KernelCapabilityUtil::getProgramTypeAsString(kern.getMiscParams().getProgramType()), kern.getMiscParams().getProgramType());
}
if (kern.getKernelVersion().isSet())
{
std::cout << " Kernel Version: " << std::dec << (uint32_t)kern.getKernelVersion().getVerMajor() << "." << (uint32_t)kern.getKernelVersion().getVerMinor() << std::endl;
fmt::print(" Kernel Version: {:d}.{:d}\n", kern.getKernelVersion().getVerMajor(), kern.getKernelVersion().getVerMinor());
}
if (kern.getHandleTableSize().isSet())
{
std::cout << " Handle Table Size: 0x" << std::hex << kern.getHandleTableSize().getHandleTableSize() << std::endl;
fmt::print(" Handle Table Size: 0x{:x}\n", kern.getHandleTableSize().getHandleTableSize());
}
if (kern.getMiscFlags().isSet())
{
auto misc_flags = kern.getMiscFlags().getMiscFlags();
std::cout << " Misc Flags:" << std::endl;
fmt::print(" Misc Flags:\n");
std::vector<std::string> misc_flags_names;
for (size_t misc_flags_bit = 0; misc_flags_bit < misc_flags.size(); misc_flags_bit++)
{
if (misc_flags.test(misc_flags_bit))
misc_flags_names.push_back(nn::hac::KernelCapabilityUtil::getMiscFlagsBitAsString(nn::hac::kc::MiscFlagsBit(misc_flags_bit)));
}
fnd::SimpleTextOutput::dumpStringList(misc_flags_names, 60, 4);
fmt::print("{:s}", tc::cli::FormatUtil::formatListWithLineLimit(misc_flags_names, 60, 4));
}
}
std::string nstool::MetaProcess::formatMappingAsString(const nn::hac::MemoryMappingHandler::sMemoryMapping& map) const
{
return fmt::format("0x{:016x} - 0x{:016x} (perm={:s}) (type={:s})", ((uint64_t)map.addr << 12), (((uint64_t)(map.addr + map.size) << 12) - 1), nn::hac::KernelCapabilityUtil::getMemoryPermissionAsString(map.perm), nn::hac::KernelCapabilityUtil::getMappingTypeAsString(map.type));
}

View file

@ -1,12 +1,10 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/IFile.h>
#include <fnd/SharedPtr.h>
#include <nn/hac/Meta.h>
#include "KeyConfiguration.h"
#include "types.h"
#include "KeyBag.h"
#include "common.h"
#include <nn/hac/Meta.h>
namespace nstool {
class MetaProcess
{
@ -15,18 +13,18 @@ public:
void process();
void setInputFile(const fnd::SharedPtr<fnd::IFile>& file);
void setKeyCfg(const KeyConfiguration& keycfg);
void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setKeyCfg(const KeyBag& keycfg);
void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify);
const nn::hac::Meta& getMeta() const;
private:
const std::string kModuleName = "MetaProcess";
std::string mModuleName;
fnd::SharedPtr<fnd::IFile> mFile;
KeyConfiguration mKeyCfg;
std::shared_ptr<tc::io::IStream> mFile;
KeyBag mKeyCfg;
CliOutputMode mCliOutputMode;
bool mVerify;
@ -43,4 +41,8 @@ private:
void displayFac(const nn::hac::FileSystemAccessControl& fac);
void displaySac(const nn::hac::ServiceAccessControl& sac);
void displayKernelCap(const nn::hac::KernelCapabilityControl& kern);
std::string formatMappingAsString(const nn::hac::MemoryMappingHandler::sMemoryMapping& map) const;
};
}

View file

@ -1,525 +1,530 @@
#include "NacpProcess.h"
#include <sstream>
#include <iostream>
#include <iomanip>
#include <fnd/SimpleTextOutput.h>
#include <fnd/OffsetAdjustedIFile.h>
#include <nn/hac/ApplicationControlPropertyUtil.h>
NacpProcess::NacpProcess() :
nstool::NacpProcess::NacpProcess() :
mModuleName("nstool::NacpProcess"),
mFile(),
mCliOutputMode(_BIT(OUTPUT_BASIC)),
mCliOutputMode(true, false, false, false),
mVerify(false)
{
}
void NacpProcess::process()
void nstool::NacpProcess::process()
{
importNacp();
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
if (mCliOutputMode.show_basic_info)
displayNacp();
}
void NacpProcess::setInputFile(const fnd::SharedPtr<fnd::IFile>& file)
void nstool::NacpProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
{
mFile = file;
}
void NacpProcess::setCliOutputMode(CliOutputMode type)
void nstool::NacpProcess::setCliOutputMode(CliOutputMode type)
{
mCliOutputMode = type;
}
void NacpProcess::setVerifyMode(bool verify)
void nstool::NacpProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
const nn::hac::ApplicationControlProperty& NacpProcess::getApplicationControlProperty() const
const nn::hac::ApplicationControlProperty& nstool::NacpProcess::getApplicationControlProperty() const
{
return mNacp;
}
void NacpProcess::importNacp()
void nstool::NacpProcess::importNacp()
{
fnd::Vec<byte_t> scratch;
if (*mFile == nullptr)
if (mFile == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
throw tc::Exception(mModuleName, "No file reader set.");
}
if (mFile->canRead() == false || mFile->canSeek() == false)
{
throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions.");
}
scratch.alloc((*mFile)->size());
(*mFile)->read(scratch.data(), 0, scratch.size());
// check if file_size does matches expected size
size_t file_size = tc::io::IOUtil::castInt64ToSize(mFile->length());
if (file_size != sizeof(nn::hac::sApplicationControlProperty))
{
throw tc::Exception(mModuleName, "File was incorrect size.");
}
// read cnmt
tc::ByteData scratch = tc::ByteData(file_size);
mFile->seek(0, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
mNacp.fromBytes(scratch.data(), scratch.size());
}
void NacpProcess::displayNacp()
void nstool::NacpProcess::displayNacp()
{
std::cout << "[ApplicationControlProperty]" << std::endl;
fmt::print("[ApplicationControlProperty]\n");
// Title
if (mNacp.getTitle().size() > 0)
{
std::cout << " Title:" << std::endl;
fmt::print(" Title:\n");
for (auto itr = mNacp.getTitle().begin(); itr != mNacp.getTitle().end(); itr++)
{
std::cout << " " << nn::hac::ApplicationControlPropertyUtil::getLanguageAsString(itr->language) << ":" << std::endl;
std::cout << " Name: " << itr->name << std::endl;
std::cout << " Publisher: " << itr->publisher << std::endl;
fmt::print(" {:s}:\n", nn::hac::ApplicationControlPropertyUtil::getLanguageAsString(itr->language));
fmt::print(" Name: {:s}\n", itr->name);
fmt::print(" Publisher: {:s}\n", itr->publisher);
}
}
else if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
else if (mCliOutputMode.show_extended_info)
{
std::cout << " Title: None" << std::endl;
fmt::print(" Title: None\n");
}
// Isbn
if (mNacp.getIsbn().empty() == false)
{
std::cout << " ISBN: " << mNacp.getIsbn() << std::endl;
fmt::print(" ISBN: {:s}\n", mNacp.getIsbn());
}
else if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
else if (mCliOutputMode.show_extended_info)
{
std::cout << " ISBN: (NotSet)" << std::endl;
fmt::print(" ISBN: (NotSet)\n");
}
// StartupUserAccount
if (mNacp.getStartupUserAccount() != nn::hac::nacp::StartupUserAccount::None || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getStartupUserAccount() != nn::hac::nacp::StartupUserAccount::None || mCliOutputMode.show_extended_info)
{
std::cout << " StartupUserAccount: " << nn::hac::ApplicationControlPropertyUtil::getStartupUserAccountAsString(mNacp.getStartupUserAccount()) << std::endl;
fmt::print(" StartupUserAccount: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getStartupUserAccountAsString(mNacp.getStartupUserAccount()));
}
// UserAccountSwitchLock
if (mNacp.getUserAccountSwitchLock() != nn::hac::nacp::UserAccountSwitchLock::Disable || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getUserAccountSwitchLock() != nn::hac::nacp::UserAccountSwitchLock::Disable || mCliOutputMode.show_extended_info)
{
std::cout << " UserAccountSwitchLock: " << nn::hac::ApplicationControlPropertyUtil::getUserAccountSwitchLockAsString(mNacp.getUserAccountSwitchLock()) << std::endl;
fmt::print(" UserAccountSwitchLock: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getUserAccountSwitchLockAsString(mNacp.getUserAccountSwitchLock()));
}
// AddOnContentRegistrationType
if (mNacp.getAddOnContentRegistrationType() != nn::hac::nacp::AddOnContentRegistrationType::AllOnLaunch || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getAddOnContentRegistrationType() != nn::hac::nacp::AddOnContentRegistrationType::AllOnLaunch || mCliOutputMode.show_extended_info)
{
std::cout << " AddOnContentRegistrationType: " << nn::hac::ApplicationControlPropertyUtil::getAddOnContentRegistrationTypeAsString(mNacp.getAddOnContentRegistrationType()) << std::endl;
fmt::print(" AddOnContentRegistrationType: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getAddOnContentRegistrationTypeAsString(mNacp.getAddOnContentRegistrationType()));
}
// Attribute
if (mNacp.getAttribute().size() > 0)
{
std::cout << " Attribute:" << std::endl;
fmt::print(" Attribute:\n");
for (auto itr = mNacp.getAttribute().begin(); itr != mNacp.getAttribute().end(); itr++)
{
std::cout << " " << nn::hac::ApplicationControlPropertyUtil::getAttributeFlagAsString(*itr) << std::endl;
fmt::print(" {:s}\n", nn::hac::ApplicationControlPropertyUtil::getAttributeFlagAsString(*itr));
}
}
else if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
else if (mCliOutputMode.show_extended_info)
{
std::cout << " Attribute: None" << std::endl;
fmt::print(" Attribute: None\n");
}
// SupportedLanguage
if (mNacp.getSupportedLanguage().size() > 0)
{
std::cout << " SupportedLanguage:" << std::endl;
fmt::print(" SupportedLanguage:\n");
for (auto itr = mNacp.getSupportedLanguage().begin(); itr != mNacp.getSupportedLanguage().end(); itr++)
{
std::cout << " " << nn::hac::ApplicationControlPropertyUtil::getLanguageAsString(*itr) << std::endl;
fmt::print(" {:s}\n", nn::hac::ApplicationControlPropertyUtil::getLanguageAsString(*itr));
}
}
else if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
else if (mCliOutputMode.show_extended_info)
{
std::cout << " SupportedLanguage: None" << std::endl;
fmt::print(" SupportedLanguage: None\n");
}
// ParentalControl
if (mNacp.getParentalControl().size() > 0)
{
std::cout << " ParentalControl:" << std::endl;
fmt::print(" ParentalControl:\n");
for (auto itr = mNacp.getParentalControl().begin(); itr != mNacp.getParentalControl().end(); itr++)
{
std::cout << " " << nn::hac::ApplicationControlPropertyUtil::getParentalControlFlagAsString(*itr) << std::endl;
fmt::print(" {:s}\n", nn::hac::ApplicationControlPropertyUtil::getParentalControlFlagAsString(*itr));
}
}
else if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
else if (mCliOutputMode.show_extended_info)
{
std::cout << " ParentalControl: None" << std::endl;
fmt::print(" ParentalControl: None\n");
}
// Screenshot
if (mNacp.getScreenshot() != nn::hac::nacp::Screenshot::Allow || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getScreenshot() != nn::hac::nacp::Screenshot::Allow || mCliOutputMode.show_extended_info)
{
std::cout << " Screenshot: " << nn::hac::ApplicationControlPropertyUtil::getScreenshotAsString(mNacp.getScreenshot()) << std::endl;
fmt::print(" Screenshot: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getScreenshotAsString(mNacp.getScreenshot()));
}
// VideoCapture
if (mNacp.getVideoCapture() != nn::hac::nacp::VideoCapture::Disable || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getVideoCapture() != nn::hac::nacp::VideoCapture::Disable || mCliOutputMode.show_extended_info)
{
std::cout << " VideoCapture: " << nn::hac::ApplicationControlPropertyUtil::getVideoCaptureAsString(mNacp.getVideoCapture()) << std::endl;
fmt::print(" VideoCapture: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getVideoCaptureAsString(mNacp.getVideoCapture()));
}
// DataLossConfirmation
if (mNacp.getDataLossConfirmation() != nn::hac::nacp::DataLossConfirmation::None || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getDataLossConfirmation() != nn::hac::nacp::DataLossConfirmation::None || mCliOutputMode.show_extended_info)
{
std::cout << " DataLossConfirmation: " << nn::hac::ApplicationControlPropertyUtil::getDataLossConfirmationAsString(mNacp.getDataLossConfirmation()) << std::endl;
fmt::print(" DataLossConfirmation: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getDataLossConfirmationAsString(mNacp.getDataLossConfirmation()));
}
// PlayLogPolicy
if (mNacp.getPlayLogPolicy() != nn::hac::nacp::PlayLogPolicy::All || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getPlayLogPolicy() != nn::hac::nacp::PlayLogPolicy::All || mCliOutputMode.show_extended_info)
{
std::cout << " PlayLogPolicy: " << nn::hac::ApplicationControlPropertyUtil::getPlayLogPolicyAsString(mNacp.getPlayLogPolicy()) << std::endl;
fmt::print(" PlayLogPolicy: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getPlayLogPolicyAsString(mNacp.getPlayLogPolicy()));
}
// PresenceGroupId
if (mNacp.getPresenceGroupId() != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getPresenceGroupId() != 0 || mCliOutputMode.show_extended_info)
{
std::cout << " PresenceGroupId: 0x" << std::hex << std::setw(16) << std::setfill('0') << mNacp.getPresenceGroupId() << std::endl;
fmt::print(" PresenceGroupId: 0x{:016x}\n", mNacp.getPresenceGroupId());
}
// RatingAge
if (mNacp.getRatingAge().size() > 0)
{
std::cout << " RatingAge:" << std::endl;
fmt::print(" RatingAge:\n");
for (auto itr = mNacp.getRatingAge().begin(); itr != mNacp.getRatingAge().end(); itr++)
{
std::cout << " " << nn::hac::ApplicationControlPropertyUtil::getOrganisationAsString(itr->organisation) << ":" << std::endl;
std::cout << " Age: " << std::dec << (uint32_t)itr->age << std::endl;
fmt::print(" {:s}:\n", nn::hac::ApplicationControlPropertyUtil::getOrganisationAsString(itr->organisation));
fmt::print(" Age: {:d}\n", itr->age);
}
}
else if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
else if (mCliOutputMode.show_extended_info)
{
std::cout << " RatingAge: None" << std::endl;
fmt::print(" RatingAge: None\n");
}
// DisplayVersion
if (mNacp.getDisplayVersion().empty() == false)
{
std::cout << " DisplayVersion: " << mNacp.getDisplayVersion() << std::endl;
fmt::print(" DisplayVersion: {:s}\n", mNacp.getDisplayVersion());
}
else if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
else if (mCliOutputMode.show_extended_info)
{
std::cout << " DisplayVersion: (NotSet)" << std::endl;
fmt::print(" DisplayVersion: (NotSet)\n");
}
// AddOnContentBaseId
if (mNacp.getAddOnContentBaseId() != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getAddOnContentBaseId() != 0 || mCliOutputMode.show_extended_info)
{
std::cout << " AddOnContentBaseId: 0x" << std::hex << std::setw(16) << std::setfill('0') << mNacp.getAddOnContentBaseId() << std::endl;
fmt::print(" AddOnContentBaseId: 0x{:016x}\n", mNacp.getAddOnContentBaseId());
}
// SaveDataOwnerId
if (mNacp.getSaveDataOwnerId() != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getSaveDataOwnerId() != 0 || mCliOutputMode.show_extended_info)
{
std::cout << " SaveDataOwnerId: 0x" << std::hex << std::setw(16) << std::setfill('0') << mNacp.getSaveDataOwnerId() << std::endl;
fmt::print(" SaveDataOwnerId: 0x{:016x}\n", mNacp.getSaveDataOwnerId());
}
// UserAccountSaveDataSize
if (mNacp.getUserAccountSaveDataSize().size != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getUserAccountSaveDataSize().size != 0 || mCliOutputMode.show_extended_info)
{
std::cout << " UserAccountSaveDataSize: " << nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getUserAccountSaveDataSize().size) << std::endl;
fmt::print(" UserAccountSaveDataSize: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getUserAccountSaveDataSize().size));
}
// UserAccountSaveDataJournalSize
if (mNacp.getUserAccountSaveDataSize().journal_size != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getUserAccountSaveDataSize().journal_size != 0 || mCliOutputMode.show_extended_info)
{
std::cout << " UserAccountSaveDataJournalSize: " << nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getUserAccountSaveDataSize().journal_size) << std::endl;
fmt::print(" UserAccountSaveDataJournalSize: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getUserAccountSaveDataSize().journal_size));
}
// DeviceSaveDataSize
if (mNacp.getDeviceSaveDataSize().size != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getDeviceSaveDataSize().size != 0 || mCliOutputMode.show_extended_info)
{
std::cout << " DeviceSaveDataSize: " << nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getDeviceSaveDataSize().size) << std::endl;
fmt::print(" DeviceSaveDataSize: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getDeviceSaveDataSize().size));
}
// DeviceSaveDataJournalSize
if (mNacp.getDeviceSaveDataSize().journal_size != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getDeviceSaveDataSize().journal_size != 0 || mCliOutputMode.show_extended_info)
{
std::cout << " DeviceSaveDataJournalSize: " << nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getDeviceSaveDataSize().journal_size) << std::endl;
fmt::print(" DeviceSaveDataJournalSize: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getDeviceSaveDataSize().journal_size));
}
// BcatDeliveryCacheStorageSize
if (mNacp.getBcatDeliveryCacheStorageSize() != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getBcatDeliveryCacheStorageSize() != 0 || mCliOutputMode.show_extended_info)
{
std::cout << " BcatDeliveryCacheStorageSize: " << nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getBcatDeliveryCacheStorageSize()) << std::endl;
fmt::print(" BcatDeliveryCacheStorageSize: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getBcatDeliveryCacheStorageSize()));
}
// ApplicationErrorCodeCategory
if (mNacp.getApplicationErrorCodeCategory().empty() == false)
{
std::cout << " ApplicationErrorCodeCategory: " << mNacp.getApplicationErrorCodeCategory() << std::endl;
fmt::print(" ApplicationErrorCodeCategory: {:s}\n", mNacp.getApplicationErrorCodeCategory());
}
else if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
else if (mCliOutputMode.show_extended_info)
{
std::cout << " ApplicationErrorCodeCategory: (NotSet)" << std::endl;
fmt::print(" ApplicationErrorCodeCategory: (NotSet)\n");
}
// LocalCommunicationId
if (mNacp.getLocalCommunicationId().size() > 0)
{
std::cout << " LocalCommunicationId:" << std::endl;
fmt::print(" LocalCommunicationId:\n");
for (auto itr = mNacp.getLocalCommunicationId().begin(); itr != mNacp.getLocalCommunicationId().end(); itr++)
{
std::cout << " 0x" << std::hex << std::setw(16) << std::setfill('0') << *itr << std::endl;
fmt::print(" 0x{:016x}\n", *itr);
}
}
else if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
else if (mCliOutputMode.show_extended_info)
{
std::cout << " LocalCommunicationId: None" << std::endl;
fmt::print(" LocalCommunicationId: None\n");
}
// LogoType
//if (mNacp.getLogoType() != nn::hac::nacp::LogoType::Nintendo || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
//if (mNacp.getLogoType() != nn::hac::nacp::LogoType::Nintendo || mCliOutputMode.show_extended_info)
//{
std::cout << " LogoType: " << nn::hac::ApplicationControlPropertyUtil::getLogoTypeAsString(mNacp.getLogoType()) << std::endl;
fmt::print(" LogoType: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getLogoTypeAsString(mNacp.getLogoType()));
//}
// LogoHandling
if (mNacp.getLogoHandling() != nn::hac::nacp::LogoHandling::Auto || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getLogoHandling() != nn::hac::nacp::LogoHandling::Auto || mCliOutputMode.show_extended_info)
{
std::cout << " LogoHandling: " << nn::hac::ApplicationControlPropertyUtil::getLogoHandlingAsString(mNacp.getLogoHandling()) << std::endl;
fmt::print(" LogoHandling: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getLogoHandlingAsString(mNacp.getLogoHandling()));
}
// RuntimeAddOnContentInstall
if (mNacp.getRuntimeAddOnContentInstall() != nn::hac::nacp::RuntimeAddOnContentInstall::Deny || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getRuntimeAddOnContentInstall() != nn::hac::nacp::RuntimeAddOnContentInstall::Deny || mCliOutputMode.show_extended_info)
{
std::cout << " RuntimeAddOnContentInstall: " << nn::hac::ApplicationControlPropertyUtil::getRuntimeAddOnContentInstallAsString(mNacp.getRuntimeAddOnContentInstall()) << std::endl;
fmt::print(" RuntimeAddOnContentInstall: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getRuntimeAddOnContentInstallAsString(mNacp.getRuntimeAddOnContentInstall()));
}
// RuntimeParameterDelivery
if (mNacp.getRuntimeParameterDelivery() != nn::hac::nacp::RuntimeParameterDelivery::Always || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getRuntimeParameterDelivery() != nn::hac::nacp::RuntimeParameterDelivery::Always || mCliOutputMode.show_extended_info)
{
std::cout << " RuntimeParameterDelivery: " << nn::hac::ApplicationControlPropertyUtil::getRuntimeParameterDeliveryAsString(mNacp.getRuntimeParameterDelivery()) << std::endl;
fmt::print(" RuntimeParameterDelivery: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getRuntimeParameterDeliveryAsString(mNacp.getRuntimeParameterDelivery()));
}
// CrashReport
if (mNacp.getCrashReport() != nn::hac::nacp::CrashReport::Deny || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getCrashReport() != nn::hac::nacp::CrashReport::Deny || mCliOutputMode.show_extended_info)
{
std::cout << " CrashReport: " << nn::hac::ApplicationControlPropertyUtil::getCrashReportAsString(mNacp.getCrashReport()) << std::endl;
fmt::print(" CrashReport: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getCrashReportAsString(mNacp.getCrashReport()));
}
// Hdcp
if (mNacp.getHdcp() != nn::hac::nacp::Hdcp::None || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getHdcp() != nn::hac::nacp::Hdcp::None || mCliOutputMode.show_extended_info)
{
std::cout << " Hdcp: " << nn::hac::ApplicationControlPropertyUtil::getHdcpAsString(mNacp.getHdcp()) << std::endl;
fmt::print(" Hdcp: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getHdcpAsString(mNacp.getHdcp()));
}
// SeedForPsuedoDeviceId
if (mNacp.getSeedForPsuedoDeviceId() != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getSeedForPsuedoDeviceId() != 0 || mCliOutputMode.show_extended_info)
{
std::cout << " SeedForPsuedoDeviceId: 0x" << std::hex << std::setw(16) << std::setfill('0') << mNacp.getSeedForPsuedoDeviceId() << std::endl;
fmt::print(" SeedForPsuedoDeviceId: 0x{:016x}\n", mNacp.getSeedForPsuedoDeviceId());
}
// BcatPassphase
if (mNacp.getBcatPassphase().empty() == false)
{
std::cout << " BcatPassphase: " << mNacp.getBcatPassphase() << std::endl;
fmt::print(" BcatPassphase: {:s}\n", mNacp.getBcatPassphase());
}
else if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
else if (mCliOutputMode.show_extended_info)
{
std::cout << " BcatPassphase: (NotSet)" << std::endl;
fmt::print(" BcatPassphase: (NotSet)\n");
}
// StartupUserAccountOption
if (mNacp.getStartupUserAccountOption().size() > 0)
{
std::cout << " StartupUserAccountOption:" << std::endl;
fmt::print(" StartupUserAccountOption:\n");
for (auto itr = mNacp.getStartupUserAccountOption().begin(); itr != mNacp.getStartupUserAccountOption().end(); itr++)
{
std::cout << " " << nn::hac::ApplicationControlPropertyUtil::getStartupUserAccountOptionFlagAsString(*itr) << std::endl;
fmt::print(" {:s}\n", nn::hac::ApplicationControlPropertyUtil::getStartupUserAccountOptionFlagAsString(*itr));
}
}
else if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
else if (mCliOutputMode.show_extended_info)
{
std::cout << " StartupUserAccountOption: None" << std::endl;
fmt::print(" StartupUserAccountOption: None\n");
}
// UserAccountSaveDataSizeMax
if (mNacp.getUserAccountSaveDataMax().size != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getUserAccountSaveDataMax().size != 0 || mCliOutputMode.show_extended_info)
{
std::cout << " UserAccountSaveDataSizeMax: " << nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getUserAccountSaveDataMax().size) << std::endl;
fmt::print(" UserAccountSaveDataSizeMax: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getUserAccountSaveDataMax().size));
}
// UserAccountSaveDataJournalSizeMax
if (mNacp.getUserAccountSaveDataMax().journal_size != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getUserAccountSaveDataMax().journal_size != 0 || mCliOutputMode.show_extended_info)
{
std::cout << " UserAccountSaveDataJournalSizeMax: " << nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getUserAccountSaveDataMax().journal_size) << std::endl;
fmt::print(" UserAccountSaveDataJournalSizeMax: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getUserAccountSaveDataMax().journal_size));
}
// DeviceSaveDataSizeMax
if (mNacp.getDeviceSaveDataMax().size != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getDeviceSaveDataMax().size != 0 || mCliOutputMode.show_extended_info)
{
std::cout << " DeviceSaveDataSizeMax: " << nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getDeviceSaveDataMax().size) << std::endl;
fmt::print(" DeviceSaveDataSizeMax: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getDeviceSaveDataMax().size));
}
// DeviceSaveDataJournalSizeMax
if (mNacp.getDeviceSaveDataMax().journal_size != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getDeviceSaveDataMax().journal_size != 0 || mCliOutputMode.show_extended_info)
{
std::cout << " DeviceSaveDataJournalSizeMax: " << nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getDeviceSaveDataMax().journal_size) << std::endl;
fmt::print(" DeviceSaveDataJournalSizeMax: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getDeviceSaveDataMax().journal_size));
}
// TemporaryStorageSize
if (mNacp.getTemporaryStorageSize() != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getTemporaryStorageSize() != 0 || mCliOutputMode.show_extended_info)
{
std::cout << " TemporaryStorageSize: " << nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getTemporaryStorageSize()) << std::endl;
fmt::print(" TemporaryStorageSize: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getTemporaryStorageSize()));
}
// CacheStorageSize
if (mNacp.getCacheStorageSize().size != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getCacheStorageSize().size != 0 || mCliOutputMode.show_extended_info)
{
std::cout << " CacheStorageSize: " << nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getCacheStorageSize().size) << std::endl;
fmt::print(" CacheStorageSize: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getCacheStorageSize().size));
}
// CacheStorageJournalSize
if (mNacp.getCacheStorageSize().journal_size != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getCacheStorageSize().journal_size != 0 || mCliOutputMode.show_extended_info)
{
std::cout << " CacheStorageJournalSize: " << nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getCacheStorageSize().journal_size) << std::endl;
fmt::print(" CacheStorageJournalSize: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getCacheStorageSize().journal_size));
}
// CacheStorageDataAndJournalSizeMax
if (mNacp.getCacheStorageDataAndJournalSizeMax() != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getCacheStorageDataAndJournalSizeMax() != 0 || mCliOutputMode.show_extended_info)
{
std::cout << " CacheStorageDataAndJournalSizeMax: " << nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getCacheStorageDataAndJournalSizeMax()) << std::endl;
fmt::print(" CacheStorageDataAndJournalSizeMax: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getSaveDataSizeAsString(mNacp.getCacheStorageDataAndJournalSizeMax()));
}
// CacheStorageIndexMax
if (mNacp.getCacheStorageIndexMax() != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getCacheStorageIndexMax() != 0 || mCliOutputMode.show_extended_info)
{
std::cout << " CacheStorageIndexMax: 0x" << std::hex << std::setw(4) << std::setfill('0') << mNacp.getCacheStorageIndexMax() << std::endl;
fmt::print(" CacheStorageIndexMax: 0x{:04x}\n", mNacp.getCacheStorageIndexMax());
}
// PlayLogQueryableApplicationId
if (mNacp.getPlayLogQueryableApplicationId().size() > 0)
{
std::cout << " PlayLogQueryableApplicationId:" << std::endl;
fmt::print(" PlayLogQueryableApplicationId:\n");
for (auto itr = mNacp.getPlayLogQueryableApplicationId().begin(); itr != mNacp.getPlayLogQueryableApplicationId().end(); itr++)
{
std::cout << " 0x" << std::hex << std::setw(16) << std::setfill('0') << *itr << std::endl;
fmt::print(" 0x{:016x}\n", *itr);
}
}
else if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
else if (mCliOutputMode.show_extended_info)
{
std::cout << " PlayLogQueryableApplicationId: None" << std::endl;
fmt::print(" PlayLogQueryableApplicationId: None\n");
}
// PlayLogQueryCapability
if (mNacp.getPlayLogQueryCapability() != nn::hac::nacp::PlayLogQueryCapability::None || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getPlayLogQueryCapability() != nn::hac::nacp::PlayLogQueryCapability::None || mCliOutputMode.show_extended_info)
{
std::cout << " PlayLogQueryCapability: " << nn::hac::ApplicationControlPropertyUtil::getPlayLogQueryCapabilityAsString(mNacp.getPlayLogQueryCapability()) << std::endl;
fmt::print(" PlayLogQueryCapability: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getPlayLogQueryCapabilityAsString(mNacp.getPlayLogQueryCapability()));
}
// Repair
if (mNacp.getRepair().size() > 0)
{
std::cout << " Repair:" << std::endl;
fmt::print(" Repair:\n");
for (auto itr = mNacp.getRepair().begin(); itr != mNacp.getRepair().end(); itr++)
{
std::cout << " " << nn::hac::ApplicationControlPropertyUtil::getRepairFlagAsString(*itr) << std::endl;
fmt::print(" {:s}\n", nn::hac::ApplicationControlPropertyUtil::getRepairFlagAsString(*itr));
}
}
else if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
else if (mCliOutputMode.show_extended_info)
{
std::cout << " Repair: None" << std::endl;
fmt::print(" Repair: None\n");
}
// ProgramIndex
if (mNacp.getProgramIndex() != 0 || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getProgramIndex() != 0 || mCliOutputMode.show_extended_info)
{
std::cout << " ProgramIndex: 0x" << std::hex << std::setw(2) << std::setfill('0') << (uint32_t)mNacp.getProgramIndex() << std::endl;
fmt::print(" ProgramIndex: 0x{:02x}\n", mNacp.getProgramIndex());
}
// RequiredNetworkServiceLicenseOnLaunch
if (mNacp.getRequiredNetworkServiceLicenseOnLaunch().size() > 0)
{
std::cout << " RequiredNetworkServiceLicenseOnLaunch:" << std::endl;
fmt::print(" RequiredNetworkServiceLicenseOnLaunch:\n");
for (auto itr = mNacp.getRequiredNetworkServiceLicenseOnLaunch().begin(); itr != mNacp.getRequiredNetworkServiceLicenseOnLaunch().end(); itr++)
{
std::cout << " " << nn::hac::ApplicationControlPropertyUtil::getRequiredNetworkServiceLicenseOnLaunchFlagAsString(*itr) << std::endl;
fmt::print(" {:s}\n", nn::hac::ApplicationControlPropertyUtil::getRequiredNetworkServiceLicenseOnLaunchFlagAsString(*itr));
}
}
else if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
else if (mCliOutputMode.show_extended_info)
{
std::cout << " RequiredNetworkServiceLicenseOnLaunch: None" << std::endl;
fmt::print(" RequiredNetworkServiceLicenseOnLaunch: None\n");
}
// NeighborDetectionClientConfiguration
auto detect_config = mNacp.getNeighborDetectionClientConfiguration();
if (detect_config.countSendGroupConfig() > 0 || detect_config.countReceivableGroupConfig() > 0)
{
std::cout << " NeighborDetectionClientConfiguration:" << std::endl;
fmt::print(" NeighborDetectionClientConfiguration:\n");
if (detect_config.countSendGroupConfig() > 0)
{
std::cout << " SendGroupConfig:" << std::endl;
std::cout << " GroupId: 0x" << std::hex << std::setw(16) << std::setfill('0') << detect_config.send_data_configuration.group_id << std::endl;
std::cout << " Key: " << fnd::SimpleTextOutput::arrayToString(detect_config.send_data_configuration.key, nn::hac::nacp::kNeighborDetectionGroupConfigurationKeyLength, false, "") << std::endl;
fmt::print(" SendGroupConfig:\n");
fmt::print(" GroupId: 0x{:016x}\n", detect_config.send_data_configuration.group_id);
fmt::print(" Key: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(detect_config.send_data_configuration.key.data(), detect_config.send_data_configuration.key.size(), false, ""));
}
else if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
else if (mCliOutputMode.show_extended_info)
{
std::cout << " SendGroupConfig: None" << std::endl;
fmt::print(" SendGroupConfig: None\n");
}
if (detect_config.countReceivableGroupConfig() > 0)
{
std::cout << " ReceivableGroupConfig:" << std::endl;
fmt::print(" ReceivableGroupConfig:\n");
for (size_t i = 0; i < nn::hac::nacp::kReceivableGroupConfigurationCount; i++)
{
if (detect_config.receivable_data_configuration[i].isNull())
continue;
std::cout << " GroupId: 0x" << std::hex << std::setw(16) << std::setfill('0') << detect_config.receivable_data_configuration[i].group_id << std::endl;
std::cout << " Key: " << fnd::SimpleTextOutput::arrayToString(detect_config.receivable_data_configuration[i].key, nn::hac::nacp::kNeighborDetectionGroupConfigurationKeyLength, false, "") << std::endl;
fmt::print(" GroupId: 0x{:016x}\n", detect_config.receivable_data_configuration[i].group_id);
fmt::print(" Key: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(detect_config.receivable_data_configuration[i].key.data(), detect_config.receivable_data_configuration[i].key.size(), false, ""));
}
}
else if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
else if (mCliOutputMode.show_extended_info)
{
std::cout << " ReceivableGroupConfig: None" << std::endl;
fmt::print(" ReceivableGroupConfig: None\n");
}
}
else if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
else if (mCliOutputMode.show_extended_info)
{
std::cout << " NeighborDetectionClientConfiguration: None" << std::endl;
fmt::print(" NeighborDetectionClientConfiguration: None\n");
}
// JitConfiguration
if (mNacp.getJitConfiguration().is_enabled || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getJitConfiguration().is_enabled || mCliOutputMode.show_extended_info)
{
std::cout << " JitConfiguration:" << std::endl;
std::cout << " IsEnabled: " << std::boolalpha << mNacp.getJitConfiguration().is_enabled << std::endl;
std::cout << " MemorySize: 0x" << std::hex << std::setw(16) << std::setfill('0') << mNacp.getJitConfiguration().memory_size << std::endl;
fmt::print(" JitConfiguration:\n");
fmt::print(" IsEnabled: {}\n", mNacp.getJitConfiguration().is_enabled);
fmt::print(" MemorySize: 0x{:016x}\n", mNacp.getJitConfiguration().memory_size);
}
// PlayReportPermission
if (mNacp.getPlayReportPermission() != nn::hac::nacp::PlayReportPermission::None || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getPlayReportPermission() != nn::hac::nacp::PlayReportPermission::None || mCliOutputMode.show_extended_info)
{
std::cout << " PlayReportPermission: " << nn::hac::ApplicationControlPropertyUtil::getPlayReportPermissionAsString(mNacp.getPlayReportPermission()) << std::endl;
fmt::print(" PlayReportPermission: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getPlayReportPermissionAsString(mNacp.getPlayReportPermission()));
}
// CrashScreenshotForProd
if (mNacp.getCrashScreenshotForProd() != nn::hac::nacp::CrashScreenshotForProd::Deny || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getCrashScreenshotForProd() != nn::hac::nacp::CrashScreenshotForProd::Deny || mCliOutputMode.show_extended_info)
{
std::cout << " CrashScreenshotForProd: " << nn::hac::ApplicationControlPropertyUtil::getCrashScreenshotForProdAsString(mNacp.getCrashScreenshotForProd()) << std::endl;
fmt::print(" CrashScreenshotForProd: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getCrashScreenshotForProdAsString(mNacp.getCrashScreenshotForProd()));
}
// CrashScreenshotForDev
if (mNacp.getCrashScreenshotForDev() != nn::hac::nacp::CrashScreenshotForDev::Deny || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mNacp.getCrashScreenshotForDev() != nn::hac::nacp::CrashScreenshotForDev::Deny || mCliOutputMode.show_extended_info)
{
std::cout << " CrashScreenshotForDev: " << nn::hac::ApplicationControlPropertyUtil::getCrashScreenshotForDevAsString(mNacp.getCrashScreenshotForDev()) << std::endl;
fmt::print(" CrashScreenshotForDev: {:s}\n", nn::hac::ApplicationControlPropertyUtil::getCrashScreenshotForDevAsString(mNacp.getCrashScreenshotForDev()));
}
// AccessibleLaunchRequiredVersion
if (mNacp.getAccessibleLaunchRequiredVersionApplicationId().size() > 0)
{
std::cout << " AccessibleLaunchRequiredVersion:" << std::endl;
std::cout << " ApplicationId:" << std::endl;
fmt::print(" AccessibleLaunchRequiredVersion:\n");
fmt::print(" ApplicationId:\n");
for (auto itr = mNacp.getAccessibleLaunchRequiredVersionApplicationId().begin(); itr != mNacp.getAccessibleLaunchRequiredVersionApplicationId().end(); itr++)
{
std::cout << " 0x" << std::hex << std::setw(16) << std::setfill('0') << *itr << std::endl;
fmt::print(" 0x{:016x}\n", *itr);
}
}
else if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
else if (mCliOutputMode.show_extended_info)
{
std::cout << " AccessibleLaunchRequiredVersion: None" << std::endl;
fmt::print(" AccessibleLaunchRequiredVersion: None\n");
}
}

View file

@ -1,11 +1,9 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/IFile.h>
#include <fnd/SharedPtr.h>
#include "types.h"
#include <nn/hac/ApplicationControlProperty.h>
#include "common.h"
namespace nstool {
class NacpProcess
{
@ -14,16 +12,16 @@ public:
void process();
void setInputFile(const fnd::SharedPtr<fnd::IFile>& file);
void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify);
const nn::hac::ApplicationControlProperty& getApplicationControlProperty() const;
private:
const std::string kModuleName = "NacpProcess";
std::string mModuleName;
fnd::SharedPtr<fnd::IFile> mFile;
std::shared_ptr<tc::io::IStream> mFile;
CliOutputMode mCliOutputMode;
bool mVerify;
@ -32,3 +30,5 @@ private:
void importNacp();
void displayNacp();
};
}

View file

@ -1,36 +1,28 @@
#include "NcaProcess.h"
#include "PfsProcess.h"
#include "RomfsProcess.h"
#include "MetaProcess.h"
#include "util.h"
#include <iostream>
#include <iomanip>
#include <sstream>
#include <fnd/SimpleTextOutput.h>
#include <fnd/OffsetAdjustedIFile.h>
#include <fnd/AesCtrWrappedIFile.h>
#include <fnd/LayeredIntegrityWrappedIFile.h>
#include <tc/crypto/detail/BlockUtilImpl.h>
#include <nn/hac/ContentArchiveUtil.h>
#include <nn/hac/AesKeygen.h>
#include <nn/hac/HierarchicalSha256Header.h>
#include <nn/hac/HierarchicalIntegrityHeader.h>
#include <nn/hac/HierarchicalSha256Stream.h>
#include <nn/hac/HierarchicalIntegrityStream.h>
#include <nn/hac/PartitionFsMetaGenerator.h>
#include <nn/hac/RomFsMetaGenerator.h>
#include <nn/hac/CombinedFsMetaGenerator.h>
NcaProcess::NcaProcess() :
nstool::NcaProcess::NcaProcess() :
mModuleName("nstool::NcaProcess"),
mFile(),
mCliOutputMode(_BIT(OUTPUT_BASIC)),
mCliOutputMode(true, false, false, false),
mVerify(false),
mListFs(false)
mFileSystem(),
mFsProcess()
{
for (size_t i = 0; i < nn::hac::nca::kPartitionNum; i++)
{
mPartitionPath[i].doExtract = false;
}
}
void NcaProcess::process()
void nstool::NcaProcess::process()
{
// import header
importHeader();
@ -46,188 +38,183 @@ void NcaProcess::process()
validateNcaSignatures();
// display header
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
if (mCliOutputMode.show_basic_info)
displayHeader();
// process partition
processPartitions();
}
void NcaProcess::setInputFile(const fnd::SharedPtr<fnd::IFile>& file)
void nstool::NcaProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
{
mFile = file;
}
void NcaProcess::setKeyCfg(const KeyConfiguration& keycfg)
void nstool::NcaProcess::setKeyCfg(const KeyBag& keycfg)
{
mKeyCfg = keycfg;
}
void NcaProcess::setCliOutputMode(CliOutputMode type)
void nstool::NcaProcess::setCliOutputMode(CliOutputMode type)
{
mCliOutputMode = type;
}
void NcaProcess::setVerifyMode(bool verify)
void nstool::NcaProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void NcaProcess::setPartition0ExtractPath(const std::string& path)
void nstool::NcaProcess::setShowFsTree(bool show_fs_tree)
{
mPartitionPath[0].path = path;
mPartitionPath[0].doExtract = true;
mFsProcess.setShowFsTree(show_fs_tree);
}
void NcaProcess::setPartition1ExtractPath(const std::string& path)
void nstool::NcaProcess::setFsRootLabel(const std::string& root_label)
{
mPartitionPath[1].path = path;
mPartitionPath[1].doExtract = true;
mFsProcess.setFsRootLabel(root_label);
}
void NcaProcess::setPartition2ExtractPath(const std::string& path)
void nstool::NcaProcess::setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs)
{
mPartitionPath[2].path = path;
mPartitionPath[2].doExtract = true;
mFsProcess.setExtractJobs(extract_jobs);
}
void NcaProcess::setPartition3ExtractPath(const std::string& path)
const std::shared_ptr<tc::io::IStorage>& nstool::NcaProcess::getFileSystem() const
{
mPartitionPath[3].path = path;
mPartitionPath[3].doExtract = true;
return mFileSystem;
}
void NcaProcess::setListFs(bool list_fs)
void nstool::NcaProcess::importHeader()
{
mListFs = list_fs;
}
void NcaProcess::importHeader()
{
if (*mFile == nullptr)
if (mFile == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
throw tc::Exception(mModuleName, "No file reader set.");
}
if (mFile->canRead() == false || mFile->canSeek() == false)
{
throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions.");
}
// read header block
(*mFile)->read((byte_t*)&mHdrBlock, 0, sizeof(nn::hac::sContentArchiveHeaderBlock));
if (mFile->length() < tc::io::IOUtil::castSizeToInt64(sizeof(nn::hac::sContentArchiveHeaderBlock)))
{
throw tc::Exception(mModuleName, "Corrupt NCA: File too small.");
}
mFile->seek(0, tc::io::SeekOrigin::Begin);
mFile->read((byte_t*)(&mHdrBlock), sizeof(nn::hac::sContentArchiveHeaderBlock));
// decrypt header block
fnd::aes::sAesXts128Key header_key;
mKeyCfg.getContentArchiveHeaderKey(header_key);
nn::hac::ContentArchiveUtil::decryptContentArchiveHeader((byte_t*)&mHdrBlock, (byte_t*)&mHdrBlock, header_key);
if (mKeyCfg.nca_header_key.isNull())
{
throw tc::Exception(mModuleName, "Failed to decrypt NCA header. (nca_header_key could not be loaded)");
}
nn::hac::ContentArchiveUtil::decryptContentArchiveHeader((byte_t*)&mHdrBlock, (byte_t*)&mHdrBlock, mKeyCfg.nca_header_key.get());
// generate header hash
fnd::sha::Sha256((byte_t*)&mHdrBlock.header, sizeof(nn::hac::sContentArchiveHeader), mHdrHash.bytes);
tc::crypto::GenerateSha256Hash(mHdrHash.data(), (byte_t*)&mHdrBlock.header, sizeof(nn::hac::sContentArchiveHeader));
// proccess main header
mHdr.fromBytes((byte_t*)&mHdrBlock.header, sizeof(nn::hac::sContentArchiveHeader));
}
void NcaProcess::generateNcaBodyEncryptionKeys()
void nstool::NcaProcess::generateNcaBodyEncryptionKeys()
{
// create zeros key
fnd::aes::sAes128Key zero_aesctr_key;
memset(zero_aesctr_key.key, 0, sizeof(zero_aesctr_key));
KeyBag::aes128_key_t zero_aesctr_key;
memset(zero_aesctr_key.data(), 0, zero_aesctr_key.size());
// get key data from header
byte_t masterkey_rev = nn::hac::ContentArchiveUtil::getMasterKeyRevisionFromKeyGeneration(mHdr.getKeyGeneration());
byte_t masterkey_rev = nn::hac::AesKeygen::getMasterKeyRevisionFromKeyGeneration(mHdr.getKeyGeneration());
byte_t keak_index = mHdr.getKeyAreaEncryptionKeyIndex();
// process key area
sKeys::sKeyAreaKey kak;
fnd::aes::sAes128Key key_area_enc_key;
const fnd::aes::sAes128Key* key_area = (const fnd::aes::sAes128Key*) mHdr.getKeyArea();
for (size_t i = 0; i < nn::hac::nca::kKeyAreaKeyNum; i++)
for (size_t i = 0; i < mHdr.getKeyArea().size(); i++)
{
if (key_area[i] != zero_aesctr_key)
if (mHdr.getKeyArea()[i] != zero_aesctr_key)
{
kak.index = (byte_t)i;
kak.enc = key_area[i];
kak.enc = mHdr.getKeyArea()[i];
kak.decrypted = false;
// key[0-3]
if (i < 4 && mKeyCfg.getNcaKeyAreaEncryptionKey(masterkey_rev, keak_index, key_area_enc_key) == true)
if (i < 4 && mKeyCfg.nca_key_area_encryption_key[keak_index].find(masterkey_rev) != mKeyCfg.nca_key_area_encryption_key[keak_index].end())
{
kak.decrypted = true;
nn::hac::AesKeygen::generateKey(kak.dec.key, kak.enc.key, key_area_enc_key.key);
nn::hac::AesKeygen::generateKey(kak.dec.data(), kak.enc.data(), mKeyCfg.nca_key_area_encryption_key[keak_index][masterkey_rev].data());
}
// key[KEY_AESCTR_HW]
else if (i == nn::hac::nca::KEY_AESCTR_HW && mKeyCfg.getNcaKeyAreaEncryptionKeyHw(masterkey_rev, keak_index, key_area_enc_key) == true)
else if (i == nn::hac::nca::KEY_AESCTR_HW && mKeyCfg.nca_key_area_encryption_key_hw[keak_index].find(masterkey_rev) != mKeyCfg.nca_key_area_encryption_key_hw[keak_index].end())
{
kak.decrypted = true;
nn::hac::AesKeygen::generateKey(kak.dec.key, kak.enc.key, key_area_enc_key.key);
nn::hac::AesKeygen::generateKey(kak.dec.data(), kak.enc.data(), mKeyCfg.nca_key_area_encryption_key_hw[keak_index][masterkey_rev].data());
}
else
{
kak.decrypted = false;
}
mContentKey.kak_list.addElement(kak);
mContentKey.kak_list.push_back(kak);
}
}
// set flag to indicate that the keys are not available
mContentKey.aes_ctr.isSet = false;
// clear content key
mContentKey.aes_ctr = tc::Optional<nn::hac::detail::aes128_key_t>();
// if this has a rights id, the key needs to be sourced from a ticket
if (mHdr.hasRightsId() == true)
{
fnd::aes::sAes128Key tmp_key;
if (mKeyCfg.getNcaExternalContentKey(mHdr.getRightsId(), tmp_key) == true)
KeyBag::aes128_key_t tmp_key;
if (mKeyCfg.external_content_keys.find(mHdr.getRightsId()) != mKeyCfg.external_content_keys.end())
{
mContentKey.aes_ctr = tmp_key;
mContentKey.aes_ctr = mKeyCfg.external_content_keys[mHdr.getRightsId()];
}
else if (mKeyCfg.getNcaExternalContentKey(kDummyRightsIdForUserTitleKey, tmp_key) == true)
else if (mKeyCfg.fallback_content_key.isSet())
{
fnd::aes::sAes128Key common_key;
if (mKeyCfg.getETicketCommonKey(masterkey_rev, common_key) == true)
mContentKey.aes_ctr = mKeyCfg.fallback_content_key.get();
}
else if (mKeyCfg.fallback_enc_content_key.isSet())
{
tmp_key = mKeyCfg.fallback_enc_content_key.get();
if (mKeyCfg.etik_common_key.find(masterkey_rev) != mKeyCfg.etik_common_key.end())
{
nn::hac::AesKeygen::generateKey(tmp_key.key, tmp_key.key, common_key.key);
nn::hac::AesKeygen::generateKey(tmp_key.data(), tmp_key.data(), mKeyCfg.etik_common_key[masterkey_rev].data());
mContentKey.aes_ctr = tmp_key;
}
mContentKey.aes_ctr = tmp_key;
}
}
// otherwise decrypt key area
// otherwise used decrypt key area
else
{
fnd::aes::sAes128Key kak_aes_ctr = zero_aesctr_key;
for (size_t i = 0; i < mContentKey.kak_list.size(); i++)
{
if (mContentKey.kak_list[i].index == nn::hac::nca::KEY_AESCTR && mContentKey.kak_list[i].decrypted)
{
kak_aes_ctr = mContentKey.kak_list[i].dec;
mContentKey.aes_ctr = mContentKey.kak_list[i].dec;
}
}
if (kak_aes_ctr != zero_aesctr_key)
{
mContentKey.aes_ctr = kak_aes_ctr;
}
}
// if the keys weren't generated, check if the keys were supplied by the user
if (mContentKey.aes_ctr.isSet == false)
if (mContentKey.aes_ctr.isNull())
{
if (mKeyCfg.getNcaExternalContentKey(kDummyRightsIdForUserBodyKey, mContentKey.aes_ctr.var) == true)
mContentKey.aes_ctr.isSet = true;
}
if (_HAS_BIT(mCliOutputMode, OUTPUT_KEY_DATA))
{
if (mContentKey.aes_ctr.isSet)
if (mKeyCfg.fallback_content_key.isSet())
{
std::cout << "[NCA Content Key]" << std::endl;
std::cout << " AES-CTR Key: " << fnd::SimpleTextOutput::arrayToString(mContentKey.aes_ctr.var.key, sizeof(mContentKey.aes_ctr.var), true, ":") << std::endl;
mContentKey.aes_ctr = mKeyCfg.fallback_content_key.get();
}
}
if (mCliOutputMode.show_keydata)
{
if (mContentKey.aes_ctr.isSet())
{
fmt::print("[NCA Content Key]\n");
fmt::print(" AES-CTR Key: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mContentKey.aes_ctr.get().data(), mContentKey.aes_ctr.get().size(), true, ""));
}
}
}
void NcaProcess::generatePartitionConfiguration()
void nstool::NcaProcess::generatePartitionConfiguration()
{
std::stringstream error;
for (size_t i = 0; i < mHdr.getPartitionEntryList().size(); i++)
{
// get reference to relevant structures
@ -238,28 +225,22 @@ void NcaProcess::generatePartitionConfiguration()
sPartitionInfo& info = mPartitions[partition.header_index];
// validate header hash
fnd::sha::sSha256Hash fs_header_hash;
fnd::sha::Sha256((const byte_t*)&mHdrBlock.fs_header[partition.header_index], sizeof(nn::hac::sContentArchiveFsHeader), fs_header_hash.bytes);
if (fs_header_hash.compare(partition.fs_header_hash) == false)
nn::hac::detail::sha256_hash_t fs_header_hash;
tc::crypto::GenerateSha256Hash(fs_header_hash.data(), (const byte_t*)&mHdrBlock.fs_header[partition.header_index], sizeof(nn::hac::sContentArchiveFsHeader));
if (fs_header_hash != partition.fs_header_hash)
{
error.clear();
error << "NCA FS Header [" << partition.header_index << "] Hash: FAIL \n";
throw fnd::Exception(kModuleName, error.str());
throw tc::Exception(mModuleName, fmt::format("NCA FS Header [{:d}] Hash: FAIL", partition.header_index));
}
if (fs_header.version.get() != nn::hac::nca::kDefaultFsHeaderVersion)
if (fs_header.version.unwrap() != nn::hac::nca::kDefaultFsHeaderVersion)
{
error.clear();
error << "NCA FS Header [" << partition.header_index << "] Version(" << fs_header.version.get() << "): UNSUPPORTED";
throw fnd::Exception(kModuleName, error.str());
throw tc::Exception(mModuleName, fmt::format("NCA FS Header [{:d}] Version({:d}): UNSUPPORTED", partition.header_index, fs_header.version.unwrap()));
}
// setup AES-CTR
nn::hac::ContentArchiveUtil::getNcaPartitionAesCtr(&fs_header, info.aes_ctr.iv);
nn::hac::ContentArchiveUtil::getNcaPartitionAesCtr(&fs_header, info.aes_ctr.data());
// save partition config
info.reader = nullptr;
// save partition configinfo
info.offset = partition.offset;
info.size = partition.size;
info.format_type = (nn::hac::nca::FormatType)fs_header.format_type;
@ -267,353 +248,318 @@ void NcaProcess::generatePartitionConfiguration()
info.enc_type = (nn::hac::nca::EncryptionType)fs_header.encryption_type;
if (info.hash_type == nn::hac::nca::HashType::HierarchicalSha256)
{
// info.hash_tree_meta.importData(fs_header.hash_info, nn::hac::nca::kHashInfoLen, LayeredIntegrityMetadata::HASH_TYPE_SHA256);
nn::hac::HierarchicalSha256Header hdr;
fnd::List<fnd::LayeredIntegrityMetadata::sLayer> hash_layers;
fnd::LayeredIntegrityMetadata::sLayer data_layer;
fnd::List<fnd::sha::sSha256Hash> master_hash_list;
// import raw data
hdr.fromBytes(fs_header.hash_info, nn::hac::nca::kHashInfoLen);
for (size_t i = 0; i < hdr.getLayerInfo().size(); i++)
{
fnd::LayeredIntegrityMetadata::sLayer layer;
layer.offset = hdr.getLayerInfo()[i].offset;
layer.size = hdr.getLayerInfo()[i].size;
layer.block_size = hdr.getHashBlockSize();
if (i + 1 == hdr.getLayerInfo().size())
{
data_layer = layer;
}
else
{
hash_layers.addElement(layer);
}
}
master_hash_list.addElement(hdr.getMasterHash());
// write data into metadata
info.layered_intergrity_metadata.setAlignHashToBlock(false);
info.layered_intergrity_metadata.setHashLayerInfo(hash_layers);
info.layered_intergrity_metadata.setDataLayerInfo(data_layer);
info.layered_intergrity_metadata.setMasterHashList(master_hash_list);
info.hierarchicalsha256_hdr.fromBytes(fs_header.hash_info.data(), fs_header.hash_info.size());
}
else if (info.hash_type == nn::hac::nca::HashType::HierarchicalIntegrity)
{
// info.hash_tree_meta.importData(fs_header.hash_info, nn::hac::nca::kHashInfoLen, LayeredIntegrityMetadata::HASH_TYPE_INTEGRITY);
nn::hac::HierarchicalIntegrityHeader hdr;
fnd::List<fnd::LayeredIntegrityMetadata::sLayer> hash_layers;
fnd::LayeredIntegrityMetadata::sLayer data_layer;
fnd::List<fnd::sha::sSha256Hash> master_hash_list;
hdr.fromBytes(fs_header.hash_info, nn::hac::nca::kHashInfoLen);
for (size_t i = 0; i < hdr.getLayerInfo().size(); i++)
{
fnd::LayeredIntegrityMetadata::sLayer layer;
layer.offset = hdr.getLayerInfo()[i].offset;
layer.size = hdr.getLayerInfo()[i].size;
layer.block_size = _BIT(hdr.getLayerInfo()[i].block_size);
if (i + 1 == hdr.getLayerInfo().size())
{
data_layer = layer;
}
else
{
hash_layers.addElement(layer);
}
}
// write data into metadata
info.layered_intergrity_metadata.setAlignHashToBlock(true);
info.layered_intergrity_metadata.setHashLayerInfo(hash_layers);
info.layered_intergrity_metadata.setDataLayerInfo(data_layer);
info.layered_intergrity_metadata.setMasterHashList(hdr.getMasterHashList());
info.hierarchicalintegrity_hdr.fromBytes(fs_header.hash_info.data(), fs_header.hash_info.size());
}
// create reader
try
{
// filter out unrecognised format types
switch (info.format_type)
// handle partition encryption and partition compaction (sparse layer)
if (fs_header.sparse_info.generation.unwrap() != 0)
{
case (nn::hac::nca::FormatType::PartitionFs):
case (nn::hac::nca::FormatType::RomFs):
break;
default:
error.clear();
error << "FormatType(" << nn::hac::ContentArchiveUtil::getFormatTypeAsString(info.format_type) << "): UNKNOWN";
throw fnd::Exception(kModuleName, error.str());
}
// create reader based on encryption type0
if (info.enc_type == nn::hac::nca::EncryptionType::None)
{
info.reader = new fnd::OffsetAdjustedIFile(mFile, info.offset, info.size);
}
else if (info.enc_type == nn::hac::nca::EncryptionType::AesCtr)
{
if (mContentKey.aes_ctr.isSet == false)
throw fnd::Exception(kModuleName, "AES-CTR Key was not determined");
info.reader = new fnd::OffsetAdjustedIFile(new fnd::AesCtrWrappedIFile(mFile, mContentKey.aes_ctr.var, info.aes_ctr), info.offset, info.size);
}
else if (info.enc_type == nn::hac::nca::EncryptionType::AesXts || info.enc_type == nn::hac::nca::EncryptionType::AesCtrEx)
{
error.clear();
error << "EncryptionType(" << nn::hac::ContentArchiveUtil::getEncryptionTypeAsString(info.enc_type) << "): UNSUPPORTED";
throw fnd::Exception(kModuleName, error.str());
throw tc::Exception("SparseStorage: Not currently supported.");
}
else
{
error.clear();
error << "EncryptionType(" << nn::hac::ContentArchiveUtil::getEncryptionTypeAsString(info.enc_type) << "): UNKNOWN";
throw fnd::Exception(kModuleName, error.str());
// create raw partition
info.reader = std::make_shared<tc::io::SubStream>(tc::io::SubStream(mFile, info.offset, info.size));
// handle encryption if required reader based on encryption type
if (info.enc_type == nn::hac::nca::EncryptionType::None)
{
// no encryption so do nothing
//info.reader = info.reader;
}
else if (info.enc_type == nn::hac::nca::EncryptionType::AesCtr)
{
if (mContentKey.aes_ctr.isNull())
throw tc::Exception(mModuleName, "AES-CTR Key was not determined");
// get partition key
nn::hac::detail::aes128_key_t partition_key = mContentKey.aes_ctr.get();
// get partition counter
nn::hac::detail::aes_iv_t partition_ctr = info.aes_ctr;
tc::crypto::detail::incr_counter<16>(partition_ctr.data(), info.offset>>4);
// create decryption stream
info.reader = std::make_shared<tc::crypto::Aes128CtrEncryptedStream>(tc::crypto::Aes128CtrEncryptedStream(info.reader, partition_key, partition_ctr));
}
else if (info.enc_type == nn::hac::nca::EncryptionType::AesXts || info.enc_type == nn::hac::nca::EncryptionType::AesCtrEx)
{
throw tc::Exception(mModuleName, fmt::format("EncryptionType({:s}): UNSUPPORTED", nn::hac::ContentArchiveUtil::getEncryptionTypeAsString(info.enc_type)));
}
else
{
throw tc::Exception(mModuleName, fmt::format("EncryptionType({:s}): UNKNOWN", nn::hac::ContentArchiveUtil::getEncryptionTypeAsString(info.enc_type)));
}
}
// filter out unrecognised hash types, and hash based readers
if (info.hash_type == nn::hac::nca::HashType::HierarchicalSha256 || info.hash_type == nn::hac::nca::HashType::HierarchicalIntegrity)
switch (info.hash_type)
{
info.reader = new fnd::LayeredIntegrityWrappedIFile(info.reader, info.layered_intergrity_metadata);
case (nn::hac::nca::HashType::None):
break;
case (nn::hac::nca::HashType::HierarchicalSha256):
info.reader = std::make_shared<nn::hac::HierarchicalSha256Stream>(nn::hac::HierarchicalSha256Stream(info.reader, info.hierarchicalsha256_hdr));
break;
case (nn::hac::nca::HashType::HierarchicalIntegrity):
info.reader = std::make_shared<nn::hac::HierarchicalIntegrityStream>(nn::hac::HierarchicalIntegrityStream(info.reader, info.hierarchicalintegrity_hdr));
break;
default:
throw tc::Exception(mModuleName, fmt::format("HashType({:s}): UNKNOWN", nn::hac::ContentArchiveUtil::getHashTypeAsString(info.hash_type)));
}
else if (info.hash_type != nn::hac::nca::HashType::None)
// filter out unrecognised format types
switch (info.format_type)
{
error.clear();
error << "HashType(" << nn::hac::ContentArchiveUtil::getHashTypeAsString(info.hash_type) << "): UNKNOWN";
throw fnd::Exception(kModuleName, error.str());
case (nn::hac::nca::FormatType::PartitionFs):
info.fs_meta = nn::hac::PartitionFsMetaGenerator(info.reader);
info.fs_reader = std::make_shared<tc::io::VirtualFileSystem>(tc::io::VirtualFileSystem(info.fs_meta));
break;
case (nn::hac::nca::FormatType::RomFs):
info.fs_meta = nn::hac::RomFsMetaGenerator(info.reader);
info.fs_reader = std::make_shared<tc::io::VirtualFileSystem>(tc::io::VirtualFileSystem(info.fs_meta));
break;
default:
throw tc::Exception(mModuleName, fmt::format("FormatType({:s}): UNKNOWN", nn::hac::ContentArchiveUtil::getFormatTypeAsString(info.format_type)));
}
}
catch (const fnd::Exception& e)
catch (const tc::Exception& e)
{
info.fail_reason = std::string(e.error());
}
}
}
void NcaProcess::validateNcaSignatures()
void nstool::NcaProcess::validateNcaSignatures()
{
// validate signature[0]
fnd::rsa::sRsa2048Key sign0_key;
mKeyCfg.getContentArchiveHeader0SignKey(sign0_key, mHdr.getSignatureKeyGeneration());
if (fnd::rsa::pss::rsaVerify(sign0_key, fnd::sha::HASH_SHA256, mHdrHash.bytes, mHdrBlock.signature_main) != 0)
if (mKeyCfg.nca_header_sign0_key.find(mHdr.getSignatureKeyGeneration()) != mKeyCfg.nca_header_sign0_key.end())
{
std::cout << "[WARNING] NCA Header Main Signature: FAIL" << std::endl;
if (tc::crypto::VerifyRsa2048PssSha256(mHdrBlock.signature_main.data(), mHdrHash.data(), mKeyCfg.nca_header_sign0_key[mHdr.getSignatureKeyGeneration()]) == false)
{
fmt::print("[WARNING] NCA Header Main Signature: FAIL\n");
}
}
else
{
fmt::print("[WARNING] NCA Header Main Signature: FAIL (could not load header key)\n");
}
// validate signature[1]
if (mHdr.getContentType() == nn::hac::nca::ContentType::Program)
{
if (mPartitions[nn::hac::nca::PARTITION_CODE].format_type == nn::hac::nca::FormatType::PartitionFs)
{
if (*mPartitions[nn::hac::nca::PARTITION_CODE].reader != nullptr)
try {
if (mPartitions[nn::hac::nca::PARTITION_CODE].format_type == nn::hac::nca::FormatType::PartitionFs)
{
PfsProcess exefs;
exefs.setInputFile(mPartitions[nn::hac::nca::PARTITION_CODE].reader);
exefs.setCliOutputMode(0);
exefs.process();
// open main.npdm
if (exefs.getPfsHeader().getFileList().hasElement(kNpdmExefsPath) == true)
if (mPartitions[nn::hac::nca::PARTITION_CODE].fs_reader != nullptr)
{
const nn::hac::PartitionFsHeader::sFile& file = exefs.getPfsHeader().getFileList().getElement(kNpdmExefsPath);
MetaProcess npdm;
npdm.setInputFile(new fnd::OffsetAdjustedIFile(mPartitions[nn::hac::nca::PARTITION_CODE].reader, file.offset, file.size));
npdm.setKeyCfg(mKeyCfg);
npdm.setVerifyMode(true);
npdm.setCliOutputMode(0);
npdm.process();
if (fnd::rsa::pss::rsaVerify(npdm.getMeta().getAccessControlInfoDesc().getContentArchiveHeaderSignature2Key(), fnd::sha::HASH_SHA256, mHdrHash.bytes, mHdrBlock.signature_acid) != 0)
{
std::cout << "[WARNING] NCA Header ACID Signature: FAIL" << std::endl;
std::shared_ptr<tc::io::IStream> npdm_file;
try {
mPartitions[nn::hac::nca::PARTITION_CODE].fs_reader->openFile(tc::io::Path(kNpdmExefsPath), tc::io::FileMode::Open, tc::io::FileAccess::Read, npdm_file);
}
catch (tc::io::FileNotFoundException&) {
throw tc::Exception(fmt::format("\"{:s}\" not present in ExeFs", kNpdmExefsPath));
}
MetaProcess npdm;
npdm.setInputFile(npdm_file);
npdm.setKeyCfg(mKeyCfg);
npdm.setVerifyMode(true);
npdm.setCliOutputMode(CliOutputMode(false, false, false, false));
npdm.process();
if (tc::crypto::VerifyRsa2048PssSha256(mHdrBlock.signature_acid.data(), mHdrHash.data(), npdm.getMeta().getAccessControlInfoDesc().getContentArchiveHeaderSignature2Key()) == false)
{
throw tc::Exception("Bad signature");
}
}
else
{
std::cout << "[WARNING] NCA Header ACID Signature: FAIL (\"" << kNpdmExefsPath << "\" not present in ExeFs)" << std::endl;
throw tc::Exception("ExeFs was not mounted");
}
}
else
{
std::cout << "[WARNING] NCA Header ACID Signature: FAIL (ExeFs unreadable)" << std::endl;
throw tc::Exception("No ExeFs partition");
}
}
else
{
std::cout << "[WARNING] NCA Header ACID Signature: FAIL (No ExeFs partition)" << std::endl;
catch (tc::Exception& e) {
fmt::print("[WARNING] NCA Header ACID Signature: FAIL ({:s})\n", e.error());
}
}
}
void NcaProcess::displayHeader()
void nstool::NcaProcess::displayHeader()
{
std::cout << "[NCA Header]" << std::endl;
std::cout << " Format Type: " << nn::hac::ContentArchiveUtil::getFormatHeaderVersionAsString((nn::hac::nca::HeaderFormatVersion)mHdr.getFormatVersion()) << std::endl;
std::cout << " Dist. Type: " << nn::hac::ContentArchiveUtil::getDistributionTypeAsString(mHdr.getDistributionType()) << std::endl;
std::cout << " Content Type: " << nn::hac::ContentArchiveUtil::getContentTypeAsString(mHdr.getContentType()) << std::endl;
std::cout << " Key Generation: " << std::dec << (uint32_t)mHdr.getKeyGeneration() << std::endl;
std::cout << " Sig. Generation: " << std::dec << (uint32_t)mHdr.getSignatureKeyGeneration() << std::endl;
std::cout << " Kaek Index: " << nn::hac::ContentArchiveUtil::getKeyAreaEncryptionKeyIndexAsString((nn::hac::nca::KeyAreaEncryptionKeyIndex)mHdr.getKeyAreaEncryptionKeyIndex()) << " (" << std::dec << (uint32_t)mHdr.getKeyAreaEncryptionKeyIndex() << ")" << std::endl;
std::cout << " Size: 0x" << std::hex << mHdr.getContentSize() << std::endl;
std::cout << " ProgID: 0x" << std::hex << std::setw(16) << std::setfill('0') << mHdr.getProgramId() << std::endl;
std::cout << " Content Index: " << std::dec << mHdr.getContentIndex() << std::endl;
std::cout << " SdkAddon Ver.: " << nn::hac::ContentArchiveUtil::getSdkAddonVersionAsString(mHdr.getSdkAddonVersion()) << " (v" << std::dec << mHdr.getSdkAddonVersion() << ")" << std::endl;
fmt::print("[NCA Header]\n");
fmt::print(" Format Type: {:s}\n", nn::hac::ContentArchiveUtil::getFormatHeaderVersionAsString((nn::hac::nca::HeaderFormatVersion)mHdr.getFormatVersion()));
fmt::print(" Dist. Type: {:s}\n", nn::hac::ContentArchiveUtil::getDistributionTypeAsString(mHdr.getDistributionType()));
fmt::print(" Content Type: {:s}\n", nn::hac::ContentArchiveUtil::getContentTypeAsString(mHdr.getContentType()));
fmt::print(" Key Generation: {:d}\n", mHdr.getKeyGeneration());
fmt::print(" Sig. Generation: {:d}\n", mHdr.getSignatureKeyGeneration());
fmt::print(" Kaek Index: {:s} ({:d})\n", nn::hac::ContentArchiveUtil::getKeyAreaEncryptionKeyIndexAsString((nn::hac::nca::KeyAreaEncryptionKeyIndex)mHdr.getKeyAreaEncryptionKeyIndex()), mHdr.getKeyAreaEncryptionKeyIndex());
fmt::print(" Size: 0x{:x}\n", mHdr.getContentSize());
fmt::print(" ProgID: 0x{:016x}\n", mHdr.getProgramId());
fmt::print(" Content Index: {:d}\n", mHdr.getContentIndex());
fmt::print(" SdkAddon Ver.: {:s} (v{:d})\n", nn::hac::ContentArchiveUtil::getSdkAddonVersionAsString(mHdr.getSdkAddonVersion()), mHdr.getSdkAddonVersion());
if (mHdr.hasRightsId())
{
std::cout << " RightsId: " << fnd::SimpleTextOutput::arrayToString(mHdr.getRightsId(), nn::hac::nca::kRightsIdLen, true, "") << std::endl;
fmt::print(" RightsId: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mHdr.getRightsId().data(), mHdr.getRightsId().size(), true, ""));
}
if (mContentKey.kak_list.size() > 0 && _HAS_BIT(mCliOutputMode, OUTPUT_KEY_DATA))
if (mContentKey.kak_list.size() > 0 && mCliOutputMode.show_keydata)
{
std::cout << " Key Area:" << std::endl;
std::cout << " <--------------------------------------------------------------------------------------------------------->" << std::endl;
std::cout << " | IDX | ENCRYPTED KEY | DECRYPTED KEY |" << std::endl;
std::cout << " |-----|-------------------------------------------------|-------------------------------------------------|" << std::endl;
fmt::print(" Key Area:\n");
fmt::print(" <--------------------------------------------------------------------------------------------------------->\n");
fmt::print(" | IDX | ENCRYPTED KEY | DECRYPTED KEY |\n");
fmt::print(" |-----|-------------------------------------------------|-------------------------------------------------|\n");
for (size_t i = 0; i < mContentKey.kak_list.size(); i++)
{
std::cout << " | " << std::dec << std::setw(3) << std::setfill(' ') << (uint32_t)mContentKey.kak_list[i].index << " | ";
std::cout << fnd::SimpleTextOutput::arrayToString(mContentKey.kak_list[i].enc.key, 16, true, ":") << " | ";
fmt::print(" | {:3d} | {:s} | ", mContentKey.kak_list[i].index, tc::cli::FormatUtil::formatBytesAsString(mContentKey.kak_list[i].enc.data(), mContentKey.kak_list[i].enc.size(), true, ""));
if (mContentKey.kak_list[i].decrypted)
std::cout << fnd::SimpleTextOutput::arrayToString(mContentKey.kak_list[i].dec.key, 16, true, ":");
fmt::print("{:s}", tc::cli::FormatUtil::formatBytesAsString(mContentKey.kak_list[i].dec.data(), mContentKey.kak_list[i].dec.size(), true, ""));
else
std::cout << "<unable to decrypt> ";
fmt::print("<unable to decrypt> ");
std::cout << " |" << std::endl;
fmt::print(" |\n");
}
std::cout << " <--------------------------------------------------------------------------------------------------------->" << std::endl;
fmt::print(" <--------------------------------------------------------------------------------------------------------->\n");
}
if (_HAS_BIT(mCliOutputMode, OUTPUT_LAYOUT))
if (mCliOutputMode.show_layout)
{
std::cout << " Partitions:" << std::endl;
fmt::print(" Partitions:\n");
for (size_t i = 0; i < mHdr.getPartitionEntryList().size(); i++)
{
uint32_t index = mHdr.getPartitionEntryList()[i].header_index;
sPartitionInfo& info = mPartitions[index];
if (info.size == 0) continue;
std::cout << " " << std::dec << index << ":" << std::endl;
std::cout << " Offset: 0x" << std::hex << (uint64_t)info.offset << std::endl;
std::cout << " Size: 0x" << std::hex << (uint64_t)info.size << std::endl;
std::cout << " Format Type: " << nn::hac::ContentArchiveUtil::getFormatTypeAsString(info.format_type) << std::endl;
std::cout << " Hash Type: " << nn::hac::ContentArchiveUtil::getHashTypeAsString(info.hash_type) << std::endl;
std::cout << " Enc. Type: " << nn::hac::ContentArchiveUtil::getEncryptionTypeAsString(info.enc_type) << std::endl;
fmt::print(" {:d}:\n", index);
fmt::print(" Offset: 0x{:x}\n", info.offset);
fmt::print(" Size: 0x{:x}\n", info.size);
fmt::print(" Format Type: {:s}\n", nn::hac::ContentArchiveUtil::getFormatTypeAsString(info.format_type));
fmt::print(" Hash Type: {:s}\n", nn::hac::ContentArchiveUtil::getHashTypeAsString(info.hash_type));
fmt::print(" Enc. Type: {:s}\n", nn::hac::ContentArchiveUtil::getEncryptionTypeAsString(info.enc_type));
if (info.enc_type == nn::hac::nca::EncryptionType::AesCtr)
{
fnd::aes::sAesIvCtr ctr;
fnd::aes::AesIncrementCounter(info.aes_ctr.iv, info.offset>>4, ctr.iv);
std::cout << " AesCtr Counter:" << std::endl;
std::cout << " " << fnd::SimpleTextOutput::arrayToString(ctr.iv, sizeof(fnd::aes::sAesIvCtr), true, ":") << std::endl;
nn::hac::detail::aes_iv_t aes_ctr;
memcpy(aes_ctr.data(), info.aes_ctr.data(), aes_ctr.size());
tc::crypto::detail::incr_counter<16>(aes_ctr.data(), info.offset>>4);
fmt::print(" AesCtr Counter:\n");
fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(aes_ctr.data(), aes_ctr.size(), true, ""));
}
if (info.hash_type == nn::hac::nca::HashType::HierarchicalIntegrity)
{
fnd::LayeredIntegrityMetadata& hash_hdr = info.layered_intergrity_metadata;
std::cout << " HierarchicalIntegrity Header:" << std::endl;
for (size_t j = 0; j < hash_hdr.getHashLayerInfo().size(); j++)
auto hash_hdr = info.hierarchicalintegrity_hdr;
fmt::print(" HierarchicalIntegrity Header:\n");
for (size_t j = 0; j < hash_hdr.getLayerInfo().size(); j++)
{
std::cout << " Hash Layer " << std::dec << j << ":" << std::endl;
std::cout << " Offset: 0x" << std::hex << (uint64_t)hash_hdr.getHashLayerInfo()[j].offset << std::endl;
std::cout << " Size: 0x" << std::hex << (uint64_t)hash_hdr.getHashLayerInfo()[j].size << std::endl;
std::cout << " BlockSize: 0x" << std::hex << (uint32_t)hash_hdr.getHashLayerInfo()[j].block_size << std::endl;
if (j+1 == hash_hdr.getLayerInfo().size())
{
fmt::print(" Data Layer:\n");
}
else
{
fmt::print(" Hash Layer {:d}:\n", j);
}
fmt::print(" Offset: 0x{:x}\n", hash_hdr.getLayerInfo()[j].offset);
fmt::print(" Size: 0x{:x}\n", hash_hdr.getLayerInfo()[j].size);
fmt::print(" BlockSize: 0x{:x}\n", hash_hdr.getLayerInfo()[j].block_size);
}
std::cout << " Data Layer:" << std::endl;
std::cout << " Offset: 0x" << std::hex << (uint64_t)hash_hdr.getDataLayer().offset << std::endl;
std::cout << " Size: 0x" << std::hex << (uint64_t)hash_hdr.getDataLayer().size << std::endl;
std::cout << " BlockSize: 0x" << std::hex << (uint32_t)hash_hdr.getDataLayer().block_size << std::endl;
for (size_t j = 0; j < hash_hdr.getMasterHashList().size(); j++)
{
std::cout << " Master Hash " << std::dec << j << ":" << std::endl;
std::cout << " " << fnd::SimpleTextOutput::arrayToString(hash_hdr.getMasterHashList()[j].bytes, 0x10, true, ":") << std::endl;
std::cout << " " << fnd::SimpleTextOutput::arrayToString(hash_hdr.getMasterHashList()[j].bytes+0x10, 0x10, true, ":") << std::endl;
fmt::print(" Master Hash {:d}:\n", j);
fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(hash_hdr.getMasterHashList()[j].data(), 0x10, true, ""));
fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(hash_hdr.getMasterHashList()[j].data()+0x10, 0x10, true, ""));
}
}
else if (info.hash_type == nn::hac::nca::HashType::HierarchicalSha256)
{
fnd::LayeredIntegrityMetadata& hash_hdr = info.layered_intergrity_metadata;
std::cout << " HierarchicalSha256 Header:" << std::endl;
std::cout << " Master Hash:" << std::endl;
std::cout << " " << fnd::SimpleTextOutput::arrayToString(hash_hdr.getMasterHashList()[0].bytes, 0x10, true, ":") << std::endl;
std::cout << " " << fnd::SimpleTextOutput::arrayToString(hash_hdr.getMasterHashList()[0].bytes+0x10, 0x10, true, ":") << std::endl;
std::cout << " HashBlockSize: 0x" << std::hex << (uint32_t)hash_hdr.getDataLayer().block_size << std::endl;
std::cout << " Hash Layer:" << std::endl;
std::cout << " Offset: 0x" << std::hex << (uint64_t)hash_hdr.getHashLayerInfo()[0].offset << std::endl;
std::cout << " Size: 0x" << std::hex << (uint64_t)hash_hdr.getHashLayerInfo()[0].size << std::endl;
std::cout << " Data Layer:" << std::endl;
std::cout << " Offset: 0x" << std::hex << (uint64_t)hash_hdr.getDataLayer().offset << std::endl;
std::cout << " Size: 0x" << std::hex << (uint64_t)hash_hdr.getDataLayer().size << std::endl;
auto hash_hdr = info.hierarchicalsha256_hdr;
fmt::print(" HierarchicalSha256 Header:\n");
fmt::print(" Master Hash:\n");
fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(hash_hdr.getMasterHash().data(), 0x10, true, ""));
fmt::print(" {:s}\n", tc::cli::FormatUtil::formatBytesAsString(hash_hdr.getMasterHash().data()+0x10, 0x10, true, ""));
fmt::print(" HashBlockSize: 0x{:x}\n", hash_hdr.getHashBlockSize());
for (size_t j = 0; j < hash_hdr.getLayerInfo().size(); j++)
{
if (j+1 == hash_hdr.getLayerInfo().size())
{
fmt::print(" Data Layer:\n");
}
else
{
fmt::print(" Hash Layer {:d}:\n", j);
}
fmt::print(" Offset: 0x{:x}\n", hash_hdr.getLayerInfo()[j].offset);
fmt::print(" Size: 0x{:x}\n", hash_hdr.getLayerInfo()[j].size);
}
}
}
}
}
void NcaProcess::processPartitions()
void nstool::NcaProcess::processPartitions()
{
std::vector<nn::hac::CombinedFsMetaGenerator::MountPointInfo> mount_points;
for (size_t i = 0; i < mHdr.getPartitionEntryList().size(); i++)
{
uint32_t index = mHdr.getPartitionEntryList()[i].header_index;
struct sPartitionInfo& partition = mPartitions[index];
// if the reader is null, skip
if (*partition.reader == nullptr)
if (partition.fs_reader == nullptr)
{
std::cout << "[WARNING] NCA Partition " << std::dec << index << " not readable.";
fmt::print("[WARNING] NCA Partition {:d} not readable.", index);
if (partition.fail_reason.empty() == false)
{
std::cout << " (" << partition.fail_reason << ")";
fmt::print(" ({:s})", partition.fail_reason);
}
std::cout << std::endl;
fmt::print("\n");
continue;
}
if (partition.format_type == nn::hac::nca::FormatType::PartitionFs)
std::string mount_point_name;
/*
if (mHdr.getContentType() == nn::hac::nca::ContentType::Program)
{
PfsProcess pfs;
pfs.setInputFile(partition.reader);
pfs.setCliOutputMode(mCliOutputMode);
pfs.setListFs(mListFs);
if (mHdr.getContentType() == nn::hac::nca::ContentType::Program)
{
pfs.setMountPointName(std::string(getContentTypeForMountStr(mHdr.getContentType())) + ":/" + nn::hac::ContentArchiveUtil::getProgramContentParititionIndexAsString((nn::hac::nca::ProgramContentPartitionIndex)index));
}
else
{
pfs.setMountPointName(std::string(getContentTypeForMountStr(mHdr.getContentType())) + ":/");
}
if (mPartitionPath[index].doExtract)
pfs.setExtractPath(mPartitionPath[index].path);
pfs.process();
mount_point_name = nn::hac::ContentArchiveUtil::getProgramContentParititionIndexAsString((nn::hac::nca::ProgramContentPartitionIndex)index);
}
else if (partition.format_type == nn::hac::nca::FormatType::RomFs)
else
*/
{
RomfsProcess romfs;
romfs.setInputFile(partition.reader);
romfs.setCliOutputMode(mCliOutputMode);
romfs.setListFs(mListFs);
if (mHdr.getContentType() == nn::hac::nca::ContentType::Program)
{
romfs.setMountPointName(std::string(getContentTypeForMountStr(mHdr.getContentType())) + ":/" + nn::hac::ContentArchiveUtil::getProgramContentParititionIndexAsString((nn::hac::nca::ProgramContentPartitionIndex)index));
}
else
{
romfs.setMountPointName(std::string(getContentTypeForMountStr(mHdr.getContentType())) + ":/");
}
if (mPartitionPath[index].doExtract)
romfs.setExtractPath(mPartitionPath[index].path);
romfs.process();
mount_point_name = fmt::format("{:d}", index);
}
mount_points.push_back( { mount_point_name, partition.fs_meta } );
}
tc::io::VirtualFileSystem::FileSystemMeta fs_meta = nn::hac::CombinedFsMetaGenerator(mount_points);
std::shared_ptr<tc::io::IStorage> nca_fs = std::make_shared<tc::io::VirtualFileSystem>(tc::io::VirtualFileSystem(fs_meta));
mFsProcess.setInputFileSystem(nca_fs);
mFsProcess.setFsFormatName("ContentArchive");
mFsProcess.setFsRootLabel(getContentTypeForMountStr(mHdr.getContentType()));
mFsProcess.process();
}
const char* NcaProcess::getContentTypeForMountStr(nn::hac::nca::ContentType cont_type) const
std::string nstool::NcaProcess::getContentTypeForMountStr(nn::hac::nca::ContentType cont_type) const
{
const char* str = nullptr;
std::string str;
switch (cont_type)
{

View file

@ -1,14 +1,13 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/IFile.h>
#include <fnd/SharedPtr.h>
#include <fnd/LayeredIntegrityMetadata.h>
#include "types.h"
#include "KeyBag.h"
#include "FsProcess.h"
#include <nn/hac/ContentArchiveHeader.h>
#include "KeyConfiguration.h"
#include <nn/hac/HierarchicalIntegrityHeader.h>
#include <nn/hac/HierarchicalSha256Header.h>
#include "common.h"
namespace nstool {
class NcaProcess
{
@ -18,39 +17,36 @@ public:
void process();
// generic
void setInputFile(const fnd::SharedPtr<fnd::IFile>& file);
void setKeyCfg(const KeyConfiguration& keycfg);
void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setKeyCfg(const KeyBag& keycfg);
void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify);
// nca specfic
void setPartition0ExtractPath(const std::string& path);
void setPartition1ExtractPath(const std::string& path);
void setPartition2ExtractPath(const std::string& path);
void setPartition3ExtractPath(const std::string& path);
void setListFs(bool list_fs);
// fs specific
void setShowFsTree(bool show_fs_tree);
void setFsRootLabel(const std::string& root_label);
void setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs);
// post process() get FS out
const std::shared_ptr<tc::io::IStorage>& getFileSystem() const;
private:
const std::string kModuleName = "NcaProcess";
const std::string kNpdmExefsPath = "main.npdm";
const std::string kNpdmExefsPath = "/main.npdm";
std::string mModuleName;
// user options
fnd::SharedPtr<fnd::IFile> mFile;
KeyConfiguration mKeyCfg;
std::shared_ptr<tc::io::IStream> mFile;
KeyBag mKeyCfg;
CliOutputMode mCliOutputMode;
bool mVerify;
struct sExtract
{
std::string path;
bool doExtract;
} mPartitionPath[nn::hac::nca::kPartitionNum];
// fs processing
std::shared_ptr<tc::io::IStorage> mFileSystem;
FsProcess mFsProcess;
bool mListFs;
// data
// nca data
nn::hac::sContentArchiveHeaderBlock mHdrBlock;
fnd::sha::sSha256Hash mHdrHash;
nn::hac::detail::sha256_hash_t mHdrHash;
nn::hac::ContentArchiveHeader mHdr;
// crypto
@ -60,8 +56,8 @@ private:
{
byte_t index;
bool decrypted;
fnd::aes::sAes128Key enc;
fnd::aes::sAes128Key dec;
KeyBag::aes128_key_t enc;
KeyBag::aes128_key_t dec;
void operator=(const sKeyAreaKey& other)
{
@ -84,25 +80,43 @@ private:
return !(*this == other);
}
};
fnd::List<sKeyAreaKey> kak_list;
std::vector<sKeyAreaKey> kak_list;
sOptional<fnd::aes::sAes128Key> aes_ctr;
tc::Optional<nn::hac::detail::aes128_key_t> aes_ctr;
} mContentKey;
struct SparseInfo
{
};
// raw partition data
struct sPartitionInfo
{
fnd::SharedPtr<fnd::IFile> reader;
std::shared_ptr<tc::io::IStream> reader;
tc::io::VirtualFileSystem::FileSystemMeta fs_meta;
std::shared_ptr<tc::io::IStorage> fs_reader;
std::string fail_reason;
size_t offset;
size_t size;
int64_t offset;
int64_t size;
// meta data
nn::hac::nca::FormatType format_type;
nn::hac::nca::HashType hash_type;
nn::hac::nca::EncryptionType enc_type;
fnd::LayeredIntegrityMetadata layered_intergrity_metadata;
fnd::aes::sAesIvCtr aes_ctr;
} mPartitions[nn::hac::nca::kPartitionNum];
// hash meta data
nn::hac::HierarchicalIntegrityHeader hierarchicalintegrity_hdr;
nn::hac::HierarchicalSha256Header hierarchicalsha256_hdr;
// crypto metadata
nn::hac::detail::aes_iv_t aes_ctr;
// sparse metadata
SparseInfo sparse_info;
};
std::array<sPartitionInfo, nn::hac::nca::kPartitionNum> mPartitions;
void importHeader();
void generateNcaBodyEncryptionKeys();
@ -111,5 +125,7 @@ private:
void displayHeader();
void processPartitions();
const char* getContentTypeForMountStr(nn::hac::nca::ContentType cont_type) const;
std::string getContentTypeForMountStr(nn::hac::nca::ContentType cont_type) const;
};
}

View file

@ -1,25 +1,21 @@
#include <iostream>
#include <iomanip>
#include <fnd/SimpleTextOutput.h>
#include <fnd/OffsetAdjustedIFile.h>
#include <fnd/Vec.h>
#include <fnd/lz4.h>
#include <nn/hac/define/nro-hb.h>
#include "NroProcess.h"
NroProcess::NroProcess():
#include <nn/hac/define/nro-hb.h>
nstool::NroProcess::NroProcess() :
mModuleName("nstool::NroProcess"),
mFile(),
mCliOutputMode(_BIT(OUTPUT_BASIC)),
mCliOutputMode(true, false, false, false),
mVerify(false)
{
}
void NroProcess::process()
void nstool::NroProcess::process()
{
importHeader();
importCodeSegments();
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
if (mCliOutputMode.show_basic_info)
displayHeader();
processRoMeta();
@ -28,86 +24,94 @@ void NroProcess::process()
mAssetProc.process();
}
void NroProcess::setInputFile(const fnd::SharedPtr<fnd::IFile>& file)
void nstool::NroProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
{
mFile = file;
}
void NroProcess::setCliOutputMode(CliOutputMode type)
void nstool::NroProcess::setCliOutputMode(CliOutputMode type)
{
mCliOutputMode = type;
}
void NroProcess::setVerifyMode(bool verify)
void nstool::NroProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void NroProcess::setIs64BitInstruction(bool flag)
void nstool::NroProcess::setIs64BitInstruction(bool flag)
{
mRoMeta.setIs64BitInstruction(flag);
}
void NroProcess::setListApi(bool listApi)
void nstool::NroProcess::setListApi(bool listApi)
{
mRoMeta.setListApi(listApi);
}
void NroProcess::setListSymbols(bool listSymbols)
void nstool::NroProcess::setListSymbols(bool listSymbols)
{
mRoMeta.setListSymbols(listSymbols);
}
void NroProcess::setAssetListFs(bool list)
{
mAssetProc.setListFs(list);
}
void NroProcess::setAssetIconExtractPath(const std::string& path)
void nstool::NroProcess::setAssetIconExtractPath(const tc::io::Path& path)
{
mAssetProc.setIconExtractPath(path);
}
void NroProcess::setAssetNacpExtractPath(const std::string& path)
void nstool::NroProcess::setAssetNacpExtractPath(const tc::io::Path& path)
{
mAssetProc.setNacpExtractPath(path);
}
void NroProcess::setAssetRomfsExtractPath(const std::string& path)
void nstool::NroProcess::setAssetRomfsShowFsTree(bool show_fs_tree)
{
mAssetProc.setRomfsExtractPath(path);
mAssetProc.setRomfsShowFsTree(show_fs_tree);
}
const RoMetadataProcess& NroProcess::getRoMetadataProcess() const
void nstool::NroProcess::setAssetRomfsExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs)
{
mAssetProc.setRomfsExtractJobs(extract_jobs);
}
const nstool::RoMetadataProcess& nstool::NroProcess::getRoMetadataProcess() const
{
return mRoMeta;
}
void NroProcess::importHeader()
void nstool::NroProcess::importHeader()
{
fnd::Vec<byte_t> scratch;
if (*mFile == nullptr)
if (mFile == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
throw tc::Exception(mModuleName, "No file reader set.");
}
if (mFile->canRead() == false || mFile->canSeek() == false)
{
throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions.");
}
if ((*mFile)->size() < sizeof(nn::hac::sNroHeader))
// check if file_size is smaller than NRO header size
if (tc::io::IOUtil::castInt64ToSize(mFile->length()) < sizeof(nn::hac::sNroHeader))
{
throw fnd::Exception(kModuleName, "Corrupt NRO: file too small");
throw tc::Exception(mModuleName, "Corrupt NRO: file too small.");
}
scratch.alloc(sizeof(nn::hac::sNroHeader));
(*mFile)->read(scratch.data(), 0, scratch.size());
// read nro
tc::ByteData scratch = tc::ByteData(sizeof(nn::hac::sNroHeader));
mFile->seek(0, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
// parse nro header
mHdr.fromBytes(scratch.data(), scratch.size());
// setup homebrew extension
nn::hac::sNroHeader* raw_hdr = (nn::hac::sNroHeader*)scratch.data();
if (((le_uint64_t*)raw_hdr->reserved_0)->get() == nn::hac::nro::kNroHomebrewStructMagic && (*mFile)->size() > mHdr.getNroSize())
int64_t file_size = mFile->length();
if (((tc::bn::le64<uint64_t>*)raw_hdr->reserved_0.data())->unwrap() == nn::hac::nro::kNroHomebrewStructMagic && file_size > int64_t(mHdr.getNroSize()))
{
mIsHomebrewNro = true;
mAssetProc.setInputFile(new fnd::OffsetAdjustedIFile(mFile, mHdr.getNroSize(), (*mFile)->size() - mHdr.getNroSize()));
mAssetProc.setInputFile(std::make_shared<tc::io::SubStream>(tc::io::SubStream(mFile, int64_t(mHdr.getNroSize()), file_size - int64_t(mHdr.getNroSize()))));
mAssetProc.setCliOutputMode(mCliOutputMode);
mAssetProc.setVerifyMode(mVerify);
}
@ -115,51 +119,65 @@ void NroProcess::importHeader()
mIsHomebrewNro = false;
}
void NroProcess::importCodeSegments()
void nstool::NroProcess::importCodeSegments()
{
mTextBlob.alloc(mHdr.getTextInfo().size);
(*mFile)->read(mTextBlob.data(), mHdr.getTextInfo().memory_offset, mTextBlob.size());
mRoBlob.alloc(mHdr.getRoInfo().size);
(*mFile)->read(mRoBlob.data(), mHdr.getRoInfo().memory_offset, mRoBlob.size());
mDataBlob.alloc(mHdr.getDataInfo().size);
(*mFile)->read(mDataBlob.data(), mHdr.getDataInfo().memory_offset, mDataBlob.size());
}
void NroProcess::displayHeader()
{
std::cout << "[NRO Header]" << std::endl;
std::cout << " RoCrt: " << std::endl;
std::cout << " EntryPoint: 0x" << std::hex << mHdr.getRoCrtEntryPoint() << std::endl;
std::cout << " ModOffset: 0x" << std::hex << mHdr.getRoCrtModOffset() << std::endl;
std::cout << " ModuleId: " << fnd::SimpleTextOutput::arrayToString(mHdr.getModuleId().data, nn::hac::nro::kModuleIdSize, false, "") << std::endl;
std::cout << " NroSize: 0x" << std::hex << mHdr.getNroSize() << std::endl;
std::cout << " Program Sections:" << std::endl;
std::cout << " .text:" << std::endl;
std::cout << " Offset: 0x" << std::hex << mHdr.getTextInfo().memory_offset << std::endl;
std::cout << " Size: 0x" << std::hex << mHdr.getTextInfo().size << std::endl;
std::cout << " .ro:" << std::endl;
std::cout << " Offset: 0x" << std::hex << mHdr.getRoInfo().memory_offset << std::endl;
std::cout << " Size: 0x" << std::hex << mHdr.getRoInfo().size << std::endl;
if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mHdr.getTextInfo().size > 0)
{
std::cout << " .api_info:" << std::endl;
std::cout << " Offset: 0x" << std::hex << mHdr.getRoEmbeddedInfo().memory_offset << std::endl;
std::cout << " Size: 0x" << std::hex << mHdr.getRoEmbeddedInfo().size << std::endl;
std::cout << " .dynstr:" << std::endl;
std::cout << " Offset: 0x" << std::hex << mHdr.getRoDynStrInfo().memory_offset << std::endl;
std::cout << " Size: 0x" << std::hex << mHdr.getRoDynStrInfo().size << std::endl;
std::cout << " .dynsym:" << std::endl;
std::cout << " Offset: 0x" << std::hex << mHdr.getRoDynSymInfo().memory_offset << std::endl;
std::cout << " Size: 0x" << std::hex << mHdr.getRoDynSymInfo().size << std::endl;
mTextBlob = tc::ByteData(mHdr.getTextInfo().size);
mFile->seek(mHdr.getTextInfo().memory_offset, tc::io::SeekOrigin::Begin);
mFile->read(mTextBlob.data(), mTextBlob.size());
}
if (mHdr.getRoInfo().size > 0)
{
mRoBlob = tc::ByteData(mHdr.getRoInfo().size);
mFile->seek(mHdr.getRoInfo().memory_offset, tc::io::SeekOrigin::Begin);
mFile->read(mRoBlob.data(), mRoBlob.size());
}
if (mHdr.getDataInfo().size > 0)
{
mDataBlob = tc::ByteData(mHdr.getDataInfo().size);
mFile->seek(mHdr.getDataInfo().memory_offset, tc::io::SeekOrigin::Begin);
mFile->read(mDataBlob.data(), mDataBlob.size());
}
std::cout << " .data:" << std::endl;
std::cout << " Offset: 0x" << std::hex << mHdr.getDataInfo().memory_offset << std::endl;
std::cout << " Size: 0x" << std::hex << mHdr.getDataInfo().size << std::endl;
std::cout << " .bss:" << std::endl;
std::cout << " Size: 0x" << std::hex << mHdr.getBssSize() << std::endl;
}
void NroProcess::processRoMeta()
void nstool::NroProcess::displayHeader()
{
fmt::print("[NRO Header]\n");
fmt::print(" RoCrt: \n");
fmt::print(" EntryPoint: 0x{:x}\n", mHdr.getRoCrtEntryPoint());
fmt::print(" ModOffset: 0x{:x}\n", mHdr.getRoCrtModOffset());
fmt::print(" ModuleId: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mHdr.getModuleId().data(), mHdr.getModuleId().size(), false, ""));
fmt::print(" NroSize: 0x{:x}\n", mHdr.getNroSize());
fmt::print(" Program Sections:\n");
fmt::print(" .text:\n");
fmt::print(" Offset: 0x{:x}\n", mHdr.getTextInfo().memory_offset);
fmt::print(" Size: 0x{:x}\n", mHdr.getTextInfo().size);
fmt::print(" .ro:\n");
fmt::print(" Offset: 0x{:x}\n", mHdr.getRoInfo().memory_offset);
fmt::print(" Size: 0x{:x}\n", mHdr.getRoInfo().size);
if (mCliOutputMode.show_extended_info)
{
fmt::print(" .api_info:\n");
fmt::print(" Offset: 0x{:x}\n", mHdr.getRoEmbeddedInfo().memory_offset);
fmt::print(" Size: 0x{:x}\n", mHdr.getRoEmbeddedInfo().size);
fmt::print(" .dynstr:\n");
fmt::print(" Offset: 0x{:x}\n", mHdr.getRoDynStrInfo().memory_offset);
fmt::print(" Size: 0x{:x}\n", mHdr.getRoDynStrInfo().size);
fmt::print(" .dynsym:\n");
fmt::print(" Offset: 0x{:x}\n", mHdr.getRoDynSymInfo().memory_offset);
fmt::print(" Size: 0x{:x}\n", mHdr.getRoDynSymInfo().size);
}
fmt::print(" .data:\n");
fmt::print(" Offset: 0x{:x}\n", mHdr.getDataInfo().memory_offset);
fmt::print(" Size: 0x{:x}\n", mHdr.getDataInfo().size);
fmt::print(" .bss:\n");
fmt::print(" Size: 0x{:x}\n", mHdr.getBssSize());
}
void nstool::NroProcess::processRoMeta()
{
if (mRoBlob.size())
{

View file

@ -1,15 +1,11 @@
#pragma once
#include <vector>
#include <string>
#include <fnd/types.h>
#include <fnd/IFile.h>
#include <fnd/SharedPtr.h>
#include <nn/hac/define/meta.h>
#include <nn/hac/NroHeader.h>
#include "types.h"
#include "RoMetadataProcess.h"
#include "AssetProcess.h"
#include "common.h"
#include "RoMetadataProcess.h"
#include <nn/hac/NroHeader.h>
namespace nstool {
class NroProcess
{
@ -18,7 +14,7 @@ public:
void process();
void setInputFile(const fnd::SharedPtr<fnd::IFile>& file);
void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify);
@ -27,27 +23,29 @@ public:
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);
void setAssetIconExtractPath(const tc::io::Path& path);
void setAssetNacpExtractPath(const tc::io::Path& path);
void setAssetRomfsShowFsTree(bool show_fs_tree);
void setAssetRomfsExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs);
const RoMetadataProcess& getRoMetadataProcess() const;
const nstool::RoMetadataProcess& getRoMetadataProcess() const;
private:
const std::string kModuleName = "NroProcess";
std::string mModuleName;
fnd::SharedPtr<fnd::IFile> mFile;
std::shared_ptr<tc::io::IStream> mFile;
CliOutputMode mCliOutputMode;
bool mVerify;
nn::hac::NroHeader mHdr;
fnd::Vec<byte_t> mTextBlob, mRoBlob, mDataBlob;
RoMetadataProcess mRoMeta;
tc::ByteData mTextBlob, mRoBlob, mDataBlob;
nstool::RoMetadataProcess mRoMeta;
bool mIsHomebrewNro;
AssetProcess mAssetProc;
nstool::AssetProcess mAssetProc;
void importHeader();
void importCodeSegments();
void displayHeader();
void processRoMeta();
};
}

View file

@ -1,14 +1,11 @@
#include <iostream>
#include <iomanip>
#include <fnd/SimpleTextOutput.h>
#include <fnd/OffsetAdjustedIFile.h>
#include <fnd/Vec.h>
#include <fnd/lz4.h>
#include "NsoProcess.h"
NsoProcess::NsoProcess():
#include <lz4.h>
nstool::NsoProcess::NsoProcess() :
mModuleName("nstool::NsoProcess"),
mFile(),
mCliOutputMode(_BIT(OUTPUT_BASIC)),
mCliOutputMode(true, false, false, false),
mVerify(false),
mIs64BitInstruction(true),
mListApi(false),
@ -16,216 +13,243 @@ NsoProcess::NsoProcess():
{
}
void NsoProcess::process()
void nstool::NsoProcess::process()
{
importHeader();
importCodeSegments();
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
if (mCliOutputMode.show_basic_info)
displayNsoHeader();
processRoMeta();
}
void NsoProcess::setInputFile(const fnd::SharedPtr<fnd::IFile>& file)
void nstool::NsoProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
{
mFile = file;
}
void NsoProcess::setCliOutputMode(CliOutputMode type)
void nstool::NsoProcess::setCliOutputMode(CliOutputMode type)
{
mCliOutputMode = type;
}
void NsoProcess::setVerifyMode(bool verify)
void nstool::NsoProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void NsoProcess::setIs64BitInstruction(bool flag)
void nstool::NsoProcess::setIs64BitInstruction(bool flag)
{
mRoMeta.setIs64BitInstruction(flag);
}
void NsoProcess::setListApi(bool listApi)
void nstool::NsoProcess::setListApi(bool listApi)
{
mRoMeta.setListApi(listApi);
}
void NsoProcess::setListSymbols(bool listSymbols)
void nstool::NsoProcess::setListSymbols(bool listSymbols)
{
mRoMeta.setListSymbols(listSymbols);
}
const RoMetadataProcess& NsoProcess::getRoMetadataProcess() const
const nstool::RoMetadataProcess& nstool::NsoProcess::getRoMetadataProcess() const
{
return mRoMeta;
}
void NsoProcess::importHeader()
void nstool::NsoProcess::importHeader()
{
fnd::Vec<byte_t> scratch;
if (*mFile == nullptr)
if (mFile == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
throw tc::Exception(mModuleName, "No file reader set.");
}
if (mFile->canRead() == false || mFile->canSeek() == false)
{
throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions.");
}
if ((*mFile)->size() < sizeof(nn::hac::sNsoHeader))
// check if file_size is smaller than NSO header size
size_t file_size = tc::io::IOUtil::castInt64ToSize(mFile->length());
if (file_size < sizeof(nn::hac::sNsoHeader))
{
throw fnd::Exception(kModuleName, "Corrupt NSO: file too small");
throw tc::Exception(mModuleName, "Corrupt NSO: file too small.");
}
scratch.alloc(sizeof(nn::hac::sNsoHeader));
(*mFile)->read(scratch.data(), 0, scratch.size());
// read nso
tc::ByteData scratch = tc::ByteData(sizeof(nn::hac::sNsoHeader));
mFile->seek(0, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
// parse nso header
mHdr.fromBytes(scratch.data(), scratch.size());
}
void NsoProcess::importCodeSegments()
void nstool::NsoProcess::importCodeSegments()
{
fnd::Vec<byte_t> scratch;
uint32_t decompressed_len;
fnd::sha::sSha256Hash calc_hash;
tc::ByteData scratch;
nn::hac::detail::sha256_hash_t calc_hash;
// process text segment
if (mHdr.getTextSegmentInfo().is_compressed)
{
scratch.alloc(mHdr.getTextSegmentInfo().file_layout.size);
(*mFile)->read(scratch.data(), mHdr.getTextSegmentInfo().file_layout.offset, scratch.size());
mTextBlob.alloc(mHdr.getTextSegmentInfo().memory_layout.size);
fnd::lz4::decompressData(scratch.data(), (uint32_t)scratch.size(), mTextBlob.data(), (uint32_t)mTextBlob.size(), decompressed_len);
if (decompressed_len != mTextBlob.size())
// allocate/read compressed text
scratch = tc::ByteData(mHdr.getTextSegmentInfo().file_layout.size);
mFile->seek(mHdr.getTextSegmentInfo().file_layout.offset, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
// allocate for decompressed text segment
mTextBlob = tc::ByteData(mHdr.getTextSegmentInfo().memory_layout.size);
// decompress text segment
if (decompressData(scratch.data(), scratch.size(), mTextBlob.data(), mTextBlob.size()) != mTextBlob.size())
{
throw fnd::Exception(kModuleName, "NSO text segment failed to decompress");
throw tc::Exception(mModuleName, "NSO text segment failed to decompress");
}
}
else
{
mTextBlob.alloc(mHdr.getTextSegmentInfo().file_layout.size);
(*mFile)->read(mTextBlob.data(), mHdr.getTextSegmentInfo().file_layout.offset, mTextBlob.size());
// read text segment directly (not compressed)
mTextBlob = tc::ByteData(mHdr.getTextSegmentInfo().file_layout.size);
mFile->seek(mHdr.getTextSegmentInfo().file_layout.offset, tc::io::SeekOrigin::Begin);
mFile->read(mTextBlob.data(), mTextBlob.size());
}
if (mHdr.getTextSegmentInfo().is_hashed)
{
fnd::sha::Sha256(mTextBlob.data(), mTextBlob.size(), calc_hash.bytes);
tc::crypto::GenerateSha256Hash(calc_hash.data(), mTextBlob.data(), mTextBlob.size());
if (calc_hash != mHdr.getTextSegmentInfo().hash)
{
throw fnd::Exception(kModuleName, "NSO text segment failed SHA256 verification");
throw tc::Exception(mModuleName, "NSO text segment failed SHA256 verification");
}
}
// process ro segment
if (mHdr.getRoSegmentInfo().is_compressed)
{
scratch.alloc(mHdr.getRoSegmentInfo().file_layout.size);
(*mFile)->read(scratch.data(), mHdr.getRoSegmentInfo().file_layout.offset, scratch.size());
mRoBlob.alloc(mHdr.getRoSegmentInfo().memory_layout.size);
fnd::lz4::decompressData(scratch.data(), (uint32_t)scratch.size(), mRoBlob.data(), (uint32_t)mRoBlob.size(), decompressed_len);
if (decompressed_len != mRoBlob.size())
// allocate/read compressed ro segment
scratch = tc::ByteData(mHdr.getRoSegmentInfo().file_layout.size);
mFile->seek(mHdr.getRoSegmentInfo().file_layout.offset, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
// allocate for decompressed ro segment
mRoBlob = tc::ByteData(mHdr.getRoSegmentInfo().memory_layout.size);
// decompress ro segment
if (decompressData(scratch.data(), scratch.size(), mRoBlob.data(), mRoBlob.size()) != mRoBlob.size())
{
throw fnd::Exception(kModuleName, "NSO ro segment failed to decompress");
throw tc::Exception(mModuleName, "NSO ro segment failed to decompress");
}
}
else
{
mRoBlob.alloc(mHdr.getRoSegmentInfo().file_layout.size);
(*mFile)->read(mRoBlob.data(), mHdr.getRoSegmentInfo().file_layout.offset, mRoBlob.size());
// read ro segment directly (not compressed)
mRoBlob = tc::ByteData(mHdr.getRoSegmentInfo().file_layout.size);
mFile->seek(mHdr.getRoSegmentInfo().file_layout.offset, tc::io::SeekOrigin::Begin);
mFile->read(mRoBlob.data(), mRoBlob.size());
}
if (mHdr.getRoSegmentInfo().is_hashed)
{
fnd::sha::Sha256(mRoBlob.data(), mRoBlob.size(), calc_hash.bytes);
tc::crypto::GenerateSha256Hash(calc_hash.data(), mRoBlob.data(), mRoBlob.size());
if (calc_hash != mHdr.getRoSegmentInfo().hash)
{
throw fnd::Exception(kModuleName, "NSO ro segment failed SHA256 verification");
throw tc::Exception(mModuleName, "NSO ro segment failed SHA256 verification");
}
}
// process data segment
// process ro segment
if (mHdr.getDataSegmentInfo().is_compressed)
{
scratch.alloc(mHdr.getDataSegmentInfo().file_layout.size);
(*mFile)->read(scratch.data(), mHdr.getDataSegmentInfo().file_layout.offset, scratch.size());
mDataBlob.alloc(mHdr.getDataSegmentInfo().memory_layout.size);
fnd::lz4::decompressData(scratch.data(), (uint32_t)scratch.size(), mDataBlob.data(), (uint32_t)mDataBlob.size(), decompressed_len);
if (decompressed_len != mDataBlob.size())
// allocate/read compressed ro segment
scratch = tc::ByteData(mHdr.getDataSegmentInfo().file_layout.size);
mFile->seek(mHdr.getDataSegmentInfo().file_layout.offset, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
// allocate for decompressed ro segment
mDataBlob = tc::ByteData(mHdr.getDataSegmentInfo().memory_layout.size);
// decompress ro segment
if (decompressData(scratch.data(), scratch.size(), mDataBlob.data(), mDataBlob.size()) != mDataBlob.size())
{
throw fnd::Exception(kModuleName, "NSO data segment failed to decompress");
throw tc::Exception(mModuleName, "NSO data segment failed to decompress");
}
}
else
{
mDataBlob.alloc(mHdr.getDataSegmentInfo().file_layout.size);
(*mFile)->read(mDataBlob.data(), mHdr.getDataSegmentInfo().file_layout.offset, mDataBlob.size());
// read ro segment directly (not compressed)
mDataBlob = tc::ByteData(mHdr.getDataSegmentInfo().file_layout.size);
mFile->seek(mHdr.getDataSegmentInfo().file_layout.offset, tc::io::SeekOrigin::Begin);
mFile->read(mDataBlob.data(), mDataBlob.size());
}
if (mHdr.getDataSegmentInfo().is_hashed)
{
fnd::sha::Sha256(mDataBlob.data(), mDataBlob.size(), calc_hash.bytes);
tc::crypto::GenerateSha256Hash(calc_hash.data(), mDataBlob.data(), mDataBlob.size());
if (calc_hash != mHdr.getDataSegmentInfo().hash)
{
throw fnd::Exception(kModuleName, "NSO data segment failed SHA256 verification");
throw tc::Exception(mModuleName, "NSO data segment failed SHA256 verification");
}
}
}
void NsoProcess::displayNsoHeader()
void nstool::NsoProcess::displayNsoHeader()
{
std::cout << "[NSO Header]" << std::endl;
std::cout << " ModuleId: " << fnd::SimpleTextOutput::arrayToString(mHdr.getModuleId().data, nn::hac::nso::kModuleIdSize, false, "") << std::endl;
if (_HAS_BIT(mCliOutputMode, OUTPUT_LAYOUT))
fmt::print("[NSO Header]\n");
fmt::print(" ModuleId: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mHdr.getModuleId().data(), mHdr.getModuleId().size(), false, ""));
if (mCliOutputMode.show_layout)
{
std::cout << " Program Segments:" << std::endl;
std::cout << " .module_name:" << std::endl;
std::cout << " FileOffset: 0x" << std::hex << mHdr.getModuleNameInfo().offset << std::endl;
std::cout << " FileSize: 0x" << std::hex << mHdr.getModuleNameInfo().size << std::endl;
std::cout << " .text:" << std::endl;
std::cout << " FileOffset: 0x" << std::hex << mHdr.getTextSegmentInfo().file_layout.offset << std::endl;
std::cout << " FileSize: 0x" << std::hex << mHdr.getTextSegmentInfo().file_layout.size << (mHdr.getTextSegmentInfo().is_compressed? " (COMPRESSED)" : "") << std::endl;
std::cout << " .ro:" << std::endl;
std::cout << " FileOffset: 0x" << std::hex << mHdr.getRoSegmentInfo().file_layout.offset << std::endl;
std::cout << " FileSize: 0x" << std::hex << mHdr.getRoSegmentInfo().file_layout.size << (mHdr.getRoSegmentInfo().is_compressed? " (COMPRESSED)" : "") << std::endl;
std::cout << " .data:" << std::endl;
std::cout << " FileOffset: 0x" << std::hex << mHdr.getDataSegmentInfo().file_layout.offset << std::endl;
std::cout << " FileSize: 0x" << std::hex << mHdr.getDataSegmentInfo().file_layout.size << (mHdr.getDataSegmentInfo().is_compressed? " (COMPRESSED)" : "") << std::endl;
fmt::print(" Program Segments:\n");
fmt::print(" .module_name:\n");
fmt::print(" FileOffset: 0x{:x}\n", mHdr.getModuleNameInfo().offset);
fmt::print(" FileSize: 0x{:x}\n", mHdr.getModuleNameInfo().size);
fmt::print(" .text:\n");
fmt::print(" FileOffset: 0x{:x}\n", mHdr.getTextSegmentInfo().file_layout.offset);
fmt::print(" FileSize: 0x{:x}{:s}\n", mHdr.getTextSegmentInfo().file_layout.size, (mHdr.getTextSegmentInfo().is_compressed? " (COMPRESSED)" : ""));
fmt::print(" .ro:\n");
fmt::print(" FileOffset: 0x{:x}\n", mHdr.getRoSegmentInfo().file_layout.offset);
fmt::print(" FileSize: 0x{:x}{:s}\n", mHdr.getRoSegmentInfo().file_layout.size, (mHdr.getRoSegmentInfo().is_compressed? " (COMPRESSED)" : ""));
fmt::print(" .data:\n");
fmt::print(" FileOffset: 0x{:x}\n", mHdr.getDataSegmentInfo().file_layout.offset);
fmt::print(" FileSize: 0x{:x}{:s}\n", mHdr.getDataSegmentInfo().file_layout.size, (mHdr.getDataSegmentInfo().is_compressed? " (COMPRESSED)" : ""));
}
std::cout << " Program Sections:" << std::endl;
std::cout << " .text:" << std::endl;
std::cout << " MemoryOffset: 0x" << std::hex << mHdr.getTextSegmentInfo().memory_layout.offset << std::endl;
std::cout << " MemorySize: 0x" << std::hex << mHdr.getTextSegmentInfo().memory_layout.size << std::endl;
if (mHdr.getTextSegmentInfo().is_hashed && _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
fmt::print(" Program Sections:\n");
fmt::print(" .text:\n");
fmt::print(" MemoryOffset: 0x{:x}\n", mHdr.getTextSegmentInfo().memory_layout.offset);
fmt::print(" MemorySize: 0x{:x}\n", mHdr.getTextSegmentInfo().memory_layout.size);
if (mHdr.getTextSegmentInfo().is_hashed && mCliOutputMode.show_extended_info)
{
std::cout << " Hash: " << fnd::SimpleTextOutput::arrayToString(mHdr.getTextSegmentInfo().hash.bytes, 32, false, "") << std::endl;
fmt::print(" Hash: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mHdr.getTextSegmentInfo().hash.data(), mHdr.getTextSegmentInfo().hash.size(), false, ""));
}
std::cout << " .ro:" << std::endl;
std::cout << " MemoryOffset: 0x" << std::hex << mHdr.getRoSegmentInfo().memory_layout.offset << std::endl;
std::cout << " MemorySize: 0x" << std::hex << mHdr.getRoSegmentInfo().memory_layout.size << std::endl;
if (mHdr.getRoSegmentInfo().is_hashed && _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
fmt::print(" .ro:\n");
fmt::print(" MemoryOffset: 0x{:x}\n", mHdr.getRoSegmentInfo().memory_layout.offset);
fmt::print(" MemorySize: 0x{:x}\n", mHdr.getRoSegmentInfo().memory_layout.size);
if (mHdr.getRoSegmentInfo().is_hashed && mCliOutputMode.show_extended_info)
{
std::cout << " Hash: " << fnd::SimpleTextOutput::arrayToString(mHdr.getRoSegmentInfo().hash.bytes, 32, false, "") << std::endl;
fmt::print(" Hash: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mHdr.getRoSegmentInfo().hash.data(), mHdr.getRoSegmentInfo().hash.size(), false, ""));
}
if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
if (mCliOutputMode.show_extended_info)
{
std::cout << " .api_info:" << std::endl;
std::cout << " MemoryOffset: 0x" << std::hex << mHdr.getRoEmbeddedInfo().offset << std::endl;
std::cout << " MemorySize: 0x" << std::hex << mHdr.getRoEmbeddedInfo().size << std::endl;
std::cout << " .dynstr:" << std::endl;
std::cout << " MemoryOffset: 0x" << std::hex << mHdr.getRoDynStrInfo().offset << std::endl;
std::cout << " MemorySize: 0x" << std::hex << mHdr.getRoDynStrInfo().size << std::endl;
std::cout << " .dynsym:" << std::endl;
std::cout << " MemoryOffset: 0x" << std::hex << mHdr.getRoDynSymInfo().offset << std::endl;
std::cout << " MemorySize: 0x" << std::hex << mHdr.getRoDynSymInfo().size << std::endl;
fmt::print(" .api_info:\n");
fmt::print(" MemoryOffset: 0x{:x}\n", mHdr.getRoEmbeddedInfo().offset);
fmt::print(" MemorySize: 0x{:x}\n", mHdr.getRoEmbeddedInfo().size);
fmt::print(" .dynstr:\n");
fmt::print(" MemoryOffset: 0x{:x}\n", mHdr.getRoDynStrInfo().offset);
fmt::print(" MemorySize: 0x{:x}\n", mHdr.getRoDynStrInfo().size);
fmt::print(" .dynsym:\n");
fmt::print(" MemoryOffset: 0x{:x}\n", mHdr.getRoDynSymInfo().offset);
fmt::print(" MemorySize: 0x{:x}\n", mHdr.getRoDynSymInfo().size);
}
std::cout << " .data:" << std::endl;
std::cout << " MemoryOffset: 0x" << std::hex << mHdr.getDataSegmentInfo().memory_layout.offset << std::endl;
std::cout << " MemorySize: 0x" << std::hex << mHdr.getDataSegmentInfo().memory_layout.size << std::endl;
if (mHdr.getDataSegmentInfo().is_hashed && _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
fmt::print(" .data:\n");
fmt::print(" MemoryOffset: 0x{:x}\n", mHdr.getDataSegmentInfo().memory_layout.offset);
fmt::print(" MemorySize: 0x{:x}\n", mHdr.getDataSegmentInfo().memory_layout.size);
if (mHdr.getDataSegmentInfo().is_hashed && mCliOutputMode.show_extended_info)
{
std::cout << " Hash: " << fnd::SimpleTextOutput::arrayToString(mHdr.getDataSegmentInfo().hash.bytes, 32, false, "") << std::endl;
fmt::print(" Hash: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(mHdr.getDataSegmentInfo().hash.data(), mHdr.getDataSegmentInfo().hash.size(), false, ""));
}
std::cout << " .bss:" << std::endl;
std::cout << " MemorySize: 0x" << std::hex << mHdr.getBssSize() << std::endl;
fmt::print(" .bss:\n");
fmt::print(" MemorySize: 0x{:x}\n", mHdr.getBssSize());
}
void NsoProcess::processRoMeta()
void nstool::NsoProcess::processRoMeta()
{
if (mRoBlob.size())
{
@ -238,3 +262,24 @@ void NsoProcess::processRoMeta()
mRoMeta.process();
}
}
size_t nstool::NsoProcess::decompressData(const byte_t* src, size_t src_len, byte_t* dst, size_t dst_capacity)
{
if (src_len >= LZ4_MAX_INPUT_SIZE)
{
return 0;
}
int32_t src_len_input = int32_t(src_len);
int32_t dst_capcacity_input = (dst_capacity < LZ4_MAX_INPUT_SIZE) ? int32_t(dst_capacity) : LZ4_MAX_INPUT_SIZE;
int32_t decomp_size = LZ4_decompress_safe((const char*)src, (char*)dst, src_len_input, dst_capcacity_input);
if (decomp_size < 0)
{
memset(dst, 0, dst_capacity);
return 0;
}
return size_t(decomp_size);
}

View file

@ -1,14 +1,11 @@
#pragma once
#include <vector>
#include <string>
#include <fnd/types.h>
#include <fnd/IFile.h>
#include <fnd/SharedPtr.h>
#include "types.h"
#include "RoMetadataProcess.h"
#include <nn/hac/define/meta.h>
#include <nn/hac/NsoHeader.h>
#include "common.h"
#include "RoMetadataProcess.h"
namespace nstool {
class NsoProcess
{
@ -17,7 +14,7 @@ public:
void process();
void setInputFile(const fnd::SharedPtr<fnd::IFile>& file);
void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify);
@ -25,11 +22,11 @@ public:
void setListApi(bool listApi);
void setListSymbols(bool listSymbols);
const RoMetadataProcess& getRoMetadataProcess() const;
const nstool::RoMetadataProcess& getRoMetadataProcess() const;
private:
const std::string kModuleName = "NsoProcess";
std::string mModuleName;
fnd::SharedPtr<fnd::IFile> mFile;
std::shared_ptr<tc::io::IStream> mFile;
CliOutputMode mCliOutputMode;
bool mVerify;
bool mIs64BitInstruction;
@ -37,11 +34,15 @@ private:
bool mListSymbols;
nn::hac::NsoHeader mHdr;
fnd::Vec<byte_t> mTextBlob, mRoBlob, mDataBlob;
RoMetadataProcess mRoMeta;
tc::ByteData mTextBlob, mRoBlob, mDataBlob;
nstool::RoMetadataProcess mRoMeta;
void importHeader();
void importCodeSegments();
void displayNsoHeader();
void processRoMeta();
size_t decompressData(const byte_t* src, size_t src_len, byte_t* dst, size_t dst_capacity);
};
}

View file

@ -1,199 +1,132 @@
#include "PfsProcess.h"
#include <iostream>
#include <iomanip>
#include <fnd/SimpleFile.h>
#include <fnd/io.h>
#include "util.h"
#include <nn/hac/PartitionFsUtil.h>
#include <tc/io/LocalStorage.h>
#include <tc/io/VirtualFileSystem.h>
#include <nn/hac/PartitionFsMetaGenerator.h>
PfsProcess::PfsProcess() :
nstool::PfsProcess::PfsProcess() :
mModuleName("nstool::PfsProcess"),
mFile(),
mCliOutputMode(_BIT(OUTPUT_BASIC)),
mCliOutputMode(true, false, false, false),
mVerify(false),
mExtractPath(),
mExtract(false),
mMountName(),
mListFs(false),
mPfs()
mPfs(),
mFileSystem(),
mFsProcess()
{
mFsProcess.setFsFormatName("PartitionFs");
}
void PfsProcess::process()
void nstool::PfsProcess::process()
{
importHeader();
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
if (mFile == nullptr)
{
displayHeader();
if (mListFs || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
displayFs();
throw tc::Exception(mModuleName, "No file reader set.");
}
if (mPfs.getFsType() == mPfs.TYPE_HFS0 && mVerify)
validateHfs();
if (mExtract)
extractFs();
if (mFile->canRead() == false || mFile->canSeek() == false)
{
throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions.");
}
tc::ByteData scratch;
// read base header to determine complete header size
if (mFile->length() < tc::io::IOUtil::castSizeToInt64(sizeof(nn::hac::sPfsHeader)))
{
throw tc::Exception(mModuleName, "Corrupt PartitionFs: File too small");
}
scratch = tc::ByteData(sizeof(nn::hac::sPfsHeader));
mFile->seek(0, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
if (validateHeaderMagic(((nn::hac::sPfsHeader*)scratch.data())) == false)
{
throw tc::Exception(mModuleName, "Corrupt PartitionFs: Header had incorrect struct magic.");
}
// read complete size header
size_t pfsHeaderSize = determineHeaderSize(((nn::hac::sPfsHeader*)scratch.data()));
if (mFile->length() < tc::io::IOUtil::castSizeToInt64(pfsHeaderSize))
{
throw tc::Exception(mModuleName, "Corrupt PartitionFs: File too small");
}
scratch = tc::ByteData(pfsHeaderSize);
mFile->seek(0, tc::io::SeekOrigin::Begin);
mFile->read(scratch.data(), scratch.size());
// process PFS
mPfs.fromBytes(scratch.data(), scratch.size());
// create virtual filesystem
mFileSystem = std::make_shared<tc::io::VirtualFileSystem>(tc::io::VirtualFileSystem(nn::hac::PartitionFsMetaGenerator(mFile, mVerify ? nn::hac::PartitionFsMetaGenerator::ValidationMode_Warn : nn::hac::PartitionFsMetaGenerator::ValidationMode_None)));
mFsProcess.setInputFileSystem(mFileSystem);
// set properties for FsProcess
mFsProcess.setFsProperties({
fmt::format("Type: {:s}", nn::hac::PartitionFsUtil::getFsTypeAsString(mPfs.getFsType())),
fmt::format("FileNum: {:d}", mPfs.getFileList().size())
});
mFsProcess.process();
}
void PfsProcess::setInputFile(const fnd::SharedPtr<fnd::IFile>& file)
void nstool::PfsProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
{
mFile = file;
}
void PfsProcess::setCliOutputMode(CliOutputMode type)
void nstool::PfsProcess::setCliOutputMode(CliOutputMode type)
{
mCliOutputMode = type;
mFsProcess.setShowFsInfo(mCliOutputMode.show_basic_info);
}
void PfsProcess::setVerifyMode(bool verify)
void nstool::PfsProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void PfsProcess::setMountPointName(const std::string& mount_name)
void nstool::PfsProcess::setShowFsTree(bool show_fs_tree)
{
mMountName = mount_name;
mFsProcess.setShowFsTree(show_fs_tree);
}
void PfsProcess::setExtractPath(const std::string& path)
void nstool::PfsProcess::setFsRootLabel(const std::string& root_label)
{
mExtract = true;
mExtractPath = path;
mFsProcess.setFsRootLabel(root_label);
}
void PfsProcess::setListFs(bool list_fs)
void nstool::PfsProcess::setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs)
{
mListFs = list_fs;
mFsProcess.setExtractJobs(extract_jobs);
}
const nn::hac::PartitionFsHeader& PfsProcess::getPfsHeader() const
const nn::hac::PartitionFsHeader& nstool::PfsProcess::getPfsHeader() const
{
return mPfs;
}
void PfsProcess::importHeader()
const std::shared_ptr<tc::io::IStorage>& nstool::PfsProcess::getFileSystem() const
{
fnd::Vec<byte_t> scratch;
if (*mFile == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
}
// open minimum header to get full header size
scratch.alloc(sizeof(nn::hac::sPfsHeader));
(*mFile)->read(scratch.data(), 0, scratch.size());
if (validateHeaderMagic(((nn::hac::sPfsHeader*)scratch.data())) == false)
{
throw fnd::Exception(kModuleName, "Corrupt Header");
}
size_t pfsHeaderSize = determineHeaderSize(((nn::hac::sPfsHeader*)scratch.data()));
// open minimum header to get full header size
scratch.alloc(pfsHeaderSize);
(*mFile)->read(scratch.data(), 0, scratch.size());
mPfs.fromBytes(scratch.data(), scratch.size());
return mFileSystem;
}
void PfsProcess::displayHeader()
{
std::cout << "[PartitionFS]" << std::endl;
std::cout << " Type: " << nn::hac::PartitionFsUtil::getFsTypeAsString(mPfs.getFsType()) << std::endl;
std::cout << " FileNum: " << std::dec << mPfs.getFileList().size() << std::endl;
if (mMountName.empty() == false)
{
std::cout << " MountPoint: " << mMountName;
if (mMountName.at(mMountName.length()-1) != '/')
std::cout << "/";
std::cout << std::endl;
}
}
void PfsProcess::displayFs()
{
for (size_t i = 0; i < mPfs.getFileList().size(); i++)
{
const nn::hac::PartitionFsHeader::sFile& file = mPfs.getFileList()[i];
std::cout << " " << file.name;
if (_HAS_BIT(mCliOutputMode, OUTPUT_LAYOUT))
{
switch (mPfs.getFsType())
{
case (nn::hac::PartitionFsHeader::TYPE_PFS0):
std::cout << std::hex << " (offset=0x" << file.offset << ", size=0x" << file.size << ")";
break;
case (nn::hac::PartitionFsHeader::TYPE_HFS0):
std::cout << std::hex << " (offset=0x" << file.offset << ", size=0x" << file.size << ", hash_protected_size=0x" << file.hash_protected_size << ")";
break;
}
}
std::cout << std::endl;
}
}
size_t PfsProcess::determineHeaderSize(const nn::hac::sPfsHeader* hdr)
size_t nstool::PfsProcess::determineHeaderSize(const nn::hac::sPfsHeader* hdr)
{
size_t fileEntrySize = 0;
if (hdr->st_magic.get() == nn::hac::pfs::kPfsStructMagic)
if (hdr->st_magic.unwrap() == nn::hac::pfs::kPfsStructMagic)
fileEntrySize = sizeof(nn::hac::sPfsFile);
else
fileEntrySize = sizeof(nn::hac::sHashedPfsFile);
return sizeof(nn::hac::sPfsHeader) + hdr->file_num.get() * fileEntrySize + hdr->name_table_size.get();
return sizeof(nn::hac::sPfsHeader) + hdr->file_num.unwrap() * fileEntrySize + hdr->name_table_size.unwrap();
}
bool PfsProcess::validateHeaderMagic(const nn::hac::sPfsHeader* hdr)
bool nstool::PfsProcess::validateHeaderMagic(const nn::hac::sPfsHeader* hdr)
{
return hdr->st_magic.get() == nn::hac::pfs::kPfsStructMagic || hdr->st_magic.get() == nn::hac::pfs::kHashedPfsStructMagic;
}
void PfsProcess::validateHfs()
{
fnd::sha::sSha256Hash hash;
const fnd::List<nn::hac::PartitionFsHeader::sFile>& file = mPfs.getFileList();
for (size_t i = 0; i < file.size(); i++)
{
mCache.alloc(file[i].hash_protected_size);
(*mFile)->read(mCache.data(), file[i].offset, file[i].hash_protected_size);
fnd::sha::Sha256(mCache.data(), file[i].hash_protected_size, hash.bytes);
if (hash != file[i].hash)
{
printf("[WARNING] HFS0 %s%s%s: FAIL (bad hash)\n", !mMountName.empty()? mMountName.c_str() : "", (!mMountName.empty() && mMountName.at(mMountName.length()-1) != '/' )? "/" : "", file[i].name.c_str());
}
}
}
void PfsProcess::extractFs()
{
// allocate only when extractDir is invoked
mCache.alloc(kCacheSize);
// make extract dir
fnd::io::makeDirectory(mExtractPath);
fnd::SimpleFile outFile;
const fnd::List<nn::hac::PartitionFsHeader::sFile>& file = mPfs.getFileList();
std::string file_path;
for (size_t i = 0; i < file.size(); i++)
{
file_path.clear();
fnd::io::appendToPath(file_path, mExtractPath);
fnd::io::appendToPath(file_path, file[i].name);
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
printf("extract=[%s]\n", file_path.c_str());
outFile.open(file_path, outFile.Create);
(*mFile)->seek(file[i].offset);
for (size_t j = 0; j < ((file[i].size / kCacheSize) + ((file[i].size % kCacheSize) != 0)); j++)
{
(*mFile)->read(mCache.data(), _MIN(file[i].size - (kCacheSize * j),kCacheSize));
outFile.write(mCache.data(), _MIN(file[i].size - (kCacheSize * j),kCacheSize));
}
outFile.close();
}
return hdr->st_magic.unwrap() == nn::hac::pfs::kPfsStructMagic || hdr->st_magic.unwrap() == nn::hac::pfs::kHashedPfsStructMagic;
}

View file

@ -1,11 +1,10 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/IFile.h>
#include <fnd/SharedPtr.h>
#include "types.h"
#include "FsProcess.h"
#include <nn/hac/PartitionFsHeader.h>
#include "common.h"
namespace nstool {
class PfsProcess
{
@ -15,39 +14,35 @@ public:
void process();
// generic
void setInputFile(const fnd::SharedPtr<fnd::IFile>& file);
void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify);
// pfs specific
void setMountPointName(const std::string& mount_name);
void setExtractPath(const std::string& path);
void setListFs(bool list_fs);
// fs specific
void setShowFsTree(bool show_fs_tree);
void setFsRootLabel(const std::string& root_label);
void setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs);
// post process() get PFS/FS out
const nn::hac::PartitionFsHeader& getPfsHeader() const;
const std::shared_ptr<tc::io::IStorage>& getFileSystem() const;
private:
const std::string kModuleName = "PfsProcess";
static const size_t kCacheSize = 0x10000;
fnd::SharedPtr<fnd::IFile> mFile;
std::string mModuleName;
std::shared_ptr<tc::io::IStream> mFile;
CliOutputMode mCliOutputMode;
bool mVerify;
std::string mExtractPath;
bool mExtract;
std::string mMountName;
bool mListFs;
fnd::Vec<byte_t> mCache;
nn::hac::PartitionFsHeader mPfs;
void importHeader();
void displayHeader();
void displayFs();
std::shared_ptr<tc::io::IStorage> mFileSystem;
FsProcess mFsProcess;
size_t determineHeaderSize(const nn::hac::sPfsHeader* hdr);
bool validateHeaderMagic(const nn::hac::sPfsHeader* hdr);
void validateHfs();
void extractFs();
};
}

View file

@ -1,193 +0,0 @@
#include <iostream>
#include <iomanip>
#include <fnd/SimpleTextOutput.h>
#include <fnd/OffsetAdjustedIFile.h>
#include <nn/pki/SignUtils.h>
#include "PkiCertProcess.h"
#include "PkiValidator.h"
PkiCertProcess::PkiCertProcess() :
mFile(),
mCliOutputMode(_BIT(OUTPUT_BASIC)),
mVerify(false)
{
}
void PkiCertProcess::process()
{
importCerts();
if (mVerify)
validateCerts();
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
displayCerts();
}
void PkiCertProcess::setInputFile(const fnd::SharedPtr<fnd::IFile>& file)
{
mFile = file;
}
void PkiCertProcess::setKeyCfg(const KeyConfiguration& keycfg)
{
mKeyCfg = keycfg;
}
void PkiCertProcess::setCliOutputMode(CliOutputMode mode)
{
mCliOutputMode = mode;
}
void PkiCertProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void PkiCertProcess::importCerts()
{
fnd::Vec<byte_t> scratch;
if (*mFile == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
}
scratch.alloc((*mFile)->size());
(*mFile)->read(scratch.data(), 0, scratch.size());
nn::pki::SignedData<nn::pki::CertificateBody> cert;
for (size_t f_pos = 0; f_pos < scratch.size(); f_pos += cert.getBytes().size())
{
cert.fromBytes(scratch.data() + f_pos, scratch.size() - f_pos);
mCert.addElement(cert);
}
}
void PkiCertProcess::validateCerts()
{
PkiValidator pki;
try
{
pki.setKeyCfg(mKeyCfg);
pki.addCertificates(mCert);
}
catch (const fnd::Exception& e)
{
std::cout << "[WARNING] " << e.error() << std::endl;
return;
}
}
void PkiCertProcess::displayCerts()
{
for (size_t i = 0; i < mCert.size(); i++)
{
displayCert(mCert[i]);
}
}
void PkiCertProcess::displayCert(const nn::pki::SignedData<nn::pki::CertificateBody>& cert)
{
std::cout << "[NNPKI Certificate]" << std::endl;
std::cout << " SignType " << getSignTypeStr(cert.getSignature().getSignType());
if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
std::cout << " (0x" << std::hex << cert.getSignature().getSignType() << ") (" << getEndiannessStr(cert.getSignature().isLittleEndian()) << ")";
std::cout << std::endl;
std::cout << " Issuer: " << cert.getBody().getIssuer() << std::endl;
std::cout << " Subject: " << cert.getBody().getSubject() << std::endl;
std::cout << " PublicKeyType: " << getPublicKeyTypeStr(cert.getBody().getPublicKeyType());
if (_HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
std::cout << " (" << std::dec << cert.getBody().getPublicKeyType() << ")";
std::cout << std::endl;
std::cout << " CertID: 0x" << std::hex << cert.getBody().getCertId() << std::endl;
if (cert.getBody().getPublicKeyType() == nn::pki::cert::RSA4096)
{
std::cout << " PublicKey:" << std::endl;
std::cout << " Modulus:" << std::endl;
fnd::SimpleTextOutput::hexDump(cert.getBody().getRsa4098PublicKey().modulus, getHexDumpLen(fnd::rsa::kRsa4096Size), 0x10, 6);
std::cout << " Public Exponent:" << std::endl;
fnd::SimpleTextOutput::hexDump(cert.getBody().getRsa4098PublicKey().public_exponent, fnd::rsa::kRsaPublicExponentSize, 0x10, 6);
}
else if (cert.getBody().getPublicKeyType() == nn::pki::cert::RSA2048)
{
std::cout << " PublicKey:" << std::endl;
std::cout << " Modulus:" << std::endl;
fnd::SimpleTextOutput::hexDump(cert.getBody().getRsa2048PublicKey().modulus, getHexDumpLen(fnd::rsa::kRsa2048Size), 0x10, 6);
std::cout << " Public Exponent:" << std::endl;
fnd::SimpleTextOutput::hexDump(cert.getBody().getRsa2048PublicKey().public_exponent, fnd::rsa::kRsaPublicExponentSize, 0x10, 6);
}
else if (cert.getBody().getPublicKeyType() == nn::pki::cert::ECDSA240)
{
std::cout << " PublicKey:" << std::endl;
std::cout << " R:" << std::endl;
fnd::SimpleTextOutput::hexDump(cert.getBody().getEcdsa240PublicKey().r, getHexDumpLen(fnd::ecdsa::kEcdsa240Size), 0x10, 6);
std::cout << " S:" << std::endl;
fnd::SimpleTextOutput::hexDump(cert.getBody().getEcdsa240PublicKey().s, getHexDumpLen(fnd::ecdsa::kEcdsa240Size), 0x10, 6);
}
}
size_t PkiCertProcess::getHexDumpLen(size_t max_size) const
{
return _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED) ? max_size : kSmallHexDumpLen;
}
const char* PkiCertProcess::getSignTypeStr(nn::pki::sign::SignatureId type) const
{
const char* str;
switch (type)
{
case (nn::pki::sign::SIGN_ID_RSA4096_SHA1):
str = "RSA4096-SHA1";
break;
case (nn::pki::sign::SIGN_ID_RSA2048_SHA1):
str = "RSA2048-SHA1";
break;
case (nn::pki::sign::SIGN_ID_ECDSA240_SHA1):
str = "ECDSA240-SHA1";
break;
case (nn::pki::sign::SIGN_ID_RSA4096_SHA256):
str = "RSA4096-SHA256";
break;
case (nn::pki::sign::SIGN_ID_RSA2048_SHA256):
str = "RSA2048-SHA256";
break;
case (nn::pki::sign::SIGN_ID_ECDSA240_SHA256):
str = "ECDSA240-SHA256";
break;
default:
str = "Unknown";
break;
}
return str;
}
const char* PkiCertProcess::getEndiannessStr(bool isLittleEndian) const
{
return isLittleEndian ? "LittleEndian" : "BigEndian";
}
const char* PkiCertProcess::getPublicKeyTypeStr(nn::pki::cert::PublicKeyType type) const
{
const char* str;
switch (type)
{
case (nn::pki::cert::RSA4096):
str = "RSA4096";
break;
case (nn::pki::cert::RSA2048):
str = "RSA2048";
break;
case (nn::pki::cert::ECDSA240):
str = "ECDSA240";
break;
default:
str = "Unknown";
break;
}
return str;
}

View file

@ -1,45 +0,0 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/IFile.h>
#include <fnd/SharedPtr.h>
#include <fnd/List.h>
#include <fnd/Vec.h>
#include <nn/pki/SignedData.h>
#include <nn/pki/CertificateBody.h>
#include "KeyConfiguration.h"
#include "common.h"
class PkiCertProcess
{
public:
PkiCertProcess();
void process();
void setInputFile(const fnd::SharedPtr<fnd::IFile>& file);
void setKeyCfg(const KeyConfiguration& keycfg);
void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify);
private:
const std::string kModuleName = "PkiCertProcess";
static const size_t kSmallHexDumpLen = 0x10;
fnd::SharedPtr<fnd::IFile> mFile;
KeyConfiguration mKeyCfg;
CliOutputMode mCliOutputMode;
bool mVerify;
fnd::List<nn::pki::SignedData<nn::pki::CertificateBody>> mCert;
void importCerts();
void validateCerts();
void displayCerts();
void displayCert(const nn::pki::SignedData<nn::pki::CertificateBody>& cert);
size_t getHexDumpLen(size_t max_size) const;
const char* getSignTypeStr(nn::pki::sign::SignatureId type) const;
const char* getEndiannessStr(bool isLittleEndian) const;
const char* getPublicKeyTypeStr(nn::pki::cert::PublicKeyType type) const;
};

View file

@ -1,18 +1,19 @@
#include <iostream>
#include <iomanip>
#include <sstream>
#include <nn/pki/SignUtils.h>
#include "PkiValidator.h"
PkiValidator::PkiValidator()
#include <tc/crypto.h>
#include <nn/hac/define/types.h>
#include <nn/pki/SignUtils.h>
nstool::PkiValidator::PkiValidator() :
mModuleName("nstool::PkiValidator")
{
clearCertificates();
}
void PkiValidator::setKeyCfg(const KeyConfiguration& keycfg)
void nstool::PkiValidator::setKeyCfg(const KeyBag& keycfg)
{
// save a copy of the certificate bank
fnd::List<nn::pki::SignedData<nn::pki::CertificateBody>> old_certs = mCertificateBank;
std::vector<nn::pki::SignedData<nn::pki::CertificateBody>> old_certs = mCertificateBank;
// clear the certificate bank
mCertificateBank.clear();
@ -27,7 +28,7 @@ void PkiValidator::setKeyCfg(const KeyConfiguration& keycfg)
}
}
void PkiValidator::addCertificates(const fnd::List<nn::pki::SignedData<nn::pki::CertificateBody>>& certs)
void nstool::PkiValidator::addCertificates(const std::vector<nn::pki::SignedData<nn::pki::CertificateBody>>& certs)
{
for (size_t i = 0; i < certs.size(); i++)
{
@ -35,12 +36,12 @@ void PkiValidator::addCertificates(const fnd::List<nn::pki::SignedData<nn::pki::
}
}
void PkiValidator::addCertificate(const nn::pki::SignedData<nn::pki::CertificateBody>& cert)
void nstool::PkiValidator::addCertificate(const nn::pki::SignedData<nn::pki::CertificateBody>& cert)
{
std::string cert_ident;
nn::pki::sign::SignatureAlgo cert_sign_algo;
nn::pki::sign::HashAlgo cert_hash_algo;
fnd::Vec<byte_t> cert_hash;
tc::ByteData cert_hash;
try
{
@ -48,7 +49,7 @@ void PkiValidator::addCertificate(const nn::pki::SignedData<nn::pki::Certificate
if (doesCertExist(cert_ident) == true)
{
throw fnd::Exception(kModuleName, "Certificate already exists");
throw tc::Exception(mModuleName, "Certificate already exists");
}
cert_sign_algo = nn::pki::sign::getSignatureAlgo(cert.getSignature().getSignType());
@ -58,66 +59,65 @@ void PkiValidator::addCertificate(const nn::pki::SignedData<nn::pki::Certificate
switch (cert_hash_algo)
{
case (nn::pki::sign::HASH_ALGO_SHA1):
cert_hash.alloc(fnd::sha::kSha1HashLen);
fnd::sha::Sha1(cert.getBody().getBytes().data(), cert.getBody().getBytes().size(), cert_hash.data());
cert_hash = tc::ByteData(tc::crypto::Sha1Generator::kHashSize);
tc::crypto::GenerateSha1Hash(cert_hash.data(), cert.getBody().getBytes().data(), cert.getBody().getBytes().size());
break;
case (nn::pki::sign::HASH_ALGO_SHA256):
cert_hash.alloc(fnd::sha::kSha256HashLen);
fnd::sha::Sha256(cert.getBody().getBytes().data(), cert.getBody().getBytes().size(), cert_hash.data());
cert_hash = tc::ByteData(tc::crypto::Sha256Generator::kHashSize);
tc::crypto::GenerateSha256Hash(cert_hash.data(), cert.getBody().getBytes().data(), cert.getBody().getBytes().size());
break;
default:
throw fnd::Exception(kModuleName, "Unrecognised hash type");
throw tc::Exception(mModuleName, "Unrecognised hash type");
}
validateSignature(cert.getBody().getIssuer(), cert.getSignature().getSignType(), cert.getSignature().getSignature(), cert_hash);
mCertificateBank.addElement(cert);
mCertificateBank.push_back(cert);
}
catch (const fnd::Exception& e)
catch (const tc::Exception& e)
{
std::stringstream ss;
ss << "Failed to add certificate " << cert_ident << " (" << e.error() << ")";
throw fnd::Exception(kModuleName, ss.str());
throw tc::Exception(mModuleName, fmt::format("Failed to add certificate {:s} ({:s})", cert_ident, e.error()));
}
}
void PkiValidator::clearCertificates()
void nstool::PkiValidator::clearCertificates()
{
mCertificateBank.clear();
}
void PkiValidator::validateSignature(const std::string& issuer, nn::pki::sign::SignatureId signature_id, const fnd::Vec<byte_t>& signature, const fnd::Vec<byte_t>& hash) const
void nstool::PkiValidator::validateSignature(const std::string& issuer, nn::pki::sign::SignatureId signature_id, const tc::ByteData& signature, const tc::ByteData& hash) const
{
nn::pki::sign::SignatureAlgo sign_algo = nn::pki::sign::getSignatureAlgo(signature_id);
nn::pki::sign::HashAlgo hash_algo = nn::pki::sign::getHashAlgo(signature_id);
// validate signature
int sig_validate_res = -1;
bool sig_valid = false;
// special case if signed by Root
if (issuer.find('-', 0) == std::string::npos)
// get public key
// tc::crypto::EccKey ecc_key;
tc::crypto::RsaKey rsa_key;
// special case if signed by Root (legacy nstool only defers to keybag for "Root", it did not store certificates)
if (issuer == "Root")
{
fnd::rsa::sRsa4096Key rsa4096_pub;
fnd::rsa::sRsa2048Key rsa2048_pub;
fnd::ecdsa::sEcdsa240Key ecdsa_pub;
auto itr = mKeyCfg.broadon_signer.find(issuer);
if (mKeyCfg.getPkiRootSignKey(issuer, rsa4096_pub) == true && sign_algo == nn::pki::sign::SIGN_ALGO_RSA4096)
if (itr == mKeyCfg.broadon_signer.end())
{
sig_validate_res = fnd::rsa::pkcs::rsaVerify(rsa4096_pub, getCryptoHashAlgoFromEsSignHashAlgo(hash_algo), hash.data(), signature.data());
throw tc::Exception(mModuleName, fmt::format("Public key for issuer \"{:s}\" does not exist.", issuer));
}
else if (mKeyCfg.getPkiRootSignKey(issuer, rsa2048_pub) == true && sign_algo == nn::pki::sign::SIGN_ALGO_RSA2048)
if (sign_algo != itr->second.key_type)
{
sig_validate_res = fnd::rsa::pkcs::rsaVerify(rsa2048_pub, getCryptoHashAlgoFromEsSignHashAlgo(hash_algo), hash.data(), signature.data());
throw tc::Exception(mModuleName, fmt::format("Public key for issuer \"{:s}\" cannot verify this signature.", issuer));
}
else if (mKeyCfg.getPkiRootSignKey(issuer, ecdsa_pub) == true && sign_algo == nn::pki::sign::SIGN_ALGO_ECDSA240)
if (sign_algo == nn::pki::sign::SIGN_ALGO_ECDSA240)
{
throw fnd::Exception(kModuleName, "ECDSA signatures are not supported");
}
else
{
throw fnd::Exception(kModuleName, "Public key for issuer \"" + issuer + "\" does not exist.");
throw tc::Exception(mModuleName, "ECDSA signatures are not supported");
}
rsa_key = itr->second.rsa_key;
}
else
{
@ -127,42 +127,65 @@ void PkiValidator::validateSignature(const std::string& issuer, nn::pki::sign::S
if (issuer_pubk_type == nn::pki::cert::RSA4096 && sign_algo == nn::pki::sign::SIGN_ALGO_RSA4096)
{
sig_validate_res = fnd::rsa::pkcs::rsaVerify(issuer_cert.getRsa4098PublicKey(), getCryptoHashAlgoFromEsSignHashAlgo(hash_algo), hash.data(), signature.data());
rsa_key = issuer_cert.getRsa4096PublicKey();
}
else if (issuer_pubk_type == nn::pki::cert::RSA2048 && sign_algo == nn::pki::sign::SIGN_ALGO_RSA2048)
{
sig_validate_res = fnd::rsa::pkcs::rsaVerify(issuer_cert.getRsa2048PublicKey(), getCryptoHashAlgoFromEsSignHashAlgo(hash_algo), hash.data(), signature.data());
rsa_key = issuer_cert.getRsa2048PublicKey();
}
else if (issuer_pubk_type == nn::pki::cert::ECDSA240 && sign_algo == nn::pki::sign::SIGN_ALGO_ECDSA240)
{
throw fnd::Exception(kModuleName, "ECDSA signatures are not supported");
// ecc_key = issuer_cert.getEcdsa240PublicKey();
throw tc::Exception(mModuleName, "ECDSA signatures are not supported");
}
else
{
throw fnd::Exception(kModuleName, "Mismatch between issuer public key and signature type");
throw tc::Exception(mModuleName, "Mismatch between issuer public key and signature type");
}
}
if (sig_validate_res != 0)
// verify signature
switch (signature_id) {
case (nn::pki::sign::SIGN_ID_RSA4096_SHA1):
sig_valid = tc::crypto::VerifyRsa4096Pkcs1Sha1(signature.data(), hash.data(), rsa_key);
break;
case (nn::pki::sign::SIGN_ID_RSA2048_SHA1):
sig_valid = tc::crypto::VerifyRsa2048Pkcs1Sha1(signature.data(), hash.data(), rsa_key);
break;
case (nn::pki::sign::SIGN_ID_ECDSA240_SHA1):
sig_valid = false;
break;
case (nn::pki::sign::SIGN_ID_RSA4096_SHA256):
sig_valid = tc::crypto::VerifyRsa4096Pkcs1Sha256(signature.data(), hash.data(), rsa_key);
break;
case (nn::pki::sign::SIGN_ID_RSA2048_SHA256):
sig_valid = tc::crypto::VerifyRsa2048Pkcs1Sha256(signature.data(), hash.data(), rsa_key);
break;
case (nn::pki::sign::SIGN_ID_ECDSA240_SHA256):
sig_valid = false;
break;
}
if (sig_valid == false)
{
throw fnd::Exception(kModuleName, "Incorrect signature");
throw tc::Exception(mModuleName, "Incorrect signature");
}
}
void PkiValidator::makeCertIdent(const nn::pki::SignedData<nn::pki::CertificateBody>& cert, std::string& ident) const
void nstool::PkiValidator::makeCertIdent(const nn::pki::SignedData<nn::pki::CertificateBody>& cert, std::string& ident) const
{
makeCertIdent(cert.getBody().getIssuer(), cert.getBody().getSubject(), ident);
}
void PkiValidator::makeCertIdent(const std::string& issuer, const std::string& subject, std::string& ident) const
void nstool::PkiValidator::makeCertIdent(const std::string& issuer, const std::string& subject, std::string& ident) const
{
ident = issuer + nn::pki::sign::kIdentDelimiter + subject;
ident = ident.substr(0, _MIN(ident.length(),64));
ident = ident.substr(0, std::min<size_t>(ident.length(),64));
}
bool PkiValidator::doesCertExist(const std::string& ident) const
bool nstool::PkiValidator::doesCertExist(const std::string& ident) const
{
bool exists = false;
std::string full_cert_name;
@ -179,7 +202,7 @@ bool PkiValidator::doesCertExist(const std::string& ident) const
return exists;
}
const nn::pki::SignedData<nn::pki::CertificateBody>& PkiValidator::getCert(const std::string& ident) const
const nn::pki::SignedData<nn::pki::CertificateBody>& nstool::PkiValidator::getCert(const std::string& ident) const
{
std::string full_cert_name;
for (size_t i = 0; i < mCertificateBank.size(); i++)
@ -191,22 +214,5 @@ const nn::pki::SignedData<nn::pki::CertificateBody>& PkiValidator::getCert(const
}
}
throw fnd::Exception(kModuleName, "Issuer certificate does not exist");
}
fnd::sha::HashType PkiValidator::getCryptoHashAlgoFromEsSignHashAlgo(nn::pki::sign::HashAlgo hash_algo) const
{
fnd::sha::HashType hash_type = fnd::sha::HASH_SHA1;
switch (hash_algo)
{
case (nn::pki::sign::HASH_ALGO_SHA1):
hash_type = fnd::sha::HASH_SHA1;
break;
case (nn::pki::sign::HASH_ALGO_SHA256):
hash_type = fnd::sha::HASH_SHA256;
break;
};
return hash_type;
throw tc::Exception(mModuleName, "Issuer certificate does not exist");
}

View file

@ -1,34 +1,34 @@
#pragma once
#include <fnd/types.h>
#include <fnd/List.h>
#include <fnd/Vec.h>
#include <fnd/rsa.h>
#include "types.h"
#include "KeyBag.h"
#include <nn/pki/SignedData.h>
#include <nn/pki/CertificateBody.h>
#include <string>
#include "KeyConfiguration.h"
namespace nstool {
class PkiValidator
{
public:
PkiValidator();
void setKeyCfg(const KeyConfiguration& keycfg);
void addCertificates(const fnd::List<nn::pki::SignedData<nn::pki::CertificateBody>>& certs);
void setKeyCfg(const KeyBag& keycfg);
void addCertificates(const std::vector<nn::pki::SignedData<nn::pki::CertificateBody>>& certs);
void addCertificate(const nn::pki::SignedData<nn::pki::CertificateBody>& cert);
void clearCertificates();
void validateSignature(const std::string& issuer, nn::pki::sign::SignatureId signature_id, const fnd::Vec<byte_t>& signature, const fnd::Vec<byte_t>& hash) const;
void validateSignature(const std::string& issuer, nn::pki::sign::SignatureId signature_id, const tc::ByteData& signature, const tc::ByteData& hash) const;
private:
const std::string kModuleName = "NNPkiValidator";
std::string mModuleName;
KeyConfiguration mKeyCfg;
fnd::List<nn::pki::SignedData<nn::pki::CertificateBody>> mCertificateBank;
KeyBag mKeyCfg;
std::vector<nn::pki::SignedData<nn::pki::CertificateBody>> mCertificateBank;
void makeCertIdent(const nn::pki::SignedData<nn::pki::CertificateBody>& cert, std::string& ident) const;
void makeCertIdent(const std::string& issuer, const std::string& subject, std::string& ident) const;
bool doesCertExist(const std::string& ident) const;
const nn::pki::SignedData<nn::pki::CertificateBody>& getCert(const std::string& ident) const;
fnd::sha::HashType getCryptoHashAlgoFromEsSignHashAlgo(nn::pki::sign::HashAlgo hash_algo) const;
};
}

View file

@ -1,12 +1,12 @@
#include "RoMetadataProcess.h"
#include <sstream>
#include <iostream>
#include <iomanip>
#include <fnd/types.h>
#include "RoMetadataProcess.h"
RoMetadataProcess::RoMetadataProcess() :
mCliOutputMode(_BIT(OUTPUT_BASIC)),
nstool::RoMetadataProcess::RoMetadataProcess() :
mModuleName("nstool::RoMetadataProcess"),
mCliOutputMode(true, false, false, false),
mIs64BitInstruction(true),
mListApi(false),
mListSymbols(false),
@ -23,90 +23,90 @@ RoMetadataProcess::RoMetadataProcess() :
}
void RoMetadataProcess::process()
void nstool::RoMetadataProcess::process()
{
importApiList();
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
if (mCliOutputMode.show_basic_info)
displayRoMetaData();
}
void RoMetadataProcess::setRoBinary(const fnd::Vec<byte_t>& bin)
void nstool::RoMetadataProcess::setRoBinary(const tc::ByteData& bin)
{
mRoBlob = bin;
}
void RoMetadataProcess::setApiInfo(size_t offset, size_t size)
void nstool::RoMetadataProcess::setApiInfo(size_t offset, size_t size)
{
mApiInfo.offset = offset;
mApiInfo.size = size;
}
void RoMetadataProcess::setDynSym(size_t offset, size_t size)
void nstool::RoMetadataProcess::setDynSym(size_t offset, size_t size)
{
mDynSym.offset = offset;
mDynSym.size = size;
}
void RoMetadataProcess::setDynStr(size_t offset, size_t size)
void nstool::RoMetadataProcess::setDynStr(size_t offset, size_t size)
{
mDynStr.offset = offset;
mDynStr.size = size;
}
void RoMetadataProcess::setCliOutputMode(CliOutputMode type)
void nstool::RoMetadataProcess::setCliOutputMode(CliOutputMode type)
{
mCliOutputMode = type;
}
void RoMetadataProcess::setIs64BitInstruction(bool flag)
void nstool::RoMetadataProcess::setIs64BitInstruction(bool flag)
{
mIs64BitInstruction = flag;
}
void RoMetadataProcess::setListApi(bool listApi)
void nstool::RoMetadataProcess::setListApi(bool listApi)
{
mListApi = listApi;
}
void RoMetadataProcess::setListSymbols(bool listSymbols)
void nstool::RoMetadataProcess::setListSymbols(bool listSymbols)
{
mListSymbols = listSymbols;
}
const std::vector<SdkApiString>& RoMetadataProcess::getSdkVerApiList() const
const std::vector<nstool::SdkApiString>& nstool::RoMetadataProcess::getSdkVerApiList() const
{
return mSdkVerApiList;
}
const std::vector<SdkApiString>& RoMetadataProcess::getPublicApiList() const
const std::vector<nstool::SdkApiString>& nstool::RoMetadataProcess::getPublicApiList() const
{
return mPublicApiList;
}
const std::vector<SdkApiString>& RoMetadataProcess::getDebugApiList() const
const std::vector<nstool::SdkApiString>& nstool::RoMetadataProcess::getDebugApiList() const
{
return mDebugApiList;
}
const std::vector<SdkApiString>& RoMetadataProcess::getPrivateApiList() const
const std::vector<nstool::SdkApiString>& nstool::RoMetadataProcess::getPrivateApiList() const
{
return mPrivateApiList;
}
const std::vector<SdkApiString>& RoMetadataProcess::getGuidelineApiList() const
const std::vector<nstool::SdkApiString>& nstool::RoMetadataProcess::getGuidelineApiList() const
{
return mGuidelineApiList;
}
const fnd::List<ElfSymbolParser::sElfSymbol>& RoMetadataProcess::getSymbolList() const
const std::vector<nstool::ElfSymbolParser::sElfSymbol>& nstool::RoMetadataProcess::getSymbolList() const
{
return mSymbolList.getSymbolList();
}
void RoMetadataProcess::importApiList()
void nstool::RoMetadataProcess::importApiList()
{
if (mRoBlob.size() == 0)
{
throw fnd::Exception(kModuleName, "No ro binary set.");
throw tc::Exception(mModuleName, "No ro binary set.");
}
if (mApiInfo.size > 0)
@ -147,85 +147,85 @@ void RoMetadataProcess::importApiList()
}
}
void RoMetadataProcess::displayRoMetaData()
void nstool::RoMetadataProcess::displayRoMetaData()
{
size_t api_num = mSdkVerApiList.size() + mPublicApiList.size() + mDebugApiList.size() + mPrivateApiList.size();
if (api_num > 0 && (mListApi || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED)))
if (api_num > 0 && (mListApi || mCliOutputMode.show_extended_info))
{
std::cout << "[SDK API List]" << std::endl;
fmt::print("[SDK API List]\n");
if (mSdkVerApiList.size() > 0)
{
std::cout << " Sdk Revision: " << mSdkVerApiList[0].getModuleName() << std::endl;
fmt::print(" Sdk Revision: {:s}\n", mSdkVerApiList[0].getModuleName());
}
if (mPublicApiList.size() > 0)
{
std::cout << " Public APIs:" << std::endl;
fmt::print(" Public APIs:\n");
for (size_t i = 0; i < mPublicApiList.size(); i++)
{
std::cout << " " << mPublicApiList[i].getModuleName() << " (vender: " << mPublicApiList[i].getVenderName() << ")" << std::endl;
fmt::print(" {:s} (vender: {:s})\n", mPublicApiList[i].getModuleName(), mPublicApiList[i].getVenderName());
}
}
if (mDebugApiList.size() > 0)
{
std::cout << " Debug APIs:" << std::endl;
fmt::print(" Debug APIs:\n");
for (size_t i = 0; i < mDebugApiList.size(); i++)
{
std::cout << " " << mDebugApiList[i].getModuleName() << " (vender: " << mDebugApiList[i].getVenderName() << ")" << std::endl;
fmt::print(" {:s} (vender: {:s})\n", mDebugApiList[i].getModuleName(), mDebugApiList[i].getVenderName());
}
}
if (mPrivateApiList.size() > 0)
{
std::cout << " Private APIs:" << std::endl;
fmt::print(" Private APIs:\n");
for (size_t i = 0; i < mPrivateApiList.size(); i++)
{
std::cout << " " << mPrivateApiList[i].getModuleName() << " (vender: " << mPrivateApiList[i].getVenderName() << ")" << std::endl;
fmt::print(" {:s} (vender: {:s})\n", mPrivateApiList[i].getModuleName(), mPrivateApiList[i].getVenderName());
}
}
if (mGuidelineApiList.size() > 0)
{
std::cout << " Guideline APIs:" << std::endl;
fmt::print(" Guideline APIs:\n");
for (size_t i = 0; i < mGuidelineApiList.size(); i++)
{
std::cout << " " << mGuidelineApiList[i].getModuleName() << " (vender: " << mGuidelineApiList[i].getVenderName() << ")" << std::endl;
fmt::print(" {:s} (vender: {:s})\n", mGuidelineApiList[i].getModuleName(), mGuidelineApiList[i].getVenderName());
}
}
}
if (mSymbolList.getSymbolList().size() > 0 && (mListSymbols || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED)))
if (mSymbolList.getSymbolList().size() > 0 && (mListSymbols || mCliOutputMode.show_extended_info))
{
std::cout << "[Symbol List]" << std::endl;
fmt::print("[Symbol List]\n");
for (size_t i = 0; i < mSymbolList.getSymbolList().size(); i++)
{
const ElfSymbolParser::sElfSymbol& symbol = mSymbolList.getSymbolList()[i];
std::cout << " " << symbol.name << " [SHN=" << getSectionIndexStr(symbol.shn_index) << " (" << std::hex << std::setw(4) << std::setfill('0') << symbol.shn_index << ")][STT=" << getSymbolTypeStr(symbol.symbol_type) << "][STB=" << getSymbolBindingStr(symbol.symbol_binding) << "]" << std::endl;
fmt::print(" {:s} [SHN={:s} ({:04x})][STT={:s}][STB={:s}]\n", symbol.name, getSectionIndexStr(symbol.shn_index), symbol.shn_index, getSymbolTypeStr(symbol.symbol_type), getSymbolBindingStr(symbol.symbol_binding));
}
}
}
const char* RoMetadataProcess::getSectionIndexStr(uint16_t shn_index) const
std::string nstool::RoMetadataProcess::getSectionIndexStr(uint16_t shn_index) const
{
const char* str;
std::string str;
switch (shn_index)
{
case (fnd::elf::SHN_UNDEF):
case (elf::SHN_UNDEF):
str = "UNDEF";
break;
case (fnd::elf::SHN_LOPROC):
case (elf::SHN_LOPROC):
str = "LOPROC";
break;
case (fnd::elf::SHN_HIPROC):
case (elf::SHN_HIPROC):
str = "HIPROC";
break;
case (fnd::elf::SHN_LOOS):
case (elf::SHN_LOOS):
str = "LOOS";
break;
case (fnd::elf::SHN_HIOS):
case (elf::SHN_HIOS):
str = "HIOS";
break;
case (fnd::elf::SHN_ABS):
case (elf::SHN_ABS):
str = "ABS";
break;
case (fnd::elf::SHN_COMMON):
case (elf::SHN_COMMON):
str = "COMMON";
break;
default:
@ -235,36 +235,36 @@ const char* RoMetadataProcess::getSectionIndexStr(uint16_t shn_index) const
return str;
}
const char* RoMetadataProcess::getSymbolTypeStr(byte_t symbol_type) const
std::string nstool::RoMetadataProcess::getSymbolTypeStr(byte_t symbol_type) const
{
const char* str;
std::string str;
switch (symbol_type)
{
case (fnd::elf::STT_NOTYPE):
case (elf::STT_NOTYPE):
str = "NOTYPE";
break;
case (fnd::elf::STT_OBJECT):
case (elf::STT_OBJECT):
str = "OBJECT";
break;
case (fnd::elf::STT_FUNC):
case (elf::STT_FUNC):
str = "FUNC";
break;
case (fnd::elf::STT_SECTION):
case (elf::STT_SECTION):
str = "SECTION";
break;
case (fnd::elf::STT_FILE):
case (elf::STT_FILE):
str = "FILE";
break;
case (fnd::elf::STT_LOOS):
case (elf::STT_LOOS):
str = "LOOS";
break;
case (fnd::elf::STT_HIOS):
case (elf::STT_HIOS):
str = "HIOS";
break;
case (fnd::elf::STT_LOPROC):
case (elf::STT_LOPROC):
str = "LOPROC";
break;
case (fnd::elf::STT_HIPROC):
case (elf::STT_HIPROC):
str = "HIPROC";
break;
default:
@ -274,30 +274,30 @@ const char* RoMetadataProcess::getSymbolTypeStr(byte_t symbol_type) const
return str;
}
const char* RoMetadataProcess::getSymbolBindingStr(byte_t symbol_binding) const
std::string nstool::RoMetadataProcess::getSymbolBindingStr(byte_t symbol_binding) const
{
const char* str;
std::string str;
switch (symbol_binding)
{
case (fnd::elf::STB_LOCAL):
case (elf::STB_LOCAL):
str = "LOCAL";
break;
case (fnd::elf::STB_GLOBAL):
case (elf::STB_GLOBAL):
str = "GLOBAL";
break;
case (fnd::elf::STB_WEAK):
case (elf::STB_WEAK):
str = "WEAK";
break;
case (fnd::elf::STB_LOOS):
case (elf::STB_LOOS):
str = "LOOS";
break;
case (fnd::elf::STB_HIOS):
case (elf::STB_HIOS):
str = "HIOS";
break;
case (fnd::elf::STB_LOPROC):
case (elf::STB_LOPROC):
str = "LOPROC";
break;
case (fnd::elf::STB_HIPROC):
case (elf::STB_HIPROC):
str = "HIPROC";
break;
default:

View file

@ -1,14 +1,11 @@
#pragma once
#include <vector>
#include <string>
#include <fnd/types.h>
#include <fnd/Vec.h>
#include "types.h"
#include "SdkApiString.h"
#include "ElfSymbolParser.h"
#include <nn/hac/define/meta.h>
#include "common.h"
#include "SdkApiString.h"
#include "ElfSymbolParser.h"
namespace nstool {
class RoMetadataProcess
{
@ -17,7 +14,7 @@ public:
void process();
void setRoBinary(const fnd::Vec<byte_t>& bin);
void setRoBinary(const tc::ByteData& bin);
void setApiInfo(size_t offset, size_t size);
void setDynSym(size_t offset, size_t size);
void setDynStr(size_t offset, size_t size);
@ -28,14 +25,14 @@ public:
void setListApi(bool listApi);
void setListSymbols(bool listSymbols);
const std::vector<SdkApiString>& getSdkVerApiList() const;
const std::vector<SdkApiString>& getPublicApiList() const;
const std::vector<SdkApiString>& getDebugApiList() const;
const std::vector<SdkApiString>& getPrivateApiList() const;
const std::vector<SdkApiString>& getGuidelineApiList() const;
const fnd::List<ElfSymbolParser::sElfSymbol>& getSymbolList() const;
const std::vector<nstool::SdkApiString>& getSdkVerApiList() const;
const std::vector<nstool::SdkApiString>& getPublicApiList() const;
const std::vector<nstool::SdkApiString>& getDebugApiList() const;
const std::vector<nstool::SdkApiString>& getPrivateApiList() const;
const std::vector<nstool::SdkApiString>& getGuidelineApiList() const;
const std::vector<nstool::ElfSymbolParser::sElfSymbol>& getSymbolList() const;
private:
const std::string kModuleName = "RoMetadataProcess";
std::string mModuleName;
CliOutputMode mCliOutputMode;
bool mIs64BitInstruction;
@ -52,7 +49,7 @@ private:
sLayout mApiInfo;
sLayout mDynSym;
sLayout mDynStr;
fnd::Vec<byte_t> mRoBlob;
tc::ByteData mRoBlob;
std::vector<SdkApiString> mSdkVerApiList;
std::vector<SdkApiString> mPublicApiList;
std::vector<SdkApiString> mDebugApiList;
@ -64,7 +61,9 @@ private:
void importApiList();
void displayRoMetaData();
const char* getSectionIndexStr(uint16_t shn_index) const;
const char* getSymbolTypeStr(byte_t symbol_type) const;
const char* getSymbolBindingStr(byte_t symbol_binding) const;
std::string getSectionIndexStr(uint16_t shn_index) const;
std::string getSymbolTypeStr(byte_t symbol_type) const;
std::string getSymbolBindingStr(byte_t symbol_binding) const;
};
}

View file

@ -1,341 +1,128 @@
#include <iostream>
#include <iomanip>
#include <fnd/SimpleTextOutput.h>
#include <fnd/SimpleFile.h>
#include <fnd/io.h>
#include "CompressedArchiveIFile.h"
#include "RomfsProcess.h"
#include "util.h"
RomfsProcess::RomfsProcess() :
#include <tc/io/VirtualFileSystem.h>
#include <nn/hac/RomFsMetaGenerator.h>
nstool::RomfsProcess::RomfsProcess() :
mModuleName("nstool::RomfsProcess"),
mFile(),
mCliOutputMode(_BIT(OUTPUT_BASIC)),
mCliOutputMode(true, false, false, false),
mVerify(false),
mExtractPath(),
mExtract(false),
mMountName(),
mListFs(false),
mDirNum(0),
mFileNum(0)
mFileNum(0),
mFileSystem(),
mFsProcess()
{
mRootDir.name.clear();
mRootDir.dir_list.clear();
mRootDir.file_list.clear();
mFsProcess.setFsFormatName("RomFs");
}
void RomfsProcess::process()
void nstool::RomfsProcess::process()
{
resolveRomfs();
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
if (mFile == nullptr)
{
displayHeader();
if (mListFs || _HAS_BIT(mCliOutputMode, OUTPUT_EXTENDED))
displayFs();
throw tc::Exception(mModuleName, "No file reader set.");
}
if (mFile->canRead() == false || mFile->canSeek() == false)
{
throw tc::NotSupportedException(mModuleName, "Input stream requires read/seek permissions.");
}
if (mExtract)
extractFs();
tc::ByteData scratch;
// read base header to determine complete header size
if (mFile->length() < tc::io::IOUtil::castSizeToInt64(sizeof(nn::hac::sRomfsHeader)))
{
throw tc::Exception(mModuleName, "Corrupt RomFs: File too small");
}
mFile->seek(0, tc::io::SeekOrigin::Begin);
mFile->read((byte_t*)&mRomfsHeader, sizeof(mRomfsHeader));
if (mRomfsHeader.header_size.unwrap() != sizeof(nn::hac::sRomfsHeader) ||
mRomfsHeader.dir_entry.offset.unwrap() != (mRomfsHeader.dir_hash_bucket.offset.unwrap() + mRomfsHeader.dir_hash_bucket.size.unwrap()) ||
mRomfsHeader.data_offset.unwrap() != align<int64_t>(mRomfsHeader.header_size.unwrap(), nn::hac::romfs::kRomfsHeaderAlign))
{
throw tc::ArgumentOutOfRangeException(mModuleName, "Corrupt RomFs: RomFsHeader is corrupted.");
}
// get dir entry ptr
tc::ByteData dir_entry_table = tc::ByteData(tc::io::IOUtil::castInt64ToSize(mRomfsHeader.dir_entry.size.unwrap()));
mFile->seek(mRomfsHeader.dir_entry.offset.unwrap(), tc::io::SeekOrigin::Begin);
mFile->read(dir_entry_table.data(), dir_entry_table.size());
// get file entry ptr
tc::ByteData file_entry_table = tc::ByteData(tc::io::IOUtil::castInt64ToSize(mRomfsHeader.file_entry.size.unwrap()));
mFile->seek(mRomfsHeader.file_entry.offset.unwrap(), tc::io::SeekOrigin::Begin);
mFile->read(file_entry_table.data(), file_entry_table.size());
// count dir num
mDirNum = 0;
for (uint32_t v_addr = 0; size_t(v_addr) < dir_entry_table.size();)
{
uint32_t total_size = sizeof(nn::hac::sRomfsDirEntry) + align<uint32_t>(((nn::hac::sRomfsDirEntry*)(dir_entry_table.data() + v_addr))->name_size.unwrap(), 4);
// don't count root directory
if (v_addr != 0)
{
mDirNum += 1;
}
v_addr += total_size;
}
// count file num
mFileNum = 0;
for (uint32_t v_addr = 0; size_t(v_addr) < file_entry_table.size();)
{
uint32_t total_size = sizeof(nn::hac::sRomfsFileEntry) + align<uint32_t>(((nn::hac::sRomfsFileEntry*)(file_entry_table.data() + v_addr))->name_size.unwrap(), 4);
mFileNum += 1;
v_addr += total_size;
}
// create virtual filesystem
mFileSystem = std::make_shared<tc::io::VirtualFileSystem>(tc::io::VirtualFileSystem(nn::hac::RomFsMetaGenerator(mFile)));
mFsProcess.setInputFileSystem(mFileSystem);
// set properties for FsProcess
mFsProcess.setFsProperties({
fmt::format("DirNum: {:d}", mDirNum),
fmt::format("FileNum: {:d}", mFileNum)
});
// process filesystem
mFsProcess.process();
}
void RomfsProcess::setInputFile(const fnd::SharedPtr<fnd::IFile>& file)
void nstool::RomfsProcess::setInputFile(const std::shared_ptr<tc::io::IStream>& file)
{
mFile = file;
}
void RomfsProcess::setCliOutputMode(CliOutputMode type)
void nstool::RomfsProcess::setCliOutputMode(CliOutputMode type)
{
mCliOutputMode = type;
mFsProcess.setShowFsInfo(mCliOutputMode.show_basic_info);
}
void RomfsProcess::setVerifyMode(bool verify)
void nstool::RomfsProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void RomfsProcess::setMountPointName(const std::string& mount_name)
void nstool::RomfsProcess::setFsRootLabel(const std::string& root_label)
{
mMountName = mount_name;
mFsProcess.setFsRootLabel(root_label);
}
void RomfsProcess::setExtractPath(const std::string& path)
void nstool::RomfsProcess::setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs)
{
mExtract = true;
mExtractPath = path;
mFsProcess.setExtractJobs(extract_jobs);
}
void RomfsProcess::setListFs(bool list_fs)
void nstool::RomfsProcess::setShowFsTree(bool list_fs)
{
mListFs = list_fs;
}
const RomfsProcess::sDirectory& RomfsProcess::getRootDir() const
{
return mRootDir;
}
void RomfsProcess::printTab(size_t tab) const
{
for (size_t i = 0; i < tab; i++)
{
std::cout << " ";
}
}
void RomfsProcess::displayFile(const sFile& file, size_t tab) const
{
printTab(tab);
std::cout << file.name;
if (_HAS_BIT(mCliOutputMode, OUTPUT_LAYOUT))
{
std::cout << std::hex << " (offset=0x" << file.offset << ", size=0x" << file.size << ")";
}
std::cout << std::endl;
}
void RomfsProcess::displayDir(const sDirectory& dir, size_t tab) const
{
if (dir.name.empty() == false)
{
printTab(tab);
std::cout << dir.name << std::endl;
}
for (size_t i = 0; i < dir.dir_list.size(); i++)
{
displayDir(dir.dir_list[i], tab+1);
}
for (size_t i = 0; i < dir.file_list.size(); i++)
{
displayFile(dir.file_list[i], tab+1);
}
}
void RomfsProcess::displayHeader()
{
std::cout << "[RomFS]" << std::endl;
std::cout << " DirNum: " << std::dec << mDirNum << std::endl;
std::cout << " FileNum: " << std::dec << mFileNum << std::endl;
if (mMountName.empty() == false)
{
std::cout << " MountPoint: " << mMountName;
if (mMountName.at(mMountName.length()-1) != '/')
std::cout << "/";
std::cout << std::endl;
}
}
void RomfsProcess::displayFs()
{
displayDir(mRootDir, 1);
}
void RomfsProcess::extractDir(const std::string& path, const sDirectory& dir)
{
std::string dir_path;
std::string file_path;
// make dir path
fnd::io::appendToPath(dir_path, path);
if (dir.name.empty() == false)
fnd::io::appendToPath(dir_path, dir.name);
// make directory
fnd::io::makeDirectory(dir_path);
// extract files
fnd::SimpleFile outFile;
for (size_t i = 0; i < dir.file_list.size(); i++)
{
file_path.clear();
fnd::io::appendToPath(file_path, dir_path);
fnd::io::appendToPath(file_path, dir.file_list[i].name);
if (_HAS_BIT(mCliOutputMode, OUTPUT_BASIC))
std::cout << "extract=[" << file_path << "]" << std::endl;
outFile.open(file_path, outFile.Create);
(*mFile)->seek(dir.file_list[i].offset);
for (size_t j = 0; j < ((dir.file_list[i].size / kCacheSize) + ((dir.file_list[i].size % kCacheSize) != 0)); j++)
{
(*mFile)->read(mCache.data(), _MIN(dir.file_list[i].size - (kCacheSize * j),kCacheSize));
outFile.write(mCache.data(), _MIN(dir.file_list[i].size - (kCacheSize * j),kCacheSize));
}
outFile.close();
}
for (size_t i = 0; i < dir.dir_list.size(); i++)
{
extractDir(dir_path, dir.dir_list[i]);
}
}
void RomfsProcess::extractFs()
{
// allocate only when extractDir is invoked
mCache.alloc(kCacheSize);
extractDir(mExtractPath, mRootDir);
}
bool RomfsProcess::validateHeaderLayout(const nn::hac::sRomfsHeader* hdr) const
{
bool validLayout = true;
if (hdr->header_size.get() != sizeof(nn::hac::sRomfsHeader))
{
validLayout = false;
}
uint64_t pos = hdr->sections[0].offset.get();
for (size_t i = 0; i < nn::hac::romfs::SECTION_NUM; i++)
{
if (hdr->sections[i].offset.get() != pos)
{
validLayout = false;
}
pos += hdr->sections[i].size.get();
}
return validLayout;
}
void RomfsProcess::importDirectory(uint32_t dir_offset, sDirectory& dir)
{
nn::hac::sRomfsDirEntry* d_node = get_dir_node(dir_offset);
/*
printf("[DIR-NODE]\n");
printf(" parent=%08x\n", d_node->parent.get());
printf(" sibling=%08x\n", d_node->sibling.get());
printf(" child=%08x\n", d_node->child.get());
printf(" file=%08x\n", d_node->file.get());
printf(" hash=%08x\n", d_node->hash.get());
printf(" name_size=%08x\n", d_node->name_size.get());
printf(" name=%s\n", d_node->name);
*/
for (uint32_t file_addr = d_node->file.get(); file_addr != nn::hac::romfs::kInvalidAddr; )
{
nn::hac::sRomfsFileEntry* f_node = get_file_node(file_addr);
/*
printf("[FILE-NODE]\n");
printf(" parent=%08x\n", f_node->parent.get());
printf(" sibling=%08x\n", f_node->sibling.get());
printf(" offset=%08" PRIx64 "\n", f_node->offset.get());
printf(" size=%08" PRIx64 "\n", f_node->size.get());
printf(" hash=%08x\n", f_node->hash.get());
printf(" name_size=%08x\n", f_node->name_size.get());
printf(" name=%s\n", f_node->name);
*/
dir.file_list.addElement({std::string(f_node->name(), f_node->name_size.get()), mHdr.data_offset.get() + f_node->offset.get(), f_node->size.get()});
file_addr = f_node->sibling.get();
mFileNum++;
}
for (uint32_t child_addr = d_node->child.get(); child_addr != nn::hac::romfs::kInvalidAddr; )
{
nn::hac::sRomfsDirEntry* c_node = get_dir_node(child_addr);
dir.dir_list.addElement({std::string(c_node->name(), c_node->name_size.get())});
importDirectory(child_addr, dir.dir_list.atBack());
child_addr = c_node->sibling.get();
mDirNum++;
}
}
void RomfsProcess::resolveRomfs()
{
if (*mFile == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
}
// read header
(*mFile)->read((byte_t*)&mHdr, 0, sizeof(nn::hac::sRomfsHeader));
// logic check on the header layout
if (validateHeaderLayout(&mHdr) == false)
{
throw fnd::Exception(kModuleName, "Invalid ROMFS Header");
}
// check for romfs compression
size_t physical_size = (*mFile)->size();
size_t logical_size = mHdr.sections[nn::hac::romfs::FILE_NODE_TABLE].offset.get() + mHdr.sections[nn::hac::romfs::FILE_NODE_TABLE].size.get();
// if logical size is greater than the physical size, check for compression meta footer
if (logical_size > physical_size)
{
// initial and final entries
nn::hac::sCompressionEntry entry[2];
// read final compression entry
(*mFile)->read((byte_t*)&entry[1], physical_size - sizeof(nn::hac::sCompressionEntry), sizeof(nn::hac::sCompressionEntry));
// the final compression entry should be for the (final part, in the case of metadata > 0x10000) romfs footer, for which the logical offset is detailed in the romfs header
// the compression is always enabled for non-header compression entries
uint64_t romfs_metadata_begin_offset = mHdr.sections[nn::hac::romfs::DIR_HASHMAP_TABLE].offset.get();
uint64_t romfs_metadata_end_offset = mHdr.sections[nn::hac::romfs::FILE_NODE_TABLE].offset.get() + mHdr.sections[nn::hac::romfs::FILE_NODE_TABLE].size.get();
if ((entry[1].virtual_offset.get() >= romfs_metadata_begin_offset && entry[1].virtual_offset.get() < romfs_metadata_end_offset) == false || \
entry[1].compression_type != (byte_t)nn::hac::compression::CompressionType::Lz4)
{
throw fnd::Exception(kModuleName, "RomFs appears corrupted (bad final compression entry virtual offset/compression type)");
}
// the first compression entry follows the physical placement of the final data chunk (specified in the final compression entry)
size_t first_entry_offset = align(entry[1].physical_offset.get() + entry[1].physical_size.get(), nn::hac::compression::kRomfsBlockAlign);
// quick check to make sure the offset at least before the last entry offset
if (first_entry_offset >= (physical_size - sizeof(nn::hac::sCompressionEntry)))
{
throw fnd::Exception(kModuleName, "RomFs appears corrupted (bad final compression entry physical offset/size)");
}
// read first compression entry
(*mFile)->read((byte_t*)&entry[0], first_entry_offset, sizeof(nn::hac::sCompressionEntry));
// validate first compression entry
// this should be the same for all compressed romfs
if (entry[0].virtual_offset.get() != 0x0 || \
entry[0].physical_offset.get() != 0x0 || \
entry[0].physical_size.get() != 0x200 || \
entry[0].compression_type != (byte_t)nn::hac::compression::CompressionType::None)
{
throw fnd::Exception(kModuleName, "RomFs appears corrupted (bad first compression entry)");
}
// wrap mFile in a class to transparantly decompress the image.
mFile = new CompressedArchiveIFile(mFile, first_entry_offset);
}
// read directory nodes
mDirNodes.alloc(mHdr.sections[nn::hac::romfs::DIR_NODE_TABLE].size.get());
(*mFile)->read(mDirNodes.data(), mHdr.sections[nn::hac::romfs::DIR_NODE_TABLE].offset.get(), mDirNodes.size());
//printf("[RAW DIR NODES]\n");
//fnd::SimpleTextOutput::hxdStyleDump(mDirNodes.data(), mDirNodes.size());
// read file nodes
mFileNodes.alloc(mHdr.sections[nn::hac::romfs::FILE_NODE_TABLE].size.get());
(*mFile)->read(mFileNodes.data(), mHdr.sections[nn::hac::romfs::FILE_NODE_TABLE].offset.get(), mFileNodes.size());
//printf("[RAW FILE NODES]\n");
//fnd::SimpleTextOutput::hxdStyleDump(mFileNodes.data(), mFileNodes.size());
// A logic check on the root directory node
if ( get_dir_node(0)->parent.get() != 0 \
|| get_dir_node(0)->sibling.get() != nn::hac::romfs::kInvalidAddr \
|| get_dir_node(0)->hash.get() != nn::hac::romfs::kInvalidAddr \
|| get_dir_node(0)->name_size.get() != 0)
{
throw fnd::Exception(kModuleName, "Invalid root directory node");
}
// import directory into internal structure
mDirNum = 0;
mFileNum = 0;
importDirectory(0, mRootDir);
mFsProcess.setShowFsTree(list_fs);
}

View file

@ -1,134 +1,42 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/IFile.h>
#include <fnd/SharedPtr.h>
#include <fnd/Vec.h>
#include <fnd/List.h>
#include "types.h"
#include "FsProcess.h"
#include <nn/hac/define/romfs.h>
#include "common.h"
namespace nstool {
class RomfsProcess
{
public:
struct sDirectory;
struct sFile;
struct sDirectory
{
std::string name;
fnd::List<sDirectory> dir_list;
fnd::List<sFile> file_list;
void operator=(const sDirectory& other)
{
name = other.name;
dir_list = other.dir_list;
file_list = other.file_list;
}
bool operator==(const sDirectory& other) const
{
return (name == other.name) \
&& (dir_list == other.dir_list) \
&& (file_list == other.file_list);
}
bool operator!=(const sDirectory& other) const
{
return !operator==(other);
}
bool operator==(const std::string& other) const
{
return (name == other);
}
};
struct sFile
{
std::string name;
uint64_t offset;
uint64_t size;
void operator=(const sFile& other)
{
name = other.name;
offset = other.offset;
size = other.size;
}
bool operator==(const sFile& other) const
{
return (name == other.name) \
&& (offset == other.offset) \
&& (size == other.size);
}
bool operator!=(const sFile& other) const
{
return !operator==(other);
}
bool operator==(const std::string& other) const
{
return (name == other);
}
};
RomfsProcess();
void process();
// generic
void setInputFile(const fnd::SharedPtr<fnd::IFile>& file);
void setInputFile(const std::shared_ptr<tc::io::IStream>& file);
void setCliOutputMode(CliOutputMode type);
void setVerifyMode(bool verify);
// romfs specific
void setMountPointName(const std::string& mount_name);
void setExtractPath(const std::string& path);
void setListFs(bool list_fs);
const sDirectory& getRootDir() const;
// fs specific
void setFsRootLabel(const std::string& root_label);
void setExtractJobs(const std::vector<nstool::ExtractJob>& extract_jobs);
void setShowFsTree(bool show_fs_tree);
private:
const std::string kModuleName = "RomfsProcess";
static const size_t kCacheSize = 0x10000;
fnd::SharedPtr<fnd::IFile> mFile;
std::string mModuleName;
std::shared_ptr<tc::io::IStream> mFile;
CliOutputMode mCliOutputMode;
bool mVerify;
std::string mExtractPath;
bool mExtract;
std::string mMountName;
bool mListFs;
fnd::Vec<byte_t> mCache;
nn::hac::sRomfsHeader mRomfsHeader;
size_t mDirNum;
size_t mFileNum;
nn::hac::sRomfsHeader mHdr;
fnd::Vec<byte_t> mDirNodes;
fnd::Vec<byte_t> mFileNodes;
sDirectory mRootDir;
inline nn::hac::sRomfsDirEntry* get_dir_node(uint32_t offset) { return (nn::hac::sRomfsDirEntry*)(mDirNodes.data() + offset); }
inline nn::hac::sRomfsFileEntry* get_file_node(uint32_t offset) { return (nn::hac::sRomfsFileEntry*)(mFileNodes.data() + offset); }
void printTab(size_t tab) const;
void displayFile(const sFile& file, size_t tab) const;
void displayDir(const sDirectory& dir, size_t tab) const;
void displayHeader();
void displayFs();
void extractDir(const std::string& path, const sDirectory& dir);
void extractFs();
bool validateHeaderLayout(const nn::hac::sRomfsHeader* hdr) const;
void importDirectory(uint32_t dir_offset, sDirectory& dir);
void resolveRomfs();
std::shared_ptr<tc::io::IStorage> mFileSystem;
FsProcess mFsProcess;
};
}

View file

@ -1,13 +1,13 @@
#include <sstream>
#include "SdkApiString.h"
SdkApiString::SdkApiString(const std::string& full_str) :
nstool::SdkApiString::SdkApiString(const std::string& full_str) :
SdkApiString(API_MIDDLEWARE, "", "")
{
resolveApiString(full_str);
}
SdkApiString::SdkApiString(ApiType type, const std::string& vender_name, const std::string& module_name) :
nstool::SdkApiString::SdkApiString(ApiType type, const std::string& vender_name, const std::string& module_name) :
mApiType(type),
mVenderName(vender_name),
mModuleName(module_name)
@ -15,44 +15,44 @@ SdkApiString::SdkApiString(ApiType type, const std::string& vender_name, const s
}
void SdkApiString::operator=(const SdkApiString& other)
void nstool::SdkApiString::operator=(const SdkApiString& other)
{
mApiType = other.mApiType;
mVenderName = other.mVenderName;
mModuleName = other.mModuleName;
}
SdkApiString::ApiType SdkApiString::getApiType() const
nstool::SdkApiString::ApiType nstool::SdkApiString::getApiType() const
{
return mApiType;
}
void SdkApiString::setApiType(ApiType type)
void nstool::SdkApiString::setApiType(ApiType type)
{
mApiType = type;
}
const std::string& SdkApiString::getVenderName() const
const std::string& nstool::SdkApiString::getVenderName() const
{
return mVenderName;
}
void SdkApiString::setVenderName(const std::string& name)
void nstool::SdkApiString::setVenderName(const std::string& name)
{
mVenderName = name;
}
const std::string& SdkApiString::getModuleName() const
const std::string& nstool::SdkApiString::getModuleName() const
{
return mModuleName;
}
void SdkApiString::setModuleName(const std::string& name)
void nstool::SdkApiString::setModuleName(const std::string& name)
{
mModuleName = name;
}
void SdkApiString::resolveApiString(const std::string& full_str)
void nstool::SdkApiString::resolveApiString(const std::string& full_str)
{
std::stringstream list_stream(full_str);
std::string api_type, vender, module;

View file

@ -1,5 +1,7 @@
#pragma once
#include <string>
#include "types.h"
namespace nstool {
class SdkApiString
{
@ -43,3 +45,5 @@ private:
void resolveApiString(const std::string& full_str);
};
}

1071
src/Settings.cpp Normal file

File diff suppressed because it is too large Load diff

148
src/Settings.h Normal file
View file

@ -0,0 +1,148 @@
#pragma once
#include "types.h"
#include <string>
#include <vector>
#include <tc/Optional.h>
#include <tc/io.h>
#include "KeyBag.h"
namespace nstool {
struct Settings
{
enum FileType
{
FILE_TYPE_ERROR,
FILE_TYPE_GAMECARD,
FILE_TYPE_NSP,
FILE_TYPE_PARTITIONFS,
FILE_TYPE_ROMFS,
FILE_TYPE_NCA,
FILE_TYPE_META,
FILE_TYPE_CNMT,
FILE_TYPE_NSO,
FILE_TYPE_NRO,
FILE_TYPE_NACP,
FILE_TYPE_INI,
FILE_TYPE_KIP,
FILE_TYPE_ES_CERT,
FILE_TYPE_ES_TIK,
FILE_TYPE_HB_ASSET,
};
struct InputFileOptions
{
FileType filetype;
tc::Optional<tc::io::Path> path;
} infile;
struct Options
{
CliOutputMode cli_output_mode;
bool verify;
bool is_dev;
KeyBag keybag;
} opt;
// code options
struct CodeOptions
{
bool list_api;
bool list_symbols;
bool is_64bit_instruction; // true=64bit, false=32bit
} code;
// Generic FS options
struct FsOptions
{
bool show_fs_tree;
std::vector<ExtractJob> extract_jobs;
} fs;
// XCI options
struct XciOptions
{
tc::Optional<tc::io::Path> update_extract_path;
tc::Optional<tc::io::Path> logo_extract_path;
tc::Optional<tc::io::Path> normal_extract_path;
tc::Optional<tc::io::Path> secure_extract_path;
} xci;
// NCA options
struct NcaOptions
{
tc::Optional<tc::io::Path> part0_extract_path;
tc::Optional<tc::io::Path> part1_extract_path;
tc::Optional<tc::io::Path> part2_extract_path;
tc::Optional<tc::io::Path> part3_extract_path;
} nca;
// KIP options
struct KipOptions
{
tc::Optional<tc::io::Path> extract_path;
} kip;
// ASET Options
struct AsetOptions
{
tc::Optional<tc::io::Path> icon_extract_path;
tc::Optional<tc::io::Path> nacp_extract_path;
} aset;
Settings()
{
infile.filetype = FILE_TYPE_ERROR;
infile.path = tc::Optional<tc::io::Path>();
opt.cli_output_mode = CliOutputMode();
opt.verify = false;
opt.is_dev = false;
opt.keybag = KeyBag();
code.list_api = false;
code.list_symbols = false;
code.is_64bit_instruction = true;
fs.show_fs_tree = false;
fs.extract_jobs = std::vector<ExtractJob>();
kip.extract_path = tc::Optional<tc::io::Path>();
aset.icon_extract_path = tc::Optional<tc::io::Path>();
aset.nacp_extract_path = tc::Optional<tc::io::Path>();
}
};
class SettingsInitializer : public Settings
{
public:
SettingsInitializer(const std::vector<std::string>& args);
private:
void parse_args(const std::vector<std::string>& args);
void determine_filetype();
void usage_text() const;
void dump_keys() const;
void dump_rsa_key(const KeyBag::rsa_key_t& key, const std::string& label, size_t indent, bool expanded_key_data) const;
std::string mModuleLabel;
bool mShowLayout;
bool mShowKeydata;
bool mVerbose;
tc::Optional<tc::io::Path> mKeysetPath;
tc::Optional<KeyBag::aes128_key_t> mNcaEncryptedContentKey;
tc::Optional<KeyBag::aes128_key_t> mNcaContentKey;
tc::Optional<tc::io::Path> mTikPath;
tc::Optional<tc::io::Path> mCertPath;
bool determineValidNcaFromSample(const tc::ByteData& raw_data) const;
bool determineValidEsCertFromSample(const tc::ByteData& raw_data) const;
bool determineValidEsTikFromSample(const tc::ByteData& raw_data) const;
bool determineValidCnmtFromSample(const tc::ByteData& raw_data) const;
bool determineValidNacpFromSample(const tc::ByteData& raw_data) const;
};
}

File diff suppressed because it is too large Load diff

View file

@ -1,138 +0,0 @@
#pragma once
#include <vector>
#include <string>
#include <fnd/types.h>
#include <fnd/Vec.h>
#include <fnd/List.h>
#include <nn/pki/SignedData.h>
#include <nn/pki/CertificateBody.h>
#include <nn/hac/define/meta.h>
#include "common.h"
#include "KeyConfiguration.h"
class UserSettings
{
public:
UserSettings();
void parseCmdArgs(const std::vector<std::string>& arg_list);
void showHelp();
// generic options
const std::string getInputPath() const;
const KeyConfiguration& getKeyCfg() const;
FileType getFileType() const;
bool isVerifyFile() const;
CliOutputMode getCliOutputMode() const;
// specialised toggles
bool isListFs() const;
bool isListApi() const;
bool isListSymbols() const;
bool getIs64BitInstruction() const;
// specialised paths
const sOptional<std::string>& getXciUpdatePath() const;
const sOptional<std::string>& getXciLogoPath() const;
const sOptional<std::string>& getXciNormalPath() const;
const sOptional<std::string>& getXciSecurePath() const;
const sOptional<std::string>& getFsPath() const;
const sOptional<std::string>& getNcaPart0Path() const;
const sOptional<std::string>& getNcaPart1Path() const;
const sOptional<std::string>& getNcaPart2Path() const;
const sOptional<std::string>& getNcaPart3Path() const;
const sOptional<std::string>& getKipExtractPath() const;
const sOptional<std::string>& getAssetIconPath() const;
const sOptional<std::string>& getAssetNacpPath() const;
const fnd::List<nn::pki::SignedData<nn::pki::CertificateBody>>& getCertificateChain() const;
private:
const std::string kModuleName = "UserSettings";
const std::string kHomeSwitchDirStr = ".switch";
const std::string kGeneralKeyfileName[2] = { "prod.keys", "dev.keys" };
const std::string kTitleKeyfileName = "title.keys";
struct sCmdArgs
{
sCmdArgs() {}
sOptional<std::string> input_path;
sOptional<bool> devkit_keys;
sOptional<std::string> keyset_path;
sOptional<std::string> file_type;
sOptional<bool> verify_file;
sOptional<bool> show_keys;
sOptional<bool> show_layout;
sOptional<bool> verbose_output;
sOptional<bool> list_fs;
sOptional<std::string> update_path;
sOptional<std::string> logo_path;
sOptional<std::string> normal_path;
sOptional<std::string> secure_path;
sOptional<std::string> fs_path;
sOptional<std::string> nca_titlekey;
sOptional<std::string> nca_bodykey;
sOptional<std::string> ticket_path;
sOptional<std::string> cert_path;
sOptional<std::string> part0_path;
sOptional<std::string> part1_path;
sOptional<std::string> part2_path;
sOptional<std::string> part3_path;
sOptional<std::string> kip_extract_path;
sOptional<bool> list_api;
sOptional<bool> list_sym;
sOptional<std::string> inst_type;
sOptional<std::string> asset_icon_path;
sOptional<std::string> asset_nacp_path;
};
std::string mInputPath;
FileType mFileType;
KeyConfiguration mKeyCfg;
bool mVerifyFile;
CliOutputMode mOutputMode;
bool mListFs;
sOptional<std::string> mXciUpdatePath;
sOptional<std::string> mXciLogoPath;
sOptional<std::string> mXciNormalPath;
sOptional<std::string> mXciSecurePath;
sOptional<std::string> mFsPath;
sOptional<std::string> mNcaPart0Path;
sOptional<std::string> mNcaPart1Path;
sOptional<std::string> mNcaPart2Path;
sOptional<std::string> mNcaPart3Path;
sOptional<std::string> mKipExtractPath;
sOptional<std::string> mAssetIconPath;
sOptional<std::string> mAssetNacpPath;
fnd::List<nn::pki::SignedData<nn::pki::CertificateBody>> mCertChain;
bool mListApi;
bool mListSymbols;
bool mIs64BitInstruction;
void populateCmdArgs(const std::vector<std::string>& arg_list, sCmdArgs& cmd_args);
void populateKeyset(sCmdArgs& args);
void populateUserSettings(sCmdArgs& args);
FileType getFileTypeFromString(const std::string& type_str);
FileType determineFileTypeFromFile(const std::string& path);
bool determineValidNcaFromSample(const fnd::Vec<byte_t>& sample) const;
bool determineValidCnmtFromSample(const fnd::Vec<byte_t>& sample) const;
bool determineValidNacpFromSample(const fnd::Vec<byte_t>& sample) const;
bool determineValidEsCertFromSample(const fnd::Vec<byte_t>& sample) const;
bool determineValidEsTikFromSample(const fnd::Vec<byte_t>& sample) const;
bool getIs64BitInstructionFromString(const std::string& type_str);
void getHomePath(std::string& path) const;
void getSwitchPath(std::string& path) const;
void dumpKeyConfig() const;
void dumpRsa2048Key(const fnd::rsa::sRsa2048Key& key, const std::string& name, size_t indent) const;
void dumpRsa4096Key(const fnd::rsa::sRsa4096Key& key, const std::string& name, size_t indent) const;
void dumpAesKey(const fnd::aes::sAes128Key& key, const std::string& name, size_t indent) const;
void dumpAesXtsKey(const fnd::aes::sAesXts128Key& key, const std::string& name, size_t indent) const;
};

View file

@ -1,62 +0,0 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/aes.h>
#include <fnd/rsa.h>
#include <nn/hac/define/nca.h>
static const size_t kMasterKeyNum = 0x20;
static const size_t kNcaKeakNum = nn::hac::nca::kKeyAreaEncryptionKeyNum;
enum FileType
{
FILE_GAMECARD,
FILE_NSP,
FILE_PARTITIONFS,
FILE_ROMFS,
FILE_NCA,
FILE_META,
FILE_CNMT,
FILE_NSO,
FILE_NRO,
FILE_NACP,
FILE_INI,
FILE_KIP,
FILE_PKI_CERT,
FILE_ES_TIK,
FILE_HB_ASSET,
FILE_INVALID = -1,
};
enum CliOutputModeFlag
{
OUTPUT_BASIC,
OUTPUT_LAYOUT,
OUTPUT_KEY_DATA,
OUTPUT_EXTENDED
};
typedef byte_t CliOutputMode;
template <typename T>
struct sOptional
{
bool isSet;
T var;
inline sOptional() : isSet(false) {}
inline sOptional(const T& other) : isSet(true), var(other) {}
inline sOptional(const sOptional& other) : isSet(other.isSet), var(other.var) {}
inline const T& operator=(const T& other) { isSet = true; var = other; return var; }
inline const sOptional<T>& operator=(const sOptional<T>& other)
{
isSet = other.isSet;
if (isSet) {
var = other.var;
}
return *this;
}
inline T& operator*() { return var; }
};
const byte_t kDummyRightsIdForUserTitleKey[nn::hac::nca::kRightsIdLen] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
const byte_t kDummyRightsIdForUserBodyKey[nn::hac::nca::kRightsIdLen] = {0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};

458
src/elf.h Normal file
View file

@ -0,0 +1,458 @@
#pragma once
#include "types.h"
namespace nstool
{
namespace elf
{
/* These constants are for the segment types stored in the image headers */
enum SegmentType
{
PT_NULL = 0,
PT_LOAD = 1,
PT_DYNAMIC = 2,
PT_INTERP = 3,
PT_NOTE = 4,
PT_SHLIB = 5,
PT_PHDR = 6,
PT_TLS = 7, /* Thread local storage segment */
PT_LOOS = 0x60000000, /* OS-specific */
PT_HIOS = 0x6fffffff, /* OS-specific */
PT_LOPROC = 0x70000000,
PT_HIPROC = 0x7fffffff
};
/* These constants define the different elf file types */
enum ElfType
{
ET_NONE = 0,
ET_REL = 1,
ET_EXEC = 2,
ET_DYN = 3,
ET_CORE = 4,
ET_LOPROC = 0xff00,
ET_HIPROC = 0xffff
};
/* This is the info that is needed to parse the dynamic section of the file */
enum DynamicSectionType
{
DT_NULL = 0,
DT_NEEDED = 1,
DT_PLTRELSZ = 2,
DT_PLTGOT = 3,
DT_HASH = 4,
DT_STRTAB = 5,
DT_SYMTAB = 6,
DT_RELA = 7,
DT_RELASZ = 8,
DT_RELAENT = 9,
DT_STRSZ = 10,
DT_SYMENT = 11,
DT_INIT = 12,
DT_FINI = 13,
DT_SONAME = 14,
DT_RPATH = 15,
DT_SYMBOLIC = 16,
DT_REL = 17,
DT_RELSZ = 18,
DT_RELENT = 19,
DT_PLTREL = 20,
DT_DEBUG = 21,
DT_TEXTREL = 22,
DT_JMPREL = 23,
DT_ENCODING = 32,
OLD_DT_LOOS = 0x60000000,
DT_LOOS = 0x6000000d,
DT_HIOS = 0x6ffff000,
DT_VALRNGLO = 0x6ffffd00,
DT_VALRNGHI = 0x6ffffdff,
DT_ADDRRNGLO = 0x6ffffe00,
DT_ADDRRNGHI = 0x6ffffeff,
DT_VERSYM = 0x6ffffff0,
DT_RELACOUNT = 0x6ffffff9,
DT_RELCOUNT = 0x6ffffffa,
DT_FLAGS_1 = 0x6ffffffb,
DT_VERDEF = 0x6ffffffc,
DT_VERDEFNUM = 0x6ffffffd,
DT_VERNEED = 0x6ffffffe,
DT_VERNEEDNUM = 0x6fffffff,
OLD_DT_HIOS = 0x6fffffff,
DT_LOPROC = 0x70000000,
DT_HIPROC = 0x7fffffff
};
/* This info is needed when parsing the symbol table */
enum SymbolBinding
{
STB_LOCAL = 0,
STB_GLOBAL = 1,
STB_WEAK = 2,
STB_LOOS = 10,
STB_HIOS = 12,
STB_LOPROC,
STB_HIPROC = 0xf
};
enum SymbolType
{
STT_NOTYPE = 0,
STT_OBJECT = 1,
STT_FUNC = 2,
STT_SECTION = 3,
STT_FILE = 4,
STT_COMMON = 5,
STT_TLS = 6,
STT_LOOS = 10,
STT_HIOS = 12,
STT_LOPROC,
STT_HIPROC = 0xf
};
/* These constants define the permissions on sections in the program
header, p_flags. */
enum PermissionFlag
{
PF_R = 0x4,
PF_W = 0x2,
PF_X = 0x1
};
/* sh_type */
enum SectionHeaderType
{
SHT_NULL = 0,
SHT_PROGBITS = 1,
SHT_SYMTAB = 2,
SHT_STRTAB = 3,
SHT_RELA = 4,
SHT_HASH = 5,
SHT_DYNAMIC = 6,
SHT_NOTE = 7,
SHT_NOBITS = 8,
SHT_REL = 9,
SHT_SHLIB = 10,
SHT_DYNSYM = 11,
SHT_NUM = 12,
SHT_LOPROC = 0x70000000,
SHT_HIPROC = 0x7fffffff,
SHT_LOUSER = 0x80000000,
SHT_HIUSER = 0xffffffff
};
/* sh_flags */
enum SectionHeaderFlag
{
SHF_WRITE = 0x1,
SHF_ALLOC = 0x2,
SHF_EXECINSTR = 0x4,
SHF_RELA_LIVEPATCH = 0x00100000,
SHF_RO_AFTER_INIT = 0x00200000,
SHF_MASKPROC = 0xf0000000
};
/* special section indexes */
enum SpecialSectionIndex
{
SHN_UNDEF = 0,
SHN_LORESERVE = 0xff00,
SHN_LOPROC = 0xff00,
SHN_HIPROC = 0xff1f,
SHN_LOOS = 0xff20,
SHN_HIOS = 0xff3f,
SHN_ABS = 0xfff1,
SHN_COMMON = 0xfff2,
SHN_HIRESERVE = 0xffff
};
enum ElfIdentIndex
{
EI_MAG0 = 0, /* e_ident[] indexes */
EI_MAG1 = 1,
EI_MAG2 = 2,
EI_MAG3 = 3,
EI_CLASS = 4,
EI_DATA = 5,
EI_VERSION = 6,
EI_OSABI = 7,
EI_PAD = 8
};
enum ElfClass
{
ELFCLASSNONE = 0, /* EI_CLASS */
ELFCLASS32 = 1,
ELFCLASS64 = 2,
ELFCLASSNUM = 3
};
enum ElfData
{
ELFDATANONE = 0, /* e_ident[EI_DATA] */
ELFDATA2LSB = 1,
ELFDATA2MSB = 2
};
enum ElfVersion
{
EV_NONE = 0, /* e_version, EI_VERSION */
EV_CURRENT = 1,
EV_NUM = 2,
};
enum ElfOsAbi
{
ELFOSABI_NONE = 0,
ELFOSABI_LINUX =3
};
/*
* Notes used in ET_CORE. Architectures export some of the arch register sets
* using the corresponding note types via the PTRACE_GETREGSET and
* PTRACE_SETREGSET requests.
*/
enum NoteType
{
NT_PRSTATUS = 1,
NT_PRFPREG = 2,
NT_PRPSINFO = 3,
NT_TASKSTRUCT = 4,
NT_AUXV = 6,
/*
* Note to userspace developers: size of NT_SIGINFO note may increase
* in the future to accomodate more fields, don't assume it is fixed!
*/
NT_SIGINFO = 0x53494749,
NT_FILE = 0x46494c45,
NT_PRXFPREG = 0x46e62b7f, /* copied from gdb5.1/include/elf/common.h */
NT_PPC_VMX = 0x100, /* PowerPC Altivec/VMX registers */
NT_PPC_SPE = 0x101, /* PowerPC SPE/EVR registers */
NT_PPC_VSX = 0x102, /* PowerPC VSX registers */
NT_PPC_TAR = 0x103, /* Target Address Register */
NT_PPC_PPR = 0x104, /* Program Priority Register */
NT_PPC_DSCR = 0x105, /* Data Stream Control Register */
NT_PPC_EBB = 0x106, /* Event Based Branch Registers */
NT_PPC_PMU = 0x107, /* Performance Monitor Registers */
NT_PPC_TM_CGPR = 0x108, /* TM checkpointed GPR Registers */
NT_PPC_TM_CFPR = 0x109, /* TM checkpointed FPR Registers */
NT_PPC_TM_CVMX = 0x10a, /* TM checkpointed VMX Registers */
NT_PPC_TM_CVSX = 0x10b, /* TM checkpointed VSX Registers */
NT_PPC_TM_SPR = 0x10c, /* TM Special Purpose Registers */
NT_PPC_TM_CTAR = 0x10d, /* TM checkpointed Target Address Register */
NT_PPC_TM_CPPR = 0x10e, /* TM checkpointed Program Priority Register */
NT_PPC_TM_CDSCR = 0x10f, /* TM checkpointed Data Stream Control Register */
NT_PPC_PKEY = 0x110, /* Memory Protection Keys registers */
NT_386_TLS = 0x200, /* i386 TLS slots (struct user_desc) */
NT_386_IOPERM = 0x201, /* x86 io permission bitmap (1=deny) */
NT_X86_XSTATE = 0x202, /* x86 extended state using xsave */
NT_S390_HIGH_GPRS = 0x300, /* s390 upper register halves */
NT_S390_TIMER = 0x301, /* s390 timer register */
NT_S390_TODCMP = 0x302, /* s390 TOD clock comparator register */
NT_S390_TODPREG = 0x303, /* s390 TOD programmable register */
NT_S390_CTRS = 0x304, /* s390 control registers */
NT_S390_PREFIX = 0x305, /* s390 prefix register */
NT_S390_LAST_BREAK = 0x306, /* s390 breaking event address */
NT_S390_SYSTEM_CALL = 0x307, /* s390 system call restart data */
NT_S390_TDB = 0x308, /* s390 transaction diagnostic block */
NT_S390_VXRS_LOW = 0x309, /* s390 vector registers 0-15 upper half */
NT_S390_VXRS_HIGH = 0x30a, /* s390 vector registers 16-31 */
NT_S390_GS_CB = 0x30b, /* s390 guarded storage registers */
NT_S390_GS_BC = 0x30c, /* s390 guarded storage broadcast control block */
NT_S390_RI_CB = 0x30d, /* s390 runtime instrumentation */
NT_ARM_VFP = 0x400, /* ARM VFP/NEON registers */
NT_ARM_TLS = 0x401, /* ARM TLS register */
NT_ARM_HW_BREAK = 0x402, /* ARM hardware breakpoint registers */
NT_ARM_HW_WATCH = 0x403, /* ARM hardware watchpoint registers */
NT_ARM_SYSTEM_CALL = 0x404, /* ARM system call number */
NT_ARM_SVE = 0x405, /* ARM Scalable Vector Extension registers */
NT_ARC_V2 = 0x600, /* ARCv2 accumulator/extra registers */
NT_VMCOREDD = 0x700, /* Vmcore Device Dump Note */
NT_MIPS_DSP = 0x800, /* MIPS DSP ASE registers */
NT_MIPS_FP_MODE = 0x801, /* MIPS floating-point mode */
};
static const size_t kEIdentSize = 0x10;
static const byte_t kElfMagic[sizeof(uint32_t)] = {0x7f, 'E', 'L', 'F'};
inline byte_t get_elf_st_bind(byte_t st_info) { return st_info >> 4; }
inline byte_t get_elf_st_type(byte_t st_info) { return st_info & 0xf; }
inline byte_t get_elf_st_info(byte_t st_bind, byte_t st_type) { return (st_type & 0xf) | ((st_bind & 0xf) << 4);}
/* The following are used with relocations */
#define ELF32_R_SYM(x) ((x) >> 8)
#define ELF32_R_TYPE(x) ((x) & 0xff)
#define ELF64_R_SYM(i) ((i) >> 32)
#define ELF64_R_TYPE(i) ((i) & 0xffffffff)
}
struct Elf32_Dyn
{
int32_t d_tag;
union{
int32_t d_val;
uint32_t d_ptr;
} d_un;
};
struct Elf64_Dyn
{
int64_t d_tag; /* entry tag value */
union {
uint64_t d_val;
uint64_t d_ptr;
} d_un;
};
struct Elf32_Rel
{
uint32_t r_offset;
uint32_t r_info;
};
struct Elf64_Rel
{
uint64_t r_offset; /* Location at which to apply the action */
uint64_t r_info; /* index and type of relocation */
};
struct Elf32_Rela
{
uint32_t r_offset;
uint32_t r_info;
int32_t r_addend;
};
struct Elf64_Rela
{
uint64_t r_offset; /* Location at which to apply the action */
uint64_t r_info; /* index and type of relocation */
int64_t r_addend; /* Constant addend used to compute value */
};
struct Elf32_Sym
{
uint32_t st_name;
uint32_t st_value;
uint32_t st_size;
byte_t st_info;
byte_t st_other;
uint16_t st_shndx;
};
struct Elf64_Sym
{
uint32_t st_name; /* Symbol name, index in string tbl */
byte_t st_info; /* Type and binding attributes */
byte_t st_other; /* No defined meaning, 0 */
uint16_t st_shndx; /* Associated section index */
uint64_t st_value; /* Value of the symbol */
uint64_t st_size; /* Associated symbol size */
};
struct Elf32_Ehdr
{
byte_t e_ident[elf::kEIdentSize];
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
uint32_t e_entry; /* Entry point */
uint32_t e_phoff;
uint32_t e_shoff;
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
};
struct Elf64_Ehdr
{
byte_t e_ident[elf::kEIdentSize]; /* ELF "magic number" */
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
uint64_t e_entry; /* Entry point virtual address */
uint64_t e_phoff; /* Program header table file offset */
uint64_t e_shoff; /* Section header table file offset */
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
};
struct Elf32_Phdr
{
uint32_t p_type;
uint32_t p_offset;
uint32_t p_vaddr;
uint32_t p_paddr;
uint32_t p_filesz;
uint32_t p_memsz;
uint32_t p_flags;
uint32_t p_align;
};
struct Elf64_Phdr
{
uint32_t p_type;
uint32_t p_flags;
uint64_t p_offset; /* Segment file offset */
uint64_t p_vaddr; /* Segment virtual address */
uint64_t p_paddr; /* Segment physical address */
uint64_t p_filesz; /* Segment size in file */
uint64_t p_memsz; /* Segment size in memory */
uint64_t p_align; /* Segment alignment, file & memory */
};
struct Elf32_Shdr
{
uint32_t sh_name;
uint32_t sh_type;
uint32_t sh_flags;
uint32_t sh_addr;
uint32_t sh_offset;
uint32_t sh_size;
uint32_t sh_link;
uint32_t sh_info;
uint32_t sh_addralign;
uint32_t sh_entsize;
};
struct Elf64_Shdr
{
uint32_t sh_name; /* Section name, index in string tbl */
uint32_t sh_type; /* Type of section */
uint64_t sh_flags; /* Miscellaneous section attributes */
uint64_t sh_addr; /* Section virtual addr at execution */
uint64_t sh_offset; /* Section file offset */
uint64_t sh_size; /* Size of section in bytes */
uint32_t sh_link; /* Index of another section */
uint32_t sh_info; /* Additional section information */
uint64_t sh_addralign; /* Section alignment */
uint64_t sh_entsize; /* Entry size if section holds table */
};
/* Note header in a PT_NOTE section */
struct Elf32_Nhdr
{
uint32_t n_namesz; /* Name size */
uint32_t n_descsz; /* Content size */
uint32_t n_type; /* Content type */
};
/* Note header in a PT_NOTE section */
struct Elf64_Nhdr
{
uint32_t n_namesz; /* Name size */
uint32_t n_descsz; /* Content size */
uint32_t n_type; /* Content type */
};
}

View file

@ -1,8 +1,8 @@
#include <cstdio>
#include <fnd/SimpleFile.h>
#include <fnd/SharedPtr.h>
#include <fnd/StringConv.h>
#include "UserSettings.h"
#include <tc.h>
#include <tc/os/UnicodeMain.h>
#include "Settings.h"
#include "GameCardProcess.h"
#include "PfsProcess.h"
#include "RomfsProcess.h"
@ -14,244 +14,212 @@
#include "NacpProcess.h"
#include "IniProcess.h"
#include "KipProcess.h"
#include "PkiCertProcess.h"
#include "EsCertProcess.h"
#include "EsTikProcess.h"
#include "AssetProcess.h"
#ifdef _WIN32
int wmain(int argc, wchar_t** argv)
#else
int main(int argc, char** argv)
#endif
int umain(const std::vector<std::string>& args, const std::vector<std::string>& env)
{
std::vector<std::string> args;
for (size_t i = 0; i < (size_t)argc; i++)
try
{
#ifdef _WIN32
args.push_back(fnd::StringConv::ConvertChar16ToChar8(std::u16string((char16_t*)argv[i])));
#else
args.push_back(argv[i]);
#endif
}
nstool::Settings set = nstool::SettingsInitializer(args);
UserSettings user_set;
try {
user_set.parseCmdArgs(args);
std::shared_ptr<tc::io::IStream> infile_stream = std::make_shared<tc::io::FileStream>(tc::io::FileStream(set.infile.path.get(), tc::io::FileMode::Open, tc::io::FileAccess::Read));
fnd::SharedPtr<fnd::IFile> inputFile(new fnd::SimpleFile(user_set.getInputPath(), fnd::SimpleFile::Read));
if (user_set.getFileType() == FILE_GAMECARD)
if (set.infile.filetype == nstool::Settings::FILE_TYPE_GAMECARD)
{
GameCardProcess obj;
nstool::GameCardProcess obj;
obj.setInputFile(inputFile);
obj.setInputFile(infile_stream);
obj.setKeyCfg(user_set.getKeyCfg());
obj.setCliOutputMode(user_set.getCliOutputMode());
obj.setVerifyMode(user_set.isVerifyFile());
obj.setKeyCfg(set.opt.keybag);
obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify);
if (user_set.getXciUpdatePath().isSet)
obj.setPartitionForExtract(nn::hac::gc::kUpdatePartitionStr, user_set.getXciUpdatePath().var);
if (user_set.getXciLogoPath().isSet)
obj.setPartitionForExtract(nn::hac::gc::kLogoPartitionStr, user_set.getXciLogoPath().var);
if (user_set.getXciNormalPath().isSet)
obj.setPartitionForExtract(nn::hac::gc::kNormalPartitionStr, user_set.getXciNormalPath().var);
if (user_set.getXciSecurePath().isSet)
obj.setPartitionForExtract(nn::hac::gc::kSecurePartitionStr, user_set.getXciSecurePath().var);
obj.setListFs(user_set.isListFs());
obj.setShowFsTree(set.fs.show_fs_tree);
obj.setExtractJobs(set.fs.extract_jobs);
obj.process();
}
else if (user_set.getFileType() == FILE_PARTITIONFS || user_set.getFileType() == FILE_NSP)
else if (set.infile.filetype == nstool::Settings::FILE_TYPE_PARTITIONFS || set.infile.filetype == nstool::Settings::FILE_TYPE_NSP)
{
PfsProcess obj;
nstool::PfsProcess obj;
obj.setInputFile(inputFile);
obj.setCliOutputMode(user_set.getCliOutputMode());
obj.setVerifyMode(user_set.isVerifyFile());
obj.setInputFile(infile_stream);
if (user_set.getFsPath().isSet)
obj.setExtractPath(user_set.getFsPath().var);
obj.setListFs(user_set.isListFs());
obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify);
obj.setShowFsTree(set.fs.show_fs_tree);
obj.setExtractJobs(set.fs.extract_jobs);
obj.process();
}
else if (user_set.getFileType() == FILE_ROMFS)
else if (set.infile.filetype == nstool::Settings::FILE_TYPE_ROMFS)
{
RomfsProcess obj;
nstool::RomfsProcess obj;
obj.setInputFile(inputFile);
obj.setCliOutputMode(user_set.getCliOutputMode());
obj.setVerifyMode(user_set.isVerifyFile());
obj.setInputFile(infile_stream);
obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify);
if (user_set.getFsPath().isSet)
obj.setExtractPath(user_set.getFsPath().var);
obj.setListFs(user_set.isListFs());
obj.setShowFsTree(set.fs.show_fs_tree);
obj.setExtractJobs(set.fs.extract_jobs);
obj.process();
}
else if (user_set.getFileType() == FILE_NCA)
else if (set.infile.filetype == nstool::Settings::FILE_TYPE_NCA)
{
NcaProcess obj;
nstool::NcaProcess obj;
obj.setInputFile(inputFile);
obj.setKeyCfg(user_set.getKeyCfg());
obj.setCliOutputMode(user_set.getCliOutputMode());
obj.setVerifyMode(user_set.isVerifyFile());
obj.setInputFile(infile_stream);
obj.setKeyCfg(set.opt.keybag);
obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify);
if (user_set.getNcaPart0Path().isSet)
obj.setPartition0ExtractPath(user_set.getNcaPart0Path().var);
if (user_set.getNcaPart1Path().isSet)
obj.setPartition1ExtractPath(user_set.getNcaPart1Path().var);
if (user_set.getNcaPart2Path().isSet)
obj.setPartition2ExtractPath(user_set.getNcaPart2Path().var);
if (user_set.getNcaPart3Path().isSet)
obj.setPartition3ExtractPath(user_set.getNcaPart3Path().var);
obj.setListFs(user_set.isListFs());
obj.setShowFsTree(set.fs.show_fs_tree);
obj.setExtractJobs(set.fs.extract_jobs);
obj.process();
}
else if (user_set.getFileType() == FILE_META)
else if (set.infile.filetype == nstool::Settings::FILE_TYPE_META)
{
MetaProcess obj;
nstool::MetaProcess obj;
obj.setInputFile(inputFile);
obj.setKeyCfg(user_set.getKeyCfg());
obj.setCliOutputMode(user_set.getCliOutputMode());
obj.setVerifyMode(user_set.isVerifyFile());
obj.setInputFile(infile_stream);
obj.setKeyCfg(set.opt.keybag);
obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify);
obj.process();
}
else if (user_set.getFileType() == FILE_CNMT)
else if (set.infile.filetype == nstool::Settings::FILE_TYPE_CNMT)
{
CnmtProcess obj;
nstool::CnmtProcess obj;
obj.setInputFile(inputFile);
obj.setCliOutputMode(user_set.getCliOutputMode());
obj.setVerifyMode(user_set.isVerifyFile());
obj.setInputFile(infile_stream);
obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify);
obj.process();
}
else if (user_set.getFileType() == FILE_NSO)
else if (set.infile.filetype == nstool::Settings::FILE_TYPE_NSO)
{
NsoProcess obj;
nstool::NsoProcess obj;
obj.setInputFile(inputFile);
obj.setCliOutputMode(user_set.getCliOutputMode());
obj.setVerifyMode(user_set.isVerifyFile());
obj.setInputFile(infile_stream);
obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify);
obj.setIs64BitInstruction(user_set.getIs64BitInstruction());
obj.setListApi(user_set.isListApi());
obj.setListSymbols(user_set.isListSymbols());
obj.setIs64BitInstruction(set.code.is_64bit_instruction);
obj.setListApi(set.code.list_api);
obj.setListSymbols(set.code.list_symbols);
obj.process();
}
else if (user_set.getFileType() == FILE_NRO)
else if (set.infile.filetype == nstool::Settings::FILE_TYPE_NRO)
{
NroProcess obj;
nstool::NroProcess obj;
obj.setInputFile(inputFile);
obj.setCliOutputMode(user_set.getCliOutputMode());
obj.setVerifyMode(user_set.isVerifyFile());
obj.setInputFile(infile_stream);
obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify);
obj.setIs64BitInstruction(user_set.getIs64BitInstruction());
obj.setListApi(user_set.isListApi());
obj.setListSymbols(user_set.isListSymbols());
obj.setIs64BitInstruction(set.code.is_64bit_instruction);
obj.setListApi(set.code.list_api);
obj.setListSymbols(set.code.list_symbols);
if (user_set.getAssetIconPath().isSet)
obj.setAssetIconExtractPath(user_set.getAssetIconPath().var);
if (user_set.getAssetNacpPath().isSet)
obj.setAssetNacpExtractPath(user_set.getAssetNacpPath().var);
if (set.aset.icon_extract_path.isSet())
obj.setAssetIconExtractPath(set.aset.icon_extract_path.get());
if (set.aset.nacp_extract_path.isSet())
obj.setAssetNacpExtractPath(set.aset.nacp_extract_path.get());
if (user_set.getFsPath().isSet)
obj.setAssetRomfsExtractPath(user_set.getFsPath().var);
obj.setAssetListFs(user_set.isListFs());
obj.setAssetRomfsShowFsTree(set.fs.show_fs_tree);
obj.setAssetRomfsExtractJobs(set.fs.extract_jobs);
obj.process();
}
else if (user_set.getFileType() == FILE_NACP)
else if (set.infile.filetype == nstool::Settings::FILE_TYPE_NACP)
{
NacpProcess obj;
nstool::NacpProcess obj;
obj.setInputFile(inputFile);
obj.setCliOutputMode(user_set.getCliOutputMode());
obj.setVerifyMode(user_set.isVerifyFile());
obj.setInputFile(infile_stream);
obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify);
obj.process();
}
else if (user_set.getFileType() == FILE_INI)
else if (set.infile.filetype == nstool::Settings::FILE_TYPE_INI)
{
IniProcess obj;
nstool::IniProcess obj;
obj.setInputFile(inputFile);
obj.setCliOutputMode(user_set.getCliOutputMode());
obj.setVerifyMode(user_set.isVerifyFile());
obj.setInputFile(infile_stream);
obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify);
if (user_set.getKipExtractPath().isSet)
obj.setKipExtractPath(user_set.getKipExtractPath().var);
if (set.kip.extract_path.isSet())
obj.setKipExtractPath(set.kip.extract_path.get());
obj.process();
}
else if (user_set.getFileType() == FILE_KIP)
else if (set.infile.filetype == nstool::Settings::FILE_TYPE_KIP)
{
KipProcess obj;
nstool::KipProcess obj;
obj.setInputFile(inputFile);
obj.setCliOutputMode(user_set.getCliOutputMode());
obj.setVerifyMode(user_set.isVerifyFile());
obj.setInputFile(infile_stream);
obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify);
obj.process();
}
else if (user_set.getFileType() == FILE_PKI_CERT)
else if (set.infile.filetype == nstool::Settings::FILE_TYPE_ES_CERT)
{
PkiCertProcess obj;
nstool::EsCertProcess obj;
obj.setInputFile(inputFile);
obj.setKeyCfg(user_set.getKeyCfg());
obj.setCliOutputMode(user_set.getCliOutputMode());
obj.setVerifyMode(user_set.isVerifyFile());
obj.setInputFile(infile_stream);
obj.setKeyCfg(set.opt.keybag);
obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify);
obj.process();
}
else if (user_set.getFileType() == FILE_ES_TIK)
else if (set.infile.filetype == nstool::Settings::FILE_TYPE_ES_TIK)
{
EsTikProcess obj;
nstool::EsTikProcess obj;
obj.setInputFile(inputFile);
obj.setKeyCfg(user_set.getKeyCfg());
obj.setCertificateChain(user_set.getCertificateChain());
obj.setCliOutputMode(user_set.getCliOutputMode());
obj.setVerifyMode(user_set.isVerifyFile());
obj.setInputFile(infile_stream);
obj.setKeyCfg(set.opt.keybag);
//obj.setCertificateChain(user_set.getCertificateChain());
obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify);
obj.process();
}
else if (user_set.getFileType() == FILE_HB_ASSET)
else if (set.infile.filetype == nstool::Settings::FILE_TYPE_HB_ASSET)
{
AssetProcess obj;
nstool::AssetProcess obj;
obj.setInputFile(inputFile);
obj.setCliOutputMode(user_set.getCliOutputMode());
obj.setVerifyMode(user_set.isVerifyFile());
obj.setInputFile(infile_stream);
obj.setCliOutputMode(set.opt.cli_output_mode);
obj.setVerifyMode(set.opt.verify);
if (user_set.getAssetIconPath().isSet)
obj.setIconExtractPath(user_set.getAssetIconPath().var);
if (user_set.getAssetNacpPath().isSet)
obj.setNacpExtractPath(user_set.getAssetNacpPath().var);
if (set.aset.icon_extract_path.isSet())
obj.setIconExtractPath(set.aset.icon_extract_path.get());
if (set.aset.nacp_extract_path.isSet())
obj.setNacpExtractPath(set.aset.nacp_extract_path.get());
if (user_set.getFsPath().isSet)
obj.setRomfsExtractPath(user_set.getFsPath().var);
obj.setListFs(user_set.isListFs());
obj.setRomfsShowFsTree(set.fs.show_fs_tree);
obj.setRomfsExtractJobs(set.fs.extract_jobs);
obj.process();
}
else
{
throw fnd::Exception("main", "Unhandled file type");
}
}
catch (const fnd::Exception& e) {
printf("\n\n%s\n", e.what());
catch (tc::Exception& e)
{
fmt::print("[{0}{1}ERROR] {2}\n", e.module(), (strlen(e.module()) != 0 ? " ": ""), e.error());
return 1;
}
return 0;
}

32
src/types.h Normal file
View file

@ -0,0 +1,32 @@
#pragma once
#include <tc/types.h>
#include <tc/Exception.h>
#include <tc/Optional.h>
#include <tc/io.h>
#include <tc/io/IOUtil.h>
#include <tc/cli.h>
#include <fmt/core.h>
namespace nstool {
struct CliOutputMode
{
bool show_basic_info;
bool show_extended_info;
bool show_layout;
bool show_keydata;
CliOutputMode() : show_basic_info(false), show_extended_info(false), show_layout(false), show_keydata(false)
{}
CliOutputMode(bool show_basic_info, bool show_extended_info, bool show_layout, bool show_keydata) : show_basic_info(show_basic_info), show_extended_info(show_extended_info), show_layout(show_layout), show_keydata(show_keydata)
{}
};
struct ExtractJob {
tc::io::Path virtual_path;
tc::io::Path extract_path;
};
}

150
src/util.cpp Normal file
View file

@ -0,0 +1,150 @@
#include "util.h"
#include <tc/io/FileStream.h>
#include <tc/io/SubStream.h>
#include <tc/io/IOUtil.h>
#include <sstream>
#include <algorithm>
#include <iostream>
inline bool isNotPrintable(char chr) { return isprint(chr) == false; }
void nstool::processResFile(const std::shared_ptr<tc::io::IStream>& file, std::map<std::string, std::string>& dict)
{
if (file == nullptr || !file->canRead() || file->length() == 0)
{
return;
}
std::stringstream in_stream;
// populate string stream
tc::ByteData cache = tc::ByteData(0x1000);
file->seek(0, tc::io::SeekOrigin::Begin);
for (int64_t pos = 0; pos < file->length();)
{
size_t bytes_read = file->read(cache.data(), cache.size());
in_stream << std::string((char*)cache.data(), bytes_read);
pos += tc::io::IOUtil::castSizeToInt64(bytes_read);
}
// process stream
std::string line, key, value;
while (std::getline(in_stream, line))
{
// read up to comment line
if (line.find(";") != std::string::npos)
line = line.substr(0, line.find(";"));
// change chars to lower string
std::transform(line.begin(), line.end(), line.begin(), ::tolower);
// strip whitespace
line.erase(std::remove(line.begin(), line.end(), ' '), line.end());
// strip nonprintable
line.erase(std::remove_if(line.begin(), line.end(), isNotPrintable), line.end());
// skip lines that don't have '='
if (line.find("=") == std::string::npos)
continue;
key = line.substr(0,line.find("="));
value = line.substr(line.find("=")+1);
// skip if key or value is empty
if (key.empty() || value.empty())
continue;
//std::cout << "[" + key + "]=(" + value + ")" << std::endl;
dict[key] = value;
}
}
void nstool::writeSubStreamToFile(const std::shared_ptr<tc::io::IStream>& in_stream, int64_t offset, int64_t length, const tc::io::Path& out_path, tc::ByteData& cache)
{
writeStreamToStream(std::make_shared<tc::io::SubStream>(tc::io::SubStream(in_stream, offset, length)), std::make_shared<tc::io::FileStream>(tc::io::FileStream(out_path, tc::io::FileMode::Create, tc::io::FileAccess::Write)), cache);
}
void nstool::writeSubStreamToFile(const std::shared_ptr<tc::io::IStream>& in_stream, int64_t offset, int64_t length, const tc::io::Path& out_path, size_t cache_size)
{
writeStreamToStream(std::make_shared<tc::io::SubStream>(tc::io::SubStream(in_stream, offset, length)), std::make_shared<tc::io::FileStream>(tc::io::FileStream(out_path, tc::io::FileMode::Create, tc::io::FileAccess::Write)), cache_size);
}
void nstool::writeStreamToFile(const std::shared_ptr<tc::io::IStream>& in_stream, const tc::io::Path& out_path, tc::ByteData& cache)
{
writeStreamToStream(in_stream, std::make_shared<tc::io::FileStream>(tc::io::FileStream(out_path, tc::io::FileMode::Create, tc::io::FileAccess::Write)), cache);
}
void nstool::writeStreamToFile(const std::shared_ptr<tc::io::IStream>& in_stream, const tc::io::Path& out_path, size_t cache_size)
{
writeStreamToStream(in_stream, std::make_shared<tc::io::FileStream>(tc::io::FileStream(out_path, tc::io::FileMode::Create, tc::io::FileAccess::Write)), cache_size);
}
void nstool::writeStreamToStream(const std::shared_ptr<tc::io::IStream>& in_stream, const std::shared_ptr<tc::io::IStream>& out_stream, tc::ByteData& cache)
{
// iterate thru child files
size_t cache_read_len;
in_stream->seek(0, tc::io::SeekOrigin::Begin);
out_stream->seek(0, tc::io::SeekOrigin::Begin);
for (int64_t remaining_data = in_stream->length(); remaining_data > 0;)
{
cache_read_len = in_stream->read(cache.data(), cache.size());
if (cache_read_len == 0)
{
throw tc::io::IOException("nstool::writeStreamToStream()", "Failed to read from source streeam.");
}
out_stream->write(cache.data(), cache_read_len);
remaining_data -= int64_t(cache_read_len);
}
}
void nstool::writeStreamToStream(const std::shared_ptr<tc::io::IStream>& in_stream, const std::shared_ptr<tc::io::IStream>& out_stream, size_t cache_size)
{
tc::ByteData cache = tc::ByteData(cache_size);
writeStreamToStream(in_stream, out_stream, cache);
}
std::string nstool::getTruncatedBytesString(const byte_t* data, size_t len)
{
if (data == nullptr) { return fmt::format(""); }
std::string str = "";
if (len <= 8)
{
str = tc::cli::FormatUtil::formatBytesAsString(data, len, true, "");
}
else
{
str = fmt::format("{:02X}{:02X}{:02X}{:02X}...{:02X}{:02X}{:02X}{:02X}", data[0], data[1], data[2], data[3], data[len-4], data[len-3], data[len-2], data[len-1]);
}
return str;
}
std::string nstool::getTruncatedBytesString(const byte_t* data, size_t len, bool do_not_truncate)
{
if (data == nullptr) { return fmt::format(""); }
std::string str = "";
if (len <= 8 || do_not_truncate)
{
str = tc::cli::FormatUtil::formatBytesAsString(data, len, true, "");
}
else
{
str = fmt::format("{:02X}{:02X}{:02X}{:02X}...{:02X}{:02X}{:02X}{:02X}", data[0], data[1], data[2], data[3], data[len-4], data[len-3], data[len-2], data[len-1]);
}
return str;
}

20
src/util.h Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include "types.h"
namespace nstool
{
void processResFile(const std::shared_ptr<tc::io::IStream>& file, std::map<std::string, std::string>& dict);
void writeSubStreamToFile(const std::shared_ptr<tc::io::IStream>& in_stream, int64_t offset, int64_t length, const tc::io::Path& out_path, tc::ByteData& cache);
void writeSubStreamToFile(const std::shared_ptr<tc::io::IStream>& in_stream, int64_t offset, int64_t length, const tc::io::Path& out_path, size_t cache_size = 0x10000);
void writeStreamToFile(const std::shared_ptr<tc::io::IStream>& in_stream, const tc::io::Path& out_path, tc::ByteData& cache);
void writeStreamToFile(const std::shared_ptr<tc::io::IStream>& in_stream, const tc::io::Path& out_path, size_t cache_size = 0x10000);
void writeStreamToStream(const std::shared_ptr<tc::io::IStream>& in_stream, const std::shared_ptr<tc::io::IStream>& out_stream, tc::ByteData& cache);
void writeStreamToStream(const std::shared_ptr<tc::io::IStream>& in_stream, const std::shared_ptr<tc::io::IStream>& out_stream, size_t cache_size = 0x10000);
std::string getTruncatedBytesString(const byte_t* data, size_t len);
std::string getTruncatedBytesString(const byte_t* data, size_t len, bool do_not_truncate);
}

View file

@ -2,6 +2,6 @@
#define APP_NAME "NSTool"
#define BIN_NAME "nstool"
#define VER_MAJOR 1
#define VER_MINOR 4
#define VER_PATCH 1
#define VER_MINOR 6
#define VER_PATCH 0
#define AUTHORS "jakcron"