mirror of
https://github.com/jakcron/nstool
synced 2024-11-15 02:06:40 +00:00
Merge pull request #73 from jakcron/v1.6-stable
Update NSTool to v1.6.0
This commit is contained in:
commit
31f67325f7
75 changed files with 6670 additions and 5654 deletions
23
.github/workflows/build_master.yml
vendored
23
.github/workflows/build_master.yml
vendored
|
@ -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
15
.gitmodules
vendored
|
@ -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
|
||||
|
|
|
@ -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
202
README.md
|
@ -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.
|
||||
|
||||
|
|
163
SWITCH_KEYS.md
163
SWITCH_KEYS.md
|
@ -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
|
|
@ -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}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
1
deps/libfmt
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 53d084cc0c6ea61bbb535a873299b0ae5ff9a05d
|
1
deps/libfnd
vendored
1
deps/libfnd
vendored
|
@ -1 +0,0 @@
|
|||
Subproject commit 19c1683060a8b39b737da8505b5e23660ed86282
|
2
deps/liblz4
vendored
2
deps/liblz4
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 0e5a8c29295a9046fff4ad5371a8ea682c7e0cb3
|
||||
Subproject commit b40b46406e87a753328abfda3b53dfabd2408da2
|
2
deps/libmbedtls
vendored
2
deps/libmbedtls
vendored
|
@ -1 +1 @@
|
|||
Subproject commit bc43e5e079529455749d81d1a3b77a9574d5ab01
|
||||
Subproject commit e9e56bb773111f831af8dd36ad74de73b0c31aa2
|
2
deps/libnintendo-es
vendored
2
deps/libnintendo-es
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 9e3f1ea763be033f60b3b2db0b2d6e2aac462a37
|
||||
Subproject commit af4d79ad2aca410ce2d451456023b0a03904ceeb
|
2
deps/libnintendo-hac
vendored
2
deps/libnintendo-hac
vendored
|
@ -1 +1 @@
|
|||
Subproject commit afbbe3900d4c0dab6b3c4cd06927aff227cc1f95
|
||||
Subproject commit e7d93cea7c7bac93661c9ef6500279db19c264a0
|
2
deps/libnintendo-hac-hb
vendored
2
deps/libnintendo-hac-hb
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 95fb4d7762eb5b395fa5023fd3a9b6f34151505a
|
||||
Subproject commit 42abe9b9a90ef05ea13df6caf320ac4f50e34537
|
2
deps/libnintendo-pki
vendored
2
deps/libnintendo-pki
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 5097871222f6e2cd07b7b8e8b58551e913eb1c15
|
||||
Subproject commit 11b99025b11862b0828a186bd462b0097e341da7
|
1
deps/libtoolchain
vendored
Submodule
1
deps/libtoolchain
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 5966167aa5065c0e582bfbca151e28db580e972f
|
52
makefile
52
makefile
|
@ -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)";)
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
}
|
|
@ -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 + " ");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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
228
src/EsCertProcess.cpp
Normal 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
42
src/EsCertProcess.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
|
@ -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
275
src/FsProcess.cpp
Normal 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
50
src/FsProcess.h
Normal 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);
|
||||
};
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
};
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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
1014
src/KeyBag.cpp
Normal file
File diff suppressed because it is too large
Load diff
85
src/KeyBag.h
Normal file
85
src/KeyBag.h
Normal 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);
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
};
|
|
@ -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));
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
};
|
||||
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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");
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
1071
src/Settings.cpp
Normal file
File diff suppressed because it is too large
Load diff
148
src/Settings.h
Normal file
148
src/Settings.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
1086
src/UserSettings.cpp
1086
src/UserSettings.cpp
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||
};
|
62
src/common.h
62
src/common.h
|
@ -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
458
src/elf.h
Normal 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 */
|
||||
};
|
||||
}
|
274
src/main.cpp
274
src/main.cpp
|
@ -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
32
src/types.h
Normal 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
150
src/util.cpp
Normal 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
20
src/util.h
Normal 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);
|
||||
|
||||
}
|
|
@ -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"
|
Loading…
Reference in a new issue