diff --git a/.github/workflows/build_master.yml b/.github/workflows/build_master.yml index 2cf9ff8..ff9020d 100644 --- a/.github/workflows/build_master.yml +++ b/.github/workflows/build_master.yml @@ -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 diff --git a/.gitmodules b/.gitmodules index 6775067..d7defa6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/BUILDING.md b/BUILDING.md index ba5df5c..2d94f09 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -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 diff --git a/README.md b/README.md index 38cf29b..38ee11c 100644 --- a/README.md +++ b/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... ] - - 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 --logo --normal --secure ] <.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 ] - --listfs Print file system. - --fsdir Extract file system to directory. - - NCA (Nintendo Content Archive) - nstool [--listfs] [--bodykey --titlekey ] [--part0 ...] <.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 ] - --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 ] - --kipdir Extract embedded KIPs to directory. - - ASET (Homebrew Asset Blob) - nstool [--listfs] [--icon --nacp --fsdir ] - --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. diff --git a/SWITCH_KEYS.md b/SWITCH_KEYS.md index 2bb3efd..1834679 100644 --- a/SWITCH_KEYS.md +++ b/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_## -``` \ No newline at end of file +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 \ No newline at end of file diff --git a/build/visualstudio/nstool.sln b/build/visualstudio/nstool.sln index ad181a1..8180974 100644 --- a/build/visualstudio/nstool.sln +++ b/build/visualstudio/nstool.sln @@ -1,129 +1,126 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28010.2036 -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 -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {775EF5EB-CA49-4994-8AC4-47B4A5385266}.Debug|x64.ActiveCfg = Debug|x64 - {775EF5EB-CA49-4994-8AC4-47B4A5385266}.Debug|x64.Build.0 = Debug|x64 - {775EF5EB-CA49-4994-8AC4-47B4A5385266}.Debug|x86.ActiveCfg = Debug|Win32 - {775EF5EB-CA49-4994-8AC4-47B4A5385266}.Debug|x86.Build.0 = Debug|Win32 - {775EF5EB-CA49-4994-8AC4-47B4A5385266}.Release|x64.ActiveCfg = Release|x64 - {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 - {E741ADED-7900-4E07-8DB0-D008C336C3FB}.Debug|x86.Build.0 = Debug|Win32 - {E741ADED-7900-4E07-8DB0-D008C336C3FB}.Release|x64.ActiveCfg = Release|x64 - {E741ADED-7900-4E07-8DB0-D008C336C3FB}.Release|x64.Build.0 = Release|x64 - {E741ADED-7900-4E07-8DB0-D008C336C3FB}.Release|x86.ActiveCfg = Release|Win32 - {E741ADED-7900-4E07-8DB0-D008C336C3FB}.Release|x86.Build.0 = Release|Win32 - {8616D6C9-C8DE-4C3F-AFC2-625636664C2B}.Debug|x64.ActiveCfg = Debug|x64 - {8616D6C9-C8DE-4C3F-AFC2-625636664C2B}.Debug|x64.Build.0 = Debug|x64 - {8616D6C9-C8DE-4C3F-AFC2-625636664C2B}.Debug|x86.ActiveCfg = Debug|Win32 - {8616D6C9-C8DE-4C3F-AFC2-625636664C2B}.Debug|x86.Build.0 = Debug|Win32 - {8616D6C9-C8DE-4C3F-AFC2-625636664C2B}.Release|x64.ActiveCfg = Release|x64 - {8616D6C9-C8DE-4C3F-AFC2-625636664C2B}.Release|x64.Build.0 = Release|x64 - {8616D6C9-C8DE-4C3F-AFC2-625636664C2B}.Release|x86.ActiveCfg = Release|Win32 - {8616D6C9-C8DE-4C3F-AFC2-625636664C2B}.Release|x86.Build.0 = Release|Win32 - {8885C125-83FB-4F73-A93A-C712B1434D54}.Debug|x64.ActiveCfg = Debug|x64 - {8885C125-83FB-4F73-A93A-C712B1434D54}.Debug|x64.Build.0 = Debug|x64 - {8885C125-83FB-4F73-A93A-C712B1434D54}.Debug|x86.ActiveCfg = Debug|Win32 - {8885C125-83FB-4F73-A93A-C712B1434D54}.Debug|x86.Build.0 = Debug|Win32 - {8885C125-83FB-4F73-A93A-C712B1434D54}.Release|x64.ActiveCfg = Release|x64 - {8885C125-83FB-4F73-A93A-C712B1434D54}.Release|x64.Build.0 = Release|x64 - {8885C125-83FB-4F73-A93A-C712B1434D54}.Release|x86.ActiveCfg = Release|Win32 - {8885C125-83FB-4F73-A93A-C712B1434D54}.Release|x86.Build.0 = Release|Win32 - {24D001B4-D439-4967-9371-DC3E0523EB19}.Debug|x64.ActiveCfg = Debug|x64 - {24D001B4-D439-4967-9371-DC3E0523EB19}.Debug|x64.Build.0 = Debug|x64 - {24D001B4-D439-4967-9371-DC3E0523EB19}.Debug|x86.ActiveCfg = Debug|Win32 - {24D001B4-D439-4967-9371-DC3E0523EB19}.Debug|x86.Build.0 = Debug|Win32 - {24D001B4-D439-4967-9371-DC3E0523EB19}.Release|x64.ActiveCfg = Release|x64 - {24D001B4-D439-4967-9371-DC3E0523EB19}.Release|x64.Build.0 = Release|x64 - {24D001B4-D439-4967-9371-DC3E0523EB19}.Release|x86.ActiveCfg = Release|Win32 - {24D001B4-D439-4967-9371-DC3E0523EB19}.Release|x86.Build.0 = Release|Win32 - {0BEF63A0-2801-4563-AB65-1E2FD881C3AF}.Debug|x64.ActiveCfg = Debug|x64 - {0BEF63A0-2801-4563-AB65-1E2FD881C3AF}.Debug|x64.Build.0 = Debug|x64 - {0BEF63A0-2801-4563-AB65-1E2FD881C3AF}.Debug|x86.ActiveCfg = Debug|Win32 - {0BEF63A0-2801-4563-AB65-1E2FD881C3AF}.Debug|x86.Build.0 = Debug|Win32 - {0BEF63A0-2801-4563-AB65-1E2FD881C3AF}.Release|x64.ActiveCfg = Release|x64 - {0BEF63A0-2801-4563-AB65-1E2FD881C3AF}.Release|x64.Build.0 = Release|x64 - {0BEF63A0-2801-4563-AB65-1E2FD881C3AF}.Release|x86.ActiveCfg = Release|Win32 - {0BEF63A0-2801-4563-AB65-1E2FD881C3AF}.Release|x86.Build.0 = Release|Win32 - {7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Debug|x64.ActiveCfg = Debug|x64 - {7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Debug|x64.Build.0 = Debug|x64 - {7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Debug|x86.ActiveCfg = Debug|Win32 - {7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Debug|x86.Build.0 = Debug|Win32 - {7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Release|x64.ActiveCfg = Release|x64 - {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 - 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} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {ABDCFB40-D6B3-44A9-92B5-0D7AB38D9FB8} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# 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}") = "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}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libnintendo-hac", "..\..\deps\libnintendo-hac\build\visualstudio\libnintendo-hac\libnintendo-hac.vcxproj", "{8885C125-83FB-4F73-A93A-C712B1434D54}" +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 + {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}" +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 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {775EF5EB-CA49-4994-8AC4-47B4A5385266}.Debug|x64.ActiveCfg = Debug|x64 + {775EF5EB-CA49-4994-8AC4-47B4A5385266}.Debug|x64.Build.0 = Debug|x64 + {775EF5EB-CA49-4994-8AC4-47B4A5385266}.Debug|x86.ActiveCfg = Debug|Win32 + {775EF5EB-CA49-4994-8AC4-47B4A5385266}.Debug|x86.Build.0 = Debug|Win32 + {775EF5EB-CA49-4994-8AC4-47B4A5385266}.Release|x64.ActiveCfg = Release|x64 + {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 + {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 + {E741ADED-7900-4E07-8DB0-D008C336C3FB}.Debug|x86.Build.0 = Debug|Win32 + {E741ADED-7900-4E07-8DB0-D008C336C3FB}.Release|x64.ActiveCfg = Release|x64 + {E741ADED-7900-4E07-8DB0-D008C336C3FB}.Release|x64.Build.0 = Release|x64 + {E741ADED-7900-4E07-8DB0-D008C336C3FB}.Release|x86.ActiveCfg = Release|Win32 + {E741ADED-7900-4E07-8DB0-D008C336C3FB}.Release|x86.Build.0 = Release|Win32 + {8616D6C9-C8DE-4C3F-AFC2-625636664C2B}.Debug|x64.ActiveCfg = Debug|x64 + {8616D6C9-C8DE-4C3F-AFC2-625636664C2B}.Debug|x64.Build.0 = Debug|x64 + {8616D6C9-C8DE-4C3F-AFC2-625636664C2B}.Debug|x86.ActiveCfg = Debug|Win32 + {8616D6C9-C8DE-4C3F-AFC2-625636664C2B}.Debug|x86.Build.0 = Debug|Win32 + {8616D6C9-C8DE-4C3F-AFC2-625636664C2B}.Release|x64.ActiveCfg = Release|x64 + {8616D6C9-C8DE-4C3F-AFC2-625636664C2B}.Release|x64.Build.0 = Release|x64 + {8616D6C9-C8DE-4C3F-AFC2-625636664C2B}.Release|x86.ActiveCfg = Release|Win32 + {8616D6C9-C8DE-4C3F-AFC2-625636664C2B}.Release|x86.Build.0 = Release|Win32 + {8885C125-83FB-4F73-A93A-C712B1434D54}.Debug|x64.ActiveCfg = Debug|x64 + {8885C125-83FB-4F73-A93A-C712B1434D54}.Debug|x64.Build.0 = Debug|x64 + {8885C125-83FB-4F73-A93A-C712B1434D54}.Debug|x86.ActiveCfg = Debug|Win32 + {8885C125-83FB-4F73-A93A-C712B1434D54}.Debug|x86.Build.0 = Debug|Win32 + {8885C125-83FB-4F73-A93A-C712B1434D54}.Release|x64.ActiveCfg = Release|x64 + {8885C125-83FB-4F73-A93A-C712B1434D54}.Release|x64.Build.0 = Release|x64 + {8885C125-83FB-4F73-A93A-C712B1434D54}.Release|x86.ActiveCfg = Release|Win32 + {8885C125-83FB-4F73-A93A-C712B1434D54}.Release|x86.Build.0 = Release|Win32 + {24D001B4-D439-4967-9371-DC3E0523EB19}.Debug|x64.ActiveCfg = Debug|x64 + {24D001B4-D439-4967-9371-DC3E0523EB19}.Debug|x64.Build.0 = Debug|x64 + {24D001B4-D439-4967-9371-DC3E0523EB19}.Debug|x86.ActiveCfg = Debug|Win32 + {24D001B4-D439-4967-9371-DC3E0523EB19}.Debug|x86.Build.0 = Debug|Win32 + {24D001B4-D439-4967-9371-DC3E0523EB19}.Release|x64.ActiveCfg = Release|x64 + {24D001B4-D439-4967-9371-DC3E0523EB19}.Release|x64.Build.0 = Release|x64 + {24D001B4-D439-4967-9371-DC3E0523EB19}.Release|x86.ActiveCfg = Release|Win32 + {24D001B4-D439-4967-9371-DC3E0523EB19}.Release|x86.Build.0 = Release|Win32 + {0BEF63A0-2801-4563-AB65-1E2FD881C3AF}.Debug|x64.ActiveCfg = Debug|x64 + {0BEF63A0-2801-4563-AB65-1E2FD881C3AF}.Debug|x64.Build.0 = Debug|x64 + {0BEF63A0-2801-4563-AB65-1E2FD881C3AF}.Debug|x86.ActiveCfg = Debug|Win32 + {0BEF63A0-2801-4563-AB65-1E2FD881C3AF}.Debug|x86.Build.0 = Debug|Win32 + {0BEF63A0-2801-4563-AB65-1E2FD881C3AF}.Release|x64.ActiveCfg = Release|x64 + {0BEF63A0-2801-4563-AB65-1E2FD881C3AF}.Release|x64.Build.0 = Release|x64 + {0BEF63A0-2801-4563-AB65-1E2FD881C3AF}.Release|x86.ActiveCfg = Release|Win32 + {0BEF63A0-2801-4563-AB65-1E2FD881C3AF}.Release|x86.Build.0 = Release|Win32 + {7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Debug|x64.ActiveCfg = Debug|x64 + {7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Debug|x64.Build.0 = Debug|x64 + {7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Debug|x86.ActiveCfg = Debug|Win32 + {7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Debug|x86.Build.0 = Debug|Win32 + {7A7C66F3-2B5B-4E23-85D8-2A74FEDAD92C}.Release|x64.ActiveCfg = Release|x64 + {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 + {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} + EndGlobalSection +EndGlobal diff --git a/build/visualstudio/nstool/nstool.vcxproj b/build/visualstudio/nstool/nstool.vcxproj index f56dfbe..e5e67e1 100644 --- a/build/visualstudio/nstool/nstool.vcxproj +++ b/build/visualstudio/nstool/nstool.vcxproj @@ -1,196 +1,206 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {775EF5EB-CA49-4994-8AC4-47B4A5385266} - nstool - 10.0.17763.0 - - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - - - - - - - - - - - - - - - - - - - - - Level3 - Disabled - true - true - $(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 - - - - - Level3 - Disabled - true - true - $(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 - - - - - Level3 - MaxSpeed - true - true - true - true - $(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 - - - true - true - - - - - Level3 - MaxSpeed - true - true - true - true - $(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 - - - true - true - - - - - {4e578016-34ba-4a1e-b8ec-37a48780b6ca} - - - {e741aded-7900-4e07-8db0-d008c336c3fb} - - - {8616d6c9-c8de-4c3f-afc2-625636664c2b} - - - {24d001b4-d439-4967-9371-dc3e0523eb19} - - - {8885c125-83fb-4f73-a93a-c712b1434d54} - - - {0bef63a0-2801-4563-ab65-1e2fd881c3af} - - - {7a7c66f3-2b5b-4e23-85d8-2a74fedad92c} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {775EF5EB-CA49-4994-8AC4-47B4A5385266} + nstool + 10.0 + + + + Application + true + v142 + MultiByte + + + Application + false + v142 + true + MultiByte + + + Application + true + v142 + MultiByte + + + Application + false + v142 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + true + $(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\liblz4\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include + MultiThreadedDebug + + + + + Level3 + Disabled + true + true + $(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\liblz4\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include + MultiThreadedDebug + + + + + Level3 + MaxSpeed + true + true + true + true + $(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\liblz4\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include + MultiThreaded + + + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + $(ProjectDir)..\..\..\include;$(SolutionDir)..\..\deps\libfnd\include;$(SolutionDir)..\..\deps\liblz4\include;$(SolutionDir)..\..\deps\libtoolchain\include;$(SolutionDir)..\..\deps\libfmt\include;$(SolutionDir)..\..\deps\libnintendo-es\include;$(SolutionDir)..\..\deps\libnintendo-pki\include;$(SolutionDir)..\..\deps\libnintendo-hac\include;$(SolutionDir)..\..\deps\libnintendo-hac-hb\include + MultiThreaded + + + true + true + + + + + {f4b0540e-0aae-4006-944b-356944ef61fa} + + + {e741aded-7900-4e07-8db0-d008c336c3fb} + + + {8616d6c9-c8de-4c3f-afc2-625636664c2b} + + + {24d001b4-d439-4967-9371-dc3e0523eb19} + + + {8885c125-83fb-4f73-a93a-c712b1434d54} + + + {0bef63a0-2801-4563-ab65-1e2fd881c3af} + + + {7a7c66f3-2b5b-4e23-85d8-2a74fedad92c} + + + {e194e4b8-1482-40a2-901b-75d4387822e9} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/visualstudio/nstool/nstool.vcxproj.filters b/build/visualstudio/nstool/nstool.vcxproj.filters index 82a1d26..9af3240 100644 --- a/build/visualstudio/nstool/nstool.vcxproj.filters +++ b/build/visualstudio/nstool/nstool.vcxproj.filters @@ -1,156 +1,165 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + \ No newline at end of file diff --git a/deps/libfmt b/deps/libfmt new file mode 160000 index 0000000..53d084c --- /dev/null +++ b/deps/libfmt @@ -0,0 +1 @@ +Subproject commit 53d084cc0c6ea61bbb535a873299b0ae5ff9a05d diff --git a/deps/libfnd b/deps/libfnd deleted file mode 160000 index 19c1683..0000000 --- a/deps/libfnd +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 19c1683060a8b39b737da8505b5e23660ed86282 diff --git a/deps/liblz4 b/deps/liblz4 index 0e5a8c2..b40b464 160000 --- a/deps/liblz4 +++ b/deps/liblz4 @@ -1 +1 @@ -Subproject commit 0e5a8c29295a9046fff4ad5371a8ea682c7e0cb3 +Subproject commit b40b46406e87a753328abfda3b53dfabd2408da2 diff --git a/deps/libmbedtls b/deps/libmbedtls index bc43e5e..e9e56bb 160000 --- a/deps/libmbedtls +++ b/deps/libmbedtls @@ -1 +1 @@ -Subproject commit bc43e5e079529455749d81d1a3b77a9574d5ab01 +Subproject commit e9e56bb773111f831af8dd36ad74de73b0c31aa2 diff --git a/deps/libnintendo-es b/deps/libnintendo-es index 9e3f1ea..af4d79a 160000 --- a/deps/libnintendo-es +++ b/deps/libnintendo-es @@ -1 +1 @@ -Subproject commit 9e3f1ea763be033f60b3b2db0b2d6e2aac462a37 +Subproject commit af4d79ad2aca410ce2d451456023b0a03904ceeb diff --git a/deps/libnintendo-hac b/deps/libnintendo-hac index afbbe39..e7d93ce 160000 --- a/deps/libnintendo-hac +++ b/deps/libnintendo-hac @@ -1 +1 @@ -Subproject commit afbbe3900d4c0dab6b3c4cd06927aff227cc1f95 +Subproject commit e7d93cea7c7bac93661c9ef6500279db19c264a0 diff --git a/deps/libnintendo-hac-hb b/deps/libnintendo-hac-hb index 95fb4d7..42abe9b 160000 --- a/deps/libnintendo-hac-hb +++ b/deps/libnintendo-hac-hb @@ -1 +1 @@ -Subproject commit 95fb4d7762eb5b395fa5023fd3a9b6f34151505a +Subproject commit 42abe9b9a90ef05ea13df6caf320ac4f50e34537 diff --git a/deps/libnintendo-pki b/deps/libnintendo-pki index 5097871..11b9902 160000 --- a/deps/libnintendo-pki +++ b/deps/libnintendo-pki @@ -1 +1 @@ -Subproject commit 5097871222f6e2cd07b7b8e8b58551e913eb1c15 +Subproject commit 11b99025b11862b0828a186bd462b0097e341da7 diff --git a/deps/libtoolchain b/deps/libtoolchain new file mode 160000 index 0000000..5966167 --- /dev/null +++ b/deps/libtoolchain @@ -0,0 +1 @@ +Subproject commit 5966167aa5065c0e582bfbca151e28db580e972f diff --git a/makefile b/makefile index 4f4ca61..33eb3d2 100644 --- a/makefile +++ b/makefile @@ -1,6 +1,6 @@ # C++/C Recursive Project Makefile # (c) Jack -# Version 3 +# Version 6 (20211110) # Project Name PROJECT_NAME = nstool @@ -13,7 +13,7 @@ PROJECT_SRC_SUBDIRS = $(PROJECT_SRC_PATH) #PROJECT_TESTSRC_PATH = test #PROJECT_TESTSRC_SUBDIRS = $(PROJECT_TESTSRC_PATH) PROJECT_BIN_PATH = bin -#PROJECT_DOCS_PATH = docs +#PROJECT_DOCS_PATH = docs #PROJECT_DOXYFILE_PATH = Doxyfile # Determine if the root makefile has been established, and if not establish this makefile as the root makefile @@ -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)";) \ No newline at end of file + @$(foreach lib,$(PROJECT_DEPEND_LOCAL_DIR), cd "$(ROOT_PROJECT_DEPENDENCY_PATH)/$(lib)" && $(MAKE) clean && cd "$(PROJECT_PATH)";) \ No newline at end of file diff --git a/src/AssetProcess.cpp b/src/AssetProcess.cpp index fcca0ce..6f52bde 100644 --- a/src/AssetProcess.cpp +++ b/src/AssetProcess.cpp @@ -1,115 +1,108 @@ -#include -#include -#include -#include -#include #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& file) +void nstool::AssetProcess::setInputFile(const std::shared_ptr& 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& extract_jobs) { - fnd::Vec 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 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 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(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(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); } } diff --git a/src/AssetProcess.h b/src/AssetProcess.h index 7364d15..2ce3d0a 100644 --- a/src/AssetProcess.h +++ b/src/AssetProcess.h @@ -1,13 +1,11 @@ #pragma once -#include -#include -#include -#include -#include +#include "types.h" #include "NacpProcess.h" #include "RomfsProcess.h" -#include "common.h" +#include + +namespace nstool { class AssetProcess { @@ -16,26 +14,24 @@ public: void process(); - void setInputFile(const fnd::SharedPtr& file); + void setInputFile(const std::shared_ptr& 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& extract_jobs); private: - const std::string kModuleName = "AssetProcess"; + std::string mModuleName; - fnd::SharedPtr mFile; + std::shared_ptr mFile; CliOutputMode mCliOutputMode; bool mVerify; - sOptional mIconExtractPath; - sOptional mNacpExtractPath; + tc::Optional mIconExtractPath; + tc::Optional mNacpExtractPath; nn::hac::AssetHeader mHdr; NacpProcess mNacp; @@ -44,4 +40,6 @@ private: void importHeader(); void processSections(); void displayHeader(); -}; \ No newline at end of file +}; + +} \ No newline at end of file diff --git a/src/CnmtProcess.cpp b/src/CnmtProcess.cpp index 8e8c4ed..c8c6039 100644 --- a/src/CnmtProcess.cpp +++ b/src/CnmtProcess.cpp @@ -1,140 +1,144 @@ #include "CnmtProcess.h" -#include -#include - -#include -#include - #include -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& file) +void nstool::CnmtProcess::setInputFile(const std::shared_ptr& 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 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 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."); } - scratch.alloc((*mFile)->size()); - (*mFile)->read(scratch.data(), 0, scratch.size()); + // 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 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 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& content_meta_info_list, const std::string& prefix) +void nstool::CnmtProcess::displayContentMetaInfoList(const std::vector& 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 + " "); + } } \ No newline at end of file diff --git a/src/CnmtProcess.h b/src/CnmtProcess.h index 2353f68..ed38187 100644 --- a/src/CnmtProcess.h +++ b/src/CnmtProcess.h @@ -1,11 +1,9 @@ #pragma once -#include -#include -#include -#include +#include "types.h" + #include -#include "common.h" +namespace nstool { class CnmtProcess { @@ -14,16 +12,15 @@ public: void process(); - void setInputFile(const fnd::SharedPtr& file); + void setInputFile(const std::shared_ptr& 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 mFile; + std::shared_ptr mFile; CliOutputMode mCliOutputMode; bool mVerify; @@ -34,4 +31,6 @@ private: void displayContentMetaInfo(const nn::hac::ContentMetaInfo& content_meta_info, const std::string& prefix); void displayContentMetaInfoList(const std::vector& content_meta_info_list, const std::string& prefix); -}; \ No newline at end of file +}; + +} \ No newline at end of file diff --git a/src/CompressedArchiveIFile.cpp b/src/CompressedArchiveIFile.cpp deleted file mode 100644 index 4467bfc..0000000 --- a/src/CompressedArchiveIFile.cpp +++ /dev/null @@ -1,195 +0,0 @@ -#include "CompressedArchiveIFile.h" -#include - -#include - -CompressedArchiveIFile::CompressedArchiveIFile(const fnd::SharedPtr& base_file, size_t compression_meta_offset) : - mFile(base_file), - mCompEntries(), - mLogicalFileSize(0), - mCacheCapacity(nn::hac::compression::kRomfsBlockSize), - mCurrentCacheDataSize(0), - mCache(std::shared_ptr(new byte_t[mCacheCapacity])), - mScratch(std::shared_ptr(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 entries_raw = std::shared_ptr(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(offset, mLogicalFileSize); -} - -void CompressedArchiveIFile::read(byte_t* out, size_t len) -{ - // limit len to the end of the logical file - len = std::min(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(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; -} \ No newline at end of file diff --git a/src/CompressedArchiveIFile.h b/src/CompressedArchiveIFile.h deleted file mode 100644 index 795bb49..0000000 --- a/src/CompressedArchiveIFile.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include - -class CompressedArchiveIFile : public fnd::IFile -{ -public: - CompressedArchiveIFile(const fnd::SharedPtr& 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 mFile; - - // compression metadata - std::vector 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 mCache; // where decompressed data resides - std::shared_ptr 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); -}; \ No newline at end of file diff --git a/src/ElfSymbolParser.cpp b/src/ElfSymbolParser.cpp index b57c9f2..d4dfc3a 100644 --- a/src/ElfSymbolParser.cpp +++ b/src/ElfSymbolParser.cpp @@ -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::getSymbolList() const +const std::vector& nstool::ElfSymbolParser::getSymbolList() const { return mSymbolList; } \ No newline at end of file diff --git a/src/ElfSymbolParser.h b/src/ElfSymbolParser.h index e06bdf3..f2ad56d 100644 --- a/src/ElfSymbolParser.h +++ b/src/ElfSymbolParser.h @@ -1,7 +1,8 @@ #pragma once -#include -#include -#include +#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& getSymbolList() const; + const std::vector& getSymbolList() const; private: - const std::string kModuleName = "ElfSymbolParser"; + std::string mModuleName; // data - fnd::List mSymbolList; -}; \ No newline at end of file + std::vector mSymbolList; +}; + +} \ No newline at end of file diff --git a/src/EsCertProcess.cpp b/src/EsCertProcess.cpp new file mode 100644 index 0000000..8671003 --- /dev/null +++ b/src/EsCertProcess.cpp @@ -0,0 +1,228 @@ +#include "EsCertProcess.h" +#include "PkiValidator.h" +#include "util.h" + +#include + +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& 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 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& 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; +} \ No newline at end of file diff --git a/src/EsCertProcess.h b/src/EsCertProcess.h new file mode 100644 index 0000000..ad5b062 --- /dev/null +++ b/src/EsCertProcess.h @@ -0,0 +1,42 @@ +#pragma once +#include "types.h" +#include "KeyBag.h" + +#include +#include + +namespace nstool { + +class EsCertProcess +{ +public: + EsCertProcess(); + + void process(); + + void setInputFile(const std::shared_ptr& file); + void setKeyCfg(const KeyBag& keycfg); + void setCliOutputMode(CliOutputMode type); + void setVerifyMode(bool verify); + +private: + std::string mModuleName; + + std::shared_ptr mFile; + KeyBag mKeyCfg; + CliOutputMode mCliOutputMode; + bool mVerify; + + std::vector> mCert; + + void importCerts(); + void validateCerts(); + void displayCerts(); + void displayCert(const nn::pki::SignedData& 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; +}; + +} \ No newline at end of file diff --git a/src/EsTikProcess.cpp b/src/EsTikProcess.cpp index 41da96c..575fb22 100644 --- a/src/EsTikProcess.cpp +++ b/src/EsTikProcess.cpp @@ -1,85 +1,92 @@ -#include -#include -#include -#include -#include #include "EsTikProcess.h" #include "PkiValidator.h" +#include - -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& file) +void nstool::EsTikProcess::setInputFile(const std::shared_ptr& file) { mFile = file; } -void EsTikProcess::setKeyCfg(const KeyConfiguration& keycfg) +void nstool::EsTikProcess::setKeyCfg(const KeyBag& keycfg) { mKeyCfg = keycfg; } -void EsTikProcess::setCertificateChain(const fnd::List>& certs) +void nstool::EsTikProcess::setCertificateChain(const std::vector>& 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 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 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: " << std::endl; + fmt::print(" Data: \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*)&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)); } \ No newline at end of file diff --git a/src/EsTikProcess.h b/src/EsTikProcess.h index cf3e36e..1af1c30 100644 --- a/src/EsTikProcess.h +++ b/src/EsTikProcess.h @@ -1,14 +1,12 @@ #pragma once -#include -#include -#include -#include -#include +#include "types.h" +#include "KeyBag.h" + #include #include #include -#include "KeyConfiguration.h" -#include "common.h" + +namespace nstool { class EsTikProcess { @@ -17,29 +15,31 @@ public: void process(); - void setInputFile(const fnd::SharedPtr& file); - void setKeyCfg(const KeyConfiguration& keycfg); - void setCertificateChain(const fnd::List>& certs); + void setInputFile(const std::shared_ptr& file); + void setKeyCfg(const KeyBag& keycfg); + void setCertificateChain(const std::vector>& certs); void setCliOutputMode(CliOutputMode mode); void setVerifyMode(bool verify); - private: - const std::string kModuleName = "EsTikProcess"; + std::string mModuleName; - fnd::SharedPtr mFile; - KeyConfiguration mKeyCfg; + std::shared_ptr mFile; + KeyBag mKeyCfg; CliOutputMode mCliOutputMode; bool mVerify; - fnd::List> mCerts; + std::vector> mCerts; nn::pki::SignedData 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; -}; \ No newline at end of file + 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; +}; + +} \ No newline at end of file diff --git a/src/FsProcess.cpp b/src/FsProcess.cpp new file mode 100644 index 0000000..13da38c --- /dev/null +++ b/src/FsProcess.cpp @@ -0,0 +1,275 @@ +#include "FsProcess.h" +#include "util.h" + +#include +#include +#include + +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& 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& 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& 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 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 local_fs = std::make_shared(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 in_stream; + std::shared_ptr 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); + } +} \ No newline at end of file diff --git a/src/FsProcess.h b/src/FsProcess.h new file mode 100644 index 0000000..70fc909 --- /dev/null +++ b/src/FsProcess.h @@ -0,0 +1,50 @@ +#pragma once +#include +#include + +#include "types.h" + +namespace nstool +{ + +class FsProcess +{ +public: + FsProcess(); + + void process(); + + void setInputFileSystem(const std::shared_ptr& input_fs); + void setFsFormatName(const std::string& fs_format_name); + void setFsProperties(const std::vector& 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& extract_jobs); +private: + std::string mModuleLabel; + + std::shared_ptr mInputFs; + + // fs info + tc::Optional mFsFormatName; + bool mShowFsInfo; + std::vector mProperties; + + // fs tree + bool mShowFsTree; + tc::Optional mFsRootLabel; + + // extract jobs + std::vector 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); +}; + +} \ No newline at end of file diff --git a/src/GameCardProcess.cpp b/src/GameCardProcess.cpp index b0cfb4a..929f720 100644 --- a/src/GameCardProcess.cpp +++ b/src/GameCardProcess.cpp @@ -1,24 +1,29 @@ -#include -#include -#include -#include +#include "GameCardProcess.h" + +#include +#include + #include #include #include -#include "GameCardProcess.h" -GameCardProcess::GameCardProcess() : +#include +#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& file) +void nstool::GameCardProcess::setInputFile(const std::shared_ptr& 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 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 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 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& 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(rootPartitions[i].name)) - tmp.setExtractPath(mExtractInfo.getElement(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 gc_fs_raw = std::make_shared(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 gc_vfs = std::make_shared(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(); } \ No newline at end of file diff --git a/src/GameCardProcess.h b/src/GameCardProcess.h index b32ec0f..7e984f5 100644 --- a/src/GameCardProcess.h +++ b/src/GameCardProcess.h @@ -1,14 +1,11 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include "KeyConfiguration.h" +#include "types.h" +#include "KeyBag.h" #include "PfsProcess.h" -#include "common.h" +#include + +namespace nstool { class GameCardProcess { @@ -18,60 +15,42 @@ public: void process(); // generic - void setInputFile(const fnd::SharedPtr& file); - void setKeyCfg(const KeyConfiguration& keycfg); + void setInputFile(const std::shared_ptr& 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 extract_jobs); private: - const std::string kModuleName = "GameCardProcess"; - const std::string kXciMountPointName = "gamecard:/"; + const std::string kXciMountPointName = "gamecard"; - fnd::SharedPtr mFile; - KeyConfiguration mKeyCfg; + std::string mModuleName; + + std::shared_ptr 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 mExtractInfo; + std::vector 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(); -}; \ No newline at end of file +}; + +} \ No newline at end of file diff --git a/src/IniProcess.cpp b/src/IniProcess.cpp index d6b6af1..cf329c8 100644 --- a/src/IniProcess.cpp +++ b/src/IniProcess.cpp @@ -1,114 +1,113 @@ -#include -#include -#include -#include -#include -#include -#include #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& file) +void nstool::IniProcess::setInputFile(const std::shared_ptr& 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 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 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(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 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()); - // outfile object for writing KIP - fnd::SimpleFile out_file; - std::string out_path; - size_t out_size; + // out path for extracted KIP + tc::io::Path out_path; + std::string out_path_str; - 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 -{ - return sizeof(nn::hac::sKipHeader) + hdr.getTextSegmentInfo().file_layout.size + hdr.getRoSegmentInfo().file_layout.size + hdr.getDataSegmentInfo().file_layout.size; +int64_t nstool::IniProcess::getKipSizeFromHeader(const nn::hac::KernelInitialProcessHeader& hdr) const +{ + // 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); } \ No newline at end of file diff --git a/src/IniProcess.h b/src/IniProcess.h index dbfad5e..b717e5b 100644 --- a/src/IniProcess.h +++ b/src/IniProcess.h @@ -1,14 +1,10 @@ #pragma once -#include -#include -#include -#include -#include -#include +#include "types.h" + #include #include -#include "common.h" +namespace nstool { class IniProcess { @@ -17,25 +13,29 @@ public: void process(); - void setInputFile(const fnd::SharedPtr& file); + void setInputFile(const std::shared_ptr& 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 mFile; + std::string mModuleName; + + std::shared_ptr mFile; CliOutputMode mCliOutputMode; bool mVerify; - bool mDoExtractKip; - std::string mKipExtractPath; + tc::Optional mKipExtractPath; nn::hac::IniHeader mHdr; - fnd::List> mKipList; + struct InnerKipInfo + { + nn::hac::KernelInitialProcessHeader hdr; + std::shared_ptr stream; + }; + std::vector mKipList; void importHeader(); void importKipList(); @@ -43,5 +43,7 @@ private: void displayKipList(); void extractKipList(); - size_t getKipSizeFromHeader(const nn::hac::KernelInitialProcessHeader& hdr) const; -}; \ No newline at end of file + int64_t getKipSizeFromHeader(const nn::hac::KernelInitialProcessHeader& hdr) const; +}; + +} \ No newline at end of file diff --git a/src/KeyBag.cpp b/src/KeyBag.cpp new file mode 100644 index 0000000..e8320ee --- /dev/null +++ b/src/KeyBag.cpp @@ -0,0 +1,1014 @@ +#include "KeyBag.h" + +#include "util.h" +#include + +#include +#include +#include + +#include +#include +#include +#include + +nstool::KeyBagInitializer::KeyBagInitializer(bool isDev, const tc::Optional& keyfile_path, const tc::Optional& tik_path, const tc::Optional& cert_path) +{ + if (keyfile_path.isSet()) + { + importBaseKeyFile(keyfile_path.get(), isDev); + } + if (cert_path.isSet()) + { + importCertificateChain(cert_path.get()); + } + if (tik_path.isSet()) + { + importTicket(tik_path.get()); + } + + // this will populate known keys if they aren't supplied by the user provided keyfiles. + importKnownKeys(isDev); +} + +void nstool::KeyBagInitializer::importBaseKeyFile(const tc::io::Path& keyfile_path, bool isDev) +{ + std::shared_ptr keyfile_stream = std::make_shared(tc::io::FileStream(keyfile_path, tc::io::FileMode::Open, tc::io::FileAccess::Read)); + + // import keyfile into a dictionary + std::map keyfile_dict; + processResFile(keyfile_stream, keyfile_dict); + + // sources for key derivation + std::map master_key; + tc::Optional package2_key_source; + tc::Optional ticket_titlekek_source; + std::array, 3> key_area_key_source; + tc::Optional aes_kek_generation_source; + tc::Optional aes_key_generation_source; + tc::Optional nca_header_kek_source; + tc::Optional nca_header_key_source; + tc::Optional pki_root_sign_key; + + // macros for importing + +#define _SAVE_AES128KEY(key_name, dst) \ + { \ + std::string key,val; \ + tc::ByteData dec_val; \ + aes128_key_t tmp_aes128_key; \ + key = (key_name); \ + val = keyfile_dict[key]; \ + if (val.empty() == false) { \ + dec_val = tc::cli::FormatUtil::hexStringToBytes(val); \ + if (dec_val.size() != tmp_aes128_key.size()) \ + throw tc::ArgumentException("nstool::KeyBagInitializer", "Key: \"" + key_name + "\" has incorrect length"); \ + memcpy(tmp_aes128_key.data(), dec_val.data(), tmp_aes128_key.size()); \ + (dst) = tmp_aes128_key; \ + } \ + } + +#define _SAVE_AES128XTSKEY(key_name, dst) \ + { \ + std::string key,val; \ + tc::ByteData dec_val; \ + aes128_xtskey_t tmp_aes128_xtskey; \ + key = (key_name); \ + val = keyfile_dict[key]; \ + if (val.empty() == false) { \ + dec_val = tc::cli::FormatUtil::hexStringToBytes(val); \ + if (dec_val.size() != sizeof(tmp_aes128_xtskey)) \ + throw tc::ArgumentException("nstool::KeyBagInitializer", "Key: \"" + key_name + "\" has incorrect length"); \ + memcpy(tmp_aes128_xtskey[0].data(), dec_val.data(), tmp_aes128_xtskey[0].size()); \ + memcpy(tmp_aes128_xtskey[1].data(), dec_val.data()+tmp_aes128_xtskey[0].size(), tmp_aes128_xtskey[1].size()); \ + (dst) = tmp_aes128_xtskey; \ + } \ + } + +#define _SAVE_RSAKEY(key_name, dst, bitsize) \ + { \ + std::string key_mod,key_prv,val_mod,val_prv; \ + tc::ByteData dec_val; \ + rsa_key_t tmp_rsa_key; \ + key_mod = fmt::format("{:s}_modulus", (key_name)); \ + key_prv = fmt::format("{:s}_private", (key_name)); \ + val_mod = keyfile_dict[key_mod]; \ + val_prv = keyfile_dict[key_prv]; \ + if (val_mod.empty() == false) { \ + dec_val = tc::cli::FormatUtil::hexStringToBytes(val_mod); \ + if (dec_val.size() == (bitsize) >> 3) { \ + tmp_rsa_key.n = dec_val; \ + if (val_prv.empty() == false) { \ + dec_val = tc::cli::FormatUtil::hexStringToBytes(val_prv); \ + if (dec_val.size() == (bitsize) >> 3) { \ + tmp_rsa_key.d = dec_val; \ + (dst) = tc::crypto::RsaPrivateKey(tmp_rsa_key.n.data(), tmp_rsa_key.n.size(), tmp_rsa_key.d.data(), tmp_rsa_key.d.size()); \ + } \ + else { \ + fmt::print("[WARNING] Key: \"{:s}\" has incorrect length (was: {:d}, expected {:d})\n", key_prv, val_prv.size(), ((bitsize) >> 3)*2); \ + } \ + } \ + else { \ + (dst) = tc::crypto::RsaPublicKey(tmp_rsa_key.n.data(), tmp_rsa_key.n.size()); \ + } \ + } \ + else {\ + fmt::print("[WARNING] Key: \"{:s}\" has incorrect length (was: {:d}, expected {:d})\n", key_mod, val_mod.size(), ((bitsize) >> 3)*2); \ + } \ + } \ + } + + // keynames + enum NameVariantIndex + { + NNTOOLS, + LEGACY_HACTOOL, + LEGACY_0 + }; + + static const size_t kNameVariantNum = 3; + + std::vector kMasterBase = { "master" }; + std::vector kPkg1Base = { "package1" }; + std::vector kPkg2Base = { "package2" }; + std::vector kXciHeaderBase = { "xci_header" }; + std::vector kXciInitialDataBase = { "xci_initial_data" }; + std::vector kXciCertBase = { "xci_cert" }; + std::vector kContentArchiveHeaderBase = { "nca_header", "header" }; + std::vector kAcidBase = { "acid" }; + std::vector kNrrCertBase = { "nrr_certificate" }; + std::vector kPkiRootBase = { "pki_root" }; + std::vector kTicketCommonKeyBase = { "ticket_commonkey", "titlekek" }; + std::vector kNcaKeyAreaEncKeyBase = { "nca_key_area_key", "key_area_key", "nca_body_keak" }; + std::vector kNcaKeyAreaEncKeyHwBase = { "nca_key_area_key_hw", "key_area_hw_key" }; + std::vector kKekGenBase = { "aes_kek_generation" }; + std::vector kKeyGenBase = { "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"; + std::vector kNcaKeyAreaKeyIndexStr = { "application", "ocean", "system" }; + + static const size_t kKeyGenerationNum = 0x100; + /**/ + + // import key data + for (size_t name_idx = 0; name_idx < kNameVariantNum; name_idx++) + { + /* internal key sources */ + if (name_idx < kMasterBase.size()) + { + for (size_t keygen_rev = 0; keygen_rev < kKeyGenerationNum; keygen_rev++) + { + // std::map master_key; + //fmt::print("{:s}_key_{:02x}\n", kMasterBase[name_idx], keygen_rev); + _SAVE_AES128KEY(fmt::format("{:s}_{:s}_{:02x}", kMasterBase[name_idx], kKeyStr, keygen_rev), master_key[(byte_t)keygen_rev]); + } + } + + if (name_idx < kPkg2Base.size()) + { + // tc::Optional package2_key_source; + //fmt::print("{:s}_key_source\n", kPkg2Base[name_idx]); + _SAVE_AES128KEY(fmt::format("{:s}_{:s}_{:s}", kPkg2Base[name_idx], kKeyStr, kSourceStr), package2_key_source); + } + + if (name_idx < kTicketCommonKeyBase.size()) + { + // tc::Optional ticket_titlekek_source; + //fmt::print("{:s}_source\n", kTicketCommonKeyBase[name_idx]); + _SAVE_AES128KEY(fmt::format("{:s}_{:s}", kTicketCommonKeyBase[name_idx], kSourceStr), ticket_titlekek_source); + } + + if (name_idx < kNcaKeyAreaEncKeyBase.size()) + { + // std::array, 3> key_area_key_source; + + for (size_t keak_idx = 0; keak_idx < kNcaKeyAreaKeyIndexStr.size(); keak_idx++) + { + //fmt::print("{:s}_{:s}_source\n", kNcaKeyAreaEncKeyBase[name_idx], kNcaKeyAreaKeyIndexStr[keak_idx]); + _SAVE_AES128KEY(fmt::format("{:s}_{:s}_{:s}", kNcaKeyAreaEncKeyBase[name_idx], kNcaKeyAreaKeyIndexStr[keak_idx], kSourceStr), key_area_key_source[keak_idx]); + } + } + + if (name_idx < kKekGenBase.size()) + { + // tc::Optional aes_kek_generation_source; + //fmt::print("{:s}_source\n", kKekGenBase[name_idx]); + _SAVE_AES128KEY(fmt::format("{:s}_{:s}", kKekGenBase[name_idx], kSourceStr), aes_kek_generation_source); + } + + if (name_idx < kKeyGenBase.size()) + { + // tc::Optional aes_key_generation_source; + //fmt::print("{:s}_source\n", kKeyGenBase[name_idx]); + _SAVE_AES128KEY(fmt::format("{:s}_{:s}", kKeyGenBase[name_idx], kSourceStr), aes_key_generation_source); + } + + if (name_idx < kContentArchiveHeaderBase.size()) + { + // tc::Optional nca_header_kek_source; + //fmt::print("{:s}_kek_source\n", kContentArchiveHeaderBase[name_idx]); + _SAVE_AES128KEY(fmt::format("{:s}_{:s}_{:s}", kContentArchiveHeaderBase[name_idx], kKekStr, kSourceStr), nca_header_kek_source); + } + + if (name_idx < kContentArchiveHeaderBase.size()) + { + // tc::Optional nca_header_key_source; + //fmt::print("{:s}_key_source\n", kContentArchiveHeaderBase[name_idx]); + _SAVE_AES128XTSKEY(fmt::format("{:s}_{:s}_{:s}", kContentArchiveHeaderBase[name_idx], kKeyStr, kSourceStr), nca_header_key_source); + } + + /* package1 */ + // package1_key_xx + if (name_idx < kPkg1Base.size()) + { + for (size_t keygen_rev = 0; keygen_rev < kKeyGenerationNum; keygen_rev++) + { + //fmt::print("{:s}_key_{:02x}\n", kPkg1Base[name_idx], keygen_rev); + _SAVE_AES128KEY(fmt::format("{:s}_{:s}_{:02x}", kPkg1Base[name_idx], kKeyStr, keygen_rev), pkg1_key[(byte_t)keygen_rev]); + } + } + + /* package2 */ + if (name_idx < kPkg2Base.size()) + { + // package2_key_xx + for (size_t keygen_rev = 0; keygen_rev < kKeyGenerationNum; keygen_rev++) + { + //fmt::print("{:s}_key_{:02x}\n", kPkg2Base[name_idx], keygen_rev); + _SAVE_AES128KEY(fmt::format("{:s}_{:s}_{:02x}", kPkg2Base[name_idx], kKeyStr, keygen_rev), pkg2_key[(byte_t)keygen_rev]); + } + + // package2_sign_key + //fmt::print("{:s}_{:s}_{:s}\n", kPkg2Base[name_idx], kSignKey, kPrivateStr); + //fmt::print("{:s}_{:s}_{:s}\n", kPkg2Base[name_idx], kSignKey, kModulusStr); + _SAVE_RSAKEY(fmt::format("{:s}_{:s}", kPkg2Base[name_idx], kSignKey), pkg2_sign_key, 2048); + } + + /* eticket */ + // ticket common key + if (name_idx < kTicketCommonKeyBase.size()) + { + for (size_t keygen_rev = 0; keygen_rev < kKeyGenerationNum; keygen_rev++) + { + //fmt::print("{:s}_{:02x}\n", kTicketCommonKeyBase[name_idx], keygen_rev); + _SAVE_AES128KEY(fmt::format("{:s}_{:02x}", kTicketCommonKeyBase[name_idx], keygen_rev), etik_common_key[(byte_t)keygen_rev]); + } + } + + /* NCA keys */ + if (name_idx < kContentArchiveHeaderBase.size()) + { + // nca header key + //fmt::print("{:s}_{:s}\n", kContentArchiveHeaderBase[name_idx], kKeyStr); + _SAVE_AES128XTSKEY(fmt::format("{:s}_{:s}", kContentArchiveHeaderBase[name_idx], kKeyStr), nca_header_key); + + // nca header sign0 key (generations) + for (size_t keygen_rev = 0; keygen_rev < kKeyGenerationNum; keygen_rev++) + { + //fmt::print("{:s}_{:s}_{:02x}_{:s}\n", kContentArchiveHeaderBase[name_idx], kSignKey, keygen_rev, kPrivateStr); + //fmt::print("{:s}_{:s}_{:02x}_{:s}\n", kContentArchiveHeaderBase[name_idx], kSignKey, keygen_rev, kModulusStr); + _SAVE_RSAKEY(fmt::format("{:s}_{:s}_{:02x}", kContentArchiveHeaderBase[name_idx], kSignKey, keygen_rev), nca_header_sign0_key[(byte_t)keygen_rev], 2048); + } + // nca header sign0 key (generation 0) + //fmt::print("{:s}_{:s}_{:s}\n", kContentArchiveHeaderBase[name_idx], kSignKey, kPrivateStr); + //fmt::print("{:s}_{:s}_{:s}\n", kContentArchiveHeaderBase[name_idx], kSignKey, kModulusStr); + _SAVE_RSAKEY(fmt::format("{:s}_{:s}", kContentArchiveHeaderBase[name_idx], kSignKey), nca_header_sign0_key[0], 2048); + + } + + // nca body key (unused since prototype format) + + // nca key area encryption keys + if (name_idx < kNcaKeyAreaEncKeyBase.size()) + { + for (size_t keygen_rev = 0; keygen_rev < kKeyGenerationNum; keygen_rev++) + { + for (size_t keak_idx = 0; keak_idx < kNcaKeyAreaKeyIndexStr.size(); keak_idx++) + { + //fmt::print("{:s}_{:s}_{:02x}\n", kNcaKeyAreaEncKeyBase[name_idx], kNcaKeyAreaKeyIndexStr[keak_idx], keygen_rev); + _SAVE_AES128KEY(fmt::format("{:s}_{:s}_{:02x}", kNcaKeyAreaEncKeyBase[name_idx], kNcaKeyAreaKeyIndexStr[keak_idx], keygen_rev), nca_key_area_encryption_key[keak_idx][(byte_t)keygen_rev]); + } + } + } + // nca key area "hw" encryption keys + if (name_idx < kNcaKeyAreaEncKeyHwBase.size()) + { + for (size_t keygen_rev = 0; keygen_rev < kKeyGenerationNum; keygen_rev++) + { + for (size_t keak_idx = 0; keak_idx < kNcaKeyAreaKeyIndexStr.size(); keak_idx++) + { + //fmt::print("{:s}_{:s}_{:02x}\n", kNcaKeyAreaEncKeyHwBase[name_idx], kNcaKeyAreaKeyIndexStr[keak_idx], keygen_rev); + _SAVE_AES128KEY(fmt::format("{:s}_{:s}_{:02x}", kNcaKeyAreaEncKeyHwBase[name_idx], kNcaKeyAreaKeyIndexStr[keak_idx], keygen_rev), nca_key_area_encryption_key_hw[keak_idx][(byte_t)keygen_rev]); + } + } + } + + /* ACID */ + if (name_idx < kAcidBase.size()) + { + // acid sign key (generations) + for (size_t keygen_rev = 0; keygen_rev < kKeyGenerationNum; keygen_rev++) + { + //fmt::print("{:s}_{:s}_{:02x}_{:s}\n", kAcidBase[name_idx], kSignKey, keygen_rev, kPrivateStr); + //fmt::print("{:s}_{:s}_{:02x}_{:s}\n", kAcidBase[name_idx], kSignKey, keygen_rev, kModulusStr); + _SAVE_RSAKEY(fmt::format("{:s}_{:s}_{:02x}", kAcidBase[name_idx], kSignKey, keygen_rev), acid_sign_key[(byte_t)keygen_rev], 2048); + } + // acid sign key (generation 0) + //fmt::print("{:s}_{:s}_{:s}\n", kAcidBase[name_idx], kSignKey, kPrivateStr); + //fmt::print("{:s}_{:s}_{:s}\n", kAcidBase[name_idx], kSignKey, kModulusStr); + _SAVE_RSAKEY(fmt::format("{:s}_{:s}", kAcidBase[name_idx], kSignKey), acid_sign_key[0], 2048); + } + + /* NRR certificate */ + if (name_idx < kNrrCertBase.size()) + { + // nrr certificate sign key (generations) + for (size_t keygen_rev = 0; keygen_rev < kKeyGenerationNum; keygen_rev++) + { + //fmt::print("{:s}_{:s}_{:02x}_{:s}\n", kNrrCertBase[name_idx], kSignKey, keygen_rev, kPrivateStr); + //fmt::print("{:s}_{:s}_{:02x}_{:s}\n", kNrrCertBase[name_idx], kSignKey, keygen_rev, kModulusStr); + _SAVE_RSAKEY(fmt::format("{:s}_{:s}_{:02x}", kNrrCertBase[name_idx], kSignKey, keygen_rev), nrr_certificate_sign_key[(byte_t)keygen_rev], 2048); + } + // nrr certificate sign key (generation 0) + //fmt::print("{:s}_{:s}_{:s}\n", kNrrCertBase[name_idx], kSignKey, kPrivateStr); + //fmt::print("{:s}_{:s}_{:s}\n", kNrrCertBase[name_idx], kSignKey, kModulusStr); + _SAVE_RSAKEY(fmt::format("{:s}_{:s}", kNrrCertBase[name_idx], kSignKey), nrr_certificate_sign_key[0], 2048); + } + + /* XCI header */ + if (name_idx < kXciHeaderBase.size()) + { + // xci header key (based on index) + for (byte_t kek_index = 0; kek_index < 8; kek_index++) + { + //fmt::print("{:s}_{:s}_{:02x}\n", kXciHeaderBase[name_idx], kKeyStr, kek_index); + _SAVE_AES128KEY(fmt::format("{:s}_{:s}_{:02x}", kXciHeaderBase[name_idx], kKeyStr, kek_index), xci_header_key[kek_index]); + } + // xci header key (old label, prod/dev keys are actually a fake distinction, the are different key indexes available to both?, so select correct index when importing) + //fmt::print("{:s}_{:s}\n", kXciHeaderBase[name_idx], kKeyStr); + _SAVE_AES128KEY(fmt::format("{:s}_{:s}", kXciHeaderBase[name_idx], kKeyStr), xci_header_key[isDev ? nn::hac::gc::KEK_DEV : nn::hac::gc::KEK_PROD]); + + // xci header sign key + //fmt::print("{:s}_{:s}_{:s}\n", kXciHeaderBase[name_idx], kSignKey, kPrivateStr); + //fmt::print("{:s}_{:s}_{:s}\n", kXciHeaderBase[name_idx], kSignKey, kModulusStr); + _SAVE_RSAKEY(fmt::format("{:s}_{:s}", kXciHeaderBase[name_idx], kSignKey), xci_header_sign_key, 2048); + } + + /* XCI InitialData */ + if (name_idx < kXciInitialDataBase.size()) + { + // xci initial data key (based on index) + for (byte_t kek_index = 0; kek_index < 8; kek_index++) + { + //fmt::print("{:s}_{:s}_{:02x}\n", kXciInitialDataBase[name_idx], kKekStr, kek_index); + _SAVE_AES128KEY(fmt::format("{:s}_{:s}_{:02x}", kXciInitialDataBase[name_idx], kKekStr, kek_index), xci_initial_data_kek[kek_index]); + } + } + + /* XCI cert */ + if (name_idx < kXciCertBase.size()) + { + // xci cert sign key + _SAVE_RSAKEY(fmt::format("{:s}_{:s}", kXciCertBase[name_idx], kSignKey), xci_cert_sign_key, 2048); + } + + /* PKI */ + if (name_idx < kPkiRootBase.size()) + { + // tc::Optional pki_root_sign_key; + //fmt::print("{:s}_{:s}_{:s}\n", kPkiRootBase[name_idx], kSignKey, kPrivateStr); + //fmt::print("{:s}_{:s}_{:s}\n", kPkiRootBase[name_idx], kSignKey, kModulusStr); + _SAVE_RSAKEY(fmt::format("{:s}_{:s}", kPkiRootBase[name_idx], kSignKey), pki_root_sign_key, 4096); + } + + + } + +#undef _SAVE_RSAKEY +#undef _SAVE_AES128XTSKEY +#undef _SAVE_AES128KEY + + // Derive Keys + for (auto itr = master_key.begin(); itr != master_key.end(); itr++) + { + if (aes_kek_generation_source.isSet() && aes_key_generation_source.isSet()) + { + if (itr->first == 0 && nca_header_kek_source.isSet() && nca_header_key_source.isSet()) + { + if (nca_header_key.isNull()) + { + aes128_key_t nca_header_kek_tmp; + nn::hac::AesKeygen::generateKey(nca_header_kek_tmp.data(), aes_kek_generation_source.get().data(), nca_header_kek_source.get().data(), aes_key_generation_source.get().data(), itr->second.data()); + + aes128_xtskey_t nca_header_key_tmp; + nn::hac::AesKeygen::generateKey(nca_header_key_tmp[0].data(), nca_header_key_source.get()[0].data(), nca_header_kek_tmp.data()); + nn::hac::AesKeygen::generateKey(nca_header_key_tmp[1].data(), nca_header_key_source.get()[1].data(), nca_header_kek_tmp.data()); + + nca_header_key = nca_header_key_tmp; + } + } + + for (size_t keak_idx = 0; keak_idx < nn::hac::nca::kKeyAreaEncryptionKeyNum; keak_idx++) + { + if (key_area_key_source[keak_idx].isSet() && nca_key_area_encryption_key[keak_idx].find(itr->first) != nca_key_area_encryption_key[keak_idx].end()) + { + aes128_key_t nca_key_area_encryption_key_tmp; + nn::hac::AesKeygen::generateKey(nca_key_area_encryption_key_tmp.data(), aes_kek_generation_source.get().data(), key_area_key_source[keak_idx].get().data(), aes_key_generation_source.get().data(), itr->second.data()); + nca_key_area_encryption_key[keak_idx][itr->first] = nca_key_area_encryption_key_tmp; + } + } + } + if (ticket_titlekek_source.isSet() && etik_common_key.find(itr->first) == etik_common_key.end()) + { + aes128_key_t etik_common_key_tmp; + nn::hac::AesKeygen::generateKey(etik_common_key_tmp.data(), ticket_titlekek_source.get().data(), itr->second.data()); + etik_common_key[itr->first] = etik_common_key_tmp; + } + if (package2_key_source.isSet() && pkg2_key.find(itr->first) == pkg2_key.end()) + { + aes128_key_t pkg2_key_tmp; + nn::hac::AesKeygen::generateKey(pkg2_key_tmp.data(), package2_key_source.get().data(), itr->second.data()); + pkg2_key[itr->first] = pkg2_key_tmp; + } + } + + // Save PKI Root Key + if (pki_root_sign_key.isSet()) + { + broadon_signer["Root"] = { tc::ByteData(), nn::pki::sign::SIGN_ALGO_RSA4096, pki_root_sign_key.get() }; + } +} + +void nstool::KeyBagInitializer::importTitleKeyFile(const tc::io::Path& keyfile_path) +{ + +} + +void nstool::KeyBagInitializer::importCertificateChain(const tc::io::Path& cert_path) +{ + // save file path string for error messages + std::string cert_path_str; + tc::io::PathUtil::pathToUnixUTF8(cert_path, cert_path_str); + + // open cert file + std::shared_ptr certfile_stream; + try { + certfile_stream = std::make_shared(tc::io::FileStream(cert_path, tc::io::FileMode::Open, tc::io::FileAccess::Read)); + } + catch (tc::io::FileNotFoundException& e) { + fmt::print("[WARNING] Failed to open certificate file \"{:s}\" ({:s}).\n", cert_path_str, e.error()); + return; + } + + // check size + size_t cert_raw_size = tc::io::IOUtil::castInt64ToSize(certfile_stream->length()); + if (cert_raw_size > 0x10000) + { + fmt::print("[WARNING] Certificate file \"{:s}\" was too large.\n", cert_path_str); + return; + } + + // import cert data + tc::ByteData cert_raw = tc::ByteData(cert_raw_size); + certfile_stream->seek(0, tc::io::SeekOrigin::Begin); + certfile_stream->read(cert_raw.data(), cert_raw.size()); + + nn::pki::SignedData cert; + try { + for (size_t f_pos = 0; f_pos < cert_raw.size(); f_pos += cert.getBytes().size()) + { + cert.fromBytes(cert_raw.data() + f_pos, cert_raw.size() - f_pos); + + std::string cert_identity = fmt::format("{:s}-{:s}", cert.getBody().getIssuer(), cert.getBody().getSubject()); + + switch (cert.getBody().getPublicKeyType()) { + case nn::pki::cert::PublicKeyType::RSA2048: + broadon_signer[cert_identity] = { cert.getBytes(), nn::pki::sign::SIGN_ALGO_RSA2048, cert.getBody().getRsa2048PublicKey() }; + break; + case nn::pki::cert::PublicKeyType::RSA4096: + broadon_signer[cert_identity] = { cert.getBytes(), nn::pki::sign::SIGN_ALGO_RSA4096, cert.getBody().getRsa4096PublicKey() }; + break; + case nn::pki::cert::PublicKeyType::ECDSA240: + // broadon_signer[cert_identity] = { cert.getBytes(), nn::pki::sign::SIGN_ALGO_ECDSA240, cert.getBody().getRsa4096PublicKey() }; + fmt::print("[WARNING] Certificate {:s} will not be imported. ecc233 public keys are not supported yet.\n", cert_identity); + break; + default: + fmt::print("[WARNING] Certificate {:s} will not be imported. Unknown public key type.\n", cert_identity); + } + } + } + catch (tc::Exception& e) { + fmt::print("[WARNING] Certificate file \"{:s}\" is corrupted ({:s}).\n", cert_path_str, e.error()); + return; + } +} + +void nstool::KeyBagInitializer::importTicket(const tc::io::Path& tik_path) +{ + // save file path string for error messages + std::string tik_path_str; + tc::io::PathUtil::pathToUnixUTF8(tik_path, tik_path_str); + + // open cert file + std::shared_ptr tik_stream; + try { + tik_stream = std::make_shared(tc::io::FileStream(tik_path, tc::io::FileMode::Open, tc::io::FileAccess::Read)); + } + catch (tc::io::FileNotFoundException& e) { + fmt::print("[WARNING] Failed to open ticket \"{:s}\" ({:s}).\n", tik_path_str, e.error()); + return; + } + + // check size + size_t tik_raw_size = tc::io::IOUtil::castInt64ToSize(tik_stream->length()); + if (tik_raw_size > 0x10000) + { + fmt::print("[WARNING] Ticket \"{:s}\" was too large.\n", tik_path_str); + return; + } + + // import cert data + tc::ByteData tik_raw = tc::ByteData(tik_raw_size); + tik_stream->seek(0, tc::io::SeekOrigin::Begin); + tik_stream->read(tik_raw.data(), tik_raw.size()); + + nn::pki::SignedData tik; + try { + // de serialise ticket + tik.fromBytes(tik_raw.data(), tik_raw.size()); + + // save rights id + rights_id_t rights_id; + memcpy(rights_id.data(), tik.getBody().getRightsId(), rights_id.size()); + + // check ticket is not personalised + if (tik.getBody().getTitleKeyEncType() != nn::es::ticket::AES128_CBC) + { + fmt::print("[WARNING] Ticket \"{:s}\" will not be imported. Personalised tickets are not supported.\n", tc::cli::FormatUtil::formatBytesAsString(rights_id.data(), rights_id.size(), true, "")); + return; + } + + // save enc title key + aes128_key_t enc_title_key; + memcpy(enc_title_key.data(), tik.getBody().getEncTitleKey(), enc_title_key.size()); + + // save the encrypted title key as the fallback enc content key incase the ticket was malformed and workarounds to decrypt it in isolation fail + if (fallback_enc_content_key.isNull()) + { + fallback_enc_content_key = enc_title_key; + } + + // determine key to decrypt title key + byte_t common_key_index = tik.getBody().getCommonKeyId(); + + // work around for bad scene tickets where they don't set the commonkey id field (detect scene ticket with ffff.... signature) + if (common_key_index == 0 && *((uint64_t*)tik.getSignature().getSignature().data()) == (uint64_t)0xffffffffffffffff) + { + fmt::print("[WARNING] Ticket \"{:s}\" is fake-signed, and NCA decryption may fail if ticket was incorrectly generated.\n", tc::cli::FormatUtil::formatBytesAsString(rights_id.data(), rights_id.size(), true, "")); + // the keygeneration was included in the rights_id from keygeneration 0x03 and onwards, so in those cases we can copy from there + if (rights_id[15] >= 0x03) + common_key_index = rights_id[15]; + } + + // convert key_generation + common_key_index = nn::hac::AesKeygen::getMasterKeyRevisionFromKeyGeneration(common_key_index); + + if (etik_common_key.find(common_key_index) == etik_common_key.end()) + { + fmt::print("[WARNING] Ticket \"{:s}\" will not be imported. Could not decrypt title key.\n", tc::cli::FormatUtil::formatBytesAsString(rights_id.data(), rights_id.size(), true, "")); + return; + } + + // decrypt title key + aes128_key_t dec_title_key; + tc::crypto::DecryptAes128Ecb(dec_title_key.data(), enc_title_key.data(), sizeof(aes128_key_t), etik_common_key[common_key_index].data(), sizeof(aes128_key_t)); + + // add to key dict + external_content_keys[rights_id] = dec_title_key; + + } + catch (tc::Exception& e) { + fmt::print("[WARNING] Ticket \"{:s}\" is corrupted ({:s}).\n", tik_path_str, e.error()); + return; + } +} + +void nstool::KeyBagInitializer::importKnownKeys(bool isDev) +{ + static const nn::hac::detail::rsa2048_block_t kXciHeaderSignModulus = { + 0x98, 0xC7, 0x26, 0xB6, 0x0D, 0x0A, 0x50, 0xA7, 0x39, 0x21, 0x0A, 0xE3, 0x2F, 0xE4, 0x3E, 0x2E, + 0x5B, 0xA2, 0x86, 0x75, 0xAA, 0x5C, 0xEE, 0x34, 0xF1, 0xA3, 0x3A, 0x7E, 0xBD, 0x90, 0x4E, 0xF7, + 0x8D, 0xFA, 0x17, 0xAA, 0x6B, 0xC6, 0x36, 0x6D, 0x4C, 0x9A, 0x6D, 0x57, 0x2F, 0x80, 0xA2, 0xBC, + 0x38, 0x4D, 0xDA, 0x99, 0xA1, 0xD8, 0xC3, 0xE2, 0x99, 0x79, 0x36, 0x71, 0x90, 0x20, 0x25, 0x9D, + 0x4D, 0x11, 0xB8, 0x2E, 0x63, 0x6B, 0x5A, 0xFA, 0x1E, 0x9C, 0x04, 0xD1, 0xC5, 0xF0, 0x9C, 0xB1, + 0x0F, 0xB8, 0xC1, 0x7B, 0xBF, 0xE8, 0xB0, 0xD2, 0x2B, 0x47, 0x01, 0x22, 0x6B, 0x23, 0xC9, 0xD0, + 0xBC, 0xEB, 0x75, 0x6E, 0x41, 0x7D, 0x4C, 0x26, 0xA4, 0x73, 0x21, 0xB4, 0xF0, 0x14, 0xE5, 0xD9, + 0x8D, 0xB3, 0x64, 0xEE, 0xA8, 0xFA, 0x84, 0x1B, 0xB8, 0xB8, 0x7C, 0x88, 0x6B, 0xEF, 0xCC, 0x97, + 0x04, 0x04, 0x9A, 0x67, 0x2F, 0xDF, 0xEC, 0x0D, 0xB2, 0x5F, 0xB5, 0xB2, 0xBD, 0xB5, 0x4B, 0xDE, + 0x0E, 0x88, 0xA3, 0xBA, 0xD1, 0xB4, 0xE0, 0x91, 0x81, 0xA7, 0x84, 0xEB, 0x77, 0x85, 0x8B, 0xEF, + 0xA5, 0xE3, 0x27, 0xB2, 0xF2, 0x82, 0x2B, 0x29, 0xF1, 0x75, 0x2D, 0xCE, 0xCC, 0xAE, 0x9B, 0x8D, + 0xED, 0x5C, 0xF1, 0x8E, 0xDB, 0x9A, 0xD7, 0xAF, 0x42, 0x14, 0x52, 0xCD, 0xE3, 0xC5, 0xDD, 0xCE, + 0x08, 0x12, 0x17, 0xD0, 0x7F, 0x1A, 0xAA, 0x1F, 0x7D, 0xE0, 0x93, 0x54, 0xC8, 0xBC, 0x73, 0x8A, + 0xCB, 0xAD, 0x6E, 0x93, 0xE2, 0x19, 0x72, 0x6B, 0xD3, 0x45, 0xF8, 0x73, 0x3D, 0x2B, 0x6A, 0x55, + 0xD2, 0x3A, 0x8B, 0xB0, 0x8A, 0x42, 0xE3, 0x3D, 0xF1, 0x92, 0x23, 0x42, 0x2E, 0xBA, 0xCC, 0x9C, + 0x9A, 0xC1, 0xDD, 0x62, 0x86, 0x9C, 0x2E, 0xE1, 0x2D, 0x6F, 0x62, 0x67, 0x51, 0x08, 0x0E, 0xCF + }; + + static const nn::hac::detail::rsa2048_block_t kXciCertSignModulus = { + 0xCD, 0xF3, 0x2C, 0xB0, 0xF5, 0x14, 0x78, 0x34, 0xE5, 0x02, 0xD0, 0x29, 0x6A, 0xA5, 0xFD, 0x97, + 0x6A, 0xE0, 0xB0, 0xBB, 0xB0, 0x3B, 0x1A, 0x80, 0xB7, 0xD7, 0x58, 0x92, 0x79, 0x84, 0xC0, 0x36, + 0xB1, 0x55, 0x23, 0xD8, 0xA5, 0x60, 0x91, 0x26, 0x48, 0x1A, 0x80, 0x4A, 0xEA, 0x00, 0x98, 0x2A, + 0xEC, 0x52, 0x17, 0x72, 0x92, 0x4D, 0xF5, 0x42, 0xA7, 0x8A, 0x6F, 0x7F, 0xD2, 0x48, 0x51, 0x8E, + 0xDF, 0xCB, 0xBF, 0x77, 0xF6, 0x18, 0xBD, 0xE5, 0x00, 0xD9, 0x70, 0x8C, 0xEF, 0x57, 0xB2, 0x96, + 0xD0, 0x36, 0x83, 0x88, 0x9C, 0xC5, 0xFB, 0xA0, 0x33, 0x81, 0xA2, 0x12, 0x23, 0xC6, 0xC7, 0x86, + 0x0A, 0x98, 0x57, 0x4D, 0x2E, 0xB5, 0xAE, 0x64, 0xE4, 0x6F, 0xC2, 0xC5, 0xAC, 0x6A, 0x1D, 0xDB, + 0xA5, 0xAF, 0x12, 0x22, 0xAB, 0x1F, 0x51, 0xC8, 0x0E, 0x0D, 0xC9, 0xF5, 0x03, 0xE8, 0xD2, 0xFC, + 0x84, 0x62, 0x26, 0x55, 0xA4, 0xC3, 0xE2, 0xA8, 0x98, 0x05, 0x67, 0x23, 0xFD, 0xA5, 0x46, 0x40, + 0x78, 0x51, 0x09, 0x3D, 0x91, 0x74, 0xD6, 0xD0, 0x54, 0x23, 0x0D, 0xA0, 0xFB, 0x07, 0xD0, 0xAA, + 0x9D, 0x50, 0x4E, 0x2B, 0x26, 0x9A, 0x14, 0xE5, 0x6C, 0x73, 0x66, 0x24, 0x18, 0xA1, 0x93, 0x9C, + 0x2A, 0x40, 0x40, 0x05, 0x6B, 0xF1, 0x45, 0xDF, 0x22, 0x8B, 0x40, 0x61, 0xA4, 0x11, 0x06, 0x03, + 0xA5, 0x53, 0x84, 0xC0, 0x12, 0xE1, 0x88, 0x9D, 0x55, 0x55, 0x07, 0x40, 0x88, 0x01, 0x8C, 0xAB, + 0xA2, 0xFD, 0xFD, 0x19, 0x48, 0x25, 0xAB, 0x59, 0x59, 0x28, 0x63, 0x68, 0x69, 0x1B, 0x99, 0x73, + 0x8D, 0xAB, 0x5A, 0xFA, 0x71, 0x60, 0x1B, 0x12, 0xE7, 0x99, 0x70, 0xF1, 0x99, 0x2A, 0x50, 0x18, + 0x8B, 0x6B, 0x61, 0x90, 0xE2, 0x7E, 0x8B, 0x90, 0xD4, 0xD5, 0xC0, 0xCB, 0x7C, 0x08, 0x06, 0xD9 + }; + + /* Keydata for very early beta NCA0 archives' RSA-OAEP. */ + /* + static const nn::hac::detail::rsa2048_block_t beta_nca0_modulus = { + 0xAD, 0x58, 0xEE, 0x97, 0xF9, 0x47, 0x90, 0x7D, 0xF9, 0x29, 0x5F, 0x1F, 0x39, 0x68, 0xEE, 0x49, + 0x4C, 0x1E, 0x8D, 0x84, 0x91, 0x31, 0x5D, 0xE5, 0x96, 0x27, 0xB2, 0xB3, 0x59, 0x7B, 0xDE, 0xFD, + 0xB7, 0xEB, 0x40, 0xA1, 0xE7, 0xEB, 0xDC, 0x60, 0xD0, 0x3D, 0xC5, 0x50, 0x92, 0xAD, 0x3D, 0xC4, + 0x8C, 0x17, 0xD2, 0x37, 0x66, 0xE3, 0xF7, 0x14, 0x34, 0x38, 0x6B, 0xA7, 0x2B, 0x21, 0x10, 0x9B, + 0x73, 0x49, 0x15, 0xD9, 0x2A, 0x90, 0x86, 0x76, 0x81, 0x6A, 0x10, 0xBD, 0x74, 0xC4, 0x20, 0x55, + 0x25, 0xA8, 0x02, 0xC5, 0xA0, 0x34, 0x36, 0x7B, 0x66, 0x47, 0x2C, 0x7E, 0x47, 0x82, 0xA5, 0xD4, + 0xA3, 0x42, 0x45, 0xE8, 0xFD, 0x65, 0x72, 0x48, 0xA1, 0xB0, 0x44, 0x10, 0xEF, 0xAC, 0x1D, 0x0F, + 0xB5, 0x12, 0x19, 0xA8, 0x41, 0x0B, 0x76, 0x3B, 0xBC, 0xF1, 0x4A, 0x10, 0x46, 0x22, 0xB8, 0xF1, + 0xBC, 0x21, 0x81, 0x69, 0x9B, 0x63, 0x6F, 0xD7, 0xB9, 0x60, 0x2A, 0x9A, 0xE5, 0x2C, 0x47, 0x72, + 0x59, 0x65, 0xA2, 0x21, 0x60, 0xC4, 0xFC, 0xB0, 0xD7, 0x6F, 0x42, 0xC9, 0x0C, 0xF5, 0x76, 0x7D, + 0xF2, 0x5C, 0xE0, 0x80, 0x0F, 0xEE, 0x45, 0x7E, 0x4E, 0x3A, 0x8D, 0x9C, 0x5B, 0x5B, 0xD9, 0xD1, + 0x43, 0x94, 0x2C, 0xC7, 0x2E, 0xB9, 0x4A, 0xE5, 0x3E, 0x15, 0xDD, 0x43, 0x00, 0xF7, 0x78, 0xE7, + 0x7C, 0x39, 0xB0, 0x4D, 0xC5, 0xD1, 0x1C, 0xF2, 0xB4, 0x7A, 0x2A, 0xEA, 0x0A, 0x8E, 0xB9, 0x13, + 0xB4, 0x4F, 0xD7, 0x5B, 0x4D, 0x7B, 0x43, 0xB0, 0x3A, 0x9A, 0x60, 0x22, 0x47, 0x91, 0x78, 0xC7, + 0x10, 0x64, 0xE0, 0x2C, 0x69, 0xD1, 0x66, 0x3C, 0x42, 0x2E, 0xEF, 0x19, 0x21, 0x89, 0x8E, 0xE1, + 0xB0, 0xB4, 0xD0, 0x17, 0xA1, 0x0F, 0x73, 0x98, 0x5A, 0xF6, 0xEE, 0xC0, 0x2F, 0x9E, 0xCE, 0xC5 + }; + + static const nn::hac::detail::sha256_hash_t beta_nca0_label_hash = { + 0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC, 0x1C, 0x14, 0x9A, 0xFB, 0xF4, 0xC8, 0x99, 0x6F, 0xB9, 0x24, + 0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, 0xA4, 0x95, 0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55 + }; + */ + + struct sRsaKeyForGeneration { + byte_t generation; + nn::hac::detail::rsa2048_block_t modulus; + }; + + static const nn::hac::detail::rsa2048_block_t kProdPackage2HeaderModulus = { + 0x8D, 0x13, 0xA7, 0x77, 0x6A, 0xE5, 0xDC, 0xC0, 0x3B, 0x25, 0xD0, 0x58, 0xE4, 0x20, 0x69, 0x59, + 0x55, 0x4B, 0xAB, 0x70, 0x40, 0x08, 0x28, 0x07, 0xA8, 0xA7, 0xFD, 0x0F, 0x31, 0x2E, 0x11, 0xFE, + 0x47, 0xA0, 0xF9, 0x9D, 0xDF, 0x80, 0xDB, 0x86, 0x5A, 0x27, 0x89, 0xCD, 0x97, 0x6C, 0x85, 0xC5, + 0x6C, 0x39, 0x7F, 0x41, 0xF2, 0xFF, 0x24, 0x20, 0xC3, 0x95, 0xA6, 0xF7, 0x9D, 0x4A, 0x45, 0x74, + 0x8B, 0x5D, 0x28, 0x8A, 0xC6, 0x99, 0x35, 0x68, 0x85, 0xA5, 0x64, 0x32, 0x80, 0x9F, 0xD3, 0x48, + 0x39, 0xA2, 0x1D, 0x24, 0x67, 0x69, 0xDF, 0x75, 0xAC, 0x12, 0xB5, 0xBD, 0xC3, 0x29, 0x90, 0xBE, + 0x37, 0xE4, 0xA0, 0x80, 0x9A, 0xBE, 0x36, 0xBF, 0x1F, 0x2C, 0xAB, 0x2B, 0xAD, 0xF5, 0x97, 0x32, + 0x9A, 0x42, 0x9D, 0x09, 0x8B, 0x08, 0xF0, 0x63, 0x47, 0xA3, 0xE9, 0x1B, 0x36, 0xD8, 0x2D, 0x8A, + 0xD7, 0xE1, 0x54, 0x11, 0x95, 0xE4, 0x45, 0x88, 0x69, 0x8A, 0x2B, 0x35, 0xCE, 0xD0, 0xA5, 0x0B, + 0xD5, 0x5D, 0xAC, 0xDB, 0xAF, 0x11, 0x4D, 0xCA, 0xB8, 0x1E, 0xE7, 0x01, 0x9E, 0xF4, 0x46, 0xA3, + 0x8A, 0x94, 0x6D, 0x76, 0xBD, 0x8A, 0xC8, 0x3B, 0xD2, 0x31, 0x58, 0x0C, 0x79, 0xA8, 0x26, 0xE9, + 0xD1, 0x79, 0x9C, 0xCB, 0xD4, 0x2B, 0x6A, 0x4F, 0xC6, 0xCC, 0xCF, 0x90, 0xA7, 0xB9, 0x98, 0x47, + 0xFD, 0xFA, 0x4C, 0x6C, 0x6F, 0x81, 0x87, 0x3B, 0xCA, 0xB8, 0x50, 0xF6, 0x3E, 0x39, 0x5D, 0x4D, + 0x97, 0x3F, 0x0F, 0x35, 0x39, 0x53, 0xFB, 0xFA, 0xCD, 0xAB, 0xA8, 0x7A, 0x62, 0x9A, 0x3F, 0xF2, + 0x09, 0x27, 0x96, 0x3F, 0x07, 0x9A, 0x91, 0xF7, 0x16, 0xBF, 0xC6, 0x3A, 0x82, 0x5A, 0x4B, 0xCF, + 0x49, 0x50, 0x95, 0x8C, 0x55, 0x80, 0x7E, 0x39, 0xB1, 0x48, 0x05, 0x1E, 0x21, 0xC7, 0x24, 0x4F + }; + + static const std::vector kProdNcaHeaderSign0Modulus = + { + { + 0x00, + {0xBF, 0xBE, 0x40, 0x6C, 0xF4, 0xA7, 0x80, 0xE9, 0xF0, 0x7D, 0x0C, 0x99, 0x61, 0x1D, 0x77, 0x2F, + 0x96, 0xBC, 0x4B, 0x9E, 0x58, 0x38, 0x1B, 0x03, 0xAB, 0xB1, 0x75, 0x49, 0x9F, 0x2B, 0x4D, 0x58, + 0x34, 0xB0, 0x05, 0xA3, 0x75, 0x22, 0xBE, 0x1A, 0x3F, 0x03, 0x73, 0xAC, 0x70, 0x68, 0xD1, 0x16, + 0xB9, 0x04, 0x46, 0x5E, 0xB7, 0x07, 0x91, 0x2F, 0x07, 0x8B, 0x26, 0xDE, 0xF6, 0x00, 0x07, 0xB2, + 0xB4, 0x51, 0xF8, 0x0D, 0x0A, 0x5E, 0x58, 0xAD, 0xEB, 0xBC, 0x9A, 0xD6, 0x49, 0xB9, 0x64, 0xEF, + 0xA7, 0x82, 0xB5, 0xCF, 0x6D, 0x70, 0x13, 0xB0, 0x0F, 0x85, 0xF6, 0xA9, 0x08, 0xAA, 0x4D, 0x67, + 0x66, 0x87, 0xFA, 0x89, 0xFF, 0x75, 0x90, 0x18, 0x1E, 0x6B, 0x3D, 0xE9, 0x8A, 0x68, 0xC9, 0x26, + 0x04, 0xD9, 0x80, 0xCE, 0x3F, 0x5E, 0x92, 0xCE, 0x01, 0xFF, 0x06, 0x3B, 0xF2, 0xC1, 0xA9, 0x0C, + 0xCE, 0x02, 0x6F, 0x16, 0xBC, 0x92, 0x42, 0x0A, 0x41, 0x64, 0xCD, 0x52, 0xB6, 0x34, 0x4D, 0xAE, + 0xC0, 0x2E, 0xDE, 0xA4, 0xDF, 0x27, 0x68, 0x3C, 0xC1, 0xA0, 0x60, 0xAD, 0x43, 0xF3, 0xFC, 0x86, + 0xC1, 0x3E, 0x6C, 0x46, 0xF7, 0x7C, 0x29, 0x9F, 0xFA, 0xFD, 0xF0, 0xE3, 0xCE, 0x64, 0xE7, 0x35, + 0xF2, 0xF6, 0x56, 0x56, 0x6F, 0x6D, 0xF1, 0xE2, 0x42, 0xB0, 0x83, 0x40, 0xA5, 0xC3, 0x20, 0x2B, + 0xCC, 0x9A, 0xAE, 0xCA, 0xED, 0x4D, 0x70, 0x30, 0xA8, 0x70, 0x1C, 0x70, 0xFD, 0x13, 0x63, 0x29, + 0x02, 0x79, 0xEA, 0xD2, 0xA7, 0xAF, 0x35, 0x28, 0x32, 0x1C, 0x7B, 0xE6, 0x2F, 0x1A, 0xAA, 0x40, + 0x7E, 0x32, 0x8C, 0x27, 0x42, 0xFE, 0x82, 0x78, 0xEC, 0x0D, 0xEB, 0xE6, 0x83, 0x4B, 0x6D, 0x81, + 0x04, 0x40, 0x1A, 0x9E, 0x9A, 0x67, 0xF6, 0x72, 0x29, 0xFA, 0x04, 0xF0, 0x9D, 0xE4, 0xF4, 0x03,} + }, + { + 0x01, + {0xAD, 0xE3, 0xE1, 0xFA, 0x04, 0x35, 0xE5, 0xB6, 0xDD, 0x49, 0xEA, 0x89, 0x29, 0xB1, 0xFF, 0xB6, + 0x43, 0xDF, 0xCA, 0x96, 0xA0, 0x4A, 0x13, 0xDF, 0x43, 0xD9, 0x94, 0x97, 0x96, 0x43, 0x65, 0x48, + 0x70, 0x58, 0x33, 0xA2, 0x7D, 0x35, 0x7B, 0x96, 0x74, 0x5E, 0x0B, 0x5C, 0x32, 0x18, 0x14, 0x24, + 0xC2, 0x58, 0xB3, 0x6C, 0x22, 0x7A, 0xA1, 0xB7, 0xCB, 0x90, 0xA7, 0xA3, 0xF9, 0x7D, 0x45, 0x16, + 0xA5, 0xC8, 0xED, 0x8F, 0xAD, 0x39, 0x5E, 0x9E, 0x4B, 0x51, 0x68, 0x7D, 0xF8, 0x0C, 0x35, 0xC6, + 0x3F, 0x91, 0xAE, 0x44, 0xA5, 0x92, 0x30, 0x0D, 0x46, 0xF8, 0x40, 0xFF, 0xD0, 0xFF, 0x06, 0xD2, + 0x1C, 0x7F, 0x96, 0x18, 0xDC, 0xB7, 0x1D, 0x66, 0x3E, 0xD1, 0x73, 0xBC, 0x15, 0x8A, 0x2F, 0x94, + 0xF3, 0x00, 0xC1, 0x83, 0xF1, 0xCD, 0xD7, 0x81, 0x88, 0xAB, 0xDF, 0x8C, 0xEF, 0x97, 0xDD, 0x1B, + 0x17, 0x5F, 0x58, 0xF6, 0x9A, 0xE9, 0xE8, 0xC2, 0x2F, 0x38, 0x15, 0xF5, 0x21, 0x07, 0xF8, 0x37, + 0x90, 0x5D, 0x2E, 0x02, 0x40, 0x24, 0x15, 0x0D, 0x25, 0xB7, 0x26, 0x5D, 0x09, 0xCC, 0x4C, 0xF4, + 0xF2, 0x1B, 0x94, 0x70, 0x5A, 0x9E, 0xEE, 0xED, 0x77, 0x77, 0xD4, 0x51, 0x99, 0xF5, 0xDC, 0x76, + 0x1E, 0xE3, 0x6C, 0x8C, 0xD1, 0x12, 0xD4, 0x57, 0xD1, 0xB6, 0x83, 0xE4, 0xE4, 0xFE, 0xDA, 0xE9, + 0xB4, 0x3B, 0x33, 0xE5, 0x37, 0x8A, 0xDF, 0xB5, 0x7F, 0x89, 0xF1, 0x9B, 0x9E, 0xB0, 0x15, 0xB2, + 0x3A, 0xFE, 0xEA, 0x61, 0x84, 0x5B, 0x7D, 0x4B, 0x23, 0x12, 0x0B, 0x83, 0x12, 0xF2, 0x22, 0x6B, + 0xB9, 0x22, 0x96, 0x4B, 0x26, 0x0B, 0x63, 0x5E, 0x96, 0x57, 0x52, 0xA3, 0x67, 0x64, 0x22, 0xCA, + 0xD0, 0x56, 0x3E, 0x74, 0xB5, 0x98, 0x1F, 0x0D, 0xF8, 0xB3, 0x34, 0xE6, 0x98, 0x68, 0x5A, 0xAD,} + }, + }; + + static const std::vector kProdAcidSignModulus = + { + { + 0x00, + {0xDD, 0xC8, 0xDD, 0xF2, 0x4E, 0x6D, 0xF0, 0xCA, 0x9E, 0xC7, 0x5D, 0xC7, 0x7B, 0xAD, 0xFE, 0x7D, + 0x23, 0x89, 0x69, 0xB6, 0xF2, 0x06, 0xA2, 0x02, 0x88, 0xE1, 0x55, 0x91, 0xAB, 0xCB, 0x4D, 0x50, + 0x2E, 0xFC, 0x9D, 0x94, 0x76, 0xD6, 0x4C, 0xD8, 0xFF, 0x10, 0xFA, 0x5E, 0x93, 0x0A, 0xB4, 0x57, + 0xAC, 0x51, 0xC7, 0x16, 0x66, 0xF4, 0x1A, 0x54, 0xC2, 0xC5, 0x04, 0x3D, 0x1B, 0xFE, 0x30, 0x20, + 0x8A, 0xAC, 0x6F, 0x6F, 0xF5, 0xC7, 0xB6, 0x68, 0xB8, 0xC9, 0x40, 0x6B, 0x42, 0xAD, 0x11, 0x21, + 0xE7, 0x8B, 0xE9, 0x75, 0x01, 0x86, 0xE4, 0x48, 0x9B, 0x0A, 0x0A, 0xF8, 0x7F, 0xE8, 0x87, 0xF2, + 0x82, 0x01, 0xE6, 0xA3, 0x0F, 0xE4, 0x66, 0xAE, 0x83, 0x3F, 0x4E, 0x9F, 0x5E, 0x01, 0x30, 0xA4, + 0x00, 0xB9, 0x9A, 0xAE, 0x5F, 0x03, 0xCC, 0x18, 0x60, 0xE5, 0xEF, 0x3B, 0x5E, 0x15, 0x16, 0xFE, + 0x1C, 0x82, 0x78, 0xB5, 0x2F, 0x47, 0x7C, 0x06, 0x66, 0x88, 0x5D, 0x35, 0xA2, 0x67, 0x20, 0x10, + 0xE7, 0x6C, 0x43, 0x68, 0xD3, 0xE4, 0x5A, 0x68, 0x2A, 0x5A, 0xE2, 0x6D, 0x73, 0xB0, 0x31, 0x53, + 0x1C, 0x20, 0x09, 0x44, 0xF5, 0x1A, 0x9D, 0x22, 0xBE, 0x12, 0xA1, 0x77, 0x11, 0xE2, 0xA1, 0xCD, + 0x40, 0x9A, 0xA2, 0x8B, 0x60, 0x9B, 0xEF, 0xA0, 0xD3, 0x48, 0x63, 0xA2, 0xF8, 0xA3, 0x2C, 0x08, + 0x56, 0x52, 0x2E, 0x60, 0x19, 0x67, 0x5A, 0xA7, 0x9F, 0xDC, 0x3F, 0x3F, 0x69, 0x2B, 0x31, 0x6A, + 0xB7, 0x88, 0x4A, 0x14, 0x84, 0x80, 0x33, 0x3C, 0x9D, 0x44, 0xB7, 0x3F, 0x4C, 0xE1, 0x75, 0xEA, + 0x37, 0xEA, 0xE8, 0x1E, 0x7C, 0x77, 0xB7, 0xC6, 0x1A, 0xA2, 0xF0, 0x9F, 0x10, 0x61, 0xCD, 0x7B, + 0x5B, 0x32, 0x4C, 0x37, 0xEF, 0xB1, 0x71, 0x68, 0x53, 0x0A, 0xED, 0x51, 0x7D, 0x35, 0x22, 0xFD,} + }, + { + 0x01, + {0xE7, 0xAA, 0x25, 0xC8, 0x01, 0xA5, 0x14, 0x6B, 0x01, 0x60, 0x3E, 0xD9, 0x96, 0x5A, 0xBF, 0x90, + 0xAC, 0xA7, 0xFD, 0x9B, 0x5B, 0xBD, 0x8A, 0x26, 0xB0, 0xCB, 0x20, 0x28, 0x9A, 0x72, 0x12, 0xF5, + 0x20, 0x65, 0xB3, 0xB9, 0x84, 0x58, 0x1F, 0x27, 0xBC, 0x7C, 0xA2, 0xC9, 0x9E, 0x18, 0x95, 0xCF, + 0xC2, 0x73, 0x2E, 0x74, 0x8C, 0x66, 0xE5, 0x9E, 0x79, 0x2B, 0xB8, 0x07, 0x0C, 0xB0, 0x4E, 0x8E, + 0xAB, 0x85, 0x21, 0x42, 0xC4, 0xC5, 0x6D, 0x88, 0x9C, 0xDB, 0x15, 0x95, 0x3F, 0x80, 0xDB, 0x7A, + 0x9A, 0x7D, 0x41, 0x56, 0x25, 0x17, 0x18, 0x42, 0x4D, 0x8C, 0xAC, 0xA5, 0x7B, 0xDB, 0x42, 0x5D, + 0x59, 0x35, 0x45, 0x5D, 0x8A, 0x02, 0xB5, 0x70, 0xC0, 0x72, 0x35, 0x46, 0xD0, 0x1D, 0x60, 0x01, + 0x4A, 0xCC, 0x1C, 0x46, 0xD3, 0xD6, 0x35, 0x52, 0xD6, 0xE1, 0xF8, 0x3B, 0x5D, 0xEA, 0xDD, 0xB8, + 0xFE, 0x7D, 0x50, 0xCB, 0x35, 0x23, 0x67, 0x8B, 0xB6, 0xE4, 0x74, 0xD2, 0x60, 0xFC, 0xFD, 0x43, + 0xBF, 0x91, 0x08, 0x81, 0xC5, 0x4F, 0x5D, 0x16, 0x9A, 0xC4, 0x9A, 0xC6, 0xF6, 0xF3, 0xE1, 0xF6, + 0x5C, 0x07, 0xAA, 0x71, 0x6C, 0x13, 0xA4, 0xB1, 0xB3, 0x66, 0xBF, 0x90, 0x4C, 0x3D, 0xA2, 0xC4, + 0x0B, 0xB8, 0x3D, 0x7A, 0x8C, 0x19, 0xFA, 0xFF, 0x6B, 0xB9, 0x1F, 0x02, 0xCC, 0xB6, 0xD3, 0x0C, + 0x7D, 0x19, 0x1F, 0x47, 0xF9, 0xC7, 0x40, 0x01, 0xFA, 0x46, 0xEA, 0x0B, 0xD4, 0x02, 0xE0, 0x3D, + 0x30, 0x9A, 0x1A, 0x0F, 0xEA, 0xA7, 0x66, 0x55, 0xF7, 0xCB, 0x28, 0xE2, 0xBB, 0x99, 0xE4, 0x83, + 0xC3, 0x43, 0x03, 0xEE, 0xDC, 0x1F, 0x02, 0x23, 0xDD, 0xD1, 0x2D, 0x39, 0xA4, 0x65, 0x75, 0x03, + 0xEF, 0x37, 0x9C, 0x06, 0xD6, 0xFA, 0xA1, 0x15, 0xF0, 0xDB, 0x17, 0x47, 0x26, 0x4F, 0x49, 0x03} + }, + }; + + static const nn::hac::detail::rsa2048_block_t kDevPackage2HeaderModulus = { + 0xB3, 0x65, 0x54, 0xFB, 0x0A, 0xB0, 0x1E, 0x85, 0xA7, 0xF6, 0xCF, 0x91, 0x8E, 0xBA, 0x96, 0x99, + 0x0D, 0x8B, 0x91, 0x69, 0x2A, 0xEE, 0x01, 0x20, 0x4F, 0x34, 0x5C, 0x2C, 0x4F, 0x4E, 0x37, 0xC7, + 0xF1, 0x0B, 0xD4, 0xCD, 0xA1, 0x7F, 0x93, 0xF1, 0x33, 0x59, 0xCE, 0xB1, 0xE9, 0xDD, 0x26, 0xE6, + 0xF3, 0xBB, 0x77, 0x87, 0x46, 0x7A, 0xD6, 0x4E, 0x47, 0x4A, 0xD1, 0x41, 0xB7, 0x79, 0x4A, 0x38, + 0x06, 0x6E, 0xCF, 0x61, 0x8F, 0xCD, 0xC1, 0x40, 0x0B, 0xFA, 0x26, 0xDC, 0xC0, 0x34, 0x51, 0x83, + 0xD9, 0x3B, 0x11, 0x54, 0x3B, 0x96, 0x27, 0x32, 0x9A, 0x95, 0xBE, 0x1E, 0x68, 0x11, 0x50, 0xA0, + 0x6B, 0x10, 0xA8, 0x83, 0x8B, 0xF5, 0xFC, 0xBC, 0x90, 0x84, 0x7A, 0x5A, 0x5C, 0x43, 0x52, 0xE6, + 0xC8, 0x26, 0xE9, 0xFE, 0x06, 0xA0, 0x8B, 0x53, 0x0F, 0xAF, 0x1E, 0xC4, 0x1C, 0x0B, 0xCF, 0x50, + 0x1A, 0xA4, 0xF3, 0x5C, 0xFB, 0xF0, 0x97, 0xE4, 0xDE, 0x32, 0x0A, 0x9F, 0xE3, 0x5A, 0xAA, 0xB7, + 0x44, 0x7F, 0x5C, 0x33, 0x60, 0xB9, 0x0F, 0x22, 0x2D, 0x33, 0x2A, 0xE9, 0x69, 0x79, 0x31, 0x42, + 0x8F, 0xE4, 0x3A, 0x13, 0x8B, 0xE7, 0x26, 0xBD, 0x08, 0x87, 0x6C, 0xA6, 0xF2, 0x73, 0xF6, 0x8E, + 0xA7, 0xF2, 0xFE, 0xFB, 0x6C, 0x28, 0x66, 0x0D, 0xBD, 0xD7, 0xEB, 0x42, 0xA8, 0x78, 0xE6, 0xB8, + 0x6B, 0xAE, 0xC7, 0xA9, 0xE2, 0x40, 0x6E, 0x89, 0x20, 0x82, 0x25, 0x8E, 0x3C, 0x6A, 0x60, 0xD7, + 0xF3, 0x56, 0x8E, 0xEC, 0x8D, 0x51, 0x8A, 0x63, 0x3C, 0x04, 0x78, 0x23, 0x0E, 0x90, 0x0C, 0xB4, + 0xE7, 0x86, 0x3B, 0x4F, 0x8E, 0x13, 0x09, 0x47, 0x32, 0x0E, 0x04, 0xB8, 0x4D, 0x5B, 0xB0, 0x46, + 0x71, 0xB0, 0x5C, 0xF4, 0xAD, 0x63, 0x4F, 0xC5, 0xE2, 0xAC, 0x1E, 0xC4, 0x33, 0x96, 0x09, 0x7B + }; + + static const std::vector kDevNcaHeaderSign0Modulus = + { + { + 0x00, + {0xD8, 0xF1, 0x18, 0xEF, 0x32, 0x72, 0x4C, 0xA7, 0x47, 0x4C, 0xB9, 0xEA, 0xB3, 0x04, 0xA8, 0xA4, + 0xAC, 0x99, 0x08, 0x08, 0x04, 0xBF, 0x68, 0x57, 0xB8, 0x43, 0x94, 0x2B, 0xC7, 0xB9, 0x66, 0x49, + 0x85, 0xE5, 0x8A, 0x9B, 0xC1, 0x00, 0x9A, 0x6A, 0x8D, 0xD0, 0xEF, 0xCE, 0xFF, 0x86, 0xC8, 0x5C, + 0x5D, 0xE9, 0x53, 0x7B, 0x19, 0x2A, 0xA8, 0xC0, 0x22, 0xD1, 0xF3, 0x22, 0x0A, 0x50, 0xF2, 0x2B, + 0x65, 0x05, 0x1B, 0x9E, 0xEC, 0x61, 0xB5, 0x63, 0xA3, 0x6F, 0x3B, 0xBA, 0x63, 0x3A, 0x53, 0xF4, + 0x49, 0x2F, 0xCF, 0x03, 0xCC, 0xD7, 0x50, 0x82, 0x1B, 0x29, 0x4F, 0x08, 0xDE, 0x1B, 0x6D, 0x47, + 0x4F, 0xA8, 0xB6, 0x6A, 0x26, 0xA0, 0x83, 0x3F, 0x1A, 0xAF, 0x83, 0x8F, 0x0E, 0x17, 0x3F, 0xFE, + 0x44, 0x1C, 0x56, 0x94, 0x2E, 0x49, 0x83, 0x83, 0x03, 0xE9, 0xB6, 0xAD, 0xD5, 0xDE, 0xE3, 0x2D, + 0xA1, 0xD9, 0x66, 0x20, 0x5D, 0x1F, 0x5E, 0x96, 0x5D, 0x5B, 0x55, 0x0D, 0xD4, 0xB4, 0x77, 0x6E, + 0xAE, 0x1B, 0x69, 0xF3, 0xA6, 0x61, 0x0E, 0x51, 0x62, 0x39, 0x28, 0x63, 0x75, 0x76, 0xBF, 0xB0, + 0xD2, 0x22, 0xEF, 0x98, 0x25, 0x02, 0x05, 0xC0, 0xD7, 0x6A, 0x06, 0x2C, 0xA5, 0xD8, 0x5A, 0x9D, + 0x7A, 0xA4, 0x21, 0x55, 0x9F, 0xF9, 0x3E, 0xBF, 0x16, 0xF6, 0x07, 0xC2, 0xB9, 0x6E, 0x87, 0x9E, + 0xB5, 0x1C, 0xBE, 0x97, 0xFA, 0x82, 0x7E, 0xED, 0x30, 0xD4, 0x66, 0x3F, 0xDE, 0xD8, 0x1B, 0x4B, + 0x15, 0xD9, 0xFB, 0x2F, 0x50, 0xF0, 0x9D, 0x1D, 0x52, 0x4C, 0x1C, 0x4D, 0x8D, 0xAE, 0x85, 0x1E, + 0xEA, 0x7F, 0x86, 0xF3, 0x0B, 0x7B, 0x87, 0x81, 0x98, 0x23, 0x80, 0x63, 0x4F, 0x2F, 0xB0, 0x62, + 0xCC, 0x6E, 0xD2, 0x46, 0x13, 0x65, 0x2B, 0xD6, 0x44, 0x33, 0x59, 0xB5, 0x8F, 0xB9, 0x4A, 0xA9,} + }, + { + 0x01, + {0x9A, 0xBC, 0x88, 0xBD, 0x0A, 0xBE, 0xD7, 0x0C, 0x9B, 0x42, 0x75, 0x65, 0x38, 0x5E, 0xD1, 0x01, + 0xCD, 0x12, 0xAE, 0xEA, 0xE9, 0x4B, 0xDB, 0xB4, 0x5E, 0x36, 0x10, 0x96, 0xDA, 0x3D, 0x2E, 0x66, + 0xD3, 0x99, 0x13, 0x8A, 0xBE, 0x67, 0x41, 0xC8, 0x93, 0xD9, 0x3E, 0x42, 0xCE, 0x34, 0xCE, 0x96, + 0xFA, 0x0B, 0x23, 0xCC, 0x2C, 0xDF, 0x07, 0x3F, 0x3B, 0x24, 0x4B, 0x12, 0x67, 0x3A, 0x29, 0x36, + 0xA3, 0xAA, 0x06, 0xF0, 0x65, 0xA5, 0x85, 0xBA, 0xFD, 0x12, 0xEC, 0xF1, 0x60, 0x67, 0xF0, 0x8F, + 0xD3, 0x5B, 0x01, 0x1B, 0x1E, 0x84, 0xA3, 0x5C, 0x65, 0x36, 0xF9, 0x23, 0x7E, 0xF3, 0x26, 0x38, + 0x64, 0x98, 0xBA, 0xE4, 0x19, 0x91, 0x4C, 0x02, 0xCF, 0xC9, 0x6D, 0x86, 0xEC, 0x1D, 0x41, 0x69, + 0xDD, 0x56, 0xEA, 0x5C, 0xA3, 0x2A, 0x58, 0xB4, 0x39, 0xCC, 0x40, 0x31, 0xFD, 0xFB, 0x42, 0x74, + 0xF8, 0xEC, 0xEA, 0x00, 0xF0, 0xD9, 0x28, 0xEA, 0xFA, 0x2D, 0x00, 0xE1, 0x43, 0x53, 0xC6, 0x32, + 0xF4, 0xA2, 0x07, 0xD4, 0x5F, 0xD4, 0xCB, 0xAC, 0xCA, 0xFF, 0xDF, 0x84, 0xD2, 0x86, 0x14, 0x3C, + 0xDE, 0x22, 0x75, 0xA5, 0x73, 0xFF, 0x68, 0x07, 0x4A, 0xF9, 0x7C, 0x2C, 0xCC, 0xDE, 0x45, 0xB6, + 0x54, 0x82, 0x90, 0x36, 0x1F, 0x2C, 0x51, 0x96, 0xC5, 0x0A, 0x53, 0x5B, 0xF0, 0x8B, 0x4A, 0xAA, + 0x3B, 0x68, 0x97, 0x19, 0x17, 0x1F, 0x01, 0xB8, 0xED, 0xB9, 0x9A, 0x5E, 0x08, 0xC5, 0x20, 0x1E, + 0x6A, 0x09, 0xF0, 0xE9, 0x73, 0xA3, 0xBE, 0x10, 0x06, 0x02, 0xE9, 0xFB, 0x85, 0xFA, 0x5F, 0x01, + 0xAC, 0x60, 0xE0, 0xED, 0x7D, 0xB9, 0x49, 0xA8, 0x9E, 0x98, 0x7D, 0x91, 0x40, 0x05, 0xCF, 0xF9, + 0x1A, 0xFC, 0x40, 0x22, 0xA8, 0x96, 0x5B, 0xB0, 0xDC, 0x7A, 0xF5, 0xB7, 0xE9, 0x91, 0x4C, 0x49,} + }, + }; + + static const std::vector kDevAcidSignModulus = + { + { + 0x00, + {0xD6, 0x34, 0xA5, 0x78, 0x6C, 0x68, 0xCE, 0x5A, 0xC2, 0x37, 0x17, 0xF3, 0x82, 0x45, 0xC6, 0x89, + 0xE1, 0x2D, 0x06, 0x67, 0xBF, 0xB4, 0x06, 0x19, 0x55, 0x6B, 0x27, 0x66, 0x0C, 0xA4, 0xB5, 0x87, + 0x81, 0x25, 0xF4, 0x30, 0xBC, 0x53, 0x08, 0x68, 0xA2, 0x48, 0x49, 0x8C, 0x3F, 0x38, 0x40, 0x9C, + 0xC4, 0x26, 0xF4, 0x79, 0xE2, 0xA1, 0x85, 0xF5, 0x5C, 0x7F, 0x58, 0xBA, 0xA6, 0x1C, 0xA0, 0x8B, + 0x84, 0x16, 0x14, 0x6F, 0x85, 0xD9, 0x7C, 0xE1, 0x3C, 0x67, 0x22, 0x1E, 0xFB, 0xD8, 0xA7, 0xA5, + 0x9A, 0xBF, 0xEC, 0x0E, 0xCF, 0x96, 0x7E, 0x85, 0xC2, 0x1D, 0x49, 0x5D, 0x54, 0x26, 0xCB, 0x32, + 0x7C, 0xF6, 0xBB, 0x58, 0x03, 0x80, 0x2B, 0x5D, 0xF7, 0xFB, 0xD1, 0x9D, 0xC7, 0xC6, 0x2E, 0x53, + 0xC0, 0x6F, 0x39, 0x2C, 0x1F, 0xA9, 0x92, 0xF2, 0x4D, 0x7D, 0x4E, 0x74, 0xFF, 0xE4, 0xEF, 0xE4, + 0x7C, 0x3D, 0x34, 0x2A, 0x71, 0xA4, 0x97, 0x59, 0xFF, 0x4F, 0xA2, 0xF4, 0x66, 0x78, 0xD8, 0xBA, + 0x99, 0xE3, 0xE6, 0xDB, 0x54, 0xB9, 0xE9, 0x54, 0xA1, 0x70, 0xFC, 0x05, 0x1F, 0x11, 0x67, 0x4B, + 0x26, 0x8C, 0x0C, 0x3E, 0x03, 0xD2, 0xA3, 0x55, 0x5C, 0x7D, 0xC0, 0x5D, 0x9D, 0xFF, 0x13, 0x2F, + 0xFD, 0x19, 0xBF, 0xED, 0x44, 0xC3, 0x8C, 0xA7, 0x28, 0xCB, 0xE5, 0xE0, 0xB1, 0xA7, 0x9C, 0x33, + 0x8D, 0xB8, 0x6E, 0xDE, 0x87, 0x18, 0x22, 0x60, 0xC4, 0xAE, 0xF2, 0x87, 0x9F, 0xCE, 0x09, 0x5C, + 0xB5, 0x99, 0xA5, 0x9F, 0x49, 0xF2, 0xD7, 0x58, 0xFA, 0xF9, 0xC0, 0x25, 0x7D, 0xD6, 0xCB, 0xF3, + 0xD8, 0x6C, 0xA2, 0x69, 0x91, 0x68, 0x73, 0xB1, 0x94, 0x6F, 0xA3, 0xF3, 0xB9, 0x7D, 0xF8, 0xE0, + 0x72, 0x9E, 0x93, 0x7B, 0x7A, 0xA2, 0x57, 0x60, 0xB7, 0x5B, 0xA9, 0x84, 0xAE, 0x64, 0x88, 0x69} + }, + { + 0x01, + {0xBC, 0xA5, 0x6A, 0x7E, 0xEA, 0x38, 0x34, 0x62, 0xA6, 0x10, 0x18, 0x3C, 0xE1, 0x63, 0x7B, 0xF0, + 0xD3, 0x08, 0x8C, 0xF5, 0xC5, 0xC4, 0xC7, 0x93, 0xE9, 0xD9, 0xE6, 0x32, 0xF3, 0xA0, 0xF6, 0x6E, + 0x8A, 0x98, 0x76, 0x47, 0x33, 0x47, 0x65, 0x02, 0x70, 0xDC, 0x86, 0x5F, 0x3D, 0x61, 0x5A, 0x70, + 0xBC, 0x5A, 0xCA, 0xCA, 0x50, 0xAD, 0x61, 0x7E, 0xC9, 0xEC, 0x27, 0xFF, 0xE8, 0x64, 0x42, 0x9A, + 0xEE, 0xBE, 0xC3, 0xD1, 0x0B, 0xC0, 0xE9, 0xBF, 0x83, 0x8D, 0xC0, 0x0C, 0xD8, 0x00, 0x5B, 0x76, + 0x90, 0xD2, 0x4B, 0x30, 0x84, 0x35, 0x8B, 0x1E, 0x20, 0xB7, 0xE4, 0xDC, 0x63, 0xE5, 0xDF, 0xCD, + 0x00, 0x5F, 0x81, 0x5F, 0x67, 0xC5, 0x8B, 0xDF, 0xFC, 0xE1, 0x37, 0x5F, 0x07, 0xD9, 0xDE, 0x4F, + 0xE6, 0x7B, 0xF1, 0xFB, 0xA1, 0x5A, 0x71, 0x40, 0xFE, 0xBA, 0x1E, 0xAE, 0x13, 0x22, 0xD2, 0xFE, + 0x37, 0xA2, 0xB6, 0x8B, 0xAB, 0xEB, 0x84, 0x81, 0x4E, 0x7C, 0x1E, 0x02, 0xD1, 0xFB, 0xD7, 0x5D, + 0x11, 0x84, 0x64, 0xD2, 0x4D, 0xBB, 0x50, 0x00, 0x67, 0x54, 0xE2, 0x77, 0x89, 0xBA, 0x0B, 0xE7, + 0x05, 0x57, 0x9A, 0x22, 0x5A, 0xEC, 0x76, 0x1C, 0xFD, 0xE8, 0xA8, 0x18, 0x16, 0x41, 0x65, 0x03, + 0xFA, 0xC4, 0xA6, 0x31, 0x5C, 0x1A, 0x7F, 0xAB, 0x11, 0xC8, 0x4A, 0x99, 0xB9, 0xE6, 0xCF, 0x62, + 0x21, 0xA6, 0x72, 0x47, 0xDB, 0xBA, 0x96, 0x26, 0x4E, 0x2E, 0xD4, 0x8C, 0x46, 0xD6, 0xA7, 0x1A, + 0x6C, 0x32, 0xA7, 0xDF, 0x85, 0x1C, 0x03, 0xC3, 0x6D, 0xA9, 0xE9, 0x68, 0xF4, 0x17, 0x1E, 0xB2, + 0x70, 0x2A, 0xA1, 0xE5, 0xE1, 0xF3, 0x8F, 0x6F, 0x63, 0xAC, 0xEB, 0x72, 0x0B, 0x4C, 0x4A, 0x36, + 0x3C, 0x60, 0x91, 0x9F, 0x6E, 0x1C, 0x71, 0xEA, 0xD0, 0x78, 0x78, 0xA0, 0x2E, 0xC6, 0x32, 0x6B} + }, + }; + + struct sBroadOnRsaKeyAndCert + { + std::string issuer; + nn::pki::sign::SignatureAlgo key_type; + tc::ByteData modulus; + tc::ByteData certificate; + }; + + static const std::vector kProdBroadOnRsaKeyAndCert = + { + { + "Root", + nn::pki::sign::SIGN_ALGO_RSA4096, + tc::cli::FormatUtil::hexStringToBytes("F8246C58BAE7500301FBB7C2EBE0010571DA922378F0514EC0031DD0D21ED3D07EFC852069B5DE9BB951A8BC90A244926D379295AE9436AAA6A302510C7B1DEDD5FB20869D7F3016F6BE65D383A16DB3321B95351890B17002937EE193F57E99A2474E9D3824C7AEE38541F567E7518C7A0E38E7EBAF41191BCFF17B42A6B4EDE6CE8DE7318F7F5204B3990E226745AFD485B24493008B08C7F6B7E56B02B3E8FE0C9D859CB8B68223B8AB27EE5F6538078B2DB91E2A153E85818072A23B6DD93281054F6FB0F6F5AD283ECA0B7AF35455E03DA7B68326F3EC834AF314048AC6DF20D28508673CAB62A2C7BC131A533E0B66806B1C30664B372331BDC4B0CAD8D11EE7BBD9285548AAEC1F66E821B3C8A0476900C5E688E80CCE3C61D69CBBA137C6604F7A72DD8C7B3E3D51290DAA6A597B081F9D3633A3467A356109ACA7DD7D2E2FB2C1AEB8E20F4892D8B9F8B46F4E3C11F4F47D8B757DFEFEA3899C33595C5EFDEBCBABE8413E3A9A803C69356EB2B2AD5CC4C858455EF5F7B30644B47C64068CDF809F76025A2DB446E03D7CF62F34E702457B02A4CF5D9DD53CA53A7CA629788C67CA08BFECCA43A957AD16C94E1CD875CA107DCE7E0118F0DF6BFEE51DDBD991C26E60CD4858AA592C820075F29F526C917C6FE5403EA7D4A50CEC3B7384DE886E82D2EB4D4E42B5F2B149A81EA7CE7144DC2994CFC44E1F91CBD495"), + tc::ByteData() + }, + { + "Root-CA00000003", + nn::pki::sign::SIGN_ALGO_RSA2048, + tc::cli::FormatUtil::hexStringToBytes("B279C9E2EEE121C6EAF44FF639F88F078B4B77ED9F9560B0358281B50E55AB721115A177703C7A30FE3AE9EF1C60BC1D974676B23A68CC04B198525BC968F11DE2DB50E4D9E7F071E562DAE2092233E9D363F61DD7C19FF3A4A91E8F6553D471DD7B84B9F1B8CE7335F0F5540563A1EAB83963E09BE901011F99546361287020E9CC0DAB487F140D6626A1836D27111F2068DE4772149151CF69C61BA60EF9D949A0F71F5499F2D39AD28C7005348293C431FFBD33F6BCA60DC7195EA2BCC56D200BAF6D06D09C41DB8DE9C720154CA4832B69C08C69CD3B073A0063602F462D338061A5EA6C915CD5623579C3EB64CE44EF586D14BAAA8834019B3EEBEED379"), + tc::cli::FormatUtil::hexStringToBytes("00010003704138EFBBBDA16A987DD901326D1C9459484C88A2861B91A312587AE70EF6237EC50E1032DC39DDE89A96A8E859D76A98A6E7E36A0CFE352CA893058234FF833FCB3B03811E9F0DC0D9A52F8045B4B2F9411B67A51C44B5EF8CE77BD6D56BA75734A1856DE6D4BED6D3A242C7C8791B3422375E5C779ABF072F7695EFA0F75BCB83789FC30E3FE4CC8392207840638949C7F688565F649B74D63D8D58FFADDA571E9554426B1318FC468983D4C8A5628B06B6FC5D507C13E7A18AC1511EB6D62EA5448F83501447A9AFB3ECC2903C9DD52F922AC9ACDBEF58C6021848D96E208732D3D1D9D9EA440D91621C7A99DB8843C59C1F2E2C7D9B577D512C166D6F7E1AAD4A774A37447E78FE2021E14A95D112A068ADA019F463C7A55685AABB6888B9246483D18B9C806F474918331782344A4B8531334B26303263D9D2EB4F4BB99602B352F6AE4046C69A5E7E8E4A18EF9BC0A2DED61310417012FD824CC116CFB7C4C1F7EC7177A17446CBDE96F3EDD88FCD052F0B888A45FDAF2B631354F40D16E5FA9C2C4EDA98E798D15E6046DC5363F3096B2C607A9D8DD55B1502A6AC7D3CC8D8C575998E7D796910C804C495235057E91ECD2637C9C1845151AC6B9A0490AE3EC6F47740A0DB0BA36D075956CEE7354EA3E9A4F2720B26550C7D394324BC0CB7E9317D8A8661F42191FF10B08256CE3FD25B745E5194906B4D61CB4C2E000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000526F6F7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001434130303030303030330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007BE8EF6CB279C9E2EEE121C6EAF44FF639F88F078B4B77ED9F9560B0358281B50E55AB721115A177703C7A30FE3AE9EF1C60BC1D974676B23A68CC04B198525BC968F11DE2DB50E4D9E7F071E562DAE2092233E9D363F61DD7C19FF3A4A91E8F6553D471DD7B84B9F1B8CE7335F0F5540563A1EAB83963E09BE901011F99546361287020E9CC0DAB487F140D6626A1836D27111F2068DE4772149151CF69C61BA60EF9D949A0F71F5499F2D39AD28C7005348293C431FFBD33F6BCA60DC7195EA2BCC56D200BAF6D06D09C41DB8DE9C720154CA4832B69C08C69CD3B073A0063602F462D338061A5EA6C915CD5623579C3EB64CE44EF586D14BAAA8834019B3EEBEED3790001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + }, + { + "Root-CA00000003-XS00000020", + nn::pki::sign::SIGN_ALGO_RSA2048, + tc::cli::FormatUtil::hexStringToBytes("D21D3CE67C1069DA049D5E5310E76B907E18EEC80B337C4723E339573F4C664907DB2F0832D03DF5EA5F160A4AF24100D71AFAC2E3AE75AFA1228012A9A21616597DF71EAFCB65941470D1B40F5EF83A597E179FCB5B57C2EE17DA3BC3769864CB47856767229D67328141FC9AB1DF149E0C5C15AEB80BC58FC71BE18966642D68308B506934B8EF779F78E4DDF30A0DCF93FCAFBFA131A8839FD641949F47EE25CEECF814D55B0BE6E5677C1EFFEC6F29871EF29AA3ED9197B0D83852E050908031EF1ABBB5AFC8B3DD937A076FF6761AB362405C3F7D86A3B17A6170A659C16008950F7F5E06A5DE3E5998895EFA7DEEA060BE9575668F78AB1907B3BA1B7D"), + tc::cli::FormatUtil::hexStringToBytes("00010004969FE8288DA6B9DD52C7BD63642A4A9AE5F053ECCB93613FDA37992087BD9199DA5E6797618D77098133FD5B05CD8288139E2E975CD2608003878CDAF020F51A0E5B7692780845561B31C61808E8A47C3462224D94F736E9A14E56ACBF71B7F11BBDEE38DDB846D6BD8F0AB4E4948C5434EAF9BF26529B7EB83671D3CE60A6D7A850DBE6801EC52A7B7A3E5A27BC675BA3C53377CFC372EBCE02062F59F37003AA23AE35D4880E0E4B69F982FB1BAC806C2F75BA29587F2815FD7783998C354D52B19E3FAD9FBEF444C48579288DB0978116AFC82CE54DACB9ED7E1BFD50938F22F85EECF3A4F426AE5FEB15B72F022FB36ECCE9314DAD131429BFC9675F58EE000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000526F6F742D4341303030303030303300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015853303030303030323000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000D21D3CE67C1069DA049D5E5310E76B907E18EEC80B337C4723E339573F4C664907DB2F0832D03DF5EA5F160A4AF24100D71AFAC2E3AE75AFA1228012A9A21616597DF71EAFCB65941470D1B40F5EF83A597E179FCB5B57C2EE17DA3BC3769864CB47856767229D67328141FC9AB1DF149E0C5C15AEB80BC58FC71BE18966642D68308B506934B8EF779F78E4DDF30A0DCF93FCAFBFA131A8839FD641949F47EE25CEECF814D55B0BE6E5677C1EFFEC6F29871EF29AA3ED9197B0D83852E050908031EF1ABBB5AFC8B3DD937A076FF6761AB362405C3F7D86A3B17A6170A659C16008950F7F5E06A5DE3E5998895EFA7DEEA060BE9575668F78AB1907B3BA1B7D0001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + }, + { + "Root-CA00000003-XS00000022", + nn::pki::sign::SIGN_ALGO_RSA2048, + tc::cli::FormatUtil::hexStringToBytes("E29775264ADC30C0FDE2DEFCFBBAC406A3B1C2ADDC2644C4C3E46DAD11E4F1ED7BC8B6E8CDBBE87E01020DF807520CE8A8F88BB53BE82CAFF4A1AF7FC5CD69EB34E24BEA74781203B611D09EBF56F82E3F21A494549F407FABA948545B34B64F487E1F27B031A83D337CE40496A3034AC6A2DB9E9353A1967EC7A07A4B3672B5B3F7934FF166D90EC131D8A75DC925ED629F8381A586589A82790489D4BF170F29F8E4BE50811ECBA9564D5517362D8DE777B01FC0A0AD8DED692CEAFD028A468CD6A6BF18A2767801AF8BB954EDB4DA3CED78337F3E9B6A1602B94752C85A53993678FDFA0300A4200CA752FA0D94E5A4B74B726FB61876146E49EC148508F5"), + tc::cli::FormatUtil::hexStringToBytes("0001000458C7FFCDB0A1758925286C3C5029942683A8ED614A4E5C24EECE90548F99A0E59429C3D7D9CA37910CC1F5974E3AA2D7438627FBE456C9C830E5BF5B4440013CD41AA6F0AE02307E3EDEF3EDE0E6404A87234786FED37878F4BF4D964C2290C66AF167AC94869DC2F8378FA3FE0B764F776F9CE479B9E7A4EAE77FCA924B84035C9C06075F5F23BBF3A202AC1AF6A640C62BEEE4603925534783642663238A803D5FC87C5BEA58E09B0669CE9273509A87FACEDA94333FC6264D095708C18F9A50963F0E02A0771AEBC7174D4A89BE158C6CF407BD11658363E924F2808B15C7FE00267565CBD386E576540C906F9B2FFB3AFC64BA1198FA711A48F107E6A9AD000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000526F6F742D4341303030303030303300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015853303030303030323200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000E29775264ADC30C0FDE2DEFCFBBAC406A3B1C2ADDC2644C4C3E46DAD11E4F1ED7BC8B6E8CDBBE87E01020DF807520CE8A8F88BB53BE82CAFF4A1AF7FC5CD69EB34E24BEA74781203B611D09EBF56F82E3F21A494549F407FABA948545B34B64F487E1F27B031A83D337CE40496A3034AC6A2DB9E9353A1967EC7A07A4B3672B5B3F7934FF166D90EC131D8A75DC925ED629F8381A586589A82790489D4BF170F29F8E4BE50811ECBA9564D5517362D8DE777B01FC0A0AD8DED692CEAFD028A468CD6A6BF18A2767801AF8BB954EDB4DA3CED78337F3E9B6A1602B94752C85A53993678FDFA0300A4200CA752FA0D94E5A4B74B726FB61876146E49EC148508F50001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + }, + { + "Root-CA00000003-XS00000024", + nn::pki::sign::SIGN_ALGO_RSA2048, + tc::cli::FormatUtil::hexStringToBytes("C694562BBDC74E83069876608EAD1EE7B1BC715036E9A6BDF58A6D22FD660ABD9CF360AA893D7746C92C5B9D20E480EAD1387786739956E060EBA79BD6ED7C06B2FCC6DBC1734ED12999AFEE5396FEE722FE3B96846D903F00A32671AC0CEDAAAB0C933C950304650A76152ED1B69547031793755599939B42F075CDF137F8C16D35870DE90D1BACBDB4B746F814AED0D3CB7F7C1B789A1FDF9947717B641BA972A6460D3258F5561CAA1D8E20085E8E3B9EBC92C9B6DCBB872A2AE02D74577F65EFA5AAFF29FD52EAA400D1408B2815C5C96CD2B54DF0F6708A755FB1DFA694A7794C4A0422F1BF8FAFE9E6870578133D50FD3D5356506CFCEC94EF79D6B639"), + tc::cli::FormatUtil::hexStringToBytes("000100046EEED0424FF97D659DA43F85DF01E0997BA1FD1593997145BDE95703AABBDB7EFDE315F8CFE8AEC0F710FFF002938AEF8CEF7EBF90F725B6AC4EDA66DCF85132435E974DDBBE8F8C725CD114CE38C83404DFC0123BB4137C676C5C86EB4E211B7C35A03AB0C7E7637F1BF2486FDF0A5CCE5B047483841B5EA6BDC09AE770951ACA79EFB5497C46750585CF6C3FE4159ACE1CDFAB1BA8C00AB2446B57383F2F063344052E48E1557BD60BF973B252AC5AB4F5A12323AFCC1B06718775F3EDA4DD580A47A04D8063844235DC35D21A4FDED5A44EA401519920B73D15C66FDDF9F8286CF9122795EB9D5EA95C6E51603F6E62AA1631549EBE9E272B4BC3AEBC0D89000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000526F6F742D4341303030303030303300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015853303030303030323400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C694562BBDC74E83069876608EAD1EE7B1BC715036E9A6BDF58A6D22FD660ABD9CF360AA893D7746C92C5B9D20E480EAD1387786739956E060EBA79BD6ED7C06B2FCC6DBC1734ED12999AFEE5396FEE722FE3B96846D903F00A32671AC0CEDAAAB0C933C950304650A76152ED1B69547031793755599939B42F075CDF137F8C16D35870DE90D1BACBDB4B746F814AED0D3CB7F7C1B789A1FDF9947717B641BA972A6460D3258F5561CAA1D8E20085E8E3B9EBC92C9B6DCBB872A2AE02D74577F65EFA5AAFF29FD52EAA400D1408B2815C5C96CD2B54DF0F6708A755FB1DFA694A7794C4A0422F1BF8FAFE9E6870578133D50FD3D5356506CFCEC94EF79D6B6390001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + } + }; + + static const std::vector kDevBroadOnRsaKeyAndCert = + { + { + "Root", + nn::pki::sign::SIGN_ALGO_RSA4096, + tc::cli::FormatUtil::hexStringToBytes("D01FE100D43556B24B56DAE971B5A5D384B93003BE1BBF28A2305B060645467D5B0251D2561A274F9E9F9CEC646150AB3D2AE3366866ACA4BAE81AE3D79AA6B04A8BCBA7E6FB648945EBDFDB85BA091FD7D114B5A3A780E3A22E6ECD87B5A4C6F910E4032208814B0CEEA1A17DF739695F617EF63528DB949637A056037F7B32413895C0A8F1982E1565E38EEDC22E590EE2677B8609F48C2E303FBC405CAC18042F822084E4936803DA7F41349248562B8EE12F78F803246330BC7BE7EE724AF458A472E7AB46A1A7C10C2F18FA07C3DDD89806A11C9CC130B247A33C8D47DE67F29E5577B11C43493D5BBA7634A7E4E71531B7DF5981FE24A114554CBD8F005CE1DB35085CCFC77806B6DE254068A26CB5492D4580438FE1E5A9ED75C5ED451DCE789439CCC3BA28A2312A1B8719EF0F73B713950C02591A7462A607F37C0AA7A18FA943A36D752A5F4192F0136100AA9CB41BBE14BEB1F9FC692FDFA09446DE5A9DDE2CA5F68C1C0C21429287CB2DAAA3D263752F73E09FAF4479D2817429F69800AFDE6B592DC19882BDF581CCABF2CB91029EF35C4CFDBBFF49C1FA1B2FE31DE7A560ECB47EBCFE32425B956F81B69917487E3B789151DB2E78B1FD2EBE7E626B3EA165B4FB00CCB751AF507329C4A3939EA6DD9C50A0E7386B0145796B41AF61F78555944F3BC22DC3BD0D00F8798A42B1AAA08320659AC7395AB4F329"), + tc::ByteData() + }, + { + "Root-CA00000004", + nn::pki::sign::SIGN_ALGO_RSA2048, + tc::cli::FormatUtil::hexStringToBytes("C9CC2DC4DF2930E4DF3F8C70A078948775AD5E9AA604C5B4D8EAFF2AA1D214676564EFCA28CC00154554A1A3EA1379E9E6CAACED1593FE88D89AC6B8ACCCAB6E207CEB7CCA29809E2980440662B7D4382A15DA43085745A9AAE59AA05BDB32F66869A2DD4295386C87ECDD3508A2CF60D01E23EC2FE698F470D6001549A2F06759131E534C7006057DEF1D18A83F0AC79CFE80FF5A91F2BED4A0837061190A0329902165403C9A908FB615739F3CE33BF1BAEA16C25BCED7963FACC9D24D9C0AD76FC020B2C4B84C10A741A2CC7D9BAC3AACCCA3529BAC316A9AA75D2A26C7D7D288CBA466C5FE5F454AE679744A90A15772DB3B0E47A49AF031D16DBEAB332B"), + tc::cli::FormatUtil::hexStringToBytes("000100031949429D1E58A62E7E8B56D1B76AE302FD8B97491F778745F75388C4DD0BEB1DF122FB9642151497764A53CF78151845E42CA8FDE486FD2A4F53F8A1BA008A7485FF73B3BF7E3C980729D0656B693219ADE835EB5FFFFCCB7CBB5E307FE0688B888EF2D2053FB7E791E985FD15EF10D79CCA88D6BB15E8E4714A98EE09BF7B8AF053232B6450E6D5FDFFC20A6D1EA6A23812E1014525D56D4082703B86986959A73CD1A14364D2C2DAEA96B095F76C46E4FF4155465E70EF1ED31053D97011E010CC93E7914013687FA3A802996D1E557B1CCC7A7E8F5865C1742E28E26DEF38A93AB5D82D43ECCCBF0BEF22E1FD57E2864333582FEDEABC012F986DDFC3E9447973470308455BDC57AA170B84427F73A29B48F6DA135F66C745C142A84AFB0E6A5EED85D7B9719936F8CE2B621F395F40DC03BEF8854C1117FF0C128641CC7843B97B4346DB226F6026ACB56C278B8E0EA79A2D65EF798E1078AD80ED4B9604D2F08B2CD64A23A3DB270833B402F80851F35BED3EE4577C6660FBF16D9413E09C917A49D42C6DA375BC27F0230DB98F8973AB027B522CD57EC03D25E8B3FC3494C97FB108FE18C68A4336E46C26B6F280D27E34BE287C3E4687BC9D776B76D928D1B6352EC0347D7294AA9360268D26F5F652064AF240D7D00C7C5EA3C32DE62D9B5C4B4CAB6FD7BD371D57C2166095910E4AD8E9ED181EF761936153892D77000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000526F6F74000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014341303030303030303400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000081122A46C9CC2DC4DF2930E4DF3F8C70A078948775AD5E9AA604C5B4D8EAFF2AA1D214676564EFCA28CC00154554A1A3EA1379E9E6CAACED1593FE88D89AC6B8ACCCAB6E207CEB7CCA29809E2980440662B7D4382A15DA43085745A9AAE59AA05BDB32F66869A2DD4295386C87ECDD3508A2CF60D01E23EC2FE698F470D6001549A2F06759131E534C7006057DEF1D18A83F0AC79CFE80FF5A91F2BED4A0837061190A0329902165403C9A908FB615739F3CE33BF1BAEA16C25BCED7963FACC9D24D9C0AD76FC020B2C4B84C10A741A2CC7D9BAC3AACCCA3529BAC316A9AA75D2A26C7D7D288CBA466C5FE5F454AE679744A90A15772DB3B0E47A49AF031D16DBEAB332B0001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + }, + { + "Root-CA00000004-XS00000020", + nn::pki::sign::SIGN_ALGO_RSA2048, + tc::cli::FormatUtil::hexStringToBytes("BA278428765D879A7F215404C6EE4E0A0D3F66C33BC7F8A32FD898E52CB7B63443CED8B00527D89DEDC6BBF60AD1C5C9923021DD555F9BAD4BE0C0C406D3702915E5B34AC2D2ABE503C32A3A23B43834C9157B9A0AF2E4E9C03BEDE2B4C115F5353DFC66BD04A6C079970E38CBDD5A50A2B98FF2D765FCF83381E9E0E849C3573578378FF65951613E95F75EE8EF26183A40AAE4A76D7384EA478D2CDCE80FBA0321A6BF8D69983C3AA7AE5443B72BFE410BF1323CBD88C3560EA13D17D38A2E34041DE7AAF389DE4375225CA87EE349C760C7D99BE7E737C6261CC74E25AE46527AC9619F939057088D7435A6DE7B25AB82DD5410F2579CAEF1491A909D302B"), + tc::cli::FormatUtil::hexStringToBytes("00010004603483F7F4EF3C863FF7B46821E9D83983EFB0BA29C45B10DFB2E8A268AE06084DFEB0B9680B6D40D6DF82CAE7D651E28F31364F5AB567DD9DCCFCBB5EFF8F1CEA92489C046E8BED2AF0D7F9CA0BD50F683633D681B08D1AF417D7B7F8762AD88E3AF0D1C45DAC5407B9D8F24F167AEDC7C5DBCF4E0E176EC341D4F9BA8ADFE749CF37F45688653886566A9045ED89AD353DD289E32631867BDFD7FC67738BDF0E1E858F3BF9DEE25B02206F0B7FFB61F866DA4D862D77DDBDA9668BB61CBDEFACB2583CE5C9FAB22FC90EF8EB06954743E5A32A37A3513C6C795BC8E0C65E2F170B435A8C447765381C4050427F88E9828E6E94144D28441C7F9C7F6D92FE5B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000526F6F742D4341303030303030303400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015853303030303030323000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000BA278428765D879A7F215404C6EE4E0A0D3F66C33BC7F8A32FD898E52CB7B63443CED8B00527D89DEDC6BBF60AD1C5C9923021DD555F9BAD4BE0C0C406D3702915E5B34AC2D2ABE503C32A3A23B43834C9157B9A0AF2E4E9C03BEDE2B4C115F5353DFC66BD04A6C079970E38CBDD5A50A2B98FF2D765FCF83381E9E0E849C3573578378FF65951613E95F75EE8EF26183A40AAE4A76D7384EA478D2CDCE80FBA0321A6BF8D69983C3AA7AE5443B72BFE410BF1323CBD88C3560EA13D17D38A2E34041DE7AAF389DE4375225CA87EE349C760C7D99BE7E737C6261CC74E25AE46527AC9619F939057088D7435A6DE7B25AB82DD5410F2579CAEF1491A909D302B0001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + }, + { + "Root-CA00000004-XS00000021", + nn::pki::sign::SIGN_ALGO_RSA2048, + tc::cli::FormatUtil::hexStringToBytes("CA303087732E88FB2736A4644B6A84DF02393786EBE3DBE914F92224126AB3C38D7CA67322B7D3107B9AC0E00AED9AF13DEDDC62BCB8AC889A47B07196B45BC97AC5DD333CC63E872E30CDB52BD35E055FBA56C2521AFC8BBFC8B06390522E99699750B457C8647A42D6318BE5EAA192ADA35DA7023FC348215613141919821FD5599B93497893CA58E7A88C8D6E6E9EB0A57567BF8C12EB25241BB6082BED7A696658A58DDB66E7CC2562F2EA061E5373E6A5397BF6299F7EA9065B0047AA408F24A5D6F6E4050B99DA86831F663792E08D96F182400182D9BECB917AE315A238CA89741B6AEAF52E238590CDA60FC5B74DC638F9D6FCAF95D2CA7AE005A7B1"), + tc::cli::FormatUtil::hexStringToBytes("000100041282394569ED63B5C4E54747B31E3B32C68B0EB90712E3C04662101CA77A551A0394CC36CB28E369DFBBB9FD78C9C503C3B1F184729C2B05B4D1691581A877AF82F8EC3D70E95B27F459039B65D8221D4864CCB282FF721EBDD6C46863F8C95591C1BF36D0AC118C0027FA3E2D434821A5FEF123C65F17885321F9A520C22B9EE6FC533EEF582706A95DD0CEABACE87B0169E136336E2E9CC0399637CE4CC77B6DA418671A277614F80A0A909569018F624DE0C5E1A67DB134C63F1FFE30BB64C201FDD8DAD42F4C88B247C1C6133CC554DE6B15684320D6AA1E09CB59EAD15F37140C9DD4BE69D87CC90DD8F733C946FBA6C576C70533601AF4E8294A3B5DD4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000526F6F742D4341303030303030303400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015853303030303030323100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000CA303087732E88FB2736A4644B6A84DF02393786EBE3DBE914F92224126AB3C38D7CA67322B7D3107B9AC0E00AED9AF13DEDDC62BCB8AC889A47B07196B45BC97AC5DD333CC63E872E30CDB52BD35E055FBA56C2521AFC8BBFC8B06390522E99699750B457C8647A42D6318BE5EAA192ADA35DA7023FC348215613141919821FD5599B93497893CA58E7A88C8D6E6E9EB0A57567BF8C12EB25241BB6082BED7A696658A58DDB66E7CC2562F2EA061E5373E6A5397BF6299F7EA9065B0047AA408F24A5D6F6E4050B99DA86831F663792E08D96F182400182D9BECB917AE315A238CA89741B6AEAF52E238590CDA60FC5B74DC638F9D6FCAF95D2CA7AE005A7B10001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + }, + { + "Root-CA00000004-XS00000024", + nn::pki::sign::SIGN_ALGO_RSA2048, + tc::cli::FormatUtil::hexStringToBytes("E5E262EA591EEB885DE5A3CA451CE9E4FEA0E89DAA9BBCD9EC6C23E3FEFCE31BE7C7217DAC3E1F5A8331EF3F0C738E9310E765E71AAAB92C6E18D36071E7F93446E06E2581A28483AE9EB9C8217C1D68A21FD098E186C71B6A1E1E2626C80B69FFD562EAD515C77D616554F6B74B34DEEB0DE905F44631F56A6FF12B47AC0190578267559592FAB173146071ECFBB014951512E45299A21C0B8B3377E55DCD95625130E533877BF11157585D43DB00F61DFE77CFA44F5F29F44945DBF4214F37214921C16ED7D74455B2AE7C8CA15DFBD20A4D5E6501965B78C2C542727D7A91C071C56403738F07E858239CD150DB304BA5ACD8751145A9A91D3E97085AD217"), + tc::cli::FormatUtil::hexStringToBytes("000100049EA82934BD64EDCED4D506BB68BF1F217BAD912BEF134B687C8E590F2A9355D8EDD9BE00D85735A83820F69978B3E2876FE07BBB7F356B25CDCC98EEEA17BFA9EF649769E921BFDA7867D0570D2F0A7A223E7ACD8D9AE1F304F8EEC6C96B11805436CF686FBD05969E4C0D19A63EF89DA00899222D2CF006776FF027118E96CB027114AABBA268CB1775022B25EAF045C9E4A328A55FF85CFFFFFEAEFB78F1CD184ED9042D23A06F0161E5FBADE92638A483A1A3680DC9994A03D02F1D76EB0EE793843D31CCC172A3993C7344B2C5E634F2B89C47F85FD8B74D21745AEBCFD55E32FC03F6BCD40421499261F8AB93AC8A07E6EF5436036283A3B86192890A78000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000526F6F742D4341303030303030303400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015853303030303030323400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000E5E262EA591EEB885DE5A3CA451CE9E4FEA0E89DAA9BBCD9EC6C23E3FEFCE31BE7C7217DAC3E1F5A8331EF3F0C738E9310E765E71AAAB92C6E18D36071E7F93446E06E2581A28483AE9EB9C8217C1D68A21FD098E186C71B6A1E1E2626C80B69FFD562EAD515C77D616554F6B74B34DEEB0DE905F44631F56A6FF12B47AC0190578267559592FAB173146071ECFBB014951512E45299A21C0B8B3377E55DCD95625130E533877BF11157585D43DB00F61DFE77CFA44F5F29F44945DBF4214F37214921C16ED7D74455B2AE7C8CA15DFBD20A4D5E6501965B78C2C542727D7A91C071C56403738F07E858239CD150DB304BA5ACD8751145A9A91D3E97085AD2170001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + } + }; + + if (isDev) + { + if (xci_header_sign_key.isNull()) + xci_header_sign_key = tc::crypto::RsaPublicKey(kXciHeaderSignModulus.data(), kXciHeaderSignModulus.size()); + + if (xci_cert_sign_key.isNull()) + xci_cert_sign_key = tc::crypto::RsaPublicKey(kXciCertSignModulus.data(), kXciCertSignModulus.size()); + + if (pkg2_sign_key.isNull()) + pkg2_sign_key = tc::crypto::RsaPublicKey(kDevPackage2HeaderModulus.data(), kDevPackage2HeaderModulus.size()); + + for (auto itr = kDevNcaHeaderSign0Modulus.begin(); itr != kDevNcaHeaderSign0Modulus.end(); itr++) + { + if (nca_header_sign0_key.find(itr->generation) == nca_header_sign0_key.end()) + nca_header_sign0_key[itr->generation] = tc::crypto::RsaPublicKey(itr->modulus.data(), itr->modulus.size()); + } + + for (auto itr = kDevAcidSignModulus.begin(); itr != kDevAcidSignModulus.end(); itr++) + { + if (acid_sign_key.find(itr->generation) == acid_sign_key.end()) + acid_sign_key[itr->generation] = tc::crypto::RsaPublicKey(itr->modulus.data(), itr->modulus.size()); + } + + for (auto itr = kDevBroadOnRsaKeyAndCert.begin(); itr != kDevBroadOnRsaKeyAndCert.end(); itr++) + { + if (broadon_signer.find(itr->issuer) == broadon_signer.end()) + broadon_signer[itr->issuer] = {itr->certificate, itr->key_type, tc::crypto::RsaPublicKey(itr->modulus.data(), itr->modulus.size())}; + } + } + else + { + if (xci_header_sign_key.isNull()) + xci_header_sign_key = tc::crypto::RsaPublicKey(kXciHeaderSignModulus.data(), kXciHeaderSignModulus.size()); + + if (xci_cert_sign_key.isNull()) + xci_cert_sign_key = tc::crypto::RsaPublicKey(kXciCertSignModulus.data(), kXciCertSignModulus.size()); + + if (pkg2_sign_key.isNull()) + pkg2_sign_key = tc::crypto::RsaPublicKey(kProdPackage2HeaderModulus.data(), kProdPackage2HeaderModulus.size()); + + for (auto itr = kProdNcaHeaderSign0Modulus.begin(); itr != kProdNcaHeaderSign0Modulus.end(); itr++) + { + if (nca_header_sign0_key.find(itr->generation) == nca_header_sign0_key.end()) + nca_header_sign0_key[itr->generation] = tc::crypto::RsaPublicKey(itr->modulus.data(), itr->modulus.size()); + } + + for (auto itr = kProdAcidSignModulus.begin(); itr != kProdAcidSignModulus.end(); itr++) + { + if (acid_sign_key.find(itr->generation) == acid_sign_key.end()) + acid_sign_key[itr->generation] = tc::crypto::RsaPublicKey(itr->modulus.data(), itr->modulus.size()); + } + + for (auto itr = kProdBroadOnRsaKeyAndCert.begin(); itr != kProdBroadOnRsaKeyAndCert.end(); itr++) + { + if (broadon_signer.find(itr->issuer) == broadon_signer.end()) + broadon_signer[itr->issuer] = {itr->certificate, itr->key_type, tc::crypto::RsaPublicKey(itr->modulus.data(), itr->modulus.size())}; + } + } +} \ No newline at end of file diff --git a/src/KeyBag.h b/src/KeyBag.h new file mode 100644 index 0000000..e198932 --- /dev/null +++ b/src/KeyBag.h @@ -0,0 +1,85 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 acid_sign_key; + + // pkg1 and pkg2 + std::map pkg1_key; + std::map pkg2_key; + tc::Optional pkg2_sign_key; + + // nca + tc::Optional nca_header_key; + std::map nca_header_sign0_key; + std::array, kNcaKeakNum> nca_key_area_encryption_key; + std::array, kNcaKeakNum> nca_key_area_encryption_key_hw; + + // external content keys (nca<->ticket) + std::map external_content_keys; + tc::Optional 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 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 nrr_certificate_sign_key; + + // xci + tc::Optional xci_header_sign_key; + std::map xci_header_key; + std::map xci_initial_data_kek; + tc::Optional xci_cert_sign_key; + + // ticket + std::map 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_signer; +}; + +class KeyBagInitializer : public KeyBag +{ +public: + KeyBagInitializer(bool isDev, const tc::Optional& keyfile_path, const tc::Optional& tik_path, const tc::Optional& 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); +}; + +} \ No newline at end of file diff --git a/src/KeyConfiguration.cpp b/src/KeyConfiguration.cpp deleted file mode 100644 index 8164091..0000000 --- a/src/KeyConfiguration.cpp +++ /dev/null @@ -1,413 +0,0 @@ -#include "KeyConfiguration.h" -#include -#include -#include - -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 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; -} \ No newline at end of file diff --git a/src/KeyConfiguration.h b/src/KeyConfiguration.h deleted file mode 100644 index 4a112c6..0000000 --- a/src/KeyConfiguration.h +++ /dev/null @@ -1,217 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 mPkiRootKeyList; - - /* Nca External Keys */ - fnd::List mNcaExternalContentKeyList; - - template - 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; - } -}; \ No newline at end of file diff --git a/src/KipProcess.cpp b/src/KipProcess.cpp index 2c787e2..2b7468b 100644 --- a/src/KipProcess.cpp +++ b/src/KipProcess.cpp @@ -1,266 +1,273 @@ #include "KipProcess.h" -#include -#include - -#include -#include -#include - #include -KipProcess::KipProcess(): +#include + +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& file) +void nstool::KipProcess::setInputFile(const std::shared_ptr& 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 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 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 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 maps = kern.getMemoryMaps().getMemoryMaps(); - fnd::List 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 interupts = kern.getInterupts().getInteruptList(); - std::cout << " Interupts Flags:" << std::endl; - for (uint32_t i = 0; i < interupts.size(); i++) + std::vector 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 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)); } \ No newline at end of file diff --git a/src/KipProcess.h b/src/KipProcess.h index 09580e1..b9bfd46 100644 --- a/src/KipProcess.h +++ b/src/KipProcess.h @@ -1,12 +1,9 @@ #pragma once -#include -#include -#include -#include -#include +#include "types.h" + #include -#include "common.h" +namespace nstool { class KipProcess { @@ -15,21 +12,26 @@ public: void process(); - void setInputFile(const fnd::SharedPtr& file); + void setInputFile(const std::shared_ptr& file); void setCliOutputMode(CliOutputMode type); void setVerifyMode(bool verify); private: - const std::string kModuleName = "KipProcess"; + std::string mModuleName; - fnd::SharedPtr mFile; + std::shared_ptr mFile; CliOutputMode mCliOutputMode; bool mVerify; nn::hac::KernelInitialProcessHeader mHdr; - fnd::Vec 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); -}; \ No newline at end of file + + std::string formatMappingAsString(const nn::hac::MemoryMappingHandler::sMemoryMapping& map) const; +}; + +} \ No newline at end of file diff --git a/src/MetaProcess.cpp b/src/MetaProcess.cpp index 40877bb..c96aa08 100644 --- a/src/MetaProcess.cpp +++ b/src/MetaProcess.cpp @@ -1,23 +1,19 @@ #include "MetaProcess.h" -#include -#include - #include #include #include #include -#include - -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& file) +void nstool::MetaProcess::setInputFile(const std::shared_ptr& 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 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 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 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 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 interupts = kern.getInterupts().getInteruptList(); - std::cout << " Interupts Flags:" << std::endl; - for (uint32_t i = 0; i < interupts.size(); i++) + std::vector 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 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)); } \ No newline at end of file diff --git a/src/MetaProcess.h b/src/MetaProcess.h index 85618a5..3b83797 100644 --- a/src/MetaProcess.h +++ b/src/MetaProcess.h @@ -1,12 +1,10 @@ #pragma once -#include -#include -#include -#include -#include -#include "KeyConfiguration.h" +#include "types.h" +#include "KeyBag.h" -#include "common.h" +#include + +namespace nstool { class MetaProcess { @@ -15,18 +13,18 @@ public: void process(); - void setInputFile(const fnd::SharedPtr& file); - void setKeyCfg(const KeyConfiguration& keycfg); + void setInputFile(const std::shared_ptr& 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 mFile; - KeyConfiguration mKeyCfg; + std::shared_ptr 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); -}; \ No newline at end of file + + std::string formatMappingAsString(const nn::hac::MemoryMappingHandler::sMemoryMapping& map) const; +}; + +} \ No newline at end of file diff --git a/src/NacpProcess.cpp b/src/NacpProcess.cpp index 1dbe44e..5aeaadb 100644 --- a/src/NacpProcess.cpp +++ b/src/NacpProcess.cpp @@ -1,525 +1,530 @@ #include "NacpProcess.h" -#include -#include -#include - -#include -#include - #include -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& file) +void nstool::NacpProcess::setInputFile(const std::shared_ptr& 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 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"); } } \ No newline at end of file diff --git a/src/NacpProcess.h b/src/NacpProcess.h index 7084d81..6d88485 100644 --- a/src/NacpProcess.h +++ b/src/NacpProcess.h @@ -1,11 +1,9 @@ #pragma once -#include -#include -#include -#include +#include "types.h" + #include -#include "common.h" +namespace nstool { class NacpProcess { @@ -14,16 +12,16 @@ public: void process(); - void setInputFile(const fnd::SharedPtr& file); + void setInputFile(const std::shared_ptr& 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 mFile; + std::shared_ptr mFile; CliOutputMode mCliOutputMode; bool mVerify; @@ -31,4 +29,6 @@ private: void importNacp(); void displayNacp(); -}; \ No newline at end of file +}; + +} \ No newline at end of file diff --git a/src/NcaProcess.cpp b/src/NcaProcess.cpp index 51cad14..6cfda7a 100644 --- a/src/NcaProcess.cpp +++ b/src/NcaProcess.cpp @@ -1,36 +1,28 @@ #include "NcaProcess.h" - -#include "PfsProcess.h" -#include "RomfsProcess.h" #include "MetaProcess.h" +#include "util.h" -#include -#include -#include - -#include -#include -#include -#include +#include #include #include -#include -#include +#include +#include +#include +#include +#include -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& file) +void nstool::NcaProcess::setInputFile(const std::shared_ptr& 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& 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& 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(); // 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 hash_layers; - fnd::LayeredIntegrityMetadata::sLayer data_layer; - fnd::List 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 hash_layers; - fnd::LayeredIntegrityMetadata::sLayer data_layer; - fnd::List 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(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(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) - { - info.reader = new fnd::LayeredIntegrityWrappedIFile(info.reader, info.layered_intergrity_metadata); - } - else if (info.hash_type != nn::hac::nca::HashType::None) + switch (info.hash_type) { - error.clear(); - error << "HashType(" << nn::hac::ContentArchiveUtil::getHashTypeAsString(info.hash_type) << "): UNKNOWN"; - throw fnd::Exception(kModuleName, error.str()); + case (nn::hac::nca::HashType::None): + break; + case (nn::hac::nca::HashType::HierarchicalSha256): + info.reader = std::make_shared(nn::hac::HierarchicalSha256Stream(info.reader, info.hierarchicalsha256_hdr)); + break; + case (nn::hac::nca::HashType::HierarchicalIntegrity): + info.reader = std::make_shared(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))); + } + + // filter out unrecognised format types + switch (info.format_type) + { + case (nn::hac::nca::FormatType::PartitionFs): + info.fs_meta = nn::hac::PartitionFsMetaGenerator(info.reader); + info.fs_reader = std::make_shared(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(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); + std::shared_ptr 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(new fnd::OffsetAdjustedIFile(mPartitions[nn::hac::nca::PARTITION_CODE].reader, file.offset, file.size)); + npdm.setInputFile(npdm_file); npdm.setKeyCfg(mKeyCfg); npdm.setVerifyMode(true); - npdm.setCliOutputMode(0); + npdm.setCliOutputMode(CliOutputMode(false, false, false, false)); npdm.process(); - if (fnd::rsa::pss::rsaVerify(npdm.getMeta().getAccessControlInfoDesc().getContentArchiveHeaderSignature2Key(), fnd::sha::HASH_SHA256, mHdrHash.bytes, mHdrBlock.signature_acid) != 0) + if (tc::crypto::VerifyRsa2048PssSha256(mHdrBlock.signature_acid.data(), mHdrHash.data(), npdm.getMeta().getAccessControlInfoDesc().getContentArchiveHeaderSignature2Key()) == false) { - std::cout << "[WARNING] NCA Header ACID Signature: FAIL" << std::endl; + 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 << " "; + fmt::print(" "); - 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 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())) + ":/"); - } + mount_point_name = fmt::format("{:d}", index); + } - if (mPartitionPath[index].doExtract) - romfs.setExtractPath(mPartitionPath[index].path); - romfs.process(); - } + mount_points.push_back( { mount_point_name, partition.fs_meta } ); } + + tc::io::VirtualFileSystem::FileSystemMeta fs_meta = nn::hac::CombinedFsMetaGenerator(mount_points); + + std::shared_ptr nca_fs = std::make_shared(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) { diff --git a/src/NcaProcess.h b/src/NcaProcess.h index d467077..535a0d8 100644 --- a/src/NcaProcess.h +++ b/src/NcaProcess.h @@ -1,14 +1,13 @@ #pragma once -#include -#include -#include -#include -#include +#include "types.h" +#include "KeyBag.h" +#include "FsProcess.h" + #include -#include "KeyConfiguration.h" +#include +#include - -#include "common.h" +namespace nstool { class NcaProcess { @@ -18,39 +17,36 @@ public: void process(); // generic - void setInputFile(const fnd::SharedPtr& file); - void setKeyCfg(const KeyConfiguration& keycfg); + void setInputFile(const std::shared_ptr& 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& extract_jobs); + // post process() get FS out + const std::shared_ptr& 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 mFile; - KeyConfiguration mKeyCfg; + std::shared_ptr mFile; + KeyBag mKeyCfg; CliOutputMode mCliOutputMode; bool mVerify; - struct sExtract - { - std::string path; - bool doExtract; - } mPartitionPath[nn::hac::nca::kPartitionNum]; + // fs processing + std::shared_ptr 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 kak_list; + std::vector kak_list; - sOptional aes_ctr; + tc::Optional aes_ctr; } mContentKey; - + + struct SparseInfo + { + + }; + + // raw partition data struct sPartitionInfo { - fnd::SharedPtr reader; + std::shared_ptr reader; + tc::io::VirtualFileSystem::FileSystemMeta fs_meta; + std::shared_ptr 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 mPartitions; void importHeader(); void generateNcaBodyEncryptionKeys(); @@ -111,5 +125,7 @@ private: void displayHeader(); void processPartitions(); - const char* getContentTypeForMountStr(nn::hac::nca::ContentType cont_type) const; -}; \ No newline at end of file + std::string getContentTypeForMountStr(nn::hac::nca::ContentType cont_type) const; +}; + +} \ No newline at end of file diff --git a/src/NroProcess.cpp b/src/NroProcess.cpp index 465c1cc..66a061c 100644 --- a/src/NroProcess.cpp +++ b/src/NroProcess.cpp @@ -1,25 +1,21 @@ -#include -#include -#include -#include -#include -#include -#include #include "NroProcess.h" -NroProcess::NroProcess(): +#include + +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& file) +void nstool::NroProcess::setInputFile(const std::shared_ptr& 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& extract_jobs) +{ + mAssetProc.setRomfsExtractJobs(extract_jobs); +} + +const nstool::RoMetadataProcess& nstool::NroProcess::getRoMetadataProcess() const { return mRoMeta; } -void NroProcess::importHeader() +void nstool::NroProcess::importHeader() { - fnd::Vec 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*)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(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()) { diff --git a/src/NroProcess.h b/src/NroProcess.h index 2ef222f..ac93711 100644 --- a/src/NroProcess.h +++ b/src/NroProcess.h @@ -1,15 +1,11 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include +#include "types.h" +#include "RoMetadataProcess.h" #include "AssetProcess.h" -#include "common.h" -#include "RoMetadataProcess.h" +#include + +namespace nstool { class NroProcess { @@ -18,7 +14,7 @@ public: void process(); - void setInputFile(const fnd::SharedPtr& file); + void setInputFile(const std::shared_ptr& 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& extract_jobs); - const RoMetadataProcess& getRoMetadataProcess() const; + const nstool::RoMetadataProcess& getRoMetadataProcess() const; private: - const std::string kModuleName = "NroProcess"; + std::string mModuleName; - fnd::SharedPtr mFile; + std::shared_ptr mFile; CliOutputMode mCliOutputMode; bool mVerify; nn::hac::NroHeader mHdr; - fnd::Vec 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(); -}; \ No newline at end of file +}; + +} \ No newline at end of file diff --git a/src/NsoProcess.cpp b/src/NsoProcess.cpp index 7b8db33..7680d49 100644 --- a/src/NsoProcess.cpp +++ b/src/NsoProcess.cpp @@ -1,14 +1,11 @@ -#include -#include -#include -#include -#include -#include #include "NsoProcess.h" -NsoProcess::NsoProcess(): +#include + +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& file) +void nstool::NsoProcess::setInputFile(const std::shared_ptr& 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 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 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()) { @@ -237,4 +261,25 @@ void NsoProcess::processRoMeta() mRoMeta.setCliOutputMode(mCliOutputMode); mRoMeta.process(); } +} + +size_t nstool::NsoProcess::decompressData(const byte_t* src, size_t src_len, byte_t* dst, size_t dst_capacity) +{ + if (src_len >= LZ4_MAX_INPUT_SIZE) + { + return 0; + } + + int32_t src_len_input = int32_t(src_len); + int32_t dst_capcacity_input = (dst_capacity < LZ4_MAX_INPUT_SIZE) ? int32_t(dst_capacity) : LZ4_MAX_INPUT_SIZE; + + int32_t decomp_size = LZ4_decompress_safe((const char*)src, (char*)dst, src_len_input, dst_capcacity_input); + + if (decomp_size < 0) + { + memset(dst, 0, dst_capacity); + return 0; + } + + return size_t(decomp_size); } \ No newline at end of file diff --git a/src/NsoProcess.h b/src/NsoProcess.h index 2d29784..766e421 100644 --- a/src/NsoProcess.h +++ b/src/NsoProcess.h @@ -1,14 +1,11 @@ #pragma once -#include -#include -#include -#include -#include +#include "types.h" +#include "RoMetadataProcess.h" + #include #include -#include "common.h" -#include "RoMetadataProcess.h" +namespace nstool { class NsoProcess { @@ -17,7 +14,7 @@ public: void process(); - void setInputFile(const fnd::SharedPtr& file); + void setInputFile(const std::shared_ptr& 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 mFile; + std::shared_ptr mFile; CliOutputMode mCliOutputMode; bool mVerify; bool mIs64BitInstruction; @@ -37,11 +34,15 @@ private: bool mListSymbols; nn::hac::NsoHeader mHdr; - fnd::Vec mTextBlob, mRoBlob, mDataBlob; - RoMetadataProcess mRoMeta; + tc::ByteData mTextBlob, mRoBlob, mDataBlob; + nstool::RoMetadataProcess mRoMeta; void importHeader(); void importCodeSegments(); void displayNsoHeader(); void processRoMeta(); -}; \ No newline at end of file + + size_t decompressData(const byte_t* src, size_t src_len, byte_t* dst, size_t dst_capacity); +}; + +} \ No newline at end of file diff --git a/src/PfsProcess.cpp b/src/PfsProcess.cpp index 36287e5..c7be0ac 100644 --- a/src/PfsProcess.cpp +++ b/src/PfsProcess.cpp @@ -1,199 +1,132 @@ #include "PfsProcess.h" - -#include -#include - -#include -#include +#include "util.h" #include +#include + +#include +#include -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(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& file) +void nstool::PfsProcess::setInputFile(const std::shared_ptr& 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& 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& nstool::PfsProcess::getFileSystem() const { - fnd::Vec 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& 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& 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; } \ No newline at end of file diff --git a/src/PfsProcess.h b/src/PfsProcess.h index 8ae6fd7..e3ff3c5 100644 --- a/src/PfsProcess.h +++ b/src/PfsProcess.h @@ -1,11 +1,10 @@ #pragma once -#include -#include -#include -#include +#include "types.h" +#include "FsProcess.h" + #include -#include "common.h" +namespace nstool { class PfsProcess { @@ -15,39 +14,35 @@ public: void process(); // generic - void setInputFile(const fnd::SharedPtr& file); + void setInputFile(const std::shared_ptr& 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& extract_jobs); + // post process() get PFS/FS out const nn::hac::PartitionFsHeader& getPfsHeader() const; + const std::shared_ptr& getFileSystem() const; private: - const std::string kModuleName = "PfsProcess"; static const size_t kCacheSize = 0x10000; - fnd::SharedPtr mFile; + std::string mModuleName; + + std::shared_ptr mFile; CliOutputMode mCliOutputMode; bool mVerify; - std::string mExtractPath; - bool mExtract; - std::string mMountName; - bool mListFs; - - fnd::Vec mCache; - nn::hac::PartitionFsHeader mPfs; - void importHeader(); - void displayHeader(); - void displayFs(); + std::shared_ptr mFileSystem; + FsProcess mFsProcess; + size_t determineHeaderSize(const nn::hac::sPfsHeader* hdr); bool validateHeaderMagic(const nn::hac::sPfsHeader* hdr); - void validateHfs(); - void extractFs(); -}; \ No newline at end of file +}; + +} \ No newline at end of file diff --git a/src/PkiCertProcess.cpp b/src/PkiCertProcess.cpp deleted file mode 100644 index 4433ae7..0000000 --- a/src/PkiCertProcess.cpp +++ /dev/null @@ -1,193 +0,0 @@ -#include -#include -#include -#include -#include -#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& 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 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 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& 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; -} \ No newline at end of file diff --git a/src/PkiCertProcess.h b/src/PkiCertProcess.h deleted file mode 100644 index 88ed54d..0000000 --- a/src/PkiCertProcess.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include "KeyConfiguration.h" -#include "common.h" - -class PkiCertProcess -{ -public: - PkiCertProcess(); - - void process(); - - void setInputFile(const fnd::SharedPtr& 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 mFile; - KeyConfiguration mKeyCfg; - CliOutputMode mCliOutputMode; - bool mVerify; - - fnd::List> mCert; - - void importCerts(); - void validateCerts(); - void displayCerts(); - void displayCert(const nn::pki::SignedData& 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; -}; \ No newline at end of file diff --git a/src/PkiValidator.cpp b/src/PkiValidator.cpp index 37bb9b5..861b15d 100644 --- a/src/PkiValidator.cpp +++ b/src/PkiValidator.cpp @@ -1,18 +1,19 @@ -#include -#include -#include -#include #include "PkiValidator.h" -PkiValidator::PkiValidator() +#include +#include +#include + +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> old_certs = mCertificateBank; + std::vector> 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>& certs) +void nstool::PkiValidator::addCertificates(const std::vector>& certs) { for (size_t i = 0; i < certs.size(); i++) { @@ -35,12 +36,12 @@ void PkiValidator::addCertificates(const fnd::List& cert) +void nstool::PkiValidator::addCertificate(const nn::pki::SignedData& cert) { std::string cert_ident; nn::pki::sign::SignatureAlgo cert_sign_algo; nn::pki::sign::HashAlgo cert_hash_algo; - fnd::Vec cert_hash; + tc::ByteData cert_hash; try { @@ -48,7 +49,7 @@ void PkiValidator::addCertificate(const nn::pki::SignedData& signature, const fnd::Vec& 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); - + nn::pki::sign::SignatureAlgo sign_algo = nn::pki::sign::getSignatureAlgo(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& cert, std::string& ident) const +void nstool::PkiValidator::makeCertIdent(const nn::pki::SignedData& 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(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& PkiValidator::getCert(const std::string& ident) const +const nn::pki::SignedData& 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& 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"); } \ No newline at end of file diff --git a/src/PkiValidator.h b/src/PkiValidator.h index 7b7a0ab..27ebf57 100644 --- a/src/PkiValidator.h +++ b/src/PkiValidator.h @@ -1,34 +1,34 @@ #pragma once -#include -#include -#include -#include +#include "types.h" +#include "KeyBag.h" + #include #include -#include -#include "KeyConfiguration.h" + +namespace nstool { class PkiValidator { public: PkiValidator(); - void setKeyCfg(const KeyConfiguration& keycfg); - void addCertificates(const fnd::List>& certs); + void setKeyCfg(const KeyBag& keycfg); + void addCertificates(const std::vector>& certs); void addCertificate(const nn::pki::SignedData& cert); void clearCertificates(); - void validateSignature(const std::string& issuer, nn::pki::sign::SignatureId signature_id, const fnd::Vec& signature, const fnd::Vec& 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> mCertificateBank; + KeyBag mKeyCfg; + std::vector> mCertificateBank; void makeCertIdent(const nn::pki::SignedData& 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& getCert(const std::string& ident) const; - fnd::sha::HashType getCryptoHashAlgoFromEsSignHashAlgo(nn::pki::sign::HashAlgo hash_algo) const; -}; \ No newline at end of file +}; + +} \ No newline at end of file diff --git a/src/RoMetadataProcess.cpp b/src/RoMetadataProcess.cpp index 81e9b47..8066e5a 100644 --- a/src/RoMetadataProcess.cpp +++ b/src/RoMetadataProcess.cpp @@ -1,12 +1,12 @@ +#include "RoMetadataProcess.h" + #include #include #include -#include -#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& 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& RoMetadataProcess::getSdkVerApiList() const +const std::vector& nstool::RoMetadataProcess::getSdkVerApiList() const { return mSdkVerApiList; } -const std::vector& RoMetadataProcess::getPublicApiList() const +const std::vector& nstool::RoMetadataProcess::getPublicApiList() const { return mPublicApiList; } -const std::vector& RoMetadataProcess::getDebugApiList() const +const std::vector& nstool::RoMetadataProcess::getDebugApiList() const { return mDebugApiList; } -const std::vector& RoMetadataProcess::getPrivateApiList() const +const std::vector& nstool::RoMetadataProcess::getPrivateApiList() const { return mPrivateApiList; } -const std::vector& RoMetadataProcess::getGuidelineApiList() const +const std::vector& nstool::RoMetadataProcess::getGuidelineApiList() const { return mGuidelineApiList; } -const fnd::List& RoMetadataProcess::getSymbolList() const +const std::vector& 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: diff --git a/src/RoMetadataProcess.h b/src/RoMetadataProcess.h index df854c1..54bf085 100644 --- a/src/RoMetadataProcess.h +++ b/src/RoMetadataProcess.h @@ -1,14 +1,11 @@ #pragma once -#include -#include -#include -#include +#include "types.h" +#include "SdkApiString.h" +#include "ElfSymbolParser.h" #include -#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& 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& getSdkVerApiList() const; - const std::vector& getPublicApiList() const; - const std::vector& getDebugApiList() const; - const std::vector& getPrivateApiList() const; - const std::vector& getGuidelineApiList() const; - const fnd::List& getSymbolList() const; + const std::vector& getSdkVerApiList() const; + const std::vector& getPublicApiList() const; + const std::vector& getDebugApiList() const; + const std::vector& getPrivateApiList() const; + const std::vector& getGuidelineApiList() const; + const std::vector& 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 mRoBlob; + tc::ByteData mRoBlob; std::vector mSdkVerApiList; std::vector mPublicApiList; std::vector 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; -}; \ No newline at end of file + std::string getSectionIndexStr(uint16_t shn_index) const; + std::string getSymbolTypeStr(byte_t symbol_type) const; + std::string getSymbolBindingStr(byte_t symbol_binding) const; +}; + +} \ No newline at end of file diff --git a/src/RomfsProcess.cpp b/src/RomfsProcess.cpp index f03e0be..6fb3d7b 100644 --- a/src/RomfsProcess.cpp +++ b/src/RomfsProcess.cpp @@ -1,341 +1,128 @@ -#include -#include -#include -#include -#include -#include "CompressedArchiveIFile.h" #include "RomfsProcess.h" +#include "util.h" -RomfsProcess::RomfsProcess() : +#include +#include + + +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(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(((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(((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(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& file) +void nstool::RomfsProcess::setInputFile(const std::shared_ptr& 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& 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); } \ No newline at end of file diff --git a/src/RomfsProcess.h b/src/RomfsProcess.h index d6cee47..9d99609 100644 --- a/src/RomfsProcess.h +++ b/src/RomfsProcess.h @@ -1,134 +1,42 @@ #pragma once -#include -#include -#include -#include -#include -#include +#include "types.h" +#include "FsProcess.h" + #include -#include "common.h" +namespace nstool { class RomfsProcess { public: - struct sDirectory; - struct sFile; - - struct sDirectory - { - std::string name; - fnd::List dir_list; - fnd::List 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& file); + void setInputFile(const std::shared_ptr& 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& extract_jobs); + void setShowFsTree(bool show_fs_tree); private: - const std::string kModuleName = "RomfsProcess"; static const size_t kCacheSize = 0x10000; - fnd::SharedPtr mFile; + std::string mModuleName; + + std::shared_ptr mFile; CliOutputMode mCliOutputMode; bool mVerify; - std::string mExtractPath; - bool mExtract; - std::string mMountName; - bool mListFs; - - fnd::Vec mCache; - + nn::hac::sRomfsHeader mRomfsHeader; size_t mDirNum; size_t mFileNum; - nn::hac::sRomfsHeader mHdr; - fnd::Vec mDirNodes; - fnd::Vec 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); } + std::shared_ptr mFileSystem; + FsProcess mFsProcess; +}; - - 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(); -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/SdkApiString.cpp b/src/SdkApiString.cpp index 1f3c9e6..6714772 100644 --- a/src/SdkApiString.cpp +++ b/src/SdkApiString.cpp @@ -1,13 +1,13 @@ #include #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; diff --git a/src/SdkApiString.h b/src/SdkApiString.h index c1a8ffa..7e3a396 100644 --- a/src/SdkApiString.h +++ b/src/SdkApiString.h @@ -1,5 +1,7 @@ #pragma once -#include +#include "types.h" + +namespace nstool { class SdkApiString { @@ -42,4 +44,6 @@ private: std::string mModuleName; void resolveApiString(const std::string& full_str); -}; \ No newline at end of file +}; + +} \ No newline at end of file diff --git a/src/Settings.cpp b/src/Settings.cpp new file mode 100644 index 0000000..82487c6 --- /dev/null +++ b/src/Settings.cpp @@ -0,0 +1,1071 @@ +#include "Settings.h" +#include "types.h" +#include "version.h" +#include "util.h" + +#include +#include +#include +#include +#include + + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +class UnkOptionHandler : public tc::cli::OptionParser::IOptionHandler +{ +public: + UnkOptionHandler(const std::string& module_label) : mModuleLabel(module_label) + {} + + const std::vector& getOptionStrings() const + { + throw tc::InvalidOperationException("getOptionStrings() not defined for UnkOptionHandler."); + } + + void processOption(const std::string& option, const std::vector& params) + { + throw tc::Exception(mModuleLabel, "Unrecognized option: \"" + option + "\""); + } +private: + std::string mModuleLabel; +}; + +class DeprecatedOptionHandler : public tc::cli::OptionParser::IOptionHandler +{ +public: + DeprecatedOptionHandler(const std::string& warn_message, const std::vector& opts) : + mWarnMessage(warn_message), + mOptStrings(opts) + {} + + const std::vector& getOptionStrings() const + { + return mOptStrings; + } + + void processOption(const std::string& option, const std::vector& params) + { + fmt::print("[WARNING] Option \"{}\" is deprecated.{}{}\n", option, (mWarnMessage.empty() ? "" : " "), mWarnMessage); + } +private: + std::string mWarnMessage; + std::vector mOptStrings; +}; + +class FlagOptionHandler : public tc::cli::OptionParser::IOptionHandler +{ +public: + FlagOptionHandler(bool& flag, const std::vector& opts) : + mFlag(flag), + mOptStrings(opts) + {} + + const std::vector& getOptionStrings() const + { + return mOptStrings; + } + + void processOption(const std::string& option, const std::vector& params) + { + if (params.size() != 0) + { + throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" is a flag, that takes no parameters.", option)); + } + + mFlag = true; + } +private: + bool& mFlag; + std::vector mOptStrings; +}; + +class SingleParamStringOptionHandler : public tc::cli::OptionParser::IOptionHandler +{ +public: + SingleParamStringOptionHandler(tc::Optional& param, const std::vector& opts) : + mParam(param), + mOptStrings(opts) + {} + + const std::vector& getOptionStrings() const + { + return mOptStrings; + } + + void processOption(const std::string& option, const std::vector& params) + { + if (params.size() != 1) + { + throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option)); + } + + mParam = params[0]; + } +private: + tc::Optional& mParam; + std::vector mOptStrings; +}; + +class SingleParamPathOptionHandler : public tc::cli::OptionParser::IOptionHandler +{ +public: + SingleParamPathOptionHandler(tc::Optional& param, const std::vector& opts) : + mParam(param), + mOptStrings(opts) + {} + + const std::vector& getOptionStrings() const + { + return mOptStrings; + } + + void processOption(const std::string& option, const std::vector& params) + { + if (params.size() != 1) + { + throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option)); + } + + mParam = params[0]; + } +private: + tc::Optional& mParam; + std::vector mOptStrings; +}; + +class SingleParamSizetOptionHandler : public tc::cli::OptionParser::IOptionHandler +{ +public: + SingleParamSizetOptionHandler(size_t& param, const std::vector& opts) : + mParam(param), + mOptStrings(opts) + {} + + const std::vector& getOptionStrings() const + { + return mOptStrings; + } + + void processOption(const std::string& option, const std::vector& params) + { + if (params.size() != 1) + { + throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option)); + } + + mParam = strtoul(params[0].c_str(), nullptr, 0); + } +private: + size_t& mParam; + std::vector mOptStrings; +}; + +class SingleParamAesKeyOptionHandler : public tc::cli::OptionParser::IOptionHandler +{ +public: + SingleParamAesKeyOptionHandler(tc::Optional& param, const std::vector& opts) : + mParam(param), + mOptStrings(opts) + {} + + const std::vector& getOptionStrings() const + { + return mOptStrings; + } + + void processOption(const std::string& option, const std::vector& params) + { + if (params.size() != 1) + { + throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option)); + } + + tc::ByteData key_raw = tc::cli::FormatUtil::hexStringToBytes(params[0]); + if (key_raw.size() != sizeof(nstool::KeyBag::aes128_key_t)) + { + throw tc::ArgumentOutOfRangeException(fmt::format("Option: \"{:s}\", requires an AES128 key as the parameter (must be 32 hex chars).", option)); + } + + nstool::KeyBag::aes128_key_t key_tmp; + memcpy(key_tmp.data(), key_raw.data(), key_tmp.size()); + + mParam = key_tmp; + } +private: + tc::Optional& mParam; + std::vector mOptStrings; +}; + +class FileTypeOptionHandler : public tc::cli::OptionParser::IOptionHandler +{ +public: + FileTypeOptionHandler(nstool::Settings::FileType& param, const std::vector& opts) : + mParam(param), + mOptStrings(opts) + {} + + const std::vector& getOptionStrings() const + { + return mOptStrings; + } + + void processOption(const std::string& option, const std::vector& params) + { + if (params.size() != 1) + { + throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option)); + } + + if (params[0] == "gc" \ + || params[0] == "gamecard" \ + || params[0] == "xci" \ + || params[0] == "xcie" \ + || params[0] == "xcir") + { + mParam = nstool::Settings::FILE_TYPE_GAMECARD; + } + else if (params[0] == "nsp") + { + mParam = nstool::Settings::FILE_TYPE_NSP; + } + else if (params[0] == "partitionfs" || params[0] == "hashedpartitionfs" \ + || params[0] == "pfs" || params[0] == "pfs0" \ + || params[0] == "hfs" || params[0] == "hfs0") + { + mParam = nstool::Settings::FILE_TYPE_PARTITIONFS; + } + else if (params[0] == "romfs") + { + mParam = nstool::Settings::FILE_TYPE_ROMFS; + } + else if (params[0] == "nca" || params[0] == "contentarchive") + { + mParam = nstool::Settings::FILE_TYPE_NCA; + } + else if (params[0] == "meta" || params[0] == "npdm") + { + mParam = nstool::Settings::FILE_TYPE_META; + } + else if (params[0] == "cnmt") + { + mParam = nstool::Settings::FILE_TYPE_CNMT; + } + else if (params[0] == "nso") + { + mParam = nstool::Settings::FILE_TYPE_NSO; + } + else if (params[0] == "nro") + { + mParam = nstool::Settings::FILE_TYPE_NRO; + } + else if (params[0] == "ini") + { + mParam = nstool::Settings::FILE_TYPE_INI; + } + else if (params[0] == "kip") + { + mParam = nstool::Settings::FILE_TYPE_KIP; + } + else if (params[0] == "nacp") + { + mParam = nstool::Settings::FILE_TYPE_NACP; + } + else if (params[0] == "cert") + { + mParam = nstool::Settings::FILE_TYPE_ES_CERT; + } + else if (params[0] == "tik") + { + mParam = nstool::Settings::FILE_TYPE_ES_TIK; + } + else if (params[0] == "aset" || params[0] == "asset") + { + mParam = nstool::Settings::FILE_TYPE_HB_ASSET; + } + else + { + throw tc::ArgumentException(fmt::format("File type \"{}\" unrecognised.", params[0])); + } + } +private: + nstool::Settings::FileType& mParam; + std::vector mOptStrings; +}; + +class InstructionTypeOptionHandler : public tc::cli::OptionParser::IOptionHandler +{ +public: + InstructionTypeOptionHandler(bool& param, const std::vector& opts) : + mParam(param), + mOptStrings(opts) + {} + + const std::vector& getOptionStrings() const + { + return mOptStrings; + } + + void processOption(const std::string& option, const std::vector& params) + { + if (params.size() != 1) + { + throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option)); + } + + if (params[0] == "32bit") + { + mParam = false; + } + else if (params[0] == "64bit") + { + mParam = true; + } + else + { + throw tc::ArgumentException(fmt::format("Instruction type \"{}\" unrecognised. Try \"32bit\" or \"64bit\"", params[0])); + } + } +private: + bool& mParam; + std::vector mOptStrings; +}; + +class ExtractDataPathOptionHandler : public tc::cli::OptionParser::IOptionHandler +{ +public: + ExtractDataPathOptionHandler(std::vector& jobs, const std::vector& opts) : + mJobs(jobs), + mOptStrings(opts) + {} + + const std::vector& getOptionStrings() const + { + return mOptStrings; + } + + void processOption(const std::string& option, const std::vector& params) + { + if (params.size() == 1) + { + mJobs.push_back({tc::io::Path("/"), tc::io::Path(params[0])}); + } + else if (params.size() == 2) + { + mJobs.push_back({tc::io::Path(params[0]), tc::io::Path(params[1])}); + } + else + { + throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires parameters in the format \"[] \".", option)); + } + } +private: + std::vector& mJobs; + std::vector mOptStrings; +}; + +class CustomExtractDataPathOptionHandler : public tc::cli::OptionParser::IOptionHandler +{ +public: + CustomExtractDataPathOptionHandler(std::vector& jobs, const std::vector& opts, const tc::io::Path& custom_path) : + mJobs(jobs), + mOptStrings(opts), + mCustomPath(custom_path) + {} + + const std::vector& getOptionStrings() const + { + return mOptStrings; + } + + void processOption(const std::string& option, const std::vector& params) + { + if (params.size() != 1) + { + throw tc::ArgumentOutOfRangeException(fmt::format("Option \"{:s}\" requires a parameter.", option)); + } + + std::string custom_path_str; + tc::io::PathUtil::pathToUnixUTF8(mCustomPath, custom_path_str); + + fmt::print("[WARNING] \"{:s} {:s}\" is deprecated. ", option, params[0]); + // if custom path is root path, use the shortened version of -x + if (mCustomPath == tc::io::Path("/")) + { + fmt::print("Consider using \"-x {:s}\" instead.\n", params[0]); + } + else + { + fmt::print("Consider using \"-x {:s} {:s}\" instead.\n", custom_path_str, params[0]); + } + + + mJobs.push_back({mCustomPath, tc::io::Path(params[0])}); + } +private: + std::vector& mJobs; + std::vector mOptStrings; + tc::io::Path mCustomPath; +}; + +nstool::SettingsInitializer::SettingsInitializer(const std::vector& args) : + Settings(), + mModuleLabel("nstool::SettingsInitializer"), + mShowLayout(false), + mShowKeydata(false), + mVerbose(false), + mNcaEncryptedContentKey(), + mNcaContentKey(), + mTikPath(), + mCertPath() +{ + // parse input arguments + parse_args(args); + if (infile.path.isNull()) + throw tc::ArgumentException(mModuleLabel, "No input file was specified."); + + // determine CLI output mode + opt.cli_output_mode.show_basic_info = true; + if (mVerbose) + { + opt.cli_output_mode.show_extended_info = true; + opt.cli_output_mode.show_layout = true; + opt.cli_output_mode.show_keydata = true; + } + if (mShowKeydata) + { + opt.cli_output_mode.show_keydata = true; + } + if (mShowLayout) + { + opt.cli_output_mode.show_layout = true; + } + + // locate key file, if not specfied + if (mKeysetPath.isNull()) + { + std::string home_path_str; + if (tc::os::getEnvVar("HOME", home_path_str) || tc::os::getEnvVar("USERPROFILE", home_path_str)) + { + tc::io::Path keyfile_path = tc::io::Path(home_path_str); + keyfile_path.push_back(".switch"); + keyfile_path.push_back(opt.is_dev ? "dev.keys" : "prod.keys"); + + try { + tc::io::FileStream test = tc::io::FileStream(keyfile_path, tc::io::FileMode::Open, tc::io::FileAccess::Read); + + mKeysetPath = keyfile_path; + } + catch (tc::io::FileNotFoundException&) { + fmt::print("[WARNING] Failed to load \"{}\" keyfile. Maybe specify it with \"-k \"?\n", opt.is_dev ? "dev.keys" : "prod.keys"); + } + } + else { + fmt::print("[WARNING] Failed to located \"{}\" keyfile. Maybe specify it with \"-k \"?\n", opt.is_dev ? "dev.keys" : "prod.keys"); + } + } + + // generate keybag + opt.keybag = KeyBagInitializer(opt.is_dev, mKeysetPath, mTikPath, mCertPath); + opt.keybag.fallback_enc_content_key = mNcaEncryptedContentKey; + opt.keybag.fallback_content_key = mNcaContentKey; + + // dump keys if requires + if (mShowKeydata) // but not opt.cli_output_mode.show_keydata, since this that enabled by toggling -v,--verbose, personally I don't think a summary of imported keydata should be included in verbose output. + { + dump_keys(); + } + + // determine filetype if not manually specified + if (infile.filetype == FILE_TYPE_ERROR) + { + determine_filetype(); + if (infile.filetype == FILE_TYPE_ERROR) + { + throw tc::ArgumentException(mModuleLabel, "Input file type was undetermined."); + } + } +} + +void nstool::SettingsInitializer::parse_args(const std::vector& args) +{ + // check for minimum arguments + if (args.size() < 2) + { + usage_text(); + throw tc::ArgumentException(mModuleLabel, "Not enough arguments."); + } + + // detect request for help + for (auto itr = ++(args.begin()); itr != args.end(); itr++) + { + if (*itr == "-h" || *itr == "--help" || *itr == "-help") + { + usage_text(); + throw tc::ArgumentException(mModuleLabel, "Help required."); + } + } + + // save input file + infile.path = tc::io::Path(args.back()); + + // test new option parser + tc::cli::OptionParser opts; + + // register unk option handler + opts.registerUnrecognisedOptionHandler(std::shared_ptr(new UnkOptionHandler(mModuleLabel))); + + // register handler for deprecated options DeprecatedOptionHandler + // none just yet + + // get option flags + opts.registerOptionHandler(std::shared_ptr(new FlagOptionHandler(mShowLayout, {"--showlayout"}))); + opts.registerOptionHandler(std::shared_ptr(new FlagOptionHandler(mShowKeydata, { "--showkeys" }))); + opts.registerOptionHandler(std::shared_ptr(new FlagOptionHandler(mVerbose, {"-v", "--verbose"}))); + opts.registerOptionHandler(std::shared_ptr(new FlagOptionHandler(opt.verify, {"-y", "--verify"}))); + opts.registerOptionHandler(std::shared_ptr(new FlagOptionHandler(opt.is_dev, {"-d", "--dev"}))); + + // process input file type + opts.registerOptionHandler(std::shared_ptr(new FileTypeOptionHandler(infile.filetype, { "-t", "--intype" }))); + + // get user-provided keydata + opts.registerOptionHandler(std::shared_ptr(new SingleParamPathOptionHandler(mKeysetPath, {"-k", "--keyset"}))); + opts.registerOptionHandler(std::shared_ptr(new SingleParamAesKeyOptionHandler(mNcaEncryptedContentKey, {"--titlekey"}))); + opts.registerOptionHandler(std::shared_ptr(new SingleParamAesKeyOptionHandler(mNcaContentKey, {"--contentkey", "--bodykey"}))); + opts.registerOptionHandler(std::shared_ptr(new SingleParamPathOptionHandler(mTikPath, {"--tik"}))); + opts.registerOptionHandler(std::shared_ptr(new SingleParamPathOptionHandler(mCertPath, {"--cert"}))); + + // code options + opts.registerOptionHandler(std::shared_ptr(new FlagOptionHandler(code.list_api, { "--listapi" }))); + opts.registerOptionHandler(std::shared_ptr(new FlagOptionHandler(code.list_symbols, { "--listsym" }))); + opts.registerOptionHandler(std::shared_ptr(new InstructionTypeOptionHandler(code.is_64bit_instruction, { "--insttype" }))); + + // fs options + opts.registerOptionHandler(std::shared_ptr(new FlagOptionHandler(fs.show_fs_tree, { "--fstree", "--listfs" }))); + opts.registerOptionHandler(std::shared_ptr(new ExtractDataPathOptionHandler(fs.extract_jobs, { "-x", "--extract" }))); + opts.registerOptionHandler(std::shared_ptr(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--fsdir" }, tc::io::Path("/")))); + + // xci options + opts.registerOptionHandler(std::shared_ptr(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--update" }, tc::io::Path("/update/")))); + opts.registerOptionHandler(std::shared_ptr(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--normal" }, tc::io::Path("/normal/")))); + opts.registerOptionHandler(std::shared_ptr(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--secure" }, tc::io::Path("/secure/")))); + opts.registerOptionHandler(std::shared_ptr(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--logo" }, tc::io::Path("/logo/")))); + + // nca options + opts.registerOptionHandler(std::shared_ptr(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--part0" }, tc::io::Path("/0/")))); + opts.registerOptionHandler(std::shared_ptr(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--part1" }, tc::io::Path("/1/")))); + opts.registerOptionHandler(std::shared_ptr(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--part2" }, tc::io::Path("/2/")))); + opts.registerOptionHandler(std::shared_ptr(new CustomExtractDataPathOptionHandler(fs.extract_jobs, { "--part3" }, tc::io::Path("/3/")))); + + // kip options + opts.registerOptionHandler(std::shared_ptr(new SingleParamPathOptionHandler(kip.extract_path, { "--kipdir" }))); + + // aset options + opts.registerOptionHandler(std::shared_ptr(new SingleParamPathOptionHandler(aset.icon_extract_path, { "--icon" }))); + opts.registerOptionHandler(std::shared_ptr(new SingleParamPathOptionHandler(aset.nacp_extract_path, { "--nacp" }))); + + + // process option + opts.processOptions(args, 1, args.size() - 2); +} + +void nstool::SettingsInitializer::determine_filetype() +{ + //std::string infile_path_str; + //tc::io::PathUtil::pathToUnixUTF8(infile.path.get(), infile_path_str); + //fmt::print("infile path = \"{}\"\n", infile_path_str); + + auto file = tc::io::StreamSource(std::make_shared(tc::io::FileStream(infile.path.get(), tc::io::FileMode::Open, tc::io::FileAccess::Read))); + + auto raw_data = file.pullData(0, 0x5000); + +#define _TYPE_PTR(st) ((st*)(raw_data.data())) +#define _ASSERT_FILE_SIZE(sz) (file.length() >= tc::io::IOUtil::castSizeToInt64(sz)) + + // do easy tests + + // detect "scene" XCI + if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sGcHeader_Rsa2048Signed)) + && _TYPE_PTR(nn::hac::sGcHeader_Rsa2048Signed)->header.st_magic.unwrap() == nn::hac::gc::kGcHeaderStructMagic) + { + infile.filetype = FILE_TYPE_GAMECARD; + } + // detect "SDK" XCI + else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sSdkGcHeader)) + && _TYPE_PTR(nn::hac::sSdkGcHeader)->signed_header.header.st_magic.unwrap() == nn::hac::gc::kGcHeaderStructMagic) + { + infile.filetype = FILE_TYPE_GAMECARD; + } + // detect PFS0 + else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sPfsHeader)) + && _TYPE_PTR(nn::hac::sPfsHeader)->st_magic.unwrap() == nn::hac::pfs::kPfsStructMagic) + { + infile.filetype = FILE_TYPE_PARTITIONFS; + } + // detect HFS0 + else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sPfsHeader)) + && _TYPE_PTR(nn::hac::sPfsHeader)->st_magic.unwrap() == nn::hac::pfs::kHashedPfsStructMagic) + { + infile.filetype = FILE_TYPE_PARTITIONFS; + } + // detect ROMFS + else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sRomfsHeader)) + && _TYPE_PTR(nn::hac::sRomfsHeader)->header_size.unwrap() == sizeof(nn::hac::sRomfsHeader) + && _TYPE_PTR(nn::hac::sRomfsHeader)->dir_entry.offset.unwrap() == (_TYPE_PTR(nn::hac::sRomfsHeader)->dir_hash_bucket.offset.unwrap() + _TYPE_PTR(nn::hac::sRomfsHeader)->dir_hash_bucket.size.unwrap())) + { + infile.filetype = FILE_TYPE_ROMFS; + } + // detect NPDM + else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sMetaHeader)) + && _TYPE_PTR(nn::hac::sMetaHeader)->st_magic.unwrap() == nn::hac::meta::kMetaStructMagic) + { + infile.filetype = FILE_TYPE_META; + } + // detect NSO + else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sNsoHeader)) + && _TYPE_PTR(nn::hac::sNsoHeader)->st_magic.unwrap() == nn::hac::nso::kNsoStructMagic) + { + infile.filetype = FILE_TYPE_NSO; + } + // detect NRO + else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sNroHeader)) + && _TYPE_PTR(nn::hac::sNroHeader)->st_magic.unwrap() == nn::hac::nro::kNroStructMagic) + { + infile.filetype = FILE_TYPE_NRO; + } + // detect INI + else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sIniHeader)) + && _TYPE_PTR(nn::hac::sIniHeader)->st_magic.unwrap() == nn::hac::ini::kIniStructMagic) + { + infile.filetype = FILE_TYPE_INI; + } + // detect KIP + else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sKipHeader)) + && _TYPE_PTR(nn::hac::sKipHeader)->st_magic.unwrap() == nn::hac::kip::kKipStructMagic) + { + infile.filetype = FILE_TYPE_KIP; + } + // detect HB ASET + else if (_ASSERT_FILE_SIZE(sizeof(nn::hac::sAssetHeader)) + && _TYPE_PTR(nn::hac::sAssetHeader)->st_magic.unwrap() == nn::hac::aset::kAssetStructMagic) + { + infile.filetype = FILE_TYPE_KIP; + } + + // more complicated tests + + // detect NCA + else if (determineValidNcaFromSample(raw_data)) + { + infile.filetype = FILE_TYPE_NCA; + } + // detect Certificate + else if (determineValidEsCertFromSample(raw_data)) + { + infile.filetype = FILE_TYPE_ES_CERT; + } + // detect Ticket + else if (determineValidEsTikFromSample(raw_data)) + { + infile.filetype = FILE_TYPE_ES_TIK; + } + // detect Ticket + else if (determineValidCnmtFromSample(raw_data)) + { + infile.filetype = FILE_TYPE_CNMT; + } + // detect Ticket + else if (determineValidNacpFromSample(raw_data)) + { + infile.filetype = FILE_TYPE_NACP; + } +#undef _TYPE_PTR +#undef _ASSERT_FILE_SIZE +} + +void nstool::SettingsInitializer::usage_text() const +{ + fmt::print("{:s} v{:d}.{:d}.{:d} (C) {:s}\n", APP_NAME, VER_MAJOR, VER_MINOR, VER_PATCH, AUTHORS); + fmt::print("Built: {:s} {:s}\n\n", __TIME__, __DATE__); + fmt::print("Usage: {:s} [options... ] \n", BIN_NAME); + fmt::print("\n General Options:\n"); + fmt::print(" -d, --dev Use devkit keyset.\n"); + fmt::print(" -k, --keyset Specify keyset file.\n"); + fmt::print(" -t, --type Specify input file type. [xci, pfs, romfs, nca, meta, cnmt, nso, nro, ini, kip, nacp, aset, cert, tik]\n"); + fmt::print(" -y, --verify Verify file.\n"); + fmt::print("\n Output Options:\n"); + fmt::print(" --showkeys Show keys generated.\n"); + fmt::print(" --showlayout Show layout metadata.\n"); + fmt::print(" -v, --verbose Verbose output.\n"); + fmt::print("\n PFS0/HFS0 (PartitionFs), RomFs, NSP (Nintendo Submission Package)\n"); + fmt::print(" {:s} [--fstree] [-x [] ] \n", BIN_NAME); + fmt::print(" --fstree Print filesystem tree.\n"); + fmt::print(" -x, --extract Extract a file or directory to local filesystem.\n"); + fmt::print("\n XCI (GameCard Image)\n"); + fmt::print(" {:s} [--fstree] [-x [] ] <.xci file>\n", BIN_NAME); + fmt::print(" --fstree Print filesystem tree.\n"); + fmt::print(" -x, --extract Extract a file or directory to local filesystem.\n"); + fmt::print(" --update Extract \"update\" partition to directory. (Alias for \"-x /update \")\n"); + fmt::print(" --logo Extract \"logo\" partition to directory. (Alias for \"-x /logo \")\n"); + fmt::print(" --normal Extract \"normal\" partition to directory. (Alias for \"-x /normal \")\n"); + fmt::print(" --secure Extract \"secure\" partition to directory. (Alias for \"-x /secure \")\n"); + fmt::print("\n NCA (Nintendo Content Archive)\n"); + fmt::print(" {:s} [--fstree] [-x [] ] [--bodykey --titlekey -tik ] <.nca file>\n", BIN_NAME); + fmt::print(" --fstree Print filesystem tree.\n"); + fmt::print(" -x, --extract Extract a file or directory to local filesystem.\n"); + fmt::print(" --titlekey Specify (encrypted) title key extracted from ticket.\n"); + fmt::print(" --contentkey Specify content key.\n"); + fmt::print(" --tik Specify ticket to source title key.\n"); + fmt::print(" --cert Specify certificate chain to verify ticket.\n"); + fmt::print(" --part0 Extract partition \"0\" to directory. (Alias for \"-x /0 \")\n"); + fmt::print(" --part1 Extract partition \"1\" to directory. (Alias for \"-x /1 \")\n"); + fmt::print(" --part2 Extract partition \"2\" to directory. (Alias for \"-x /2 \")\n"); + fmt::print(" --part3 Extract partition \"3\" to directory. (Alias for \"-x /3 \")\n"); + fmt::print("\n NSO (Nintendo Shared Object), NRO (Nintendo Relocatable Object)\n"); + fmt::print(" {:s} [--listapi --listsym] [--insttype ] \n", BIN_NAME); + fmt::print(" --listapi Print SDK API List.\n"); + fmt::print(" --listsym Print Code Symbols.\n"); + fmt::print(" --insttype Specify instruction type [64bit|32bit] (64bit is assumed).\n"); + fmt::print("\n INI (Initial Program Bundle)\n"); + fmt::print(" {:s} [--kipdir ] \n", BIN_NAME); + fmt::print(" --kipdir Extract embedded Initial Programs to directory.\n"); + fmt::print("\n ASET (Homebrew Asset Blob)\n"); + fmt::print(" {:s} [--fstree] [-x [] ] [--icon --nacp ] \n", BIN_NAME); + fmt::print(" --fstree Print RomFs filesystem tree.\n"); + fmt::print(" -x, --extract Extract a file or directory from RomFs to local filesystem.\n"); + fmt::print(" --icon Extract icon partition to file.\n"); + fmt::print(" --nacp Extract NACP partition to file.\n"); +} + +void nstool::SettingsInitializer::dump_keys() const +{ + fmt::print("[KeyConfiguration]\n"); + fmt::print(" NCA Keys:\n"); + for (auto itr = opt.keybag.nca_header_sign0_key.begin(); itr != opt.keybag.nca_header_sign0_key.end(); itr++) + { + dump_rsa_key(itr->second, fmt::format("Header0-SignatureKey-{:02x}", itr->first), 4, opt.cli_output_mode.show_extended_info); + } + for (auto itr = opt.keybag.acid_sign_key.begin(); itr != opt.keybag.acid_sign_key.end(); itr++) + { + dump_rsa_key(itr->second, fmt::format("Acid-SignatureKey-{:02x}", itr->first), 4, opt.cli_output_mode.show_extended_info); + } + if (opt.keybag.nca_header_key.isSet()) + { + fmt::print(" Header-EncryptionKey:\n"); + fmt::print(" Key0: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(opt.keybag.nca_header_key.get()[0].data(), opt.keybag.nca_header_key.get()[0].size(), true, "")); + fmt::print(" Key1: {:s}\n", tc::cli::FormatUtil::formatBytesAsString(opt.keybag.nca_header_key.get()[1].data(), opt.keybag.nca_header_key.get()[1].size(), true, "")); + } + std::vector kaek_label = {"Application", "Ocean", "System"}; + for (size_t kaek_index = 0; kaek_index < opt.keybag.nca_key_area_encryption_key.size(); kaek_index++) + { + for (auto itr = opt.keybag.nca_key_area_encryption_key[kaek_index].begin(); itr != opt.keybag.nca_key_area_encryption_key[kaek_index].end(); itr++) + { + fmt::print(" KeyAreaEncryptionKey-{:s}-{:02x}:\n {:s}\n", kaek_label[kaek_index], itr->first, tc::cli::FormatUtil::formatBytesAsString(itr->second.data(), itr->second.size(), true, "")); + } + } + for (size_t kaek_index = 0; kaek_index < opt.keybag.nca_key_area_encryption_key_hw.size(); kaek_index++) + { + for (auto itr = opt.keybag.nca_key_area_encryption_key_hw[kaek_index].begin(); itr != opt.keybag.nca_key_area_encryption_key_hw[kaek_index].end(); itr++) + { + fmt::print(" KeyAreaEncryptionKeyHw-{:s}-{:02x}:\n {:s}\n", kaek_label[kaek_index], itr->first, tc::cli::FormatUtil::formatBytesAsString(itr->second.data(), itr->second.size(), true, "")); + } + } + fmt::print(" NRR Keys:\n"); + for (auto itr = opt.keybag.nrr_certificate_sign_key.begin(); itr != opt.keybag.nrr_certificate_sign_key.end(); itr++) + { + dump_rsa_key(itr->second, fmt::format("Certificate-SignatureKey-{:02x}", itr->first), 4, opt.cli_output_mode.show_extended_info); + } + fmt::print(" XCI Keys:\n"); + if (opt.keybag.xci_header_sign_key.isSet()) + { + dump_rsa_key(opt.keybag.xci_header_sign_key.get(), fmt::format("Header-SignatureKey"), 4, opt.cli_output_mode.show_extended_info); + } + for (auto itr = opt.keybag.xci_header_key.begin(); itr != opt.keybag.xci_header_key.end(); itr++) + { + fmt::print(" ExtendedHeader-EncryptionKey-{:02x}:\n {:s}\n", itr->first, tc::cli::FormatUtil::formatBytesAsString(itr->second.data(), itr->second.size(), true, "")); + } + if (opt.keybag.xci_cert_sign_key.isSet()) + { + dump_rsa_key(opt.keybag.xci_cert_sign_key.get(), fmt::format("CERT-SignatureKey"), 4, opt.cli_output_mode.show_extended_info); + } + + fmt::print(" Package1 Keys:\n"); + for (auto itr = opt.keybag.pkg1_key.begin(); itr != opt.keybag.pkg1_key.end(); itr++) + { + fmt::print(" EncryptionKey-{:02x}:\n {:s}\n", itr->first, tc::cli::FormatUtil::formatBytesAsString(itr->second.data(), itr->second.size(), true, "")); + } + + fmt::print(" Package2 Keys:\n"); + if (opt.keybag.pkg2_sign_key.isSet()) + { + dump_rsa_key(opt.keybag.pkg2_sign_key.get(), fmt::format("Header-SignatureKey"), 4, opt.cli_output_mode.show_extended_info); + } + for (auto itr = opt.keybag.pkg2_key.begin(); itr != opt.keybag.pkg2_key.end(); itr++) + { + fmt::print(" EncryptionKey-{:02x}:\n {:s}\n", itr->first, tc::cli::FormatUtil::formatBytesAsString(itr->second.data(), itr->second.size(), true, "")); + } + + fmt::print(" ETicket Keys:\n"); + for (auto itr = opt.keybag.etik_common_key.begin(); itr != opt.keybag.etik_common_key.end(); itr++) + { + fmt::print(" CommonKey-{:02x}:\n {:s}\n", itr->first, tc::cli::FormatUtil::formatBytesAsString(itr->second.data(), itr->second.size(), true, "")); + } + + fmt::print(" BroadOn Signer Profiles:\n"); + for (auto itr = opt.keybag.broadon_signer.begin(); itr != opt.keybag.broadon_signer.end(); itr++) + { + fmt::print(" {:s}:\n", itr->first); + fmt::print(" SignType: "); + switch(itr->second.key_type) { + case nn::pki::sign::SIGN_ALGO_RSA2048: + fmt::print("RSA-2048\n"); + break; + case nn::pki::sign::SIGN_ALGO_RSA4096: + fmt::print("RSA-4096\n"); + break; + case nn::pki::sign::SIGN_ALGO_ECDSA240: + fmt::print("ECDSA-240\n"); + break; + default: + fmt::print("Unknown\n"); + } + switch(itr->second.key_type) { + case nn::pki::sign::SIGN_ALGO_RSA2048: + case nn::pki::sign::SIGN_ALGO_RSA4096: + dump_rsa_key(itr->second.rsa_key, "RsaKey", 6, opt.cli_output_mode.show_extended_info); + break; + case nn::pki::sign::SIGN_ALGO_ECDSA240: + default: + break; + } + } +} + +void nstool::SettingsInitializer::dump_rsa_key(const KeyBag::rsa_key_t& key, const std::string& label, size_t indent, bool expanded_key_data) const +{ + std::string indent_str; + + indent_str.clear(); + for (size_t i = 0; i < indent; i++) + { + indent_str += " "; + } + + fmt::print("{:s}{:s}:\n", indent_str, label); + if (key.n.size() > 0) + { + if (expanded_key_data) + { + fmt::print("{:s} Modulus:\n", indent_str); + fmt::print("{:s} {:s}", indent_str, tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(key.n.data(), key.n.size(), true, "", 0x10, indent + 4, false)); + } + else + { + fmt::print("{:s} Modulus: {:s}\n", indent_str, getTruncatedBytesString(key.n.data(), key.n.size())); + } + } + if (key.d.size() > 0) + { + if (expanded_key_data) + { + fmt::print("{:s} Private Exponent:\n", indent_str); + fmt::print("{:s} {:s}", indent_str, tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(key.d.data(), key.d.size(), true, "", 0x10, indent + 4, false)); + } + else + { + fmt::print("{:s} Private Exponent: {:s}\n", indent_str, getTruncatedBytesString(key.d.data(), key.d.size())); + } + } +} + + +bool nstool::SettingsInitializer::determineValidNcaFromSample(const tc::ByteData& sample) const +{ + if (sample.size() < nn::hac::nca::kHeaderSize) + { + return false; + } + + if (opt.keybag.nca_header_key.isNull()) + { + fmt::print("[WARNING] Failed to load NCA Header Key.\n"); + return false; + } + + nn::hac::detail::aes128_xtskey_t key = opt.keybag.nca_header_key.get(); + + //fmt::print("NCA header key: {} {}\n", tc::cli::FormatUtil::formatBytesAsString(opt.keybag.nca_header_key.get()[0].data(), opt.keybag.nca_header_key.get()[0].size(), true, ""), tc::cli::FormatUtil::formatBytesAsString(opt.keybag.nca_header_key.get()[1].data(), opt.keybag.nca_header_key.get()[1].size(), true, "")); + + // init aes-xts + tc::crypto::Aes128XtsEncryptor enc; + enc.initialize(key[0].data(), key[0].size(), key[1].data(), key[1].size(), nn::hac::nca::kSectorSize, false); + + // decrypt main header + byte_t raw_hdr[nn::hac::nca::kSectorSize]; + enc.decrypt(raw_hdr, sample.data() + nn::hac::ContentArchiveUtil::sectorToOffset(1), nn::hac::nca::kSectorSize, 1); + nn::hac::sContentArchiveHeader* hdr = (nn::hac::sContentArchiveHeader*)(raw_hdr); + + /* + fmt::print("NCA Header Raw:\n"); + fmt::print("{:s}\n", tc::cli::FormatUtil::formatBytesAsHxdHexString(sample.data() + nn::hac::ContentArchiveUtil::sectorToOffset(1), nn::hac::nca::kSectorSize)); + fmt::print("{:s}\n", tc::cli::FormatUtil::formatBytesAsHxdHexString(raw_hdr, nn::hac::nca::kSectorSize)); + */ + + if (hdr->st_magic.unwrap() != nn::hac::nca::kNca2StructMagic && hdr->st_magic.unwrap() != nn::hac::nca::kNca3StructMagic) + { + return false; + } + + return true; +} + +bool nstool::SettingsInitializer::determineValidCnmtFromSample(const tc::ByteData& sample) const +{ + if (sample.size() < sizeof(nn::hac::sContentMetaHeader)) + return false; + + const nn::hac::sContentMetaHeader* data = (const nn::hac::sContentMetaHeader*)sample.data(); + + size_t minimum_size = sizeof(nn::hac::sContentMetaHeader) + data->exhdr_size.unwrap() + data->content_count.unwrap() * sizeof(nn::hac::sContentInfo) + data->content_meta_count.unwrap() * sizeof(nn::hac::sContentMetaInfo) + nn::hac::cnmt::kDigestLen; + + if (sample.size() < minimum_size) + return false; + + // include exthdr/data check if applicable + if (data->exhdr_size.unwrap() > 0) + { + if (data->type == (byte_t)nn::hac::cnmt::ContentMetaType::Application) + { + const nn::hac::sApplicationMetaExtendedHeader* meta = (const nn::hac::sApplicationMetaExtendedHeader*)(sample.data() + sizeof(nn::hac::sContentMetaHeader)); + if ((meta->patch_id.unwrap() & data->id.unwrap()) != data->id.unwrap()) + return false; + } + else if (data->type == (byte_t)nn::hac::cnmt::ContentMetaType::Patch) + { + const nn::hac::sPatchMetaExtendedHeader* meta = (const nn::hac::sPatchMetaExtendedHeader*)(sample.data() + sizeof(nn::hac::sContentMetaHeader)); + if ((meta->application_id.unwrap() & data->id.unwrap()) != meta->application_id.unwrap()) + return false; + + minimum_size += meta->extended_data_size.unwrap(); + } + else if (data->type == (byte_t)nn::hac::cnmt::ContentMetaType::AddOnContent) + { + const nn::hac::sAddOnContentMetaExtendedHeader* meta = (const nn::hac::sAddOnContentMetaExtendedHeader*)(sample.data() + sizeof(nn::hac::sContentMetaHeader)); + if ((meta->application_id.unwrap() & data->id.unwrap()) != meta->application_id.unwrap()) + return false; + } + else if (data->type == (byte_t)nn::hac::cnmt::ContentMetaType::Delta) + { + const nn::hac::sDeltaMetaExtendedHeader* meta = (const nn::hac::sDeltaMetaExtendedHeader*)(sample.data() + sizeof(nn::hac::sContentMetaHeader)); + if ((meta->application_id.unwrap() & data->id.unwrap()) != meta->application_id.unwrap()) + return false; + + minimum_size += meta->extended_data_size.unwrap(); + } + else if (data->type == (byte_t)nn::hac::cnmt::ContentMetaType::SystemUpdate) + { + const nn::hac::sSystemUpdateMetaExtendedHeader* meta = (const nn::hac::sSystemUpdateMetaExtendedHeader*)(sample.data() + sizeof(nn::hac::sContentMetaHeader)); + + minimum_size += meta->extended_data_size.unwrap(); + } + } + + if (sample.size() != minimum_size) + return false; + + return true; +} + +bool nstool::SettingsInitializer::determineValidNacpFromSample(const tc::ByteData& sample) const +{ + if (sample.size() != sizeof(nn::hac::sApplicationControlProperty)) + return false; + + const nn::hac::sApplicationControlProperty* data = (const nn::hac::sApplicationControlProperty*)sample.data(); + + if (data->logo_type > (byte_t)nn::hac::nacp::LogoType::Nintendo) + return false; + + if (data->display_version[0] == 0) + return false; + + if (data->user_account_save_data_size.unwrap() == 0 && data->user_account_save_data_journal_size.unwrap() != 0) + return false; + + if (data->user_account_save_data_journal_size.unwrap() == 0 && data->user_account_save_data_size.unwrap() != 0) + return false; + + if (*((uint32_t*)(&data->supported_language_flag)) == 0) + return false; + + return true; +} + +bool nstool::SettingsInitializer::determineValidEsCertFromSample(const tc::ByteData& sample) const +{ + nn::pki::SignatureBlock sign; + + try + { + sign.fromBytes(sample.data(), sample.size()); + } + catch (...) + { + return false; + } + + if (sign.isLittleEndian() == true) + return false; + + if (sign.getSignType() != nn::pki::sign::SIGN_ID_RSA4096_SHA256 && sign.getSignType() != nn::pki::sign::SIGN_ID_RSA2048_SHA256 && sign.getSignType() != nn::pki::sign::SIGN_ID_ECDSA240_SHA256) + return false; + + return true; +} + +bool nstool::SettingsInitializer::determineValidEsTikFromSample(const tc::ByteData& sample) const +{ + nn::pki::SignatureBlock sign; + + try + { + sign.fromBytes(sample.data(), sample.size()); + } + catch (...) + { + return false; + } + + if (sign.isLittleEndian() == false) + return false; + + if (sign.getSignType() != nn::pki::sign::SIGN_ID_RSA2048_SHA256) + return false; + + const nn::es::sTicketBody_v2* body = (const nn::es::sTicketBody_v2*)(sample.data() + sign.getBytes().size()); + + if ((body->issuer.str().substr(0, 5) == "Root-" + && body->issuer.str().substr(16, 2) == "XS") == false) + return false; + + return true; +} \ No newline at end of file diff --git a/src/Settings.h b/src/Settings.h new file mode 100644 index 0000000..2406f54 --- /dev/null +++ b/src/Settings.h @@ -0,0 +1,148 @@ +#pragma once +#include "types.h" +#include +#include +#include +#include + +#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 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 extract_jobs; + } fs; + + // XCI options + struct XciOptions + { + tc::Optional update_extract_path; + tc::Optional logo_extract_path; + tc::Optional normal_extract_path; + tc::Optional secure_extract_path; + } xci; + + // NCA options + struct NcaOptions + { + tc::Optional part0_extract_path; + tc::Optional part1_extract_path; + tc::Optional part2_extract_path; + tc::Optional part3_extract_path; + } nca; + + // KIP options + struct KipOptions + { + tc::Optional extract_path; + } kip; + + // ASET Options + struct AsetOptions + { + tc::Optional icon_extract_path; + tc::Optional nacp_extract_path; + } aset; + + Settings() + { + infile.filetype = FILE_TYPE_ERROR; + infile.path = tc::Optional(); + + 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(); + + kip.extract_path = tc::Optional(); + + aset.icon_extract_path = tc::Optional(); + aset.nacp_extract_path = tc::Optional(); + } +}; + +class SettingsInitializer : public Settings +{ +public: + SettingsInitializer(const std::vector& args); +private: + void parse_args(const std::vector& 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 mKeysetPath; + tc::Optional mNcaEncryptedContentKey; + tc::Optional mNcaContentKey; + tc::Optional mTikPath; + tc::Optional 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; +}; + +} \ No newline at end of file diff --git a/src/UserSettings.cpp b/src/UserSettings.cpp deleted file mode 100644 index 3356974..0000000 --- a/src/UserSettings.cpp +++ /dev/null @@ -1,1086 +0,0 @@ -#include "UserSettings.h" -#include "version.h" -#include "PkiValidator.h" -#include "KeyConfiguration.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -UserSettings::UserSettings() -{} - -void UserSettings::parseCmdArgs(const std::vector& arg_list) -{ - sCmdArgs args; - populateCmdArgs(arg_list, args); - populateKeyset(args); - populateUserSettings(args); - if (_HAS_BIT(mOutputMode, OUTPUT_KEY_DATA)) - dumpKeyConfig(); -} - -void UserSettings::showHelp() -{ - printf("%s v%d.%d.%d (C) %s\n", APP_NAME, VER_MAJOR, VER_MINOR, VER_PATCH, AUTHORS); - printf("Built: %s %s\n\n", __TIME__, __DATE__); - - printf("Usage: %s [options... ] \n", BIN_NAME); - printf("\n General Options:\n"); - printf(" -d, --dev Use devkit keyset.\n"); - printf(" -k, --keyset Specify keyset file.\n"); - printf(" -t, --type Specify input file type. [xci, pfs, romfs, nca, meta, cnmt, nso, nro, ini, kip, nacp, aset, cert, tik]\n"); - printf(" -y, --verify Verify file.\n"); - printf("\n Output Options:\n"); - printf(" --showkeys Show keys generated.\n"); - printf(" --showlayout Show layout metadata.\n"); - printf(" -v, --verbose Verbose output.\n"); - printf("\n XCI (GameCard Image)\n"); - printf(" %s [--listfs] [--update --logo --normal --secure ] <.xci file>\n", BIN_NAME); - printf(" --listfs Print file system in embedded partitions.\n"); - printf(" --update Extract \"update\" partition to directory.\n"); - printf(" --logo Extract \"logo\" partition to directory.\n"); - printf(" --normal Extract \"normal\" partition to directory.\n"); - printf(" --secure Extract \"secure\" partition to directory.\n"); - printf("\n PFS0/HFS0 (PartitionFs), RomFs, NSP (Ninendo Submission Package)\n"); - printf(" %s [--listfs] [--fsdir ] \n", BIN_NAME); - printf(" --listfs Print file system.\n"); - printf(" --fsdir Extract file system to directory.\n"); - printf("\n NCA (Nintendo Content Archive)\n"); - printf(" %s [--listfs] [--bodykey --titlekey ] [--part0 ...] <.nca file>\n", BIN_NAME); - printf(" --listfs Print file system in embedded partitions.\n"); - printf(" --titlekey Specify title key extracted from ticket.\n"); - printf(" --bodykey Specify body encryption key.\n"); - printf(" --tik Specify ticket to source title key.\n"); - printf(" --cert Specify certificate chain to verify ticket.\n"); - printf(" --part0 Extract \"partition 0\" to directory.\n"); - printf(" --part1 Extract \"partition 1\" to directory.\n"); - printf(" --part2 Extract \"partition 2\" to directory.\n"); - printf(" --part3 Extract \"partition 3\" to directory.\n"); - printf("\n NSO (Nintendo Software Object), NRO (Nintendo Relocatable Object)\n"); - printf(" %s [--listapi --listsym] [--insttype ] \n", BIN_NAME); - printf(" --listapi Print SDK API List.\n"); - printf(" --listsym Print Code Symbols.\n"); - printf(" --insttype Specify instruction type [64bit|32bit] (64bit is assumed).\n"); - printf("\n INI (Initial Process List Blob)\n"); - printf(" %s [--kipdir ] \n", BIN_NAME); - printf(" --kipdir Extract embedded KIPs to directory.\n"); - printf("\n ASET (Homebrew Asset Blob)\n"); - printf(" %s [--listfs] [--icon --nacp --fsdir ] \n", BIN_NAME); - printf(" --listfs Print filesystem in embedded RomFS partition.\n"); - printf(" --icon Extract icon partition to file.\n"); - printf(" --nacp Extract NACP partition to file.\n"); - printf(" --fsdir Extract RomFS partition to directory.\n"); - -} - -const std::string UserSettings::getInputPath() const -{ - return mInputPath; -} - -const KeyConfiguration& UserSettings::getKeyCfg() const -{ - return mKeyCfg; -} - -FileType UserSettings::getFileType() const -{ - return mFileType; -} - -bool UserSettings::isVerifyFile() const -{ - return mVerifyFile; -} - -CliOutputMode UserSettings::getCliOutputMode() const -{ - return mOutputMode; -} - -bool UserSettings::isListFs() const -{ - return mListFs; -} - -bool UserSettings::isListApi() const -{ - return mListApi; -} -bool UserSettings::isListSymbols() const -{ - return mListSymbols; -} - -bool UserSettings::getIs64BitInstruction() const -{ - return mIs64BitInstruction; -} - -const sOptional& UserSettings::getXciUpdatePath() const -{ - return mXciUpdatePath; -} - -const sOptional& UserSettings::getXciLogoPath() const -{ - return mXciLogoPath; -} - -const sOptional& UserSettings::getXciNormalPath() const -{ - return mXciNormalPath; -} - -const sOptional& UserSettings::getXciSecurePath() const -{ - return mXciSecurePath; -} - -const sOptional& UserSettings::getFsPath() const -{ - return mFsPath; -} - -const sOptional& UserSettings::getNcaPart0Path() const -{ - return mNcaPart0Path; -} - -const sOptional& UserSettings::getNcaPart1Path() const -{ - return mNcaPart1Path; -} - -const sOptional& UserSettings::getNcaPart2Path() const -{ - return mNcaPart2Path; -} - -const sOptional& UserSettings::getNcaPart3Path() const -{ - return mNcaPart3Path; -} - -const sOptional& UserSettings::getKipExtractPath() const -{ - return mKipExtractPath; -} - -const sOptional& UserSettings::getAssetIconPath() const -{ - return mAssetIconPath; -} - -const sOptional& UserSettings::getAssetNacpPath() const -{ - return mAssetNacpPath; -} - -const fnd::List>& UserSettings::getCertificateChain() const -{ - return mCertChain; -} - -void UserSettings::populateCmdArgs(const std::vector& arg_list, sCmdArgs& cmd_args) -{ - // show help text - if (arg_list.size() < 2) - { - showHelp(); - throw fnd::Exception(kModuleName, "Not enough arguments."); - } - - cmd_args.input_path = arg_list.back(); - - for (size_t i = 1; i < arg_list.size(); i++) - { - if (arg_list[i] == "-h" || arg_list[i] == "--help") - { - showHelp(); - throw fnd::Exception(kModuleName, "Nothing to do."); - } - } - - for (size_t i = 1; i+1 < arg_list.size(); i++) - { - bool hasParamter = arg_list[i+1][0] != '-' && i+2 < arg_list.size(); - - if (arg_list[i] == "-d" || arg_list[i] == "--dev") - { - if (hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " does not take a parameter."); - cmd_args.devkit_keys = true; - } - - else if (arg_list[i] == "-y" || arg_list[i] == "--verify") - { - if (hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " does not take a parameter."); - cmd_args.verify_file = true; - } - - else if (arg_list[i] == "--showkeys") - { - if (hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " does not take a parameter."); - cmd_args.show_keys = true; - } - - else if (arg_list[i] == "--showlayout") - { - if (hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " does not take a parameter."); - cmd_args.show_layout = true; - } - - else if (arg_list[i] == "-v" || arg_list[i] == "--verbose") - { - if (hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " does not take a parameter."); - cmd_args.verbose_output = true; - } - - else if (arg_list[i] == "-k" || arg_list[i] == "--keyset") - { - if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); - cmd_args.keyset_path = arg_list[i+1]; - } - - else if (arg_list[i] == "-t" || arg_list[i] == "--type") - { - if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); - cmd_args.file_type = arg_list[i+1]; - } - - else if (arg_list[i] == "--listfs") - { - if (hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " does not take a parameter."); - cmd_args.list_fs = true; - } - - else if (arg_list[i] == "--update") - { - if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); - cmd_args.update_path = arg_list[i+1]; - } - - else if (arg_list[i] == "--normal") - { - if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); - cmd_args.normal_path = arg_list[i+1]; - } - - else if (arg_list[i] == "--secure") - { - if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); - cmd_args.secure_path = arg_list[i+1]; - } - - else if (arg_list[i] == "--logo") - { - if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); - cmd_args.logo_path = arg_list[i+1]; - } - - else if (arg_list[i] == "--fsdir") - { - if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); - cmd_args.fs_path = arg_list[i+1]; - } - - else if (arg_list[i] == "--titlekey") - { - if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); - cmd_args.nca_titlekey = arg_list[i+1]; - } - - else if (arg_list[i] == "--bodykey") - { - if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); - cmd_args.nca_bodykey = arg_list[i+1]; - } - - else if (arg_list[i] == "--tik") - { - if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); - cmd_args.ticket_path = arg_list[i+1]; - } - - else if (arg_list[i] == "--cert") - { - if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); - cmd_args.cert_path = arg_list[i+1]; - } - - else if (arg_list[i] == "--part0") - { - if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); - cmd_args.part0_path = arg_list[i+1]; - } - - else if (arg_list[i] == "--part1") - { - if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); - cmd_args.part1_path = arg_list[i+1]; - } - - else if (arg_list[i] == "--part2") - { - if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); - cmd_args.part2_path = arg_list[i+1]; - } - - else if (arg_list[i] == "--part3") - { - if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); - cmd_args.part3_path = arg_list[i+1]; - } - - else if (arg_list[i] == "--listapi") - { - if (hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " does not take a parameter."); - cmd_args.list_api = true; - } - - else if (arg_list[i] == "--listsym") - { - if (hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " does not take a parameter."); - cmd_args.list_sym = true; - } - - else if (arg_list[i] == "--insttype") - { - if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); - cmd_args.inst_type = arg_list[i + 1]; - } - - else if (arg_list[i] == "--kipdir") - { - if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); - cmd_args.kip_extract_path = arg_list[i + 1]; - } - - else if (arg_list[i] == "--icon") - { - if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); - cmd_args.asset_icon_path = arg_list[i + 1]; - } - - else if (arg_list[i] == "--nacp") - { - if (!hasParamter) throw fnd::Exception(kModuleName, arg_list[i] + " requries a parameter."); - cmd_args.asset_nacp_path = arg_list[i + 1]; - } - - else - { - throw fnd::Exception(kModuleName, arg_list[i] + " is not recognised."); - } - - i += hasParamter; - } -} - -void UserSettings::populateKeyset(sCmdArgs& args) -{ - if (args.keyset_path.isSet) - { - mKeyCfg.importHactoolGenericKeyfile(*args.keyset_path); - } - else - { - // open other resource files in $HOME/.switch/prod.keys (or $HOME/.switch/dev.keys if -d/--dev is set). - std::string keyset_path; - getSwitchPath(keyset_path); - if (keyset_path.empty()) - return; - - fnd::io::appendToPath(keyset_path, kGeneralKeyfileName[args.devkit_keys.isSet]); - - try - { - mKeyCfg.importHactoolGenericKeyfile(keyset_path); - } - catch (const fnd::Exception&) - { - return; - } - - } - - - - if (args.nca_bodykey.isSet) - { - fnd::aes::sAes128Key tmp_key; - fnd::Vec tmp_raw; - fnd::SimpleTextOutput::stringToArray(args.nca_bodykey.var, tmp_raw); - if (tmp_raw.size() != sizeof(fnd::aes::sAes128Key)) - throw fnd::Exception(kModuleName, "Key: \"--bodykey\" has incorrect length"); - memcpy(tmp_key.key, tmp_raw.data(), 16); - mKeyCfg.addNcaExternalContentKey(kDummyRightsIdForUserBodyKey, tmp_key); - } - - if (args.nca_titlekey.isSet) - { - fnd::aes::sAes128Key tmp_key; - fnd::Vec tmp_raw; - fnd::SimpleTextOutput::stringToArray(args.nca_titlekey.var, tmp_raw); - if (tmp_raw.size() != sizeof(fnd::aes::sAes128Key)) - throw fnd::Exception(kModuleName, "Key: \"--titlekey\" has incorrect length"); - memcpy(tmp_key.key, tmp_raw.data(), 16); - mKeyCfg.addNcaExternalContentKey(kDummyRightsIdForUserTitleKey, tmp_key); - } - - // import certificate chain - if (args.cert_path.isSet) - { - fnd::SimpleFile cert_file; - fnd::Vec cert_raw; - nn::pki::SignedData cert; - - cert_file.open(args.cert_path.var, fnd::SimpleFile::Read); - cert_raw.alloc(cert_file.size()); - cert_file.read(cert_raw.data(), cert_raw.size()); - - for (size_t i = 0; i < cert_raw.size(); i+= cert.getBytes().size()) - { - cert.fromBytes(cert_raw.data() + i, cert_raw.size() - i); - mCertChain.addElement(cert); - } - } - - // get titlekey from ticket - if (args.ticket_path.isSet) - { - fnd::SimpleFile tik_file; - fnd::Vec tik_raw; - nn::pki::SignedData tik; - - // open and import ticket - tik_file.open(args.ticket_path.var, fnd::SimpleFile::Read); - tik_raw.alloc(tik_file.size()); - tik_file.read(tik_raw.data(), tik_raw.size()); - tik.fromBytes(tik_raw.data(), tik_raw.size()); - - // validate ticket signature - if (mCertChain.size() > 0) - { - PkiValidator pki_validator; - fnd::Vec tik_hash; - - switch (nn::pki::sign::getHashAlgo(tik.getSignature().getSignType())) - { - case (nn::pki::sign::HASH_ALGO_SHA1): - tik_hash.alloc(fnd::sha::kSha1HashLen); - fnd::sha::Sha1(tik.getBody().getBytes().data(), tik.getBody().getBytes().size(), tik_hash.data()); - break; - case (nn::pki::sign::HASH_ALGO_SHA256): - tik_hash.alloc(fnd::sha::kSha256HashLen); - fnd::sha::Sha256(tik.getBody().getBytes().data(), tik.getBody().getBytes().size(), tik_hash.data()); - break; - } - - try - { - pki_validator.setKeyCfg(mKeyCfg); - pki_validator.addCertificates(mCertChain); - pki_validator.validateSignature(tik.getBody().getIssuer(), tik.getSignature().getSignType(), tik.getSignature().getSignature(), tik_hash); - } - catch (const fnd::Exception& e) - { - std::cout << "[WARNING] Ticket signature could not be validated (" << e.error() << ")" << std::endl; - } - - } - - // extract title key - if (tik.getBody().getTitleKeyEncType() == nn::es::ticket::AES128_CBC) - { - fnd::aes::sAes128Key enc_title_key; - memcpy(enc_title_key.key, tik.getBody().getEncTitleKey(), 16); - fnd::aes::sAes128Key common_key, external_content_key; - if (mKeyCfg.getETicketCommonKey(nn::hac::ContentArchiveUtil::getMasterKeyRevisionFromKeyGeneration(tik.getBody().getCommonKeyId()), common_key) == true) - { - nn::hac::AesKeygen::generateKey(external_content_key.key, tik.getBody().getEncTitleKey(), common_key.key); - mKeyCfg.addNcaExternalContentKey(tik.getBody().getRightsId(), external_content_key); - } - else - { - std::cout << "[WARNING] Titlekey not imported from ticket because commonkey was not available" << std::endl; - } - } - else - { - std::cout << "[WARNING] Titlekey not imported from ticket because it is personalised" << std::endl; - } - } -} - -void UserSettings::populateUserSettings(sCmdArgs& args) -{ - // check invalid input - if (args.input_path.isSet == false) - throw fnd::Exception(kModuleName, "No input file specified"); - - // save arguments - mInputPath = *args.input_path; - mVerifyFile = args.verify_file.isSet; - mListFs = args.list_fs.isSet; - mXciUpdatePath = args.update_path; - mXciNormalPath = args.normal_path; - mXciSecurePath = args.secure_path; - mXciLogoPath = args.logo_path; - - mFsPath = args.fs_path; - mNcaPart0Path = args.part0_path; - mNcaPart1Path = args.part1_path; - mNcaPart2Path = args.part2_path; - mNcaPart3Path = args.part3_path; - - mKipExtractPath = args.kip_extract_path; - - // determine the architecture type for NSO/NRO - if (args.inst_type.isSet) - mIs64BitInstruction = getIs64BitInstructionFromString(*args.inst_type); - else - mIs64BitInstruction = true; // default 64bit - - mListApi = args.list_api.isSet; - mListSymbols = args.list_sym.isSet; - - mAssetIconPath = args.asset_icon_path; - mAssetNacpPath = args.asset_nacp_path; - - // determine output mode - mOutputMode = _BIT(OUTPUT_BASIC); - if (args.verbose_output.isSet) - { - mOutputMode |= _BIT(OUTPUT_KEY_DATA); - mOutputMode |= _BIT(OUTPUT_LAYOUT); - mOutputMode |= _BIT(OUTPUT_EXTENDED); - } - if (args.show_keys.isSet) - { - mOutputMode |= _BIT(OUTPUT_KEY_DATA); - } - if (args.show_layout.isSet) - { - mOutputMode |= _BIT(OUTPUT_LAYOUT); - } - - // determine input file type - if (args.file_type.isSet) - mFileType = getFileTypeFromString(*args.file_type); - else - mFileType = determineFileTypeFromFile(mInputPath); - - // check is the input file could be identified - if (mFileType == FILE_INVALID) - throw fnd::Exception(kModuleName, "Unknown file type."); -} - -FileType UserSettings::getFileTypeFromString(const std::string& type_str) -{ - std::string str = type_str; - std::transform(str.begin(), str.end(), str.begin(), ::tolower); - - FileType type; - if (str == "gc" || str == "gamecard" || str == "xci") - type = FILE_GAMECARD; - else if (str == "nsp") - type = FILE_NSP; - else if (str == "partitionfs" || str == "hashedpartitionfs" \ - || str == "pfs" || str == "pfs0" \ - || str == "hfs" || str == "hfs0") - type = FILE_PARTITIONFS; - else if (str == "romfs") - type = FILE_ROMFS; - else if (str == "nca" || str == "contentarchive") - type = FILE_NCA; - else if (str == "meta" || str == "npdm") - type = FILE_META; - else if (str == "cnmt") - type = FILE_CNMT; - else if (str == "nso") - type = FILE_NSO; - else if (str == "nro") - type = FILE_NRO; - else if (str == "ini") - type = FILE_INI; - else if (str == "kip") - type = FILE_KIP; - else if (str == "nacp") - type = FILE_NACP; - else if (str == "cert") - type = FILE_PKI_CERT; - else if (str == "tik") - type = FILE_ES_TIK; - else if (str == "aset" || str == "asset") - type = FILE_HB_ASSET; - else - type = FILE_INVALID; - - return type; -} - -FileType UserSettings::determineFileTypeFromFile(const std::string& path) -{ - static const size_t kMaxReadSize = 0x5000; - FileType file_type = FILE_INVALID; - fnd::SimpleFile file; - fnd::Vec scratch; - - // open file - file.open(path, file.Read); - - // read file - scratch.alloc(_MIN(kMaxReadSize, file.size())); - file.read(scratch.data(), 0, scratch.size()); - // close file - file.close(); - - // _TYPE_PTR resolves to a pointer of type 'st' located at scratch.data() -#define _TYPE_PTR(st) ((st*)(scratch.data())) -#define _ASSERT_SIZE(sz) (scratch.size() >= (sz)) - - // test gamecard - if (_ASSERT_SIZE(sizeof(nn::hac::sGcHeader_Rsa2048Signed)) && _TYPE_PTR(nn::hac::sGcHeader_Rsa2048Signed)->header.st_magic.get() == nn::hac::gc::kGcHeaderStructMagic) - file_type = FILE_GAMECARD; - else if (_ASSERT_SIZE(sizeof(nn::hac::sSdkGcHeader)) && _TYPE_PTR(nn::hac::sSdkGcHeader)->signed_header.header.st_magic.get() == nn::hac::gc::kGcHeaderStructMagic) - file_type = FILE_GAMECARD; - // test pfs0 - else if (_ASSERT_SIZE(sizeof(nn::hac::sPfsHeader)) && _TYPE_PTR(nn::hac::sPfsHeader)->st_magic.get() == nn::hac::pfs::kPfsStructMagic) - file_type = FILE_PARTITIONFS; - // test hfs0 - else if (_ASSERT_SIZE(sizeof(nn::hac::sPfsHeader)) && _TYPE_PTR(nn::hac::sPfsHeader)->st_magic.get() == nn::hac::pfs::kHashedPfsStructMagic) - file_type = FILE_PARTITIONFS; - // test romfs - else if (_ASSERT_SIZE(sizeof(nn::hac::sRomfsHeader)) && _TYPE_PTR(nn::hac::sRomfsHeader)->header_size.get() == sizeof(nn::hac::sRomfsHeader) && _TYPE_PTR(nn::hac::sRomfsHeader)->sections[1].offset.get() == (_TYPE_PTR(nn::hac::sRomfsHeader)->sections[0].offset.get() + _TYPE_PTR(nn::hac::sRomfsHeader)->sections[0].size.get())) - file_type = FILE_ROMFS; - // test npdm - else if (_ASSERT_SIZE(sizeof(nn::hac::sMetaHeader)) && _TYPE_PTR(nn::hac::sMetaHeader)->st_magic.get() == nn::hac::meta::kMetaStructMagic) - file_type = FILE_META; - // test nca - else if (determineValidNcaFromSample(scratch)) - file_type = FILE_NCA; - // test nso - else if (_ASSERT_SIZE(sizeof(nn::hac::sNsoHeader)) && _TYPE_PTR(nn::hac::sNsoHeader)->st_magic.get() == nn::hac::nso::kNsoStructMagic) - file_type = FILE_NSO; - // test nro - else if (_ASSERT_SIZE(sizeof(nn::hac::sNroHeader)) && _TYPE_PTR(nn::hac::sNroHeader)->st_magic.get() == nn::hac::nro::kNroStructMagic) - file_type = FILE_NRO; - // test ini - else if (_ASSERT_SIZE(sizeof(nn::hac::sIniHeader)) && _TYPE_PTR(nn::hac::sIniHeader)->st_magic.get() == nn::hac::ini::kIniStructMagic) - file_type = FILE_INI; - // test kip - else if (_ASSERT_SIZE(sizeof(nn::hac::sKipHeader)) && _TYPE_PTR(nn::hac::sKipHeader)->st_magic.get() == nn::hac::kip::kKipStructMagic) - file_type = FILE_KIP; - // test pki certificate - else if (determineValidEsCertFromSample(scratch)) - file_type = FILE_PKI_CERT; - // test ticket - else if (determineValidEsTikFromSample(scratch)) - file_type = FILE_ES_TIK; - // test hb asset - else if (_ASSERT_SIZE(sizeof(nn::hac::sAssetHeader)) && _TYPE_PTR(nn::hac::sAssetHeader)->st_magic.get() == nn::hac::aset::kAssetStructMagic) - file_type = FILE_HB_ASSET; - - // do heuristics - // test cnmt - else if (determineValidCnmtFromSample(scratch)) - file_type = FILE_CNMT; - // test nacp - else if (determineValidNacpFromSample(scratch)) - file_type = FILE_NACP; - - - // else unrecognised - else - file_type = FILE_INVALID; - -#undef _ASSERT_SIZE -#undef _TYPE_PTR - - return file_type; -} - -bool UserSettings::determineValidNcaFromSample(const fnd::Vec& sample) const -{ - // prepare decrypted NCA data - byte_t nca_raw[nn::hac::nca::kHeaderSize]; - nn::hac::sContentArchiveHeader* nca_header = (nn::hac::sContentArchiveHeader*)(nca_raw + nn::hac::ContentArchiveUtil::sectorToOffset(1)); - - if (sample.size() < nn::hac::nca::kHeaderSize) - return false; - - fnd::aes::sAesXts128Key header_key; - mKeyCfg.getContentArchiveHeaderKey(header_key); - nn::hac::ContentArchiveUtil::decryptContentArchiveHeader(sample.data(), nca_raw, header_key); - - if (nca_header->st_magic.get() != nn::hac::nca::kNca2StructMagic && nca_header->st_magic.get() != nn::hac::nca::kNca3StructMagic) - return false; - - return true; -} - -bool UserSettings::determineValidCnmtFromSample(const fnd::Vec& sample) const -{ - if (sample.size() < sizeof(nn::hac::sContentMetaHeader)) - return false; - - const nn::hac::sContentMetaHeader* data = (const nn::hac::sContentMetaHeader*)sample.data(); - - size_t minimum_size = sizeof(nn::hac::sContentMetaHeader) + data->exhdr_size.get() + data->content_count.get() * sizeof(nn::hac::sContentInfo) + data->content_meta_count.get() * sizeof(nn::hac::sContentMetaInfo) + nn::hac::cnmt::kDigestLen; - - if (sample.size() < minimum_size) - return false; - - // include exthdr/data check if applicable - if (data->exhdr_size.get() > 0) - { - if (data->type == (byte_t)nn::hac::cnmt::ContentMetaType::Application) - { - const nn::hac::sApplicationMetaExtendedHeader* meta = (const nn::hac::sApplicationMetaExtendedHeader*)(sample.data() + sizeof(nn::hac::sContentMetaHeader)); - if ((meta->patch_id.get() & data->id.get()) != data->id.get()) - return false; - } - else if (data->type == (byte_t)nn::hac::cnmt::ContentMetaType::Patch) - { - const nn::hac::sPatchMetaExtendedHeader* meta = (const nn::hac::sPatchMetaExtendedHeader*)(sample.data() + sizeof(nn::hac::sContentMetaHeader)); - if ((meta->application_id.get() & data->id.get()) != meta->application_id.get()) - return false; - - minimum_size += meta->extended_data_size.get(); - } - else if (data->type == (byte_t)nn::hac::cnmt::ContentMetaType::AddOnContent) - { - const nn::hac::sAddOnContentMetaExtendedHeader* meta = (const nn::hac::sAddOnContentMetaExtendedHeader*)(sample.data() + sizeof(nn::hac::sContentMetaHeader)); - if ((meta->application_id.get() & data->id.get()) != meta->application_id.get()) - return false; - } - else if (data->type == (byte_t)nn::hac::cnmt::ContentMetaType::Delta) - { - const nn::hac::sDeltaMetaExtendedHeader* meta = (const nn::hac::sDeltaMetaExtendedHeader*)(sample.data() + sizeof(nn::hac::sContentMetaHeader)); - if ((meta->application_id.get() & data->id.get()) != meta->application_id.get()) - return false; - - minimum_size += meta->extended_data_size.get(); - } - else if (data->type == (byte_t)nn::hac::cnmt::ContentMetaType::SystemUpdate) - { - const nn::hac::sSystemUpdateMetaExtendedHeader* meta = (const nn::hac::sSystemUpdateMetaExtendedHeader*)(sample.data() + sizeof(nn::hac::sContentMetaHeader)); - - minimum_size += meta->extended_data_size.get(); - } - } - - if (sample.size() != minimum_size) - return false; - - return true; -} - -bool UserSettings::determineValidNacpFromSample(const fnd::Vec& sample) const -{ - if (sample.size() != sizeof(nn::hac::sApplicationControlProperty)) - return false; - - const nn::hac::sApplicationControlProperty* data = (const nn::hac::sApplicationControlProperty*)sample.data(); - - if (data->logo_type > (byte_t)nn::hac::nacp::LogoType::Nintendo) - return false; - - if (data->display_version[0] == 0) - return false; - - if (data->user_account_save_data_size.get() == 0 && data->user_account_save_data_journal_size.get() != 0) - return false; - - if (data->user_account_save_data_journal_size.get() == 0 && data->user_account_save_data_size.get() != 0) - return false; - - if (data->supported_language_flag.get() == 0) - return false; - - return true; -} - -bool UserSettings::determineValidEsCertFromSample(const fnd::Vec& sample) const -{ - nn::pki::SignatureBlock sign; - - try - { - sign.fromBytes(sample.data(), sample.size()); - } - catch (...) - { - return false; - } - - if (sign.isLittleEndian() == true) - return false; - - if (sign.getSignType() != nn::pki::sign::SIGN_ID_RSA4096_SHA256 && sign.getSignType() != nn::pki::sign::SIGN_ID_RSA2048_SHA256 && sign.getSignType() != nn::pki::sign::SIGN_ID_ECDSA240_SHA256) - return false; - - return true; -} - -bool UserSettings::determineValidEsTikFromSample(const fnd::Vec& sample) const -{ - nn::pki::SignatureBlock sign; - - try - { - sign.fromBytes(sample.data(), sample.size()); - } - catch (...) - { - return false; - } - - if (sign.isLittleEndian() == false) - return false; - - if (sign.getSignType() != nn::pki::sign::SIGN_ID_RSA2048_SHA256) - return false; - - return true; -} - -bool UserSettings::getIs64BitInstructionFromString(const std::string & type_str) -{ - std::string str = type_str; - std::transform(str.begin(), str.end(), str.begin(), ::tolower); - - bool flag; - if (str == "32bit") - flag = false; - else if (str == "64bit") - flag = true; - else - throw fnd::Exception(kModuleName, "Unsupported instruction type: " + str); - - return flag; -} - -void UserSettings::getHomePath(std::string& path) const -{ - // open other resource files in $HOME/.switch/prod.keys (or $HOME/.switch/dev.keys if -d/--dev is set). - path.clear(); - if (path.empty()) fnd::io::getEnvironVar(path, "HOME"); - if (path.empty()) fnd::io::getEnvironVar(path, "USERPROFILE"); - if (path.empty()) return; -} - -void UserSettings::getSwitchPath(std::string& path) const -{ - std::string home; - home.clear(); - getHomePath(home); - if (home.empty()) - return; - - path.clear(); - fnd::io::appendToPath(path, home); - fnd::io::appendToPath(path, kHomeSwitchDirStr); -} - -void UserSettings::dumpKeyConfig() const -{ - fnd::aes::sAes128Key aes_key; - fnd::aes::sAesXts128Key aesxts_key; - fnd::rsa::sRsa2048Key rsa2048_key; - fnd::rsa::sRsa4096Key rsa4096_key; - - 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"}; - - - std::cout << "[KeyConfiguration]" << std::endl; - std::cout << " NCA Keys:" << std::endl; - for (size_t i = 0; i < kMasterKeyNum; i++) - { - if (mKeyCfg.getContentArchiveHeader0SignKey(rsa2048_key, byte_t(i)) == true) - dumpRsa2048Key(rsa2048_key, "Header0-SignatureKey-" + kKeyIndex[i], 2); - } - for (size_t i = 0; i < kMasterKeyNum; i++) - { - if (mKeyCfg.getAcidSignKey(rsa2048_key, byte_t(i)) == true) - dumpRsa2048Key(rsa2048_key, "Acid-SignatureKey-" + kKeyIndex[i], 2); - } - - if (mKeyCfg.getContentArchiveHeaderKey(aesxts_key) == true) - dumpAesXtsKey(aesxts_key, "Header-EncryptionKey", 2); - - for (size_t i = 0; i < kMasterKeyNum; i++) - { - if (mKeyCfg.getNcaKeyAreaEncryptionKey(byte_t(i), 0, aes_key) == true) - dumpAesKey(aes_key, "KeyAreaEncryptionKey-Application-" + kKeyIndex[i], 2); - if (mKeyCfg.getNcaKeyAreaEncryptionKey(byte_t(i), 1, aes_key) == true) - dumpAesKey(aes_key, "KeyAreaEncryptionKey-Ocean-" + kKeyIndex[i], 2); - if (mKeyCfg.getNcaKeyAreaEncryptionKey(byte_t(i), 2, aes_key) == true) - dumpAesKey(aes_key, "KeyAreaEncryptionKey-System-" + kKeyIndex[i], 2); - } - - for (size_t i = 0; i < kMasterKeyNum; i++) - { - if (mKeyCfg.getNcaKeyAreaEncryptionKeyHw(byte_t(i), 0, aes_key) == true) - dumpAesKey(aes_key, "KeyAreaEncryptionKeyHw-Application-" + kKeyIndex[i], 2); - if (mKeyCfg.getNcaKeyAreaEncryptionKeyHw(byte_t(i), 1, aes_key) == true) - dumpAesKey(aes_key, "KeyAreaEncryptionKeyHw-Ocean-" + kKeyIndex[i], 2); - if (mKeyCfg.getNcaKeyAreaEncryptionKeyHw(byte_t(i), 2, aes_key) == true) - dumpAesKey(aes_key, "KeyAreaEncryptionKeyHw-System-" + kKeyIndex[i], 2); - } - - std::cout << " NRR Keys:" << std::endl; - for (size_t i = 0; i < kMasterKeyNum; i++) - { - if (mKeyCfg.getNrrCertificateSignKey(rsa2048_key, byte_t(i)) == true) - dumpRsa2048Key(rsa2048_key, "Certificate-SignatureKey-" + kKeyIndex[i], 2); - } - - std::cout << " XCI Keys:" << std::endl; - if (mKeyCfg.getXciHeaderSignKey(rsa2048_key) == true) - dumpRsa2048Key(rsa2048_key, "Header-SignatureKey", 2); - if (mKeyCfg.getXciHeaderKey(aes_key) == true) - dumpAesKey(aes_key, "ExtendedHeader-EncryptionKey", 2); - - - - - std::cout << " Package1 Keys:" << std::endl; - for (size_t i = 0; i < kMasterKeyNum; i++) - { - if (mKeyCfg.getPkg1Key(byte_t(i), aes_key) == true) - dumpAesKey(aes_key, "EncryptionKey-" + kKeyIndex[i], 2); - } - - std::cout << " Package2 Keys:" << std::endl; - if (mKeyCfg.getPkg2SignKey(rsa2048_key) == true) - dumpRsa2048Key(rsa2048_key, "Signature Key", 2); - for (size_t i = 0; i < kMasterKeyNum; i++) - { - if (mKeyCfg.getPkg2Key(byte_t(i), aes_key) == true) - dumpAesKey(aes_key, "EncryptionKey-" + kKeyIndex[i], 2); - } - - std::cout << " ETicket Keys:" << std::endl; - for (size_t i = 0; i < kMasterKeyNum; i++) - { - if (mKeyCfg.getETicketCommonKey(byte_t(i), aes_key) == true) - dumpAesKey(aes_key, "CommonKey-" + kKeyIndex[i], 2); - } - - if (mKeyCfg.getPkiRootSignKey("Root", rsa4096_key) == true) - dumpRsa4096Key(rsa4096_key, "NNPKI Root Key", 1); -} - -void UserSettings::dumpRsa2048Key(const fnd::rsa::sRsa2048Key& key, const std::string& name, size_t indent) const -{ - std::string indent_str; - - indent_str.clear(); - for (size_t i = 0; i < indent; i++) - { - indent_str += " "; - } - - std::cout << indent_str << name << ":" << std::endl; - if (key.modulus[0] != 0x00 && key.modulus[1] != 0x00) - { - std::cout << indent_str << " Modulus:" << std::endl; - for (size_t i = 0; i < 0x10; i++) - { - std::cout << indent_str << " " << fnd::SimpleTextOutput::arrayToString(key.modulus + i * 0x10, 0x10, true, ":") << std::endl; - } - } - if (key.priv_exponent[0] != 0x00 && key.priv_exponent[1] != 0x00) - { - std::cout << indent_str << " Private Exponent:" << std::endl; - for (size_t i = 0; i < 0x10; i++) - { - std::cout << indent_str << " " << fnd::SimpleTextOutput::arrayToString(key.priv_exponent + i * 0x10, 0x10, true, ":") << std::endl; - } - } -} - -void UserSettings::dumpRsa4096Key(const fnd::rsa::sRsa4096Key& key, const std::string& name, size_t indent) const -{ - std::string indent_str; - - indent_str.clear(); - for (size_t i = 0; i < indent; i++) - { - indent_str += " "; - } - - std::cout << indent_str << name << ":" << std::endl; - if (key.modulus[0] != 0x00 && key.modulus[1] != 0x00) - { - std::cout << indent_str << " Modulus:" << std::endl; - for (size_t i = 0; i < 0x20; i++) - { - std::cout << indent_str << " " << fnd::SimpleTextOutput::arrayToString(key.modulus + i * 0x10, 0x10, true, ":") << std::endl; - } - } - if (key.priv_exponent[0] != 0x00 && key.priv_exponent[1] != 0x00) - { - std::cout << indent_str << " Private Exponent:" << std::endl; - for (size_t i = 0; i < 0x20; i++) - { - std::cout << indent_str << " " << fnd::SimpleTextOutput::arrayToString(key.priv_exponent + i * 0x10, 0x10, true, ":") << std::endl; - } - } -} - -void UserSettings::dumpAesKey(const fnd::aes::sAes128Key& key, const std::string& name, size_t indent) const -{ - std::string indent_str; - - indent_str.clear(); - for (size_t i = 0; i < indent; i++) - { - indent_str += " "; - } - - std::cout << indent_str << name << ":" << std::endl; - std::cout << indent_str << " " << fnd::SimpleTextOutput::arrayToString(key.key, 0x10, true, ":") << std::endl; -} - -void UserSettings::dumpAesXtsKey(const fnd::aes::sAesXts128Key& key, const std::string& name, size_t indent) const -{ - std::string indent_str; - - indent_str.clear(); - for (size_t i = 0; i < indent; i++) - { - indent_str += " "; - } - - std::cout << indent_str << name << ":" << std::endl; - std::cout << indent_str << " " << fnd::SimpleTextOutput::arrayToString(key.key[0], 0x20, true, ":") << std::endl; -} \ No newline at end of file diff --git a/src/UserSettings.h b/src/UserSettings.h deleted file mode 100644 index 379fb9b..0000000 --- a/src/UserSettings.h +++ /dev/null @@ -1,138 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include "common.h" -#include "KeyConfiguration.h" - -class UserSettings -{ -public: - UserSettings(); - - void parseCmdArgs(const std::vector& 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& getXciUpdatePath() const; - const sOptional& getXciLogoPath() const; - const sOptional& getXciNormalPath() const; - const sOptional& getXciSecurePath() const; - const sOptional& getFsPath() const; - const sOptional& getNcaPart0Path() const; - const sOptional& getNcaPart1Path() const; - const sOptional& getNcaPart2Path() const; - const sOptional& getNcaPart3Path() const; - const sOptional& getKipExtractPath() const; - const sOptional& getAssetIconPath() const; - const sOptional& getAssetNacpPath() const; - const fnd::List>& 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 input_path; - sOptional devkit_keys; - sOptional keyset_path; - sOptional file_type; - sOptional verify_file; - sOptional show_keys; - sOptional show_layout; - sOptional verbose_output; - sOptional list_fs; - sOptional update_path; - sOptional logo_path; - sOptional normal_path; - sOptional secure_path; - sOptional fs_path; - sOptional nca_titlekey; - sOptional nca_bodykey; - sOptional ticket_path; - sOptional cert_path; - sOptional part0_path; - sOptional part1_path; - sOptional part2_path; - sOptional part3_path; - sOptional kip_extract_path; - sOptional list_api; - sOptional list_sym; - sOptional inst_type; - sOptional asset_icon_path; - sOptional asset_nacp_path; - }; - - std::string mInputPath; - FileType mFileType; - KeyConfiguration mKeyCfg; - bool mVerifyFile; - CliOutputMode mOutputMode; - - bool mListFs; - sOptional mXciUpdatePath; - sOptional mXciLogoPath; - sOptional mXciNormalPath; - sOptional mXciSecurePath; - sOptional mFsPath; - - sOptional mNcaPart0Path; - sOptional mNcaPart1Path; - sOptional mNcaPart2Path; - sOptional mNcaPart3Path; - - sOptional mKipExtractPath; - - sOptional mAssetIconPath; - sOptional mAssetNacpPath; - - fnd::List> mCertChain; - - bool mListApi; - bool mListSymbols; - bool mIs64BitInstruction; - - void populateCmdArgs(const std::vector& 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& sample) const; - bool determineValidCnmtFromSample(const fnd::Vec& sample) const; - bool determineValidNacpFromSample(const fnd::Vec& sample) const; - bool determineValidEsCertFromSample(const fnd::Vec& sample) const; - bool determineValidEsTikFromSample(const fnd::Vec& 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; -}; \ No newline at end of file diff --git a/src/common.h b/src/common.h deleted file mode 100644 index 8625a8f..0000000 --- a/src/common.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include - -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 -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& operator=(const sOptional& 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}; \ No newline at end of file diff --git a/src/elf.h b/src/elf.h new file mode 100644 index 0000000..0fe891c --- /dev/null +++ b/src/elf.h @@ -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 */ + }; +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index bad4a3e..7e400ea 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,8 @@ -#include -#include -#include -#include -#include "UserSettings.h" +#include +#include +#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& args, const std::vector& env) { - std::vector 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); + + std::shared_ptr infile_stream = std::make_shared(tc::io::FileStream(set.infile.path.get(), tc::io::FileMode::Open, tc::io::FileAccess::Read)); - UserSettings user_set; - try { - user_set.parseCmdArgs(args); - - fnd::SharedPtr 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()); - - 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.setKeyCfg(set.opt.keybag); + 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_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; } \ No newline at end of file diff --git a/src/types.h b/src/types.h new file mode 100644 index 0000000..f11c14d --- /dev/null +++ b/src/types.h @@ -0,0 +1,32 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + + +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; +}; + +} \ No newline at end of file diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..a3645ac --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,150 @@ +#include "util.h" + +#include +#include +#include + +#include +#include +#include + +inline bool isNotPrintable(char chr) { return isprint(chr) == false; } + +void nstool::processResFile(const std::shared_ptr& file, std::map& 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& in_stream, int64_t offset, int64_t length, const tc::io::Path& out_path, tc::ByteData& cache) +{ + writeStreamToStream(std::make_shared(tc::io::SubStream(in_stream, offset, length)), std::make_shared(tc::io::FileStream(out_path, tc::io::FileMode::Create, tc::io::FileAccess::Write)), cache); +} + +void nstool::writeSubStreamToFile(const std::shared_ptr& 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(in_stream, offset, length)), std::make_shared(tc::io::FileStream(out_path, tc::io::FileMode::Create, tc::io::FileAccess::Write)), cache_size); +} + +void nstool::writeStreamToFile(const std::shared_ptr& in_stream, const tc::io::Path& out_path, tc::ByteData& cache) +{ + writeStreamToStream(in_stream, std::make_shared(tc::io::FileStream(out_path, tc::io::FileMode::Create, tc::io::FileAccess::Write)), cache); +} + +void nstool::writeStreamToFile(const std::shared_ptr& in_stream, const tc::io::Path& out_path, size_t cache_size) +{ + writeStreamToStream(in_stream, std::make_shared(tc::io::FileStream(out_path, tc::io::FileMode::Create, tc::io::FileAccess::Write)), cache_size); +} + +void nstool::writeStreamToStream(const std::shared_ptr& in_stream, const std::shared_ptr& 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& in_stream, const std::shared_ptr& 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; +} \ No newline at end of file diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..d54275f --- /dev/null +++ b/src/util.h @@ -0,0 +1,20 @@ +#pragma once +#include "types.h" + +namespace nstool +{ + +void processResFile(const std::shared_ptr& file, std::map& dict); + +void writeSubStreamToFile(const std::shared_ptr& in_stream, int64_t offset, int64_t length, const tc::io::Path& out_path, tc::ByteData& cache); +void writeSubStreamToFile(const std::shared_ptr& 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& in_stream, const tc::io::Path& out_path, tc::ByteData& cache); +void writeStreamToFile(const std::shared_ptr& in_stream, const tc::io::Path& out_path, size_t cache_size = 0x10000); +void writeStreamToStream(const std::shared_ptr& in_stream, const std::shared_ptr& out_stream, tc::ByteData& cache); +void writeStreamToStream(const std::shared_ptr& in_stream, const std::shared_ptr& 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); + +} \ No newline at end of file diff --git a/src/version.h b/src/version.h index 25ed5b5..cdf2ade 100644 --- a/src/version.h +++ b/src/version.h @@ -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" \ No newline at end of file