diff --git "a/Automation Scripts/Batch-Script/Compress NSP\342\210\225XCI to NSZ\342\210\225XCZ.cmd" "b/Automation Scripts/Batch-Script/Compress NSP\342\210\225XCI to NSZ\342\210\225XCZ.cmd" new file mode 100644 index 0000000..c4c8fc7 --- /dev/null +++ "b/Automation Scripts/Batch-Script/Compress NSP\342\210\225XCI to NSZ\342\210\225XCZ.cmd" @@ -0,0 +1,99 @@ +@ECHO OFF & CHCP 65001 1>NUL & COLOR 07 +TITLE Compress NSP/XCI to NSZ/XCZ + +REM nsz.exe file path. +SET "nszFilePath=%~dp0nsz.exe" + +REM Source directory path where to search for NSP/XCI files. +SET "SrcDirectoryPath=C:\Nintendo Switch dumps" + +REM Destination directory path where to save generated NSZ/XCZ files. +SET "DstDirectoryPath=%SrcDirectoryPath%" + +REM 'True' to enable recursive NSP/XCI file search on source directory, 'False' to disable it. +SET "EnableRecursiveSearch=False" + +REM nsz.exe compression level (maximum value is 22, default is 18). +SET /A "CompressionLevel=22" + +REM Additional nsz.exe parameters. +SET "AdditionalParameters=--long --solid --alwaysParseCnmt --undupe-rename --titlekeys --quick-verify" + +:WELCOME_SCREEN +ECHO:╔═══════════════════════════════════════════════════════════╗ +ECHO:║ TITLE │ Compress NSP/XCI to NSZ/XCZ Script ║ +ECHO:║_________│_________________________________________________║ +ECHO:║ │ Automates the compression of Nintendo Switch ║ +ECHO:║ PURPOSE │ NSP/XCI dumps into NSZ/XCZ format respectively. ║ +ECHO:║_________│_________________________________________________║ +ECHO:║ VERSION │ ElektroStudios - Ver. 1.2 'keep it simple' ║ +ECHO:╚═══════════════════════════════════════════════════════════╝ +ECHO+ +ECHO:IMPORTANT: Before proceeding, open this script file in Notepad to adjust the following script settings as needed. +ECHO+ +ECHO: ○ nsz.exe full path: +ECHO: %nszFilePath% +ECHO+ +ECHO: ○ Source directory path where to search for NSP/XCI files: +ECHO: %SrcDirectoryPath% +ECHO+ +ECHO: ○ Destination directory path where to save NSZ/XCZ files: +ECHO: %DstDirectoryPath% +ECHO+ +ECHO: ○ Enable recursive NSP/XCI file search on source directory: +ECHO: %EnableRecursiveSearch% +ECHO+ +ECHO: ○ nsz.exe compression level (max. value is 22): +ECHO: %CompressionLevel% +ECHO+ +ECHO: ○ Additional nsz.exe parameters: +ECHO: %AdditionalParameters% +ECHO+ +PAUSE +CLS + +:PRIMARY_CHECKS +REM Ensure nsz.exe file exists. +IF NOT EXIST "%nszFilePath%" ( + CALL :PRINT_ERROR_AND_EXIT nsz.exe file does not exists: "%nszFilePath%" +) +REM Ensure the source directory exists. +IF NOT EXIST "%SrcDirectoryPath%" ( + CALL :PRINT_ERROR_AND_EXIT Source directory does not exists: "%SrcDirectoryPath%" +) +REM Ensure the output directory can be created. +MKDIR "%DstDirectoryPath%" 1>NUL 2>&1 || ( + IF NOT EXIST "%DstDirectoryPath%" ( + CALL :PRINT_ERROR_AND_EXIT Output directory can't be created: "%DstDirectoryPath%" + ) +) + +:NSZ_WORK +REM FOR-loop logic. +IF /I "%EnableRecursiveSearch%" EQU "True" ( + SET "Params=/R "%SrcDirectoryPath%" %%# IN ("*.nsp" "*.xci")" +) ELSE ( + SET "Params=%%# IN ("%SrcDirectoryPath%\*.nsp" "%SrcDirectoryPath%\*.xci")" +) +FOR %Params% DO ( + TITLE nsz "%%~nx#" + ECHO:Compressing "%%~f#"... + ECHO+ + ("%nszFilePath%" -C "%%~f#" --output "%DstDirectoryPath%" --level %CompressionLevel% %AdditionalParameters%) || ( + CALL :PRINT_ERROR_AND_EXIT nsz failed to compress file: "%%~f#" + ) +) + +:GOODBYE_SCREEN +COLOR 0A +ECHO+ +ECHO:Operation Completed! +ECHO+ +PAUSE & EXIT 0 + +:PRINT_ERROR_AND_EXIT +COLOR 0C +ECHO+ +ECHO:ERROR OCCURRED: %* +ECHO+ +PAUSE & EXIT 1 \ No newline at end of file diff --git "a/Automation Scripts/Batch-Script/Decompress NSZ\342\210\225XCZ to NSP\342\210\225XCI.cmd" "b/Automation Scripts/Batch-Script/Decompress NSZ\342\210\225XCZ to NSP\342\210\225XCI.cmd" new file mode 100644 index 0000000..2c172fa --- /dev/null +++ "b/Automation Scripts/Batch-Script/Decompress NSZ\342\210\225XCZ to NSP\342\210\225XCI.cmd" @@ -0,0 +1,93 @@ +@ECHO OFF & CHCP 65001 1>NUL & COLOR 07 +TITLE Decompress NSZ/XCZ to NSP/XCI + +REM nsz.exe file path. +SET "nszFilePath=%~dp0nsz.exe" + +REM Source directory path where to search for NSZ/XCZ files. +SET "SrcDirectoryPath=C:\Nintendo Switch dumps" + +REM Destination directory path where to save decompressed NSP/XCI files. +SET "DstDirectoryPath=%SrcDirectoryPath%" + +REM 'True' to enable recursive NSZ/XCZ file search on source directory, 'False' to disable it. +SET "EnableRecursiveSearch=False" + +REM Additional nsz.exe parameters. +SET "AdditionalParameters=--alwaysParseCnmt --undupe-rename --titlekeys --quick-verify" + +:WELCOME_SCREEN +ECHO:╔════════════════════════════════════════════════════════════════╗ +ECHO:║ TITLE │ Decompress NSZ/XCZ to NSP/XCI Script ║ +ECHO:║_________│______________________________________________________║ +ECHO:║ │ Automates the decompression of Nintendo Switch ║ +ECHO:║ PURPOSE │ NSZ/XCZ dumps back into NSP/XCI format respectively. ║ +ECHO:║_________│______________________________________________________║ +ECHO:║ VERSION │ ElektroStudios - Ver. 1.2 'keep it simple' ║ +ECHO:╚════════════════════════════════════════════════════════════════╝ +ECHO+ +ECHO:IMPORTANT: Before proceeding, open this script file in Notepad to adjust the following script settings as needed. +ECHO+ +ECHO: ○ nsz.exe file path: +ECHO: %nszFilePath% +ECHO+ +ECHO: ○ Source directory path where to search for NSZ/XCZ files: +ECHO: %SrcDirectoryPath% +ECHO+ +ECHO: ○ Destination directory path where to save decompressed NSP/XCI files: +ECHO: %DstDirectoryPath% +ECHO+ +ECHO: ○ Enable recursive NSZ/XCZ file search on source directory: +ECHO: %EnableRecursiveSearch% +ECHO+ +ECHO: ○ Additional nsz.exe parameters: +ECHO: %AdditionalParameters% +ECHO+ +PAUSE +CLS + +:PRIMARY_CHECKS +REM Ensure nsz.exe file exists. +IF NOT EXIST "%nszFilePath%" ( + CALL :PRINT_ERROR_AND_EXIT nsz.exe file does not exists: "%nszFilePath%" +) +REM Ensure the source directory exists. +IF NOT EXIST "%SrcDirectoryPath%" ( + CALL :PRINT_ERROR_AND_EXIT Source directory does not exists: "%SrcDirectoryPath%" +) +REM Ensure the output directory can be created. +MKDIR "%DstDirectoryPath%" 1>NUL 2>&1 || ( + IF NOT EXIST "%DstDirectoryPath%" ( + CALL :PRINT_ERROR_AND_EXIT Output directory can't be created: "%DstDirectoryPath%" + ) +) + +:NSZ_WORK +REM FOR-loop logic. +IF /I "%EnableRecursiveSearch%" EQU "True" ( + SET "Params=/R "%SrcDirectoryPath%" %%# IN ("*.nsz" "*.xcz")" +) ELSE ( + SET "Params=%%# IN ("%SrcDirectoryPath%\*.nsz" "%SrcDirectoryPath%\*.xcz")" +) +FOR %Params% DO ( + TITLE nsz "%%~nx#" + ECHO:Decompressing "%%~f#"... + ECHO+ + ("%nszFilePath%" -D "%%~f#" --output "%DstDirectoryPath%" %AdditionalParameters%) || ( + CALL :PRINT_ERROR_AND_EXIT nsz failed to decompress file: "%%~f#" + ) +) + +:GOODBYE_SCREEN +COLOR 0A +ECHO+ +ECHO:Operation Completed! +ECHO+ +PAUSE & EXIT 0 + +:PRINT_ERROR_AND_EXIT +COLOR 0C +ECHO+ +ECHO:ERROR OCCURRED: %* +ECHO+ +PAUSE & EXIT 1 \ No newline at end of file diff --git "a/Automation Scripts/Batch-Script/Extract NSP\342\210\225NSZ\342\210\225XCI\342\210\225XCZ.cmd" "b/Automation Scripts/Batch-Script/Extract NSP\342\210\225NSZ\342\210\225XCI\342\210\225XCZ.cmd" new file mode 100644 index 0000000..e4e6fd7 --- /dev/null +++ "b/Automation Scripts/Batch-Script/Extract NSP\342\210\225NSZ\342\210\225XCI\342\210\225XCZ.cmd" @@ -0,0 +1,93 @@ +@ECHO OFF & CHCP 65001 1>NUL & COLOR 07 +TITLE Extract NSP/NSZ/XCI/XCZ + +:: nsz.exe file path. +SET "nszFilePath=%~dp0nsz.exe" + +:: Source directory path where to search for NSP/NSZ/XCI/XCZ files. +SET "SrcDirectoryPath=C:\Nintendo Switch dumps" + +:: Destination directory path where to extract NSP/NSZ/XCI/XCZ files. +SET "DstDirectoryPath=%SrcDirectoryPath%" + +:: 'True' to enable recursive NSP/NSZ/XCI/XCZ file search on source directory, 'False' to disable it. +SET "EnableRecursiveSearch=False" + +:: Additional NSZ parameters. +SET "AdditionalParameters=--alwaysParseCnmt --titlekeys --quick-verify" + +:WELCOME_SCREEN +ECHO:╔══════════════════════════════════════════════════════════╗ +ECHO:║ TITLE │ Extract NSP/NSZ/XCI/XCZ Script ║ +ECHO:║_________│________________________________________________║ +ECHO:║ │ Automates the extraction of Nintendo Switch ║ +ECHO:║ PURPOSE │ NSP/NSZ/XCI/XCZ file content into directories. ║ +ECHO:║_________│________________________________________________║ +ECHO:║ VERSION │ ElektroStudios - Ver. 1.2 'keep it simple' ║ +ECHO:╚══════════════════════════════════════════════════════════╝ +ECHO+ +ECHO:IMPORTANT: Before proceeding, open this script file in Notepad to adjust the following script settings as needed. +ECHO+ +ECHO: ○ nsz.exe file path: +ECHO: %nszFilePath% +ECHO+ +ECHO: ○ Source directory path where to search for NSP/NSZ/XCI/XCZ files: +ECHO: %SrcDirectoryPath% +ECHO+ +ECHO: ○ Destination directory path where to extract the content of NSP/NSZ/XCI/XCZ files: +ECHO: %DstDirectoryPath% +ECHO+ +ECHO: ○ Enable recursive NSP/NSZ/XCI/XCZ file search on source directory: +ECHO: %EnableRecursiveSearch% +ECHO+ +ECHO: ○ Additional nsz.exe parameters: +ECHO: %AdditionalParameters% +ECHO+ +PAUSE +CLS + +:PRIMARY_CHECKS +REM Ensure nsz.exe file exists. +IF NOT EXIST "%nszFilePath%" ( + CALL :PRINT_ERROR_AND_EXIT nsz.exe file does not exists: "%nszFilePath%" +) +REM Ensure the source directory exists. +IF NOT EXIST "%SrcDirectoryPath%" ( + CALL :PRINT_ERROR_AND_EXIT Source directory does not exists: "%SrcDirectoryPath%" +) +REM Ensure the output directory can be created. +MKDIR "%DstDirectoryPath%" 1>NUL 2>&1 || ( + IF NOT EXIST "%DstDirectoryPath%" ( + CALL :PRINT_ERROR_AND_EXIT Output directory can't be created: "%DstDirectoryPath%" + ) +) + +:NSZ_WORK +REM FOR-loop logic. +IF /I "%EnableRecursiveSearch%" EQU "True" ( + SET "Params=/R "%SrcDirectoryPath%" %%# IN ("*.nsp" "*.nsz" "*.xci" "*.xcz")" +) ELSE ( + SET "Params=%%# IN ("%SrcDirectoryPath%\*.nsp" "%SrcDirectoryPath%\*.nsz" "%SrcDirectoryPath%\*.xci" "%SrcDirectoryPath%\*.xcz")" +) +FOR %Params% DO ( + TITLE nsz "%%~nx#" + ECHO:Extracting "%%~f#"... + ECHO+ + ("%nszFilePath%" --extract "%%~f#" --output "%DstDirectoryPath%" %AdditionalParameters%) || ( + CALL :PRINT_ERROR_AND_EXIT nsz failed to extract file: "%%~f#" + ) +) + +:GOODBYE_SCREEN +COLOR 0A +ECHO+ +ECHO:Operation Completed! +ECHO+ +PAUSE & EXIT 0 + +:PRINT_ERROR_AND_EXIT +COLOR 0C +ECHO+ +ECHO:ERROR OCCURRED: %* +ECHO+ +PAUSE & EXIT 1 \ No newline at end of file diff --git a/Automation Scripts/Batch-Script/Get title keys.cmd b/Automation Scripts/Batch-Script/Get title keys.cmd new file mode 100644 index 0000000..a301cb1 --- /dev/null +++ b/Automation Scripts/Batch-Script/Get title keys.cmd @@ -0,0 +1,81 @@ +@ECHO OFF & CHCP 65001 1>NUL & COLOR 07 +TITLE Get title keys + +REM nsz.exe file path. +SET "nszFilePath=%~dp0nsz.exe" + +REM Source directory path where to search for NSP/NSZ/XCI/XCZ files. +SET "SrcDirectoryPath=C:\Nintendo Switch dumps" + +REM 'True' to enable recursive NSP/NSZ/XCI/XCZ file search on source directory, 'False' to disable it. +SET "EnableRecursiveSearch=False" + +REM Additional nsz.exe parameters. +SET "AdditionalParameters=--alwaysParseCnmt" + +:WELCOME_SCREEN +ECHO:╔══════════════════════════════════════════════════════╗ +ECHO:║ TITLE │ Extract title keys Script ║ +ECHO:║_________│____________________________________________║ +ECHO:║ │ Automates the extraction of title keys for ║ +ECHO:║ PURPOSE │ Nintendo Switch NSP/NSZ/XCI/XCZ dumps. ║ +ECHO:║_________│____________________________________________║ +ECHO:║ VERSION │ ElektroStudios - Ver. 1.2 'keep it simple' ║ +ECHO:╚══════════════════════════════════════════════════════╝ +ECHO+ +ECHO:IMPORTANT: Before proceeding, open this script file in Notepad to adjust the following script settings as needed. +ECHO+ +ECHO: ○ nsz.exe file path: +ECHO: %nszFilePath% +ECHO+ +ECHO: ○ Source directory path where to search for NSP/NSZ/XCI/XCZ files: +ECHO: %SrcDirectoryPath% +ECHO+ +ECHO: ○ Enable recursive NSP/NSZ/XCI/XCZ file search on source directory: +ECHO: %EnableRecursiveSearch% +ECHO+ +ECHO: ○ Additional nsz.exe parameters: +ECHO: %AdditionalParameters% +ECHO+ +PAUSE +CLS + +:PRIMARY_CHECKS +REM Ensure nsz.exe file exists. +IF NOT EXIST "%nszFilePath%" ( + CALL :PRINT_ERROR_AND_EXIT nsz.exe file does not exists: "%nszFilePath%" +) +REM Ensure the source directory exists. +IF NOT EXIST "%SrcDirectoryPath%" ( + CALL :PRINT_ERROR_AND_EXIT Source directory does not exists: "%SrcDirectoryPath%" +) + +:NSZ_WORK +REM FOR-loop logic. +IF /I "%EnableRecursiveSearch%" EQU "True" ( + SET "Params=/R "%SrcDirectoryPath%" %%# IN ("*.nsp" "*.xci")" +) ELSE ( + SET "Params=%%# IN ("%SrcDirectoryPath%\*.nsp" "%SrcDirectoryPath%\*.xci")" +) +FOR %Params% DO ( + TITLE nsz "%%~nx#" + ECHO:Extracting title keys for "%%~f#"... + ECHO+ + ("%nszFilePath%" --info "%%~f#" --titlekeys %AdditionalParameters%) || ( + CALL :PRINT_ERROR_AND_EXIT nsz failed to parse file: "%%~f#" + ) +) + +:GOODBYE_SCREEN +COLOR 0A +ECHO+ +ECHO:Operation Completed! +ECHO+ +PAUSE & EXIT 0 + +:PRINT_ERROR_AND_EXIT +COLOR 0C +ECHO+ +ECHO:ERROR OCCURRED: %* +ECHO+ +PAUSE & EXIT 1 \ No newline at end of file diff --git a/README.md b/README.md index f6e9865..6de243d 100644 --- a/README.md +++ b/README.md @@ -89,9 +89,11 @@ options: exception on hash mismatch and verify existing NSP and NSZ files when given as parameter. Requires --keep when used during compression. + NOTE: Some hash checks will be skipped when processing a ticketless dump file. -Q, --quick-verify Same as --verify but skips the NSP SHA256 hash verification and only verifies NCA hashes. Does not require --keep when used during compression. + NOTE: Some hash checks will be skipped when processing a ticketless dump file. -K, --keep Keep all useless files and partitions during compression to allow bit-identical recreation -F, --fix-padding Fixes PFS0 padding to match the nxdumptool/no-intro @@ -132,6 +134,7 @@ options: missing keys to ./titlekeys.txt and JSON files inside ./titledb/ (obtainable from https://github.com/blawar/titledb). + NOTE: This parameter has no effect when processing a ticketless dump file. --undupe Deleted all duplicates (games with same ID and Version). The Files folder will get parsed in order so the later in the argument list the more likely the @@ -169,6 +172,8 @@ options: To view all the possible flags and a description on what each flag, check the [Usage](https://github.com/nicoboss/nsz#usage) section. +To automate bulk file operations you might want to check out [automation script files](https://github.com/nicoboss/nsz/tree/master/Automation%20Scripts). + ## File Format Details ### NSZ diff --git a/nsz/ExtractTitlekeys.py b/nsz/ExtractTitlekeys.py index 6077386..41c7c73 100644 --- a/nsz/ExtractTitlekeys.py +++ b/nsz/ExtractTitlekeys.py @@ -19,14 +19,19 @@ def extractTitlekeys(argsFile): f = factory(filePath) f.open(str(filePath), 'rb') ticket = f.ticket() - rightsId = format(ticket.getRightsId(), 'x').zfill(32) - titleId = rightsId[0:16] - if not titleId in titlekeysDict: - titleKey = format(ticket.getTitleKeyBlock(), 'x').zfill(32) - titlekeysDict[titleId] = (rightsId, titleKey, filePath.stem) - Print.info("Found: {0}|{1}|{2}".format(rightsId, titleKey, filePath.stem)) + if ticket is None: + # This ticket conditional was added to prevent the following exception from occurring when parsing a ticketless dump file: + # nut exception: 'NoneType' object has no attribute 'getRightsId' + Print.info("Skipped ticketless {0}".format(filePath.stem)) else: - Print.info("Skipped already existing {0}".format(rightsId)) + rightsId = format(ticket.getRightsId(), 'x').zfill(32) + titleId = rightsId[0:16] + if not titleId in titlekeysDict: + titleKey = format(ticket.getTitleKeyBlock(), 'x').zfill(32) + titlekeysDict[titleId] = (rightsId, titleKey, filePath.stem) + Print.info("Found: {0}|{1}|{2}".format(rightsId, titleKey, filePath.stem)) + else: + Print.info("Skipped already existing {0}".format(rightsId)) f.close() Print.info("\ntitlekeys.txt:") with open('titlekeys.txt', 'w', encoding="utf-8") as titlekeysFile: diff --git a/nsz/Fs/Nsp.py b/nsz/Fs/Nsp.py index f3ae746..b340c7a 100644 --- a/nsz/Fs/Nsp.py +++ b/nsz/Fs/Nsp.py @@ -213,7 +213,9 @@ def dict(self): def ticket(self): for f in (f for f in self if type(f) == Ticket): return f - raise IOError('no ticket in NSP') + Fs.Ticket.isTicketless = True + # Exception suppressed to allow compress/decompress of ticketless -single base game or multicontent- dump files. + #raise IOError('no ticket in NSP') def cnmt(self): for f in (f for f in self if f._path.endswith('.cnmt.nca')): diff --git a/nsz/Fs/Ticket.py b/nsz/Fs/Ticket.py index 98c2e63..594192f 100644 --- a/nsz/Fs/Ticket.py +++ b/nsz/Fs/Ticket.py @@ -4,6 +4,9 @@ from nsz.nut import Print from nsz.nut import Keys +# Global flag used to skip some checks for ticketless dump files. +global isTicketless +isTicketless = None class Ticket(File): def __init__(self, path = None, mode = None, cryptoType = -1, cryptoKey = -1, cryptoCounter = -1): diff --git a/nsz/NszDecompressor.py b/nsz/NszDecompressor.py index 47e4aa8..61e9cf6 100644 --- a/nsz/NszDecompressor.py +++ b/nsz/NszDecompressor.py @@ -3,7 +3,7 @@ from hashlib import sha256 from nsz.nut import Print, aes128 from zstandard import ZstdDecompressor -from nsz.Fs import factory, Type, Pfs0, Hfs0, Nca, Xci +from nsz.Fs import factory, Type, Pfs0, Hfs0, Nca, Xci, Ticket from nsz.PathTools import * from nsz import Header, BlockDecompressorReader, FileExistingChecks import os, enlighten @@ -86,14 +86,19 @@ def __decompressContainer(readContainer, writeContainer, fileHashes, write, rais written, hexHash = __decompressNcz(nspf, writeContainer.get(newFileName), statusReportInfo, pleaseNoPrint) else: written, hexHash = __decompressNcz(nspf, None, statusReportInfo, pleaseNoPrint) - if hexHash in fileHashes: - Print.info(f'[NCA HASH] {hexHash}', pleaseNoPrint) - Print.info(f'[VERIFIED] {nspf._path}', pleaseNoPrint) + if Ticket.isTicketless is True: + # This ticket conditional was added to prevent the following exception from occurring when processing a ticketless dump file: + # nut exception: Verification detected hash mismatch + Print.info(f'[SKIPPED] ticketless', pleaseNoPrint) else: - Print.info(f'[NCA HASH] {hexHash}', pleaseNoPrint) - Print.info(f'[CORRUPTED] {nspf._path}', pleaseNoPrint) - if raiseVerificationException: - raise VerificationException("Verification detected hash mismatch") + if hexHash in fileHashes: + Print.info(f'[NCA HASH] {hexHash}', pleaseNoPrint) + Print.info(f'[VERIFIED] {nspf._path}', pleaseNoPrint) + else: + Print.info(f'[NCA HASH] {hexHash}', pleaseNoPrint) + Print.info(f'[CORRUPTED] {nspf._path}', pleaseNoPrint) + if raiseVerificationException: + raise VerificationException("Verification detected hash mismatch") def __getDecompressedNczSize(nspf):