diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..a03b5b7b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,18 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +*.c text +*.h text + +# Declare files that will always have CRLF line endings on checkout. +*.py text eol=crlf +*.bat text eol=crlf +*.cmd text eol=crlf + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary +*.exe binary +*.zip binary \ No newline at end of file diff --git a/.gitignore b/.gitignore index 895c7a03..0f4e2ab3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,13 @@ py/ztools/keys.txt py/INFO py/NSCB_output +py/NSCB_extracted py/extract py/ztools/Fs/__pycache__ py/ztools/lib/__pycache__ py/list.txt py/advlist.txt +py/ztools/pythac +py/ztools/hactool.exe +py/ztools/nstool.exe +py/badfiles.txt diff --git a/README.md b/README.md index 4a57132c..98f9f96b 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,8 @@ Current version of the program allows you to: 16.- Set jobs for later in multi mode 17.- Separate jobs by based-titleid in multi mode 18.- Remove bad characters from filenames (sanitize) or convert asian names to romaji +19.- Extract nca file contents for base games and dlcs or extract ncas as plaintext +20.- Joiner for xc*,ns* and *0 fat32 files ## 4. Batch modes: @@ -68,9 +70,14 @@ The behavior of the auto-mode is configured trough the * 4. Information about firmware requirements and other game data * 5. Read cnmt file from meta nca * 6. Read nacp file from control nca - * 7. Verify files with ability of detecting NSCB changes over them + * 7. Read npdm file from program nca + * 8. Verify files with ability of detecting NSCB changes over them - MODE 5: Database Mode. Lets you mass output information -- MODE 6: Advanced Mode. Currently extracts nca from xci\nsp (more to be added) +- MODE 6: Advanced Mode. + * 1. Extracts all contents from a nsp\xci + * 2. Extracts all contents from a nsp\xci in raw mode + * 3. Extracts all contents from a nsp\xci in plaintext + * 4. Extracts files from nca inside a nsp\xci - L: Legacy Mode. Old functions ## 6. Configuration mode: @@ -167,6 +174,8 @@ b.) Hacbuild: The xci repacking functions are based on hacbuild's code, made by c.) Big thx to 0Liam for his constant help. +d.) pyNCA3,pyNPDM,pyPFS0,pyRomFS libraries adapted from pythac (made by Rikikooo) + Also thanks to: AnalogMan. He made splitNSP.py, figured the needed block size for Horizon format splitted nsps (wich differs from the splitted xci block size) and the need to archive the folders) @@ -178,4 +187,4 @@ Thx to 0mn0 and the old SH crew for always being helpful. Thx to evOLved, Cinnabar and a certain dragon for their help and good suggestions. -Also thanks to all members from gbatemp, elotrolado.net and my friends at discord ;) +Also thanks to all members from gbatemp, elotrolado.net and my friends at discord ;) \ No newline at end of file diff --git a/py/English CHANGELOG.txt b/py/English CHANGELOG.txt index 4b3eedd9..6c6a051a 100644 --- a/py/English CHANGELOG.txt +++ b/py/English CHANGELOG.txt @@ -21,6 +21,36 @@ Luca Fraga's github: https://github.com/LucaFraga --------------- 0. Changelog --------------- +v0.88- Bugfixes and new stuff + - Fixes direct creation of fat32 files, to reflect that the new modes create + split nsp or xci files for fat32 sd cards directly if set in the config. + Current implemented modes: + 1. INDIVIDUAL MODE + * For now the pack options under "SPECIAL OPTIONS" are not supported + 2. MULTI-PACK MODE + 3. MULTI-CONTENT-SPLITTER MODE + - Added mode 7: FILE-JOINER mode to join fat32 .xc*,.ns* or *0 + > For *0 files drop the folder if you have several *0 files to join to not + confuse the batch operations, for .xc* and .ns* files joins are grouped by filename + - Fixes bug in verification where RSV check bar stays after finishing RSV check on + unlockers + - Fixes bug in verification where hash verification gives a really slow speed on some files. + - Fixes bug in some modes with files that use keygeneration 2 + - Fixes bug where info mode 1 doesn't show the total size of deltas + - Fix nacp reader for xci files + - Fix Language and nacp offset detection in some games + - Fixes titlekey verification on nsp and nsx dlcs + - Added main.npdm reader for nsp and xci files + - Add raw extraction option to advance mode so files with messed nca headers can be + extracted and verify the nca independently + - Added romaji option to db so outputting names in romaji can be turn off + - Added extraction of ncas in plaintext to advance mode + - Added extraction of nca contents to advance mode + NOTE: For now plaintext and extraction of ncas will skip program ncas in updates. + Process is done without extraction of ncas from nsp\xci with a fallback that + pre-extracts the nca if the the sections are not correctly detected. + NOTE2: Consider extraction and plaintext options on early stages. + v0.87c- Bugfixes - Adds check for correct original titlekey for xci conversions from nsx files without a titlekey. diff --git a/py/English Readme.txt b/py/English Readme.txt index 039beb6b..95fd9a66 100644 --- a/py/English Readme.txt +++ b/py/English Readme.txt @@ -1,23 +1,5 @@ - __ _ __ __ - ____ _____ ____ / /_ __ __(_) /___/ /__ _____ - / __ \/ ___/ ___/ / __ \/ / / / / / __ / _ \/ ___/ - / / / (__ ) /__ / /_/ / /_/ / / / /_/ / __/ / - /_/ /_/____/\___/____/_.___/\__,_/_/_/\__,_/\___/_/ - /_____/ -------------------------------------------------------------------------------------- - NINTENDO SWITCH CLEANER AND BUILDER - (THE XCI MULTI CONTENT BUILDER AND MORE) -------------------------------------------------------------------------------------- -============================= BY JULESONTHEROAD ============================= -------------------------------------------------------------------------------------- -" POWERED BY SQUIRREL " -" BASED IN THE WORK OF BLAWAR AND LUCA FRAGA " -------------------------------------------------------------------------------------- -Program's github: https://github.com/julesontheroad/NSC_BUILDER -Blawar's github: https://github.com/blawar -Blawar's tinfoil: https://github.com/digableinc/tinfoil -Luca Fraga's github: https://github.com/LucaFraga -------------------------------------------------------------------------------------- +# Nintendo Switch Cleaner and Builder (NSC_Builder) +https://github.com/julesontheroad/NSC_BUILDER ## 1. Description @@ -26,13 +8,13 @@ NSC_Builder is the merged Project that continues xci_builder and Nut_Batch_Clean NSC_Builder is based both in the works of Blawars nut.py and Luca Fragas hacbuild and powered by squirrel a nuts fork with added functions that removes the CDN based functions from nut while tweaks the title-rights modification functions and adds some useful ones for file management. From version v0.8 the program doesnt rely on hacbuild for xci generation and new code was made for a better integration on squirrel. -Squirrel will get a new github repository soon and be packed as exe for NSCB from beta v0.8. Old squirrel code can be seen in the NSCB main repository, new code will be published in its own repository at the end of NSCB beta phase after some cleanup its down on its code. +Squirrel will get a new github repository soon and be packed as exe for NSCB from beta v0.8. Old squirrel code can be seen in the NSCB main repository, new code will be published in its own repository at the end of NSCB beta phase after some cleanup its done on its code. ## 2. Whats the meaning of REMOVING TITLE RIGHTS. When you remove the titlerights encryption from nsp files you can install the games without any need of tickets, which leaves a smaller trackable footprint on your console, providing you arent sending telemetry data to Nintendo. It also helps in the conversion from nsp to xci files allowing to not install tickets externally. -## 3. What can I do with this program: +## 3. What can I do with this program? Current version of the program allows you to: 1.- Make multi-content xci or nsp files. @@ -44,7 +26,18 @@ Current version of the program allows you to: 7.- Lower the Required System Version to the actual encryption of the game. 8.- Lower the masterkey needed to decrypt a game. 9.- Check out information from a xci and nsp, including the Firmware needed to be able to execute it, the game info, the size of the nca content +10.- Check data from nacp and cnmt files without extracting them from nsp\xci 10.- Repack xci and nsp content in formats compatible with fat32 +11.- Mass build xci files and nsp files in single and multi content format +12.- Rename nsp,xci files to match it's content +13.- Verify nsp, nsx, xci y nca files +14.- Output information in text format +15.- Extract content of nsp files and secure partition of xci files +16.- Set jobs for later in multi mode +17.- Separate jobs by based-titleid in multi mode +18.- Remove bad characters from filenames (sanitize) or convert asian names to romaji +19.- Extract nca file contents for base games and dlcs or extract ncas as plaintext +20.- Joiner for xc*,ns* and *0 fat32 files ## 4. Batch modes: @@ -60,10 +53,32 @@ The behavior of the auto-mode is configured trough the - MODE 0: Configuration mode. Lets you configure the way the program works in both auto and manual mode. - MODE 1: Indidual packing. Lets you process a list of files and pack them individually + * Pack as nsp\xci + * Supertrimm xci files + * Rename xci or nsp files + * Rebuild nsp files in cnmt order and add cnmt.xml + * Verify nsp,xci files - MODE 2: Multi packing. Lets you pack a list of files in a single xci or nsp file. + * Separate files by basedid + * Set up jobs for later + * Process previous jobs - MODE 3: Multi-Content-Splitter. Lets you separate content to nsp and xci files. -- MODE 4: Update mode. Lets you erase previous updates or dlcs from a multi-content xci or nsp file and add new updates. NOTE: Always use it with multi-content files. -- MODE 5: File-Info. Lets you see and export several info about nsp and xci files +- MODE 4: File-Info. Lets you see and export several info about nsp and xci files + * 1. Data about included files in nsp\xci + * 2. Data about content ids in file + * 3. Nut info as implemented by nut by blawar + * 4. Information about firmware requirements and other game data + * 5. Read cnmt file from meta nca + * 6. Read nacp file from control nca + * 7. Read npdm file from program nca + * 8. Verify files with ability of detecting NSCB changes over them +- MODE 5: Database Mode. Lets you mass output information +- MODE 6: Advanced Mode. + * 1. Extracts all contents from a nsp\xci + * 2. Extracts all contents from a nsp\xci in raw mode + * 3. Extracts all contents from a nsp\xci in plaintext + * 4. Extracts files from nca inside a nsp\xci +- L: Legacy Mode. Old functions ## 6. Configuration mode: ### Auto Mode options. (Affects only Auto-Mode) @@ -88,7 +103,7 @@ The behavior of the auto-mode is configured trough the - Lets you choose the name and location of the output folder #### DELTA files treatment - Lets you choose if youre going to pack delta NCA files or not. Set to false by default. -#### ZIP configuration +#### ZIP configuration (currently unused) - Lets you choose if you want to create a zip storing some file information. Set to false by default. #### AUTO-EXIT configuration - Lets you choose if the cmd window closes after completing the job. @@ -101,10 +116,15 @@ Pack xci or nsp in fat32 compatible formats or exfat format. - Change CARD FORMAT to exfat (Default) - Change CARD FORMAT to fat32 for SX OS (xc0 and ns0 files) - Change CARD FORMAT to fat32 for all CFW (archive folder) - -#### How to ORGANIZE output files +#### How to ORGANIZE output files (currently unused for new modes) - Organize files separetely (default) - Organize files in folders set by content +#### Set New Mode or Legacy Mode +- Use new more advance methods (default) +- Use old file processing methods +#### ROMANIZE names when using direct-multi +- Convert names to romaji (default) +- Read names from file and keep asian namings when they're read ## 7. Important @@ -112,25 +132,32 @@ This program attempts to modify the minimum data possible in nsp and xci files, - SX OS - ReiNX https://github.com/Reisyukaku/ReiNX/releases -- RShadowhands starter pack Singularit", wich is a preconfigured atmosphere, made to auto-launch via Hekate trough fusee primary including the needed patches and some homebrew starters. -https://github.com/RShadowhand/singularite/releases +- For Kosmos use joonie86 sigpatches and Hekate5.0 or joonie86 Hekate Mod "a.k.a J" +https://github.com/Joonie86/hekate/releases/tag/5.0.0J +- For atmosphere use the4n sigpatches +https://gbatemp.net/attachments/2-0-0-8-1-0-zip.170607/ To install multi-nsp you need a installer compatible with them. Reported compatible installers are: - SX OS rom-menu - SX OS installer - Blawars tinfoil: https://github.com/digableinc/tinfoil +- Blawars lithium: +https://github.com/blawar/lithium -## 8. Requirements for 0.8 Beta +## 8. Requirements - A computer with a Window's OS is needed -- If you get dll missing messages install Visual C++ Redistributable for Visual Studio 2015 -https://www.microsoft.com/en-us/download/details.aspx?id=48145 +- Fill keys_template.txt on the ztools folder and rename it to keys.txt + You can get a full keyset with Lockpick if your console is at FW6.2 or + A friend can lend you the needed keys. + If you want to add the xci_header_key a friend will need to lend it to you. + https://github.com/shchmue/Lockpick/releases ## 9. Limitations - You can't make multi-content xci files with more than 8 games. It'll give error when loading in horizon. I suspect it may be a qlauncher limitation so it could work with theme mods but INTRO didn't test it. Note: This means games, updates and dl car not hold by that limitation. -- Title-rights remove dlcs give a message prompt of incomplete content for some games from 6.0 onwords, that message can be skipped and the dlcs will work fine despite the prompt. +- Title-rights remove dlcs give a message prompt of incomplete content for some games from 6.0 onwards, that message can be skipped and the dlcs will work fine despite the prompt. ## 7. Thanks and credits to @@ -145,6 +172,10 @@ b.) Hacbuild: The xci repacking functions are based on hacbuild's code, made by - Revised hacbuild by me: https://github.com/julesontheroad/hacbuild +c.) Big thx to 0Liam for his constant help. + +d.) pyNCA3,pyNPDM,pyPFS0,pyRomFS libraries adapted from pythac (made by Rikikooo) + Also thanks to: AnalogMan. He made splitNSP.py, figured the needed block size for Horizon format splitted nsps (wich differs from the splitted xci block size) and the need to archive the folders) @@ -152,8 +183,8 @@ https://github.com/AnalogMan151/splitNSP/releases Thx to MadScript77 his great suggestions,specially the idea of profiles for the batch. -Thx to Liam and 0mn0 from old SH discord for always being helpfull. +Thx to 0mn0 and the old SH crew for always being helpful. -Thx to XCI-Explorer's creator StudentBlake since his program made easier for me to came up with the fix for hacbuild. +Thx to evOLved, Cinnabar and a certain dragon for their help and good suggestions. -Also thanks to all members from gbatemp, elotrolado.net and my friends at discord ;) +Also thanks to all members from gbatemp, elotrolado.net and my friends at discord ;) \ No newline at end of file diff --git a/py/NSCB.bat b/py/NSCB.bat index c53c9a28..e28b0a09 100644 --- a/py/NSCB.bat +++ b/py/NSCB.bat @@ -3,7 +3,7 @@ set "prog_dir=%~dp0" set "bat_name=%~n0" set "ofile_name=%bat_name%_options.cmd" -Title NSC_Builder v0.87c -- Profile: %ofile_name% -- by JulesOnTheRoad +Title NSC_Builder v0.88 -- Profile: %ofile_name% -- by JulesOnTheRoad set "list_folder=%prog_dir%lists" ::----------------------------------------------------- ::EDIT THIS VARIABLE TO LINK OTHER OPTION FILE @@ -118,7 +118,7 @@ goto folder_ind_mode ::AUTO MODE. INDIVIDUAL REPACK PROCESSING OPTION. :folder_ind_mode -if "%fatype%" EQU "-fat fat32" goto folder_ind_mode_fat32 +rem if "%fatype%" EQU "-fat fat32" goto folder_ind_mode_fat32 call :program_logo echo -------------------------------------- echo Auto-Mode. Individual repacking is set @@ -149,9 +149,9 @@ REM endlocal & ( set "vpack=!vrepack!" ) REM if "%trn_skip%" EQU "true" ( call :check_titlerights ) if "%vrename%" EQU "true" ( call :addtags_from_nsp ) -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "nsp" -dc "%%f" ) -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "xci" -dc "%%f" ) -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "both" -dc "%%f" ) +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "nsp" -dc "%%f" ) +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "xci" -dc "%%f" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "both" -dc "%%f" ) if not exist "%fold_output%" MD "%fold_output%" >NUL 2>&1 @@ -179,9 +179,9 @@ MD "%w_folder%" call :getname if "%vrename%" EQU "true" ( call :addtags_from_xci ) -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "nsp" -dc "%%f" ) -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "xci" -dc "%%f" ) -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "both" -dc "%%f" ) +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "nsp" -dc "%%f" ) +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "xci" -dc "%%f" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "both" -dc "%%f" ) if not exist "%fold_output%" MD "%fold_output%" >NUL 2>&1 @@ -297,7 +297,7 @@ goto aut_exit_choice ::AUTO MODE. MULTIREPACK PROCESSING OPTION. :folder_mult_mode -if "%fatype%" EQU "-fat fat32" goto folder_mult_mode_fat32 +rem if "%fatype%" EQU "-fat fat32" goto folder_mult_mode_fat32 call :program_logo echo -------------------------------------- echo Auto-Mode. Multi-repacking is set @@ -313,25 +313,25 @@ echo DONE if "%vrepack%" EQU "nsp" echo ...................................... if "%vrepack%" EQU "nsp" echo REPACKING FOLDER CONTENT TO NSP if "%vrepack%" EQU "nsp" echo ...................................... -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t cnsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t cnsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "nsp" echo. if "%vrepack%" EQU "xci" echo ...................................... if "%vrepack%" EQU "xci" echo REPACKING FOLDER CONTENT TO XCI if "%vrepack%" EQU "xci" echo ...................................... -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "xci" echo. if "%vrepack%" EQU "both" echo ...................................... if "%vrepack%" EQU "both" echo REPACKING FOLDER CONTENT TO NSP if "%vrepack%" EQU "both" echo ...................................... -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t cnsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t cnsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "both" echo. if "%vrepack%" EQU "both" echo ...................................... if "%vrepack%" EQU "both" echo REPACKING FOLDER CONTENT TO XCI if "%vrepack%" EQU "both" echo ...................................... -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "both" echo. setlocal enabledelayedexpansion @@ -414,7 +414,7 @@ ECHO --------------------------------------------------- goto aut_exit_choice :folder_packbyid -::if "%fatype%" EQU "-fat fat32" goto folder_mult_mode_fat32 +rem if "%fatype%" EQU "-fat fat32" goto folder_mult_mode_fat32 call :program_logo echo -------------------------------------- echo Auto-Mode. packing-by-id is set @@ -471,7 +471,7 @@ pause goto manual :nsp -if "%fatype%" EQU "-fat fat32" goto file_nsp_fat32 +rem if "%fatype%" EQU "-fat fat32" goto file_nsp_fat32 set "orinput=%~f1" set "filename=%~n1" set "target=%~1" @@ -486,9 +486,9 @@ MD "%w_folder%" call :getname if "%vrename%" EQU "true" call :addtags_from_nsp -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "nsp" -dc "%~1" ) -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "xci" -dc "%~1" ) -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "both" -dc "%~1" ) +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "nsp" -dc "%~1" ) +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "xci" -dc "%~1" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "both" -dc "%~1" ) if not exist "%fold_output%" MD "%fold_output%" >NUL 2>&1 @@ -544,7 +544,7 @@ call :thumbup goto aut_exit_choice :xci -if "%fatype%" EQU "-fat fat32" goto file_xci_fat32 +rem if "%fatype%" EQU "-fat fat32" goto file_xci_fat32 set "filename=%~n1" set "orinput=%~f1" set "showname=%orinput%" @@ -556,9 +556,9 @@ call :getname if "%vrename%" EQU "true" call :addtags_from_xci -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "nsp" -dc "%~1" ) -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "xci" -dc "%~1" ) -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "both" -dc "%~1" ) +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "nsp" -dc "%~1" ) +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "xci" -dc "%~1" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "both" -dc "%~1" ) MD "%fold_output%\" >NUL 2>&1 @@ -647,6 +647,7 @@ echo Input "3" to enter into MULTI-CONTENT SPLITTER mode echo Input "4" to enter into FILE-INFO mode echo Input "5" to enter into DATABASE building mode echo Input "6" to enter into ADVANCED mode +echo Input "7" to enter into FILE-JOINER mode echo Input "0" to enter into CONFIGURATION mode echo. echo Input "L" to enter LEGACY MODES @@ -660,6 +661,7 @@ if /i "%bs%"=="3" goto SPLMODE if /i "%bs%"=="4" goto INFMODE if /i "%bs%"=="5" goto DBMODE if /i "%bs%"=="6" goto ADVmode +if /i "%bs%"=="7" goto JOINmode if /i "%bs%"=="L" goto LegacyMode if /i "%bs%"=="0" goto OPT_CONFIG goto manual_Reentry @@ -667,6 +669,9 @@ goto manual_Reentry :ADVmode call "%prog_dir%ztools\ADV.bat" goto manual_Reentry +:JOINmode +call "%prog_dir%ztools\JOINER.bat" +goto manual_Reentry :LegacyMode call "%prog_dir%ztools\LEGACY.bat" goto manual_Reentry @@ -1218,7 +1223,7 @@ if /i "%bs%"=="1" goto salida goto s_exit_choice :nsp_manual -if "%fatype%" EQU "-fat fat32" goto nsp_manual_fat32 +rem if "%fatype%" EQU "-fat fat32" goto nsp_manual_fat32 rem set "filename=%name%" rem set "showname=%orinput%" if "%zip_restore%" EQU "true" ( call :makezip ) @@ -1228,9 +1233,9 @@ call :squirrell if "%vrename%" EQU "true" call :addtags_from_nsp -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "nsp" -dc "%orinput%" -tfile "%prog_dir%list.txt") -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "xci" -dc "%orinput%" -tfile "%prog_dir%list.txt") -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "both" -dc "%orinput%" -tfile "%prog_dir%list.txt") +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "nsp" -dc "%orinput%" -tfile "%prog_dir%list.txt") +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "xci" -dc "%orinput%" -tfile "%prog_dir%list.txt") +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "both" -dc "%orinput%" -tfile "%prog_dir%list.txt") if "%vrepack%" EQU "nodelta" ( %pycommand% "%nut%" %buffer% --xml_gen "true" -o "%w_folder%" -tfile "%prog_dir%list.txt" --erase_deltas "") if "%vrepack%" EQU "rebuild" ( %pycommand% "%nut%" %buffer% %skdelta% --xml_gen "true" -o "%w_folder%" -tfile "%prog_dir%list.txt" --rebuild_nsp "") if "%vrepack%" EQU "verify" ( %pycommand% "%nut%" %buffer% -vt "%verif%" -tfile "%prog_dir%list.txt" -v "") @@ -1297,7 +1302,7 @@ goto end_nsp_manual exit /B :xci_manual -if "%fatype%" EQU "-fat fat32" goto xci_manual_fat32 +rem if "%fatype%" EQU "-fat fat32" goto xci_manual_fat32 ::FOR XCI FILES if exist "%w_folder%" rmdir /s /q "%w_folder%" >NUL 2>&1 MD "%w_folder%" @@ -1305,9 +1310,9 @@ MD "%w_folder%" set "filename=%name%" set "showname=%orinput%" -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "nsp" -dc "%orinput%" -tfile "%prog_dir%list.txt") -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "xci" -dc "%orinput%" -tfile "%prog_dir%list.txt") -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "both" -dc "%orinput%" -tfile "%prog_dir%list.txt") +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "nsp" -dc "%orinput%" -tfile "%prog_dir%list.txt") +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "xci" -dc "%orinput%" -tfile "%prog_dir%list.txt") +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "both" -dc "%orinput%" -tfile "%prog_dir%list.txt") if "%vrepack%" EQU "xci_supertrimmer" ( %pycommand% "%nut%" %buffer% -o "%w_folder%" -xci_st "%orinput%" -tfile "%prog_dir%list.txt") if "%vrepack%" EQU "verify" ( %pycommand% "%nut%" %buffer% -vt "%verif%" -tfile "%prog_dir%list.txt" -v "") if "%vrepack%" EQU "verify" ( goto end_xci_manual ) @@ -1719,35 +1724,35 @@ goto m_process_jobs2 cls :m_process_jobs2 dir "%mlistfol%\*.txt" /b > "%prog_dir%mlist.txt" -if "%fatype%" EQU "-fat fat32" goto m_process_jobs_fat32 +rem if "%fatype%" EQU "-fat fat32" goto m_process_jobs_fat32 for /f "tokens=*" %%f in (mlist.txt) do ( set "listname=%%f" if "%vrepack%" EQU "cnsp" call :program_logo if "%vrepack%" EQU "cnsp" call :m_split_merge_list_name -if "%vrepack%" EQU "cnsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t cnsp -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "cnsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t cnsp -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "xci" call :program_logo if "%vrepack%" EQU "xci" call :m_split_merge_list_name -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t xci -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t xci -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "nsp" call :program_logo if "%vrepack%" EQU "nsp" call :m_split_merge_list_name -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t nsp -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t nsp -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "cboth" call :program_logo if "%vrepack%" EQU "cboth" call :m_split_merge_list_name -if "%vrepack%" EQU "cboth" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t xci -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "cboth" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t xci -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "cboth" call :program_logo if "%vrepack%" EQU "cboth" call :m_split_merge_list_name -if "%vrepack%" EQU "cboth" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t cnsp -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "cboth" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t cnsp -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "both" call :program_logo if "%vrepack%" EQU "both" call :m_split_merge_list_name -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t nsp -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t nsp -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "both" call :program_logo if "%vrepack%" EQU "both" call :m_split_merge_list_name -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t xci -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t xci -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) more +1 "mlist.txt">"mlist.txt.new" move /y "mlist.txt.new" "mlist.txt" >nul if exist "%mlistfol%\%%f" del "%mlistfol%\%%f" @@ -1847,7 +1852,7 @@ echo ******************************************************* exit /B :m_normal_merge -if "%fatype%" EQU "-fat fat32" goto m_KeyChange_skip_fat32 +rem if "%fatype%" EQU "-fat fat32" goto m_KeyChange_skip_fat32 REM For the current beta the filenames are calculted. This code remains commented for future reintegration rem echo ******************************************************* rem echo ENTER FINAL FILENAME FOR THE OUTPUT FILE @@ -1861,23 +1866,23 @@ rem if /i "%finalname%"=="b" goto multi_checkagain cls if "%vrepack%" EQU "cnsp" call :program_logo -if "%vrepack%" EQU "cnsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t cnsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "cnsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t cnsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "xci" call :program_logo -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "nsp" call :program_logo -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t nsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t nsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "cboth" call :program_logo -if "%vrepack%" EQU "cboth" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "cboth" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "cboth" call :program_logo -if "%vrepack%" EQU "cboth" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t cnsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "cboth" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t cnsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "both" call :program_logo -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t nsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t nsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "both" call :program_logo -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) setlocal enabledelayedexpansion if not exist "%fold_output%" MD "%fold_output%" >NUL 2>&1 @@ -2308,16 +2313,16 @@ if /i "%bs%"=="1" goto salida goto SPLIT_exit_choice :split_content -if "%fatype%" EQU "-fat fat32" goto split_content_fat32 +rem if "%fatype%" EQU "-fat fat32" goto split_content_fat32 set "showname=%orinput%" set "sp_repack=%vrepack%" if exist "%w_folder%" RD /S /Q "%w_folder%" >NUL 2>&1 MD "%w_folder%" >NUL 2>&1 call :processing_message call :squirrell -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% -o "%w_folder%" -fat exfat -fx files -t "nsp" -dspl "%orinput%" -tfile "%prog_dir%splist.txt") -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% -o "%w_folder%" -fat exfat -fx files -t "xci" -dspl "%orinput%" -tfile "%prog_dir%splist.txt") -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% -o "%w_folder%" -fat exfat -fx files -t "both" -dspl "%orinput%" -tfile "%prog_dir%splist.txt") +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% -o "%w_folder%" %fatype% %fexport% -t "nsp" -dspl "%orinput%" -tfile "%prog_dir%splist.txt") +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% -o "%w_folder%" %fatype% %fexport% -t "xci" -dspl "%orinput%" -tfile "%prog_dir%splist.txt") +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% -o "%w_folder%" %fatype% %fexport% -t "both" -dspl "%orinput%" -tfile "%prog_dir%splist.txt") call :thumbup call :delay @@ -2728,7 +2733,7 @@ ECHO ============================= BY JULESONTHEROAD =================== ECHO ------------------------------------------------------------------------------------- ECHO " POWERED BY SQUIRREL " ECHO " BASED IN THE WORK OF BLAWAR AND LUCA FRAGA " -ECHO VERSION 0.87 (NEW) +ECHO VERSION 0.88 (NEW) ECHO ------------------------------------------------------------------------------------- ECHO Program's github: https://github.com/julesontheroad/NSC_BUILDER ECHO Blawar's github: https://github.com/blawar diff --git a/py/NSCB_KR.bat b/py/NSCB_KR.bat index 52d8cbf9..6f7799a1 100644 --- a/py/NSCB_KR.bat +++ b/py/NSCB_KR.bat @@ -3,7 +3,7 @@ set "prog_dir=%~dp0" set "bat_name=%~n0" set "ofile_name=%bat_name%_options.cmd" -Title NSC_Builder v0.87.c -- Profile: %ofile_name% -- by JulesOnTheRoad +Title NSC_Builder v0.88 -- Profile: %ofile_name% -- by JulesOnTheRoad set "list_folder=%prog_dir%lists" ::----------------------------------------------------- ::이 옵션을 다른 옵션 파일과 연결되도록 편집하십시오. @@ -118,7 +118,7 @@ goto folder_ind_mode ::자동 모드. 개별 리팩 처리 옵션. :folder_ind_mode -if "%fatype%" EQU "-fat fat32" goto folder_ind_mode_fat32 +rem if "%fatype%" EQU "-fat fat32" goto folder_ind_mode_fat32 call :program_logo echo -------------------------------------- echo 자동 모드. 개별 재포장이 설정 됨 @@ -149,9 +149,9 @@ REM endlocal & ( set "vpack=!vrepack!" ) REM if "%trn_skip%" EQU "true" ( call :check_titlerights ) if "%vrename%" EQU "true" ( call :addtags_from_nsp ) -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "nsp" -dc "%%f" ) -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "xci" -dc "%%f" ) -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "both" -dc "%%f" ) +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "nsp" -dc "%%f" ) +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "xci" -dc "%%f" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "both" -dc "%%f" ) if not exist "%fold_output%" MD "%fold_output%" >NUL 2>&1 @@ -179,9 +179,9 @@ MD "%w_folder%" call :getname if "%vrename%" EQU "true" ( call :addtags_from_xci ) -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "nsp" -dc "%%f" ) -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "xci" -dc "%%f" ) -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "both" -dc "%%f" ) +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "nsp" -dc "%%f" ) +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "xci" -dc "%%f" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "both" -dc "%%f" ) if not exist "%fold_output%" MD "%fold_output%" >NUL 2>&1 @@ -297,7 +297,7 @@ goto aut_exit_choice ::자동 모드. 멀티팩 처리 옵션.. :folder_mult_mode -if "%fatype%" EQU "-fat fat32" goto folder_mult_mode_fat32 +rem if "%fatype%" EQU "-fat fat32" goto folder_mult_mode_fat32 call :program_logo echo -------------------------------------- echo 자동 모드. 멀티-리패킹이 설정 됨 @@ -313,25 +313,25 @@ echo 완료 if "%vrepack%" EQU "nsp" echo ...................................... if "%vrepack%" EQU "nsp" echo NSP에 폴더 내용 리패키징 if "%vrepack%" EQU "nsp" echo ...................................... -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t cnsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t cnsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "nsp" echo. if "%vrepack%" EQU "xci" echo ...................................... if "%vrepack%" EQU "xci" echo XCI에 폴더 내용 리패키징 if "%vrepack%" EQU "xci" echo ...................................... -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "xci" echo. if "%vrepack%" EQU "both" echo ...................................... if "%vrepack%" EQU "both" echo NSP에 폴더 내용 리패키징 if "%vrepack%" EQU "both" echo ...................................... -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t cnsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t cnsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "both" echo. if "%vrepack%" EQU "both" echo ...................................... if "%vrepack%" EQU "both" echo XCI에 폴더 내용 리패키징 if "%vrepack%" EQU "both" echo ...................................... -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "both" echo. setlocal enabledelayedexpansion @@ -414,7 +414,7 @@ ECHO --------------------------------------------------- goto aut_exit_choice :folder_packbyid -::if "%fatype%" EQU "-fat fat32" goto folder_mult_mode_fat32 +rem if "%fatype%" EQU "-fat fat32" goto folder_mult_mode_fat32 call :program_logo echo -------------------------------------- echo 자동 모드. packing-by-id가 설정 됨 @@ -471,7 +471,7 @@ pause goto manual :nsp -if "%fatype%" EQU "-fat fat32" goto file_nsp_fat32 +rem if "%fatype%" EQU "-fat fat32" goto file_nsp_fat32 set "orinput=%~f1" set "filename=%~n1" set "target=%~1" @@ -486,9 +486,9 @@ MD "%w_folder%" call :getname if "%vrename%" EQU "true" call :addtags_from_nsp -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "nsp" -dc "%~1" ) -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "xci" -dc "%~1" ) -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "both" -dc "%~1" ) +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "nsp" -dc "%~1" ) +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "xci" -dc "%~1" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "both" -dc "%~1" ) if not exist "%fold_output%" MD "%fold_output%" >NUL 2>&1 @@ -544,7 +544,7 @@ call :thumbup goto aut_exit_choice :xci -if "%fatype%" EQU "-fat fat32" goto file_xci_fat32 +rem if "%fatype%" EQU "-fat fat32" goto file_xci_fat32 set "filename=%~n1" set "orinput=%~f1" set "showname=%orinput%" @@ -556,9 +556,9 @@ call :getname if "%vrename%" EQU "true" call :addtags_from_xci -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "nsp" -dc "%~1" ) -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "xci" -dc "%~1" ) -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "both" -dc "%~1" ) +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "nsp" -dc "%~1" ) +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "xci" -dc "%~1" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "both" -dc "%~1" ) MD "%fold_output%\" >NUL 2>&1 @@ -1183,7 +1183,7 @@ if /i "%bs%"=="1" goto salida goto s_exit_choice :nsp_manual -if "%fatype%" EQU "-fat fat32" goto nsp_manual_fat32 +rem if "%fatype%" EQU "-fat fat32" goto nsp_manual_fat32 rem set "filename=%name%" rem set "showname=%orinput%" if "%zip_restore%" EQU "true" ( call :makezip ) @@ -1193,9 +1193,9 @@ call :squirrell if "%vrename%" EQU "true" call :addtags_from_nsp -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "nsp" -dc "%orinput%" -tfile "%prog_dir%list.txt") -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "xci" -dc "%orinput%" -tfile "%prog_dir%list.txt") -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "both" -dc "%orinput%" -tfile "%prog_dir%list.txt") +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "nsp" -dc "%orinput%" -tfile "%prog_dir%list.txt") +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "xci" -dc "%orinput%" -tfile "%prog_dir%list.txt") +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "both" -dc "%orinput%" -tfile "%prog_dir%list.txt") if "%vrepack%" EQU "nodelta" ( %pycommand% "%nut%" %buffer% --xml_gen "true" -o "%w_folder%" -tfile "%prog_dir%list.txt" --erase_deltas "") if "%vrepack%" EQU "rebuild" ( %pycommand% "%nut%" %buffer% %skdelta% --xml_gen "true" -o "%w_folder%" -tfile "%prog_dir%list.txt" --rebuild_nsp "") if "%vrepack%" EQU "verify" ( %pycommand% "%nut%" %buffer% -vt "%verif%" -tfile "%prog_dir%list.txt" -v "") @@ -1262,7 +1262,7 @@ goto end_nsp_manual exit /B :xci_manual -if "%fatype%" EQU "-fat fat32" goto xci_manual_fat32 +rem if "%fatype%" EQU "-fat fat32" goto xci_manual_fat32 ::FOR XCI FILES if exist "%w_folder%" rmdir /s /q "%w_folder%" >NUL 2>&1 MD "%w_folder%" @@ -1270,9 +1270,9 @@ MD "%w_folder%" set "filename=%name%" set "showname=%orinput%" -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "nsp" -dc "%orinput%" -tfile "%prog_dir%list.txt") -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "xci" -dc "%orinput%" -tfile "%prog_dir%list.txt") -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -o "%w_folder%" -t "both" -dc "%orinput%" -tfile "%prog_dir%list.txt") +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "nsp" -dc "%orinput%" -tfile "%prog_dir%list.txt") +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "xci" -dc "%orinput%" -tfile "%prog_dir%list.txt") +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -o "%w_folder%" -t "both" -dc "%orinput%" -tfile "%prog_dir%list.txt") if "%vrepack%" EQU "xci_supertrimmer" ( %pycommand% "%nut%" %buffer% -o "%w_folder%" -xci_st "%orinput%" -tfile "%prog_dir%list.txt") if "%vrepack%" EQU "verify" ( %pycommand% "%nut%" %buffer% -vt "%verif%" -tfile "%prog_dir%list.txt" -v "") if "%vrepack%" EQU "verify" ( goto end_xci_manual ) @@ -1684,35 +1684,35 @@ goto m_process_jobs2 cls :m_process_jobs2 dir "%mlistfol%\*.txt" /b > "%prog_dir%mlist.txt" -if "%fatype%" EQU "-fat fat32" goto m_process_jobs_fat32 +rem if "%fatype%" EQU "-fat fat32" goto m_process_jobs_fat32 for /f "tokens=*" %%f in (mlist.txt) do ( set "listname=%%f" if "%vrepack%" EQU "cnsp" call :program_logo if "%vrepack%" EQU "cnsp" call :m_split_merge_list_name -if "%vrepack%" EQU "cnsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t cnsp -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "cnsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t cnsp -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "xci" call :program_logo if "%vrepack%" EQU "xci" call :m_split_merge_list_name -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t xci -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t xci -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "nsp" call :program_logo if "%vrepack%" EQU "nsp" call :m_split_merge_list_name -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t nsp -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t nsp -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "cboth" call :program_logo if "%vrepack%" EQU "cboth" call :m_split_merge_list_name -if "%vrepack%" EQU "cboth" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t xci -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "cboth" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t xci -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "cboth" call :program_logo if "%vrepack%" EQU "cboth" call :m_split_merge_list_name -if "%vrepack%" EQU "cboth" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t cnsp -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "cboth" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t cnsp -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "both" call :program_logo if "%vrepack%" EQU "both" call :m_split_merge_list_name -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t nsp -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t nsp -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "both" call :program_logo if "%vrepack%" EQU "both" call :m_split_merge_list_name -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t xci -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t xci -o "%w_folder%" -tfile "%mlistfol%\%%f" -roma %romaji% -dmul "calculate" ) more +1 "mlist.txt">"mlist.txt.new" move /y "mlist.txt.new" "mlist.txt" >nul if exist "%mlistfol%\%%f" del "%mlistfol%\%%f" @@ -1812,7 +1812,7 @@ echo ******************************************************* exit /B :m_normal_merge -if "%fatype%" EQU "-fat fat32" goto m_KeyChange_skip_fat32 +rem if "%fatype%" EQU "-fat fat32" goto m_KeyChange_skip_fat32 REM For the current beta the filenames are calculted. This code remains commented for future reintegration rem echo ******************************************************* rem echo ENTER FINAL FILENAME FOR THE OUTPUT FILE @@ -1826,23 +1826,23 @@ rem if /i "%finalname%"=="b" goto multi_checkagain cls if "%vrepack%" EQU "cnsp" call :program_logo -if "%vrepack%" EQU "cnsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t cnsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "cnsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t cnsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "xci" call :program_logo -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "nsp" call :program_logo -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t nsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t nsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "cboth" call :program_logo -if "%vrepack%" EQU "cboth" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "cboth" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "cboth" call :program_logo -if "%vrepack%" EQU "cboth" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t cnsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "cboth" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t cnsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "both" call :program_logo -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t nsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t nsp -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) if "%vrepack%" EQU "both" call :program_logo -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% -fat exfat -fx files %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% %patchRSV% %vkey% %capRSV% %fatype% %fexport% %skdelta% -t xci -o "%w_folder%" -tfile "%prog_dir%mlist.txt" -roma %romaji% -dmul "calculate" ) setlocal enabledelayedexpansion if not exist "%fold_output%" MD "%fold_output%" >NUL 2>&1 @@ -2273,16 +2273,16 @@ if /i "%bs%"=="1" goto salida goto SPLIT_exit_choice :split_content -if "%fatype%" EQU "-fat fat32" goto split_content_fat32 +rem if "%fatype%" EQU "-fat fat32" goto split_content_fat32 set "showname=%orinput%" set "sp_repack=%vrepack%" if exist "%w_folder%" RD /S /Q "%w_folder%" >NUL 2>&1 MD "%w_folder%" >NUL 2>&1 call :processing_message call :squirrell -if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% -o "%w_folder%" -fat exfat -fx files -t "nsp" -dspl "%orinput%" -tfile "%prog_dir%splist.txt") -if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% -o "%w_folder%" -fat exfat -fx files -t "xci" -dspl "%orinput%" -tfile "%prog_dir%splist.txt") -if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% -o "%w_folder%" -fat exfat -fx files -t "both" -dspl "%orinput%" -tfile "%prog_dir%splist.txt") +if "%vrepack%" EQU "nsp" ( %pycommand% "%nut%" %buffer% -o "%w_folder%" %fatype% %fexport% -t "nsp" -dspl "%orinput%" -tfile "%prog_dir%splist.txt") +if "%vrepack%" EQU "xci" ( %pycommand% "%nut%" %buffer% -o "%w_folder%" %fatype% %fexport% -t "xci" -dspl "%orinput%" -tfile "%prog_dir%splist.txt") +if "%vrepack%" EQU "both" ( %pycommand% "%nut%" %buffer% -o "%w_folder%" %fatype% %fexport% -t "both" -dspl "%orinput%" -tfile "%prog_dir%splist.txt") call :thumbup call :delay @@ -2693,7 +2693,7 @@ ECHO ============================= JULESONTHEROAD 제작 ================= ECHO ------------------------------------------------------------------------------------- ECHO " 다람쥐의 지원을 받는 NSCB " ECHO " BLAWAR 및 LUCA FRAGA 작업을 기반으로 작업 " -ECHO 버전 0.86 (신규) +ECHO 버전 0.88 (신규) ECHO ------------------------------------------------------------------------------------- ECHO Program의 github: https://github.com/julesontheroad/NSC_BUILDER ECHO Blawar의 github: https://github.com/blawar diff --git a/py/README.md b/py/README.md index 4a57132c..98f9f96b 100644 --- a/py/README.md +++ b/py/README.md @@ -36,6 +36,8 @@ Current version of the program allows you to: 16.- Set jobs for later in multi mode 17.- Separate jobs by based-titleid in multi mode 18.- Remove bad characters from filenames (sanitize) or convert asian names to romaji +19.- Extract nca file contents for base games and dlcs or extract ncas as plaintext +20.- Joiner for xc*,ns* and *0 fat32 files ## 4. Batch modes: @@ -68,9 +70,14 @@ The behavior of the auto-mode is configured trough the * 4. Information about firmware requirements and other game data * 5. Read cnmt file from meta nca * 6. Read nacp file from control nca - * 7. Verify files with ability of detecting NSCB changes over them + * 7. Read npdm file from program nca + * 8. Verify files with ability of detecting NSCB changes over them - MODE 5: Database Mode. Lets you mass output information -- MODE 6: Advanced Mode. Currently extracts nca from xci\nsp (more to be added) +- MODE 6: Advanced Mode. + * 1. Extracts all contents from a nsp\xci + * 2. Extracts all contents from a nsp\xci in raw mode + * 3. Extracts all contents from a nsp\xci in plaintext + * 4. Extracts files from nca inside a nsp\xci - L: Legacy Mode. Old functions ## 6. Configuration mode: @@ -167,6 +174,8 @@ b.) Hacbuild: The xci repacking functions are based on hacbuild's code, made by c.) Big thx to 0Liam for his constant help. +d.) pyNCA3,pyNPDM,pyPFS0,pyRomFS libraries adapted from pythac (made by Rikikooo) + Also thanks to: AnalogMan. He made splitNSP.py, figured the needed block size for Horizon format splitted nsps (wich differs from the splitted xci block size) and the need to archive the folders) @@ -178,4 +187,4 @@ Thx to 0mn0 and the old SH crew for always being helpful. Thx to evOLved, Cinnabar and a certain dragon for their help and good suggestions. -Also thanks to all members from gbatemp, elotrolado.net and my friends at discord ;) +Also thanks to all members from gbatemp, elotrolado.net and my friends at discord ;) \ No newline at end of file diff --git a/py/zconfig/NSCB_KR_options.cmd b/py/zconfig/NSCB_KR_options.cmd index bd58335a..6c6aafc3 100644 --- a/py/zconfig/NSCB_KR_options.cmd +++ b/py/zconfig/NSCB_KR_options.cmd @@ -30,7 +30,7 @@ set "pycommand=py -3" ::Buffer for the copy functions. ::Change the number for the number of bytes that works best for you ::32768=32kB ; 65536=64kB -set "buffer=buffer=-b 65536" +set "buffer=-b 65536" ::Copy function with or without deltas ::--C_clean -> Copy and remove titlerights. Don't skips deltas ::--C_clean_ND-> Copy and remove titlerights skipping deltas diff --git a/py/zconfig/NSCB_options.cmd b/py/zconfig/NSCB_options.cmd index bd58335a..6c6aafc3 100644 --- a/py/zconfig/NSCB_options.cmd +++ b/py/zconfig/NSCB_options.cmd @@ -30,7 +30,7 @@ set "pycommand=py -3" ::Buffer for the copy functions. ::Change the number for the number of bytes that works best for you ::32768=32kB ; 65536=64kB -set "buffer=buffer=-b 65536" +set "buffer=-b 65536" ::Copy function with or without deltas ::--C_clean -> Copy and remove titlerights. Don't skips deltas ::--C_clean_ND-> Copy and remove titlerights skipping deltas diff --git a/py/ztools/ADV.bat b/py/ztools/ADV.bat index b8eb2918..1baaf9c9 100644 --- a/py/ztools/ADV.bat +++ b/py/ztools/ADV.bat @@ -69,7 +69,7 @@ ECHO *********************************************** echo Input "0" to return to the MODE SELECTION MENU ECHO *********************************************** echo. -%pycommand% "%nut%" -t nsp xci -tfile "%prog_dir%advlist.txt" -uin "%uinput%" -ff "uinput" +%pycommand% "%nut%" -t nsp xci nsx -tfile "%prog_dir%advlist.txt" -uin "%uinput%" -ff "uinput" set /p eval=<"%uinput%" set eval=%eval:"=% setlocal enabledelayedexpansion @@ -170,7 +170,10 @@ echo ............ echo ******************************************************* echo CHOOSE HOW TO PROCESS THE FILES echo ******************************************************* -echo Input "1" to extract nca files +echo Input "1" to extract all files from nsp\xci +echo Input "2" for raw extraction (Use in case a nca gives magic error) +echo Input "3" to extract all nca files as plaintext +echo Input "4" to extract nca contents from nsp\xci echo. ECHO ****************************************** echo Or Input "b" to return to the list options @@ -181,6 +184,9 @@ set bs=%bs:"=% set vrepack=none if /i "%bs%"=="b" goto checkagain if /i "%bs%"=="1" goto extract +if /i "%bs%"=="2" goto raw_extract +if /i "%bs%"=="3" goto ext_plaintext +if /i "%bs%"=="4" goto ext_fromnca if %vrepack%=="none" goto s_cl_wrongchoice @@ -190,7 +196,58 @@ call :program_logo CD /d "%prog_dir%" for /f "tokens=*" %%f in (advlist.txt) do ( -%pycommand% "%nut%" %buffer% -o "%prog_dir%extract" -tfile "%prog_dir%advlist.txt" -x "" +%pycommand% "%nut%" %buffer% -o "%prog_dir%NSCB_extracted" -tfile "%prog_dir%advlist.txt" -x "" + +more +1 "advlist.txt">"advlist.txt.new" +move /y "advlist.txt.new" "advlist.txt" >nul +call :contador_NF +) +ECHO --------------------------------------------------- +ECHO *********** ALL FILES WERE PROCESSED! ************* +ECHO --------------------------------------------------- +goto s_exit_choice + +:raw_extract +cls +call :program_logo +CD /d "%prog_dir%" +for /f "tokens=*" %%f in (advlist.txt) do ( + +%pycommand% "%nut%" %buffer% -o "%prog_dir%NSCB_extracted" -tfile "%prog_dir%advlist.txt" -raw_x "" + +more +1 "advlist.txt">"advlist.txt.new" +move /y "advlist.txt.new" "advlist.txt" >nul +call :contador_NF +) +ECHO --------------------------------------------------- +ECHO *********** ALL FILES WERE PROCESSED! ************* +ECHO --------------------------------------------------- +goto s_exit_choice + +:ext_plaintext +cls +call :program_logo +CD /d "%prog_dir%" +for /f "tokens=*" %%f in (advlist.txt) do ( + +%pycommand% "%nut%" %buffer% -o "%prog_dir%NSCB_extracted" -tfile "%prog_dir%advlist.txt" -plx "" + +more +1 "advlist.txt">"advlist.txt.new" +move /y "advlist.txt.new" "advlist.txt" >nul +call :contador_NF +) +ECHO --------------------------------------------------- +ECHO *********** ALL FILES WERE PROCESSED! ************* +ECHO --------------------------------------------------- +goto s_exit_choice + +:ext_fromnca +cls +call :program_logo +CD /d "%prog_dir%" +for /f "tokens=*" %%f in (advlist.txt) do ( + +%pycommand% "%nut%" %buffer% -o "%prog_dir%NSCB_extracted" -tfile "%prog_dir%advlist.txt" -nfx "" more +1 "advlist.txt">"advlist.txt.new" move /y "advlist.txt.new" "advlist.txt" >nul @@ -264,7 +321,7 @@ ECHO ============================= BY JULESONTHEROAD =================== ECHO ------------------------------------------------------------------------------------- ECHO " POWERED BY SQUIRREL " ECHO " BASED IN THE WORK OF BLAWAR AND LUCA FRAGA " -ECHO VERSION 0.87 +ECHO VERSION 0.88 ECHO ------------------------------------------------------------------------------------- ECHO Program's github: https://github.com/julesontheroad/NSC_BUILDER ECHO Blawar's github: https://github.com/blawar diff --git a/py/ztools/Fs/Nca.py b/py/ztools/Fs/Nca.py index 1a1c0bf6..da08d7cb 100644 --- a/py/ztools/Fs/Nca.py +++ b/py/ztools/Fs/Nca.py @@ -18,6 +18,7 @@ import Fs from Fs import Type from Fs.File import File +from Fs.File import MemoryFile from Fs.Rom import Rom from Fs.Pfs0 import Pfs0 from Fs.BaseFs import BaseFs @@ -25,6 +26,7 @@ from Fs.Nacp import Nacp import sq_tools import pykakasi +from Fs.pyNCA3 import NCA3 MEDIA_SIZE = 0x200 RSA_PUBLIC_EXPONENT = 0x10001 @@ -386,6 +388,14 @@ def get_pfs0_hash(self, file = None, mode = 'rb', cryptoType = -1, cryptoKey = - hash_from_pfs0=self.read(0x20*mult) return hash_from_pfs0 + def extract(self,ofolder,buffer): + ncaname = str(self._path)[:-4]+'_nca' + ncafolder = os.path.join(ofolder,ncaname) + if not os.path.exists(ncafolder): + os.makedirs(ncafolder) + nca3 = NCA3(open(str(self._path), 'rb')) + nca3.extract_conts(ncafolder, disp=True) + def calc_htable_hash(self): indent = 2 tabs = '\t' * indent @@ -485,7 +495,49 @@ def pr_noenc_check(self, file = None, mode = 'rb'): if (str(g._path)) == 'main.npdm': check = True break - return check + return check + + def pr_noenc_check_dlc(self, file = None, mode = 'rb'): + crypto1=self.header.getCryptoType() + crypto2=self.header.getCryptoType2() + if crypto1 == 2: + if crypto1 > crypto2: + masterKeyRev=crypto1 + else: + masterKeyRev=crypto2 + else: + masterKeyRev=crypto2 + decKey = Keys.decryptTitleKey(self.header.titleKeyDec, Keys.getMasterKeyIndex(masterKeyRev)) + for f in self.sectionFilesystems: + #print(f.fsType);print(f.cryptoType) + if f.fsType == Type.Fs.ROMFS and f.cryptoType == Type.Crypto.CTR: + ncaHeader = NcaHeader() + self.header.rewind() + ncaHeader = self.header.read(0x400) + #Hex.dump(ncaHeader) + pfs0=f + #Hex.dump(pfs0.read()) + sectionHeaderBlock = f.buffer + + levelOffset = int.from_bytes(sectionHeaderBlock[0x18:0x20], byteorder='little', signed=False) + levelSize = int.from_bytes(sectionHeaderBlock[0x20:0x28], byteorder='little', signed=False) + + pfs0Header = pfs0.read(levelSize) + if sectionHeaderBlock[8:12] == b'IVFC': + data = pfs0Header; + #Hex.dump(pfs0Header) + if hx(sectionHeaderBlock[0xc8:0xc8+0x20]).decode('utf-8') == str(sha256(data).hexdigest()): + return True + else: + return False + else: + data = pfs0Header; + #Hex.dump(pfs0Header) + magic = pfs0Header[0:4] + if magic != b'PFS0': + return False + else: + return True def get_req_system(self, file = None, mode = 'rb'): indent = 1 @@ -1240,20 +1292,19 @@ def ncalist_bycnmt(self, file = None, mode = 'rb'): ncalist.append(nca_meta) return ncalist - def copy(self,ofolder,buffer): + def copy_files(self,buffer,ofolder=False,filepath=False,io=0,eo=False): i=0 - for f in self: - self.rewind() - f.rewind() - filename = str(i) - i+=1 - outfolder = str(ofolder)+'/' - filepath = os.path.join(outfolder, filename) + if ofolder == False: + outfolder = 'ofolder' if not os.path.exists(outfolder): os.makedirs(outfolder) - fp = open(filepath, 'w+b') - self.rewind() - f.rewind() + for f in self: + if filepath==False: + filename = str(i) + i+=1 + filepath = os.path.join(outfolder, filename) + fp = open(filepath, 'w+b') + self.rewind();f.seek(io) for data in iter(lambda: f.read(int(buffer)), ""): fp.write(data) fp.flush() @@ -1261,7 +1312,7 @@ def copy(self,ofolder,buffer): fp.close() break - def get_ncap_offset(self): + def get_nacp_offset(self): for f in self: self.rewind() f.rewind() @@ -1339,17 +1390,23 @@ def get_ncap_offset(self): except: ediver="-" if ediver == '-': - offset2=offset-0x300 - f.seek(offset2+0x3060) - ediver = f.read(0x10) - ediver = ediver.split(b'\0', 1)[0].decode('utf-8') - ediver = (re.sub(r'[\/\\]+', ' ', ediver)) - ediver = ediver.strip() - try: - int(ediver[0])+1 - offset=offset2 - except: - ediver="-" + for i in Langue: + try: + i=i+1 + offset2=offset-0x300*i + f.seek(offset2+0x3060) + ediver = f.read(0x10) + ediver = ediver.split(b'\0', 1)[0].decode('utf-8') + ediver = (re.sub(r'[\/\\]+', ' ', ediver)) + ediver = ediver.strip() + try: + int(ediver[0])+1 + offset=offset2 + break + except: + ediver="-" + except: + pass if ediver == '-': try: while (offset2+0x3060)<=0x18600: @@ -1455,17 +1512,23 @@ def get_langueblock(self,title,roman=True): except: ediver="-" if ediver == '-': - offset2=offset-0x300 - f.seek(offset2+0x3060) - ediver = f.read(0x10) - ediver = ediver.split(b'\0', 1)[0].decode('utf-8') - ediver = (re.sub(r'[\/\\]+', ' ', ediver)) - ediver = ediver.strip() - try: - int(ediver[0])+1 - offset=offset2 - except: - ediver="-" + for i in Langue: + try: + i=i+1 + offset2=offset-0x300*i + f.seek(offset2+0x3060) + ediver = f.read(0x10) + ediver = ediver.split(b'\0', 1)[0].decode('utf-8') + ediver = (re.sub(r'[\/\\]+', ' ', ediver)) + ediver = ediver.strip() + try: + int(ediver[0])+1 + offset=offset2 + break + except: + ediver="-" + except: + pass if ediver == '-': try: while (offset2+0x3060)<=0x18600: @@ -1485,7 +1548,8 @@ def get_langueblock(self,title,roman=True): ediver="-" break except: - ediver="-" + ediver="-" + except: pass Langue = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14] @@ -1537,7 +1601,7 @@ def get_langueblock(self,title,roman=True): SupLg.append("KOR") regionstr+='1|' if i==13: - SupLg.append("ZH (ch)") + SupLg.append("TW (ch)") regionstr+='1|' if i==14: SupLg.append("CH") @@ -1664,7 +1728,6 @@ def verify(self,feed,targetkg=False,endcheck=False,progress=False,bar=False): self.header.seek(0x200) headdata = self.header.read(0x200) #print(hx(orig_header)) - #Hex.dump(headdata) pubkey=RSA.RsaKey(n=nca_header_fixed_key_modulus, e=RSA_PUBLIC_EXPONENT) rsapss = PKCS1_PSS.new(pubkey) @@ -1674,13 +1737,13 @@ def verify(self,feed,targetkg=False,endcheck=False,progress=False,bar=False): crypto2=self.header.getCryptoType2() if crypto2>crypto1: masterKeyRev=crypto2 - if crypto2<=crypto1: + if crypto2<=crypto1: masterKeyRev=crypto1 currkg=masterKeyRev if verification == True: message=(indent+self._path+arrow+'is PROPER');print(message);feed+=message+'\n' #print(hx(headdata)) - return True,False,self._path,feed,currkg + return True,False,self._path,feed,currkg,False,False,self.header.getgamecard() else: crypto = aes128.AESECB(Keys.keyAreaKey(Keys.getMasterKeyIndex(masterKeyRev), self.header.keyIndex)) KB1L=self.header.getKB1L() @@ -1707,13 +1770,14 @@ def verify(self,feed,targetkg=False,endcheck=False,progress=False,bar=False): message=(tabs+'* '+"KEYGENERATION WAS CHANGED FROM "+str(orkg)+" TO "+str(currkg));print(message);feed+=message+'\n' message=(tabs+'* '+"Original titlerights id is -> "+(str(hx(tr)).upper())[2:-1]);print(message);feed+=message+'\n' message=(tabs+'* '+"Original titlekey is -> "+(str(hx(titlekey)).upper())[2:-1]);print(message);feed+=message+'\n' + tcheck=(str(hx(titlekey)).upper())[2:-1] if tcheck == '00000000000000000000000000000000': message=(tabs+'* '+"WARNING: sum(titlekey)=0 -> S.C. conversion may be incorrect and come from nsx file");print(message);feed+=message+'\n' - return True,orig_header,self._path,feed,orkg + return True,orig_header,self._path,feed,orkg,tr,titlekey,self.header.getgamecard() else: message=(indent+self._path+arrow+'was MODIFIED');print(message);feed+=message+'\n' message=(tabs+'* '+"NOT VERIFIABLE COULD'VE BEEN TAMPERED WITH");print(message);feed+=message+'\n' - return False,False,self._path,feed,False + return False,False,self._path,feed,False,False,False,self.header.getgamecard() else: if targetkg != False: if self.header.contentType == Type.Content.META: @@ -1752,7 +1816,7 @@ def verify(self,feed,targetkg=False,endcheck=False,progress=False,bar=False): message=(tabs+'* '+"ISGAMECARD WAS CHANGED FROM 1 TO 0");bar.write(message);feed+=message+'\n' else: message=(tabs+'* '+"ISGAMECARD WAS CHANGED FROM 1 TO 0");print(message);feed+=message+'\n' - return True,orig_header,self._path,feed,chkkg + return True,orig_header,self._path,feed,chkkg,False,False,self.header.getgamecard() else: if self.header.contentType == Type.Content.META: if targetkg == False: @@ -1779,25 +1843,41 @@ def verify(self,feed,targetkg=False,endcheck=False,progress=False,bar=False): else: message=(tabs+'* '+"INTERNAL HASH MISSMATCH");print(message);feed+=message+'\n' message=(tabs+'* '+"BAD CNMT FILE!!!");print(message);feed+=message+'\n' - return 'BADCNMT',False,self._path,feed,False + return 'BADCNMT',False,self._path,feed,False,False,False,self.header.getgamecard() else: if endcheck == False: pass elif endcheck == True: - message=(indent+self._path+arrow+'was MODIFIED');print(message);feed+=message+'\n' - message=(tabs+'* '+"NOT VERIFIABLE!!!");print(message);feed+=message+'\n' - return False,False,self._path,feed,False + if os.path.exists(self._path): + printname=str(os.path.basename(os.path.abspath(self._path))) + else: + printname=str(self._path) + if progress != False: + message=(indent+printname+arrow+'was MODIFIED');bar.write(message);feed+=message+'\n' + message=(tabs+'* '+"NOT VERIFIABLE!!!");bar.write(message);feed+=message+'\n' + else: + message=(indent+self._path+arrow+'was MODIFIED');print(message);feed+=message+'\n' + message=(tabs+'* '+"NOT VERIFIABLE!!!");print(message);feed+=message+'\n' + return False,False,self._path,feed,False,False,False,self.header.getgamecard() else: - message=(indent+self._path+arrow+'was MODIFIED');print(message);feed+=message+'\n' - message=(tabs+'* '+"NOT VERIFIABLE!!!");print(message);feed+=message+'\n' - return False,False,self._path,feed,False + if os.path.exists(self._path): + printname=str(os.path.basename(os.path.abspath(self._path))) + else: + printname=str(self._path) + if progress != False: + message=(indent+printname+arrow+'was MODIFIED');bar.write(message);feed+=message+'\n' + message=(tabs+'* '+"NOT VERIFIABLE!!!");bar.write(message);feed+=message+'\n' + else: + message=(indent+self._path+arrow+'was MODIFIED');print(message);feed+=message+'\n' + message=(tabs+'* '+"NOT VERIFIABLE!!!");print(message);feed+=message+'\n' + return False,False,self._path,feed,False,False,False,self.header.getgamecard() if progress != False: message=(indent+self._path+arrow+'was MODIFIED');bar.write(message);feed+=message+'\n' message=(tabs+'* '+"NOT VERIFIABLE!!!");bar.write(message);feed+=message+'\n' else: message=(indent+self._path+arrow+'was MODIFIED');print(message);feed+=message+'\n' message=(tabs+'* '+"NOT VERIFIABLE!!!");print(message);feed+=message+'\n' - return False,False,self._path,feed,False + return False,False,self._path,feed,False,False,False,self.header.getgamecard() def verify_cnmt_withkg(self,targetkg): targetkg=int(targetkg) @@ -2099,7 +2179,7 @@ def restorehead_ntr(self): #READ NACP FILE WITHOUT EXTRACTION def read_nacp(self,feed=''): if str(self.header.contentType) == 'Content.CONTROL': - offset=self.get_ncap_offset() + offset=self.get_nacp_offset() for f in self: f.seek(offset) nacp = Nacp() @@ -2130,61 +2210,59 @@ def read_nacp(self,feed=''): f.seek(offset+0x3060) message='...............................';print(message);feed+=message+'\n' message='NACP ATTRIBUTES';print(message);feed+=message+'\n' - message='...............................';print(message);feed+=message+'\n' - feed=nacp.par_getDisplayVersion(f.read(0xF),feed) - f.seek(offset+0x3070) - feed=nacp.par_getAddOnContentBaseId(f.readInt64('little'),feed) - f.seek(offset+0x3078) - feed=nacp.par_getSaveDataOwnerId(f.readInt64('little'),feed) - f.seek(offset+0x3080) - feed=nacp.par_getUserAccountSaveDataSize(f.readInt64('little'),feed) - f.seek(offset+0x3088) - feed=nacp.par_getUserAccountSaveDataJournalSize(f.readInt64('little'),feed) - f.seek(offset+0x3090) - feed=nacp.par_getDeviceSaveDataSize(f.readInt64('little'),feed) - f.seek(offset+0x3098) - feed=nacp.par_getDeviceSaveDataJournalSize(f.readInt64('little'),feed) - f.seek(offset+0x30A0) - feed=nacp.par_getBcatDeliveryCacheStorageSize(f.readInt64('little'),feed) - f.seek(offset+0x30A8) - feed=nacp.par_getApplicationErrorCodeCategory(f.read(0x07),feed) - f.seek(offset+0x30B0) - feed=nacp.par_getLocalCommunicationId(f.readInt64('little'),feed) - f.seek(offset+0x30F0) - feed=nacp.par_getLogoType(f.readInt8('little'),feed) - feed=nacp.par_getLogoHandling(f.readInt8('little'),feed) - feed=nacp.par_getRuntimeAddOnContentInstall(f.readInt8('little'),feed) - feed=nacp.par_getCrashReport(f.readInt8('little'),feed) - feed=nacp.par_getHdcp(f.readInt8('little'),feed) - feed=nacp.par_getSeedForPseudoDeviceId(f.readInt64('little'),feed) - f.seek(offset+0x3100) - feed=nacp.par_getBcatPassphrase(f.read(0x40),feed) - f.seek(offset+0x3148) - feed=nacp.par_UserAccountSaveDataSizeMax(f.readInt64('little'),feed) - f.seek(offset+0x3150) - feed=nacp.par_UserAccountSaveDataJournalSizeMax(f.readInt64('little'),feed) - f.seek(offset+0x3158) - feed=nacp.par_getDeviceSaveDataSizeMax(f.readInt64('little'),feed) - f.seek(offset+0x3160) - feed=nacp.par_getDeviceSaveDataJournalSizeMax(f.readInt64('little'),feed) - f.seek(offset+0x3168) - feed=nacp.par_getTemporaryStorageSize(f.readInt64('little'),feed) - feed=nacp.par_getCacheStorageSize(f.readInt64('little'),feed) - f.seek(offset+0x3178) - feed=nacp.par_getCacheStorageJournalSize(f.readInt64('little'),feed) - feed=nacp.par_getCacheStorageDataAndJournalSizeMax(f.readInt64('little'),feed) - f.seek(offset+0x3188) - feed=nacp.par_getCacheStorageIndexMax(f.readInt64('little'),feed) - feed=nacp.par_getPlayLogQueryableApplicationId(f.readInt64('little'),feed) - f.seek(offset+0x3210) - feed=nacp.par_getPlayLogQueryCapability(f.readInt8('little'),feed) - feed=nacp.par_getRepair(f.readInt8('little'),feed) - feed=nacp.par_getProgramIndex(f.readInt8('little'),feed) - feed=nacp.par_getRequiredNetworkServiceLicenseOnLaunch(f.readInt8('little'),feed) - #f.seek(offset+0x3000) - #nacp.open(MemoryFile(f.read(),32768*2)) - #nacp.printInfo() - #Hex.dump(offset) + message='...............................';print(message);feed+=message+'\n' + try: + feed=nacp.par_getDisplayVersion(f.read(0xF),feed) + f.seek(offset+0x3070) + feed=nacp.par_getAddOnContentBaseId(f.readInt64('little'),feed) + f.seek(offset+0x3078) + feed=nacp.par_getSaveDataOwnerId(f.readInt64('little'),feed) + f.seek(offset+0x3080) + feed=nacp.par_getUserAccountSaveDataSize(f.readInt64('little'),feed) + f.seek(offset+0x3088) + feed=nacp.par_getUserAccountSaveDataJournalSize(f.readInt64('little'),feed) + f.seek(offset+0x3090) + feed=nacp.par_getDeviceSaveDataSize(f.readInt64('little'),feed) + f.seek(offset+0x3098) + feed=nacp.par_getDeviceSaveDataJournalSize(f.readInt64('little'),feed) + f.seek(offset+0x30A0) + feed=nacp.par_getBcatDeliveryCacheStorageSize(f.readInt64('little'),feed) + f.seek(offset+0x30A8) + feed=nacp.par_getApplicationErrorCodeCategory(f.read(0x07),feed) + f.seek(offset+0x30B0) + feed=nacp.par_getLocalCommunicationId(f.readInt64('little'),feed) + f.seek(offset+0x30F0) + feed=nacp.par_getLogoType(f.readInt8('little'),feed) + feed=nacp.par_getLogoHandling(f.readInt8('little'),feed) + feed=nacp.par_getRuntimeAddOnContentInstall(f.readInt8('little'),feed) + feed=nacp.par_getCrashReport(f.readInt8('little'),feed) + feed=nacp.par_getHdcp(f.readInt8('little'),feed) + feed=nacp.par_getSeedForPseudoDeviceId(f.readInt64('little'),feed) + f.seek(offset+0x3100) + feed=nacp.par_getBcatPassphrase(f.read(0x40),feed) + f.seek(offset+0x3148) + feed=nacp.par_UserAccountSaveDataSizeMax(f.readInt64('little'),feed) + f.seek(offset+0x3150) + feed=nacp.par_UserAccountSaveDataJournalSizeMax(f.readInt64('little'),feed) + f.seek(offset+0x3158) + feed=nacp.par_getDeviceSaveDataSizeMax(f.readInt64('little'),feed) + f.seek(offset+0x3160) + feed=nacp.par_getDeviceSaveDataJournalSizeMax(f.readInt64('little'),feed) + f.seek(offset+0x3168) + feed=nacp.par_getTemporaryStorageSize(f.readInt64('little'),feed) + feed=nacp.par_getCacheStorageSize(f.readInt64('little'),feed) + f.seek(offset+0x3178) + feed=nacp.par_getCacheStorageJournalSize(f.readInt64('little'),feed) + feed=nacp.par_getCacheStorageDataAndJournalSizeMax(f.readInt64('little'),feed) + f.seek(offset+0x3188) + feed=nacp.par_getCacheStorageIndexMax(f.readInt64('little'),feed) + feed=nacp.par_getPlayLogQueryableApplicationId(f.readInt64('little'),feed) + f.seek(offset+0x3210) + feed=nacp.par_getPlayLogQueryCapability(f.readInt8('little'),feed) + feed=nacp.par_getRepair(f.readInt8('little'),feed) + feed=nacp.par_getProgramIndex(f.readInt8('little'),feed) + feed=nacp.par_getRequiredNetworkServiceLicenseOnLaunch(f.readInt8('little'),feed) + except:continue return feed def verify_hash_nca(self,buffer,origheader,didverify,feed): diff --git a/py/ztools/Fs/Nsp.py b/py/ztools/Fs/Nsp.py index 536b243b..db395bba 100644 --- a/py/ztools/Fs/Nsp.py +++ b/py/ztools/Fs/Nsp.py @@ -23,12 +23,16 @@ from Fs.Nacp import Nacp from Fs.Nca import NcaHeader from Fs.File import MemoryFile +from Fs.pyNCA3 import NCA3 +from Fs.pyNPDM import NPDM import math import sys import shutil if sys.platform == 'win32': import win32con, win32api from operator import itemgetter, attrgetter, methodcaller +from Crypto.Cipher import AES +import io #from Cryptodome.Signature import pss #from Cryptodome.PublicKey import RSA #from Cryptodome import Random @@ -612,15 +616,389 @@ def copy_cnmt(self,ofolder,buffer): fp.close() break - def copy_ncap(self,ofolder,buffer): + def copy_nacp(self,ofolder,buffer=32768): for nca in self: if type(nca) == Nca: if str(nca.header.contentType) == 'Content.CONTROL': + ncaname = str(nca._path)[:-4]+'_nca' + ncafolder = os.path.join(ofolder,ncaname) + filename = 'control.nacp' + filepath = os.path.join(ncafolder,filename) + if not os.path.exists(ncafolder): + os.makedirs(ncafolder) + offset=nca.get_nacp_offset() for f in nca: - nca.copy(ofolder,buffer) + totSize=f.size-offset + t = tqdm(total=totSize, unit='B', unit_scale=True, leave=False) + t.write('- Writing control.nacp...') + f.seek(offset) + fp = open(filepath, 'w+b') + for data in iter(lambda: f.read(int(buffer)), ""): + fp.write(data) + fp.flush() + t.update(len(data)) + if not data: + fp.close() + t.close() + break + break + print(' DONE') - + def copy_as_plaintext(self,ofolder,files_list,buffer=32768): + for nca in self: + tk=None;skip=False + if type(nca) == Nca: + for fs in nca.sectionFilesystems: + if fs.cryptoType == Type.Crypto.BKTR: + skip=True + break + if nca.header.getRightsId() != 0: + correct, tkey = self.verify_nca_key(str(nca._path)) + if correct == True: + crypto1=nca.header.getCryptoType() + crypto2=nca.header.getCryptoType2() + if crypto2>crypto1: + masterKeyRev=crypto2 + if crypto2<=crypto1: + masterKeyRev=crypto1 + tk = Keys.decryptTitleKey(tkey, Keys.getMasterKeyIndex(int(masterKeyRev))) + else: + tk=nca.header.titleKeyDec + if skip == False: + ncaname = str(nca._path) + PN = os.path.join(ofolder,ncaname) + if not os.path.exists(ofolder): + os.makedirs(ofolder) + for i in range(len(files_list)): + if str(nca._path) == files_list[i][0]: + offset=files_list[i][1] + #print(offset) + break + #print(nca.size) + #print(str(nca._path)[-9:]) + lon=0;test=str(nca._path)[-9:] + if test=='.cnmt.nca': + ext='.plain.cnmt.nca' + else: + ext='.plain.nca' + lon=(-1)*len(ext) + try: + fp=open(str(self._path), 'rb') + nca3=NCA3(fp,int(offset),str(nca._path),tk) + nca3.decrypt_to_plaintext(PN.replace(str(nca._path)[lon:], ext)) + fp.close(); + except BaseException as e: + #Print.error('Exception: ' + str(e)) + if nca.sizecrypto1: + masterKeyRev=crypto2 + if crypto2<=crypto1: + masterKeyRev=crypto1 + decKey = Keys.decryptTitleKey(tkey, Keys.getMasterKeyIndex(int(masterKeyRev))) + for i in range(len(files_list)): + if str(nca._path) == files_list[i][0]: + offset=files_list[i][1] + #print(offset) + break + try: + fp=open(str(self._path), 'rb') + nca3=NCA3(fp,int(offset),str(nca._path),decKey) + feed=nca3.print_npdm() + fp.close(); + except BaseException as e: + #Print.error('Exception: ' + str(e)) + nca.rewind() + for fs in nca.sectionFilesystems: + #print(fs.fsType) + #print(fs.cryptoType) + if fs.fsType == Type.Fs.PFS0 and fs.cryptoType == Type.Crypto.CTR: + nca.seek(0) + ncaHeader = NcaHeader() + ncaHeader.open(MemoryFile(nca.read(0x400), Type.Crypto.XTS, uhx(Keys.get('header_key')))) + ncaHeader.seek(0) + fs.rewind() + pfs0=fs + sectionHeaderBlock = fs.buffer + nca.seek(fs.offset) + pfs0Offset=fs.offset + pfs0Header = nca.read(0x10*14) + mem = MemoryFile(pfs0Header, Type.Crypto.CTR, decKey, pfs0.cryptoCounter, offset = pfs0Offset) + data = mem.read(); + #Hex.dump(data) + head=data[0:4] + n_files=(data[4:8]) + n_files=int.from_bytes(n_files, byteorder='little') + st_size=(data[8:12]) + st_size=int.from_bytes(st_size, byteorder='little') + junk=(data[12:16]) + offset=(0x10 + n_files * 0x18) + stringTable=(data[offset:offset+st_size]) + stringEndOffset = st_size + headerSize = 0x10 + 0x18 * n_files + st_size + #print(head) + #print(str(n_files)) + #print(str(st_size)) + #print(str((stringTable))) + files_list=list() + for i in range(n_files): + i = n_files - i - 1 + pos=0x10 + i * 0x18 + offset = data[pos:pos+8] + offset=int.from_bytes(offset, byteorder='little') + size = data[pos+8:pos+16] + size=int.from_bytes(size, byteorder='little') + nameOffset = data[pos+16:pos+20] # just the offset + nameOffset=int.from_bytes(nameOffset, byteorder='little') + name = stringTable[nameOffset:stringEndOffset].decode('utf-8').rstrip(' \t\r\n\0') + stringEndOffset = nameOffset + junk2 = data[pos+20:pos+24] # junk data + #print(name) + #print(offset) + #print(size) + files_list.append([name,offset,size]) + files_list.reverse() + #print(files_list) + for i in range(len(files_list)): + if files_list[i][0] == 'main.npdm': + off1=files_list[i][1]+pfs0Offset+headerSize + nca.seek(off1) + np=nca.read(files_list[i][2]) + mem = MemoryFile(np, Type.Crypto.CTR, decKey, pfs0.cryptoCounter, offset = off1) + data = mem.read(); + #Hex.dump(data) + inmemoryfile = io.BytesIO(data) + npdm = NPDM(inmemoryfile) + n=npdm.__str__() + print(n) + break + break + return feed + def extract_nca(self,ofolder,files_list,buffer=32768): + for nca in self: + tk=None;skip=False + if type(nca) == Nca: + for fs in nca.sectionFilesystems: + if fs.cryptoType == Type.Crypto.BKTR: + skip=True + break + if nca.header.getRightsId() != 0: + correct, tkey = self.verify_nca_key(str(nca._path)) + if correct == True: + crypto1=nca.header.getCryptoType() + crypto2=nca.header.getCryptoType2() + if crypto2>crypto1: + masterKeyRev=crypto2 + if crypto2<=crypto1: + masterKeyRev=crypto1 + tk = Keys.decryptTitleKey(tkey, Keys.getMasterKeyIndex(int(masterKeyRev))) + else: + tk=nca.header.titleKeyDec + if skip == False: + if type(nca) == Nca: + ncaname = str(nca._path)[:-4]+'_nca' + ncafolder = os.path.join(ofolder,ncaname) + ncaname2 = str(nca._path) + PN = os.path.join(ofolder,ncaname2) + if not os.path.exists(ncafolder): + os.makedirs(ncafolder) + for i in range(len(files_list)): + if str(nca._path) == files_list[i][0]: + offset=files_list[i][1] + break + #t = tqdm(total=nca.size, unit='B', unit_scale=True, leave=False) + try: + fp=open(str(self._path), 'rb') + nca3=NCA3(fp,int(offset),str(nca._path),tk,buffer) + nca3.extract_conts(ncafolder, disp=True) + fp.close() + except: + #Print.error('Exception: ' + str(e)) + if nca.size no RSV to patch\n');print(message);feed+=message+'\n' return feed - def inf_get_title(self,target,offset,content_entries,original_ID): + def inf_get_title(self,target,offset,content_entries,original_ID,roman=True): content_type='' for nca in self: if type(nca) == Nca: @@ -2743,7 +3118,7 @@ def inf_get_title(self,target,offset,content_entries,original_ID): if type(nca) == Nca: if nca_name == str(nca._path): if str(nca.header.contentType) == 'Content.CONTROL': - title,editor,ediver,SupLg,regionstr,isdemo=nca.get_langueblock(title) + title,editor,ediver,SupLg,regionstr,isdemo=nca.get_langueblock(title,roman) return(title,editor,ediver,SupLg,regionstr,isdemo) regionstr="0|0|0|0|0|0|0|0|0|0|0|0|0|0" return(title,"","","",regionstr,"") @@ -3287,6 +3662,7 @@ def c_nsp_direct(self,buffer,outfile,ofolder,fat,fx,delta,metapatch,RSV_cap,keyp for file in self: if type(file) == Ticket: masterKeyRev = file.getMasterKeyRevision() + ticket=file titleKeyDec = Keys.decryptTitleKey(file.getTitleKeyBlock().to_bytes(16, byteorder='big'), Keys.getMasterKeyIndex(masterKeyRev)) rightsId = file.getRightsId() for nca in self: @@ -3482,7 +3858,9 @@ def c_nsp_direct(self,buffer,outfile,ofolder,fat,fx,delta,metapatch,RSV_cap,keyp if fat=="fat32" and (c+len(newheader))>block: n2=block-c c=0 - dat2=newheader[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO(newheader) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -3491,7 +3869,9 @@ def c_nsp_direct(self,buffer,outfile,ofolder,fat,fx,delta,metapatch,RSV_cap,keyp outfile=outfile[0:-1] outfile=outfile+str(index) outf = open(outfile, 'wb') - dat2=newheader[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(newheader)-n2) + inmemoryfile.close() outf.write(dat2) t.update(len(dat2)) outf.flush() @@ -3502,14 +3882,13 @@ def c_nsp_direct(self,buffer,outfile,ofolder,fat,fx,delta,metapatch,RSV_cap,keyp nca.seek(0xC00) i+=1 else: - outf.write(data) - t.update(len(data)) - c=c+len(data) - outf.flush() if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=nca.read(int(n2)) + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -3517,7 +3896,18 @@ def c_nsp_direct(self,buffer,outfile,ofolder,fat,fx,delta,metapatch,RSV_cap,keyp index=index+1 outfile=outfile[0:-1] outfile=outfile+str(index) - outf = open(outfile, 'wb') + outf = open(outfile, 'wb') + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() + outf.write(dat2) + t.update(len(dat2)) + outf.flush() + else: + outf.write(data) + t.update(len(data)) + c=c+len(data) + outf.flush() if not data: break if type(nca) == Nca and str(nca.header.contentType) == 'Content.META': @@ -3529,14 +3919,13 @@ def c_nsp_direct(self,buffer,outfile,ofolder,fat,fx,delta,metapatch,RSV_cap,keyp size=os.path.getsize(filepath) t.write(tabs+'- Appending: ' + str(nca._path)) for data in iter(lambda: target.read(int(size)), ""): - outf.write(data) - t.update(len(data)) - c=c+len(data) - outf.flush() if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=nca.read(int(n2)) + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -3544,12 +3933,18 @@ def c_nsp_direct(self,buffer,outfile,ofolder,fat,fx,delta,metapatch,RSV_cap,keyp index=index+1 outfile=outfile[0:-1] outfile=outfile+str(index) - outf = open(outfile, 'wb') - if totSize>(4294934528+int(buffer)): - dat2=nca.read(int(buffer)) - outf.write(dat2) - t.update(len(dat2)) - outf.flush() + outf = open(outfile, 'wb') + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() + outf.write(dat2) + t.update(len(dat2)) + outf.flush() + else: + outf.write(data) + t.update(len(data)) + c=c+len(data) + outf.flush() if not data: target.close() break @@ -3564,14 +3959,13 @@ def c_nsp_direct(self,buffer,outfile,ofolder,fat,fx,delta,metapatch,RSV_cap,keyp t.write(tabs+'- Appending: ' + xmlname) xmlf.seek(0x00) for data in iter(lambda: xmlf.read(int(buffer)), ""): - outf.write(data) - t.update(len(data)) - c=c+len(data) - outf.flush() if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=nca.read(int(n2)) + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -3579,12 +3973,18 @@ def c_nsp_direct(self,buffer,outfile,ofolder,fat,fx,delta,metapatch,RSV_cap,keyp index=index+1 outfile=outfile[0:-1] outfile=outfile+str(index) - outf = open(outfile, 'wb') - if totSize>(4294934528+int(buffer)): - dat2=nca.read(int(buffer)) - outf.write(dat2) - t.update(len(dat2)) - outf.flush() + outf = open(outfile, 'wb') + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() + outf.write(dat2) + t.update(len(dat2)) + outf.flush() + else: + outf.write(data) + t.update(len(data)) + c=c+len(data) + outf.flush() if not data: xmlf.close() break @@ -3600,7 +4000,7 @@ def c_nsp_direct(self,buffer,outfile,ofolder,fat,fx,delta,metapatch,RSV_cap,keyp #/////////////////////////////////////////////////// #ADD TO DATABASE #/////////////////////////////////////////////////// - def addtodb(self,ofile,dbtype): + def addtodb(self,ofile,dbtype,roman=True): for nca in self: if type(nca) == Nca: if str(nca.header.contentType) == 'Content.META': @@ -3648,7 +4048,7 @@ def addtodb(self,ofile,dbtype): if ckey == '0': ckey=self.getdbkey(titlerights) target=str(nca._path) - tit_name,editor,ediver,SupLg,regionstr,isdemo = self.inf_get_title(target,offset,content_entries,original_ID) + tit_name,editor,ediver,SupLg,regionstr,isdemo = self.inf_get_title(target,offset,content_entries,original_ID,roman) if tit_name=='DLC' and (str(titlerights).endswith('000') or str(titlerights).endswith('800')): tit_name='-' editor='-' @@ -4150,7 +4550,9 @@ def c_xci_direct(self,buffer,outfile,ofolder,fat,delta,metapatch,RSV_cap,keypatc if fat=="fat32" and (c+len(newheader))>block: n2=block-c c=0 - dat2=newheader[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO(newheader) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -4159,25 +4561,26 @@ def c_xci_direct(self,buffer,outfile,ofolder,fat,delta,metapatch,RSV_cap,keypatc outfile=outfile[0:-1] outfile=outfile+str(index) outf = open(outfile, 'wb') - dat2=newheader[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(newheader)-n2) + inmemoryfile.close() outf.write(dat2) t.update(len(dat2)) outf.flush() else: outf.write(newheader) t.update(len(newheader)) - c=c+len(newheader) + c=c+len(newheader) nca.seek(0xC00) i+=1 else: - outf.write(data) - t.update(len(data)) - c=c+len(data) - outf.flush() if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=nca.read(int(n2)) + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -4185,12 +4588,18 @@ def c_xci_direct(self,buffer,outfile,ofolder,fat,delta,metapatch,RSV_cap,keypatc index=index+1 outfile=outfile[0:-1] outfile=outfile+str(index) - outf = open(outfile, 'wb') - if totSize>(4294934528+int(buffer)): - dat2=nca.read(int(buffer)) - outf.write(dat2) - t.update(len(dat2)) - outf.flush() + outf = open(outfile, 'wb') + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() + outf.write(dat2) + t.update(len(dat2)) + outf.flush() + else: + outf.write(data) + t.update(len(data)) + c=c+len(data) + outf.flush() if not data: break if type(nca) == Nca and str(nca.header.contentType) == 'Content.META': @@ -4261,14 +4670,13 @@ def c_xci_direct(self,buffer,outfile,ofolder,fat,delta,metapatch,RSV_cap,keypatc size=os.path.getsize(filepath) t.write(tabs+'- Appending: ' + str(nca._path)) for data in iter(lambda: target.read(int(size)), ""): - outf.write(data) - t.update(len(data)) - c=c+len(data) - outf.flush() if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=nca.read(int(n2)) + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -4276,12 +4684,18 @@ def c_xci_direct(self,buffer,outfile,ofolder,fat,delta,metapatch,RSV_cap,keypatc index=index+1 outfile=outfile[0:-1] outfile=outfile+str(index) - outf = open(outfile, 'wb') - if totSize>(4294934528+int(buffer)): - dat2=nca.read(int(buffer)) - outf.write(dat2) - t.update(len(dat2)) - outf.flush() + outf = open(outfile, 'wb') + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() + outf.write(dat2) + t.update(len(dat2)) + outf.flush() + else: + outf.write(data) + t.update(len(data)) + c=c+len(data) + outf.flush() if not data: target.close() break @@ -5352,7 +5766,10 @@ def append_content(self,outf,target,buffer,t,fat='exfat',fx='files',c=0,index=0) if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=data[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) fp.write(dat2) fp.flush() fp.close() @@ -5361,7 +5778,9 @@ def append_content(self,outf,target,buffer,t,fat='exfat',fx='files',c=0,index=0) outf=outf[0:-1] outf=outf+str(index) fp = open(outf, 'wb') - dat2=data[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() fp.write(dat2) t.update(len(dat2)) c=c+len(dat2) @@ -5385,7 +5804,10 @@ def append_content(self,outf,target,buffer,t,fat='exfat',fx='files',c=0,index=0) if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=data[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) fp.write(dat2) fp.flush() fp.close() @@ -5393,8 +5815,10 @@ def append_content(self,outf,target,buffer,t,fat='exfat',fx='files',c=0,index=0) index=index+1 outf=outf[0:-1] outf=outf+str(index) - fp = open(outf, 'wb') - dat2=data[0x00+int(n2)+1:] + fp = open(outf, 'wb') + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() fp.write(dat2) t.update(len(dat2)) c=c+len(dat2) @@ -5417,7 +5841,10 @@ def append_content(self,outf,target,buffer,t,fat='exfat',fx='files',c=0,index=0) if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=data[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) fp.write(dat2) fp.flush() fp.close() @@ -5426,7 +5853,9 @@ def append_content(self,outf,target,buffer,t,fat='exfat',fx='files',c=0,index=0) outf=outf[0:-1] outf=outf+str(index) fp = open(outf, 'wb') - dat2=data[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() fp.write(dat2) t.update(len(dat2)) c=c+len(dat2) @@ -5524,7 +5953,10 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R if fat=="fat32" and (c+len(newheader))>block: n2=block-c c=0 - dat2=newheader[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(newheader) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) fp.write(dat2) fp.flush() fp.close() @@ -5533,7 +5965,9 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R outf=outf[0:-1] outf=outf+str(index) fp = open(outf, 'wb') - dat2=newheader[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(newheader)-n2) + inmemoryfile.close() fp.write(dat2) t.update(len(dat2)) c=c+len(dat2) @@ -5551,7 +5985,10 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=data[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) fp.write(dat2) fp.flush() fp.close() @@ -5560,7 +5997,9 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R outf=outf[0:-1] outf=outf+str(index) fp = open(outf, 'wb') - dat2=data[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() fp.write(dat2) t.update(len(dat2)) c=c+len(dat2) @@ -5597,7 +6036,10 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R if fat=="fat32" and (c+len(newheader))>block: n2=block-c c=0 - dat2=newheader[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(newheader) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) fp.write(dat2) fp.flush() fp.close() @@ -5606,7 +6048,9 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R outf=outf[0:-1] outf=outf+str(index) fp = open(outf, 'wb') - dat2=newheader[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(newheader)-n2) + inmemoryfile.close() fp.write(dat2) t.update(len(dat2)) fp.flush() @@ -5622,7 +6066,10 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=data[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) fp.write(dat2) fp.flush() fp.close() @@ -5631,7 +6078,9 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R outf=outf[0:-1] outf=outf+str(index) fp = open(outf, 'wb') - dat2=data[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() fp.write(dat2) t.update(len(dat2)) c=c+len(dat2) @@ -5671,7 +6120,10 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=data[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) fp.write(dat2) fp.flush() fp.close() @@ -5680,7 +6132,9 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R outf=outf[0:-1] outf=outf+str(index) fp = open(outf, 'wb') - dat2=data[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() fp.write(dat2) t.update(len(dat2)) c=c+len(dat2) @@ -5713,7 +6167,10 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=data[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) fp.write(dat2) fp.flush() fp.close() @@ -5722,7 +6179,9 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R outf=outf[0:-1] outf=outf+str(index) fp = open(outf, 'wb') - dat2=data[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() fp.write(dat2) t.update(len(dat2)) c=c+len(dat2) @@ -5745,7 +6204,10 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=data[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) fp.write(dat2) fp.flush() fp.close() @@ -5754,7 +6216,9 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R outf=outf[0:-1] outf=outf+str(index) fp = open(outf, 'wb') - dat2=data[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() fp.write(dat2) t.update(len(dat2)) c=c+len(dat2) @@ -5925,6 +6389,7 @@ def cd_spl_nsp(self,buffer,ofile,ofolder,filelist,fat,fx): splitnumb=math.ceil(totSize/4294901760) index=0 filepath=filepath[:-1]+str(index) + if fx=="folder" and fat=="fat32": output_folder ="archfolder" output_folder = os.path.join(ofolder, output_folder) @@ -5939,6 +6404,7 @@ def cd_spl_nsp(self,buffer,ofile,ofolder,filelist,fat,fx): t = tqdm(total=totSize, unit='B', unit_scale=True, leave=False) t.write(tabs+'- Writing header...') outf = open(str(filepath), 'w+b') + outfile=str(filepath) outf.write(hd) t.update(len(hd)) c=c+len(hd) @@ -5965,7 +6431,10 @@ def cd_spl_nsp(self,buffer,ofile,ofolder,filelist,fat,fx): if fat=="fat32" and (c+len(newheader))>block: n2=block-c c=0 - dat2=newheader[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(newheader) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -5974,7 +6443,9 @@ def cd_spl_nsp(self,buffer,ofile,ofolder,filelist,fat,fx): outfile=outfile[0:-1] outfile=outfile+str(index) outf = open(outfile, 'wb') - dat2=newheader[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(newheader)-n2) + inmemoryfile.close() outf.write(dat2) t.update(len(dat2)) outf.flush() @@ -5985,14 +6456,13 @@ def cd_spl_nsp(self,buffer,ofile,ofolder,filelist,fat,fx): file.seek(0xC00) i+=1 else: - outf.write(data) - t.update(len(data)) - c=c+len(data) - outf.flush() if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=file.read(int(n2)) + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -6000,21 +6470,31 @@ def cd_spl_nsp(self,buffer,ofile,ofolder,filelist,fat,fx): index=index+1 outfile=outfile[0:-1] outfile=outfile+str(index) - outf = open(outfile, 'wb') + outf = open(outfile, 'wb') + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() + outf.write(dat2) + t.update(len(dat2)) + outf.flush() + else: + outf.write(data) + t.update(len(data)) + c=c+len(data) + outf.flush() if not data: break if type(file) != Nca and file._path in contentlist: file.rewind() t.write(tabs+'- Appending: ' + str(file._path)) for data in iter(lambda: file.read(int(buffer)), ""): - outf.write(data) - t.update(len(data)) - c=c+len(data) - outf.flush() if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=file.read(int(n2)) + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -6023,11 +6503,17 @@ def cd_spl_nsp(self,buffer,ofile,ofolder,filelist,fat,fx): outfile=outfile[0:-1] outfile=outfile+str(index) outf = open(outfile, 'wb') - if totSize>(4294934528+int(buffer)): - dat2=file.read(int(buffer)) - outf.write(dat2) - t.update(len(dat2)) - outf.flush() + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() + outf.write(dat2) + t.update(len(dat2)) + outf.flush() + else: + outf.write(data) + t.update(len(data)) + c=c+len(data) + outf.flush() if not data: break t.close() @@ -6141,10 +6627,14 @@ def cd_spl_xci(self,buffer,ofile,ofolder,filelist,fat,fx): Print.info('masterKeyRev =\t' + hex(masterKeyRev)) print("") + if totSize <= 4294934528: + fat="exfat" if fat=="fat32": splitnumb=math.ceil(totSize/4294934528) index=0 filepath=filepath[:-1]+str(index) + + outfile=str(filepath) c=0 t = tqdm(total=totSize, unit='B', unit_scale=True, leave=False) t.write(tabs+'- Writing XCI header...') @@ -6225,7 +6715,10 @@ def cd_spl_xci(self,buffer,ofile,ofolder,filelist,fat,fx): if fat=="fat32" and (c+len(newheader))>block: n2=block-c c=0 - dat2=newheader[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(newheader) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -6234,7 +6727,9 @@ def cd_spl_xci(self,buffer,ofile,ofolder,filelist,fat,fx): outfile=outfile[0:-1] outfile=outfile+str(index) outf = open(outfile, 'wb') - dat2=newheader[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(newheader)-n2) + inmemoryfile.close() outf.write(dat2) t.update(len(dat2)) outf.flush() @@ -6245,14 +6740,13 @@ def cd_spl_xci(self,buffer,ofile,ofolder,filelist,fat,fx): nca.seek(0xC00) i+=1 else: - outf.write(data) - t.update(len(data)) - c=c+len(data) - outf.flush() if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=nca.read(int(n2)) + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -6261,11 +6755,17 @@ def cd_spl_xci(self,buffer,ofile,ofolder,filelist,fat,fx): outfile=outfile[0:-1] outfile=outfile+str(index) outf = open(outfile, 'wb') - if totSize>(4294934528+int(buffer)): - dat2=nca.read(int(buffer)) - outf.write(dat2) - t.update(len(dat2)) - outf.flush() + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() + outf.write(dat2) + t.update(len(dat2)) + outf.flush() + else: + outf.write(data) + t.update(len(data)) + c=c+len(data) + outf.flush() if not data: break t.close() @@ -6485,7 +6985,7 @@ def verify(self): validfiles.append(str(file._path)) for file in listed_files: - correct=False + correct=False;baddec=False if file in validfiles: if file.endswith('cnmt.nca'): for f in self: @@ -6505,7 +7005,12 @@ def verify(self): if str(f._path) == file: message=(str(f.header.titleId)+' - '+str(f.header.contentType));print(message);feed+=message+'\n' if str(f.header.contentType) != 'Content.PROGRAM': - correct = self.verify_enforcer(file) + correct = self.verify_enforcer(file) + if correct == True: + if str(f.header.contentType) == 'Content.PUBLIC_DATA' and f.header.getRightsId() == 0: + correct = f.pr_noenc_check_dlc() + if correct == False: + baddec=True else: for nf in f: nf.rewind() @@ -6520,7 +7025,7 @@ def verify(self): if correct == False and f.header.getRightsId() == 0: correct = f.pr_noenc_check() if correct == False and f.header.getRightsId() != 0: - correct = self.verify_nca_key(file) + correct,tk = self.verify_nca_key(file) if correct == True and f.header.getRightsId() == 0: correct = f.pr_noenc_check() if correct == False: @@ -6647,7 +7152,7 @@ def verify_sig(self,feed,tmpfolder): for f in self: if type(f) == Nca and f.header.contentType != Type.Content.META: message=(str(f.header.titleId)+' - '+str(f.header.contentType));print(message);feed+=message+'\n' - verify,origheader,ncaname,feed,origkg=f.verify(feed) + verify,origheader,ncaname,feed,origkg,tr,tkey,iGC=f.verify(feed) headerlist.append([ncaname,origheader,hlisthash]) keygenerationlist.append([ncaname,origkg]) if verdict == True: @@ -6659,7 +7164,7 @@ def verify_sig(self,feed,tmpfolder): f.rewind();meta_dat=f.read() message=(str(f.header.titleId)+' - '+str(f.header.contentType));print(message);feed+=message+'\n' targetkg,minrsv=self.find_addecuatekg(meta_nca,keygenerationlist) - verify,origheader,ncaname,feed,origkg=f.verify(feed) + verify,origheader,ncaname,feed,origkg,tr,tkey,iGC=f.verify(feed) #print(targetkg) if verify == False: tempfile=os.path.join(tmpfolder,meta_nca) @@ -6723,7 +7228,7 @@ def verify_sig(self,feed,tmpfolder): fp.close() fp = Fs.Nca(tempfile, 'r+b') progress=True - verify,origheader,ncapath,feed,origkg=fp.verify(feed,targetkg,rsv_endcheck,progress,t) + verify,origheader,ncapath,feed,origkg,tr,tkey,iGC=fp.verify(feed,targetkg,rsv_endcheck,progress,t) fp.close() t.update(1) if verify == True: @@ -6755,7 +7260,7 @@ def verify_sig(self,feed,tmpfolder): if hlisthash == True: sha0=sha0.hexdigest() hlisthash=sha0 - headerlist.append([ncaname,origheader,hlisthash]) + headerlist.append([ncaname,origheader,hlisthash,tr,tkey,iGC]) message='';print(message);feed+=message+'\n' try: shutil.rmtree(tmpfolder) @@ -6937,13 +7442,14 @@ def verify_enforcer(self,nca): return False def verify_nca_key(self,nca): - check=False + check=False;titleKey=0 for file in self: if (file._path).endswith('.tik'): + titleKey = file.getTitleKeyBlock().to_bytes(16, byteorder='big') check=self.verify_key(nca,str(file._path)) if check==True: break - return check + return check,titleKey def verify_key(self,nca,ticket): for file in self: @@ -6978,6 +7484,8 @@ def verify_key(self,nca,ticket): if str(f._path) == nca: if type(f) == Fs.Nca and f.header.getRightsId() != 0: for fs in f.sectionFilesystems: + #print(fs.fsType) + #print(fs.cryptoType) if fs.fsType == Type.Fs.PFS0 and fs.cryptoType == Type.Crypto.CTR: f.seek(0) ncaHeader = NcaHeader() @@ -6994,8 +7502,8 @@ def verify_key(self,nca,ticket): mem = MemoryFile(pfs0Header, Type.Crypto.CTR, decKey, pfs0.cryptoCounter, offset = pfs0Offset) data = mem.read(); #Hex.dump(data) - #print('hash = %s' % str(_sha256(data))) - if hx(sectionHeaderBlock[0xc8:0xc8+0x20]).decode('utf-8') == str(_sha256(data)): + #print('hash = %s' % str(sha256(data).hexdigest())) + if hx(sectionHeaderBlock[0xc8:0xc8+0x20]).decode('utf-8') == str(sha256(data).hexdigest()): return True else: return False @@ -7014,16 +7522,38 @@ def verify_key(self,nca,ticket): f.seek(0) ncaHeader = NcaHeader() ncaHeader.open(MemoryFile(f.read(0x400), Type.Crypto.XTS, uhx(Keys.get('header_key')))) - romfs=fs + ncaHeader = f.read(0x400) + pfs0=fs sectionHeaderBlock = fs.buffer - f.seek(fs.offset) - romfsOffset=fs.offset - romfsHeader = f.read(0x10) - mem = MemoryFile(romfsHeader, Type.Crypto.CTR, decKey, romfs.cryptoCounter, offset = romfsOffset) - magic = mem.read()[0:4] - return True - else: - return False + + levelOffset = int.from_bytes(sectionHeaderBlock[0x18:0x20], byteorder='little', signed=False) + levelSize = int.from_bytes(sectionHeaderBlock[0x20:0x28], byteorder='little', signed=False) + + pfs0Offset = fs.offset + levelOffset + f.seek(pfs0Offset) + pfs0Header = f.read(levelSize) + #print(sectionHeaderBlock[8:12] == b'IVFC') + if sectionHeaderBlock[8:12] == b'IVFC': + #Hex.dump(self.sectionHeaderBlock) + #Print.info(hx(self.sectionHeaderBlock[0xc8:0xc8+0x20]).decode('utf-8')) + mem = MemoryFile(pfs0Header, Type.Crypto.CTR, decKey, pfs0.cryptoCounter, offset = pfs0Offset) + data = mem.read(); + #Hex.dump(data) + #print('hash = %s' % str(sha256(data).hexdigest())) + if hx(sectionHeaderBlock[0xc8:0xc8+0x20]).decode('utf-8') == str(sha256(data).hexdigest()): + return True + else: + return False + else: + mem = MemoryFile(pfs0Header, Type.Crypto.CTR, decKey, pfs0.cryptoCounter, offset = pfs0Offset) + data = mem.read(); + #Hex.dump(data) + magic = mem.read()[0:4] + #print(magic) + if magic != b'PFS0': + return False + else: + return True def cnmt_get_baseids(self): ctype='addon' @@ -7080,5 +7610,9 @@ def cnmt_get_baseids(self): print(i) ''' return ctype,idlist + +################## +#FILE RESTORATION +################## \ No newline at end of file diff --git a/py/ztools/Fs/Xci.py b/py/ztools/Fs/Xci.py index 19dc47ae..45e7906d 100644 --- a/py/ztools/Fs/Xci.py +++ b/py/ztools/Fs/Xci.py @@ -5,6 +5,8 @@ from Fs.File import File from Fs.Nca import NcaHeader from Fs.File import MemoryFile +from Fs.pyNCA3 import NCA3 +from Fs.pyNPDM import NPDM import Fs.Type from Fs import Type from Fs.Nacp import Nacp @@ -26,7 +28,7 @@ import math from operator import itemgetter, attrgetter, methodcaller import shutil -#from Cryptodome.Cipher import PKCS1_v1_5 #Add new dependency Cryptodome +import io MEDIA_SIZE = 0x200 RSA_PUBLIC_EXPONENT = 65537 @@ -468,6 +470,7 @@ def cr_tr_nca(self,ofolder,buffer,metapatch,keypatch,RSV_cap): Print.info('rightsId =\t' + hex(rightsId)) Print.info('titleKeyDec =\t' + str(hx(titleKeyDec))) Print.info('masterKeyRev =\t' + hex(masterKeyRev)) + tik=ticket for nca in nspF: if type(nca) == Nca: if nca.header.getRightsId() != 0: @@ -481,7 +484,7 @@ def cr_tr_nca(self,ofolder,buffer,metapatch,keypatch,RSV_cap): if nca.header.getCryptoType2() == 0: if nca.header.getCryptoType() == 2: masterKeyRev = 2 - titleKeyDec = Keys.decryptTitleKey(ticket.getTitleKeyBlock().to_bytes(16, byteorder='big'), Keys.getMasterKeyIndex(masterKeyRev)) + titleKeyDec = Keys.decryptTitleKey(tik.getTitleKeyBlock().to_bytes(16, byteorder='big'), Keys.getMasterKeyIndex(masterKeyRev)) break for nca in nspF: @@ -581,6 +584,7 @@ def cr_tr_nca_nd(self,ofolder,buffer,metapatch,keypatch,RSV_cap): Print.info('rightsId =\t' + hex(rightsId)) Print.info('titleKeyDec =\t' + str(hx(titleKeyDec))) Print.info('masterKeyRev =\t' + hex(masterKeyRev)) + tik=ticket for nca in nspF: if type(nca) == Nca: if nca.header.getRightsId() != 0: @@ -593,7 +597,7 @@ def cr_tr_nca_nd(self,ofolder,buffer,metapatch,keypatch,RSV_cap): if nca.header.getCryptoType2() == 0: if nca.header.getCryptoType() == 2: masterKeyRev = 2 - titleKeyDec = Keys.decryptTitleKey(ticket.getTitleKeyBlock().to_bytes(16, byteorder='big'), Keys.getMasterKeyIndex(masterKeyRev)) + titleKeyDec = Keys.decryptTitleKey(tik.getTitleKeyBlock().to_bytes(16, byteorder='big'), Keys.getMasterKeyIndex(masterKeyRev)) break for nca in nspF: @@ -836,7 +840,7 @@ def read_nacp(self,feed=''): for nca in nspF: if type(nca) == Nca: if str(nca.header.contentType) == 'Content.CONTROL': - offset=nca.get_ncap_offset() + offset=nca.get_nacp_offset() for f in nca: f.seek(offset) nacp = Nacp() @@ -867,63 +871,421 @@ def read_nacp(self,feed=''): f.seek(offset+0x3060) message='...............................';print(message);feed+=message+'\n' message='NACP ATTRIBUTES';print(message);feed+=message+'\n' - message='...............................';print(message);feed+=message+'\n' - feed=nacp.par_getDisplayVersion(f.read(0xF),feed) - f.seek(offset+0x3070) - feed=nacp.par_getAddOnContentBaseId(f.readInt64('little'),feed) - f.seek(offset+0x3078) - feed=nacp.par_getSaveDataOwnerId(f.readInt64('little'),feed) - f.seek(offset+0x3080) - feed=nacp.par_getUserAccountSaveDataSize(f.readInt64('little'),feed) - f.seek(offset+0x3088) - feed=nacp.par_getUserAccountSaveDataJournalSize(f.readInt64('little'),feed) - f.seek(offset+0x3090) - feed=nacp.par_getDeviceSaveDataSize(f.readInt64('little'),feed) - f.seek(offset+0x3098) - feed=nacp.par_getDeviceSaveDataJournalSize(f.readInt64('little'),feed) - f.seek(offset+0x30A0) - feed=nacp.par_getBcatDeliveryCacheStorageSize(f.readInt64('little'),feed) - f.seek(offset+0x30A8) - feed=nacp.par_getApplicationErrorCodeCategory(f.read(0x07),feed) - f.seek(offset+0x30B0) - feed=nacp.par_getLocalCommunicationId(f.readInt64('little'),feed) - f.seek(offset+0x30F0) - feed=nacp.par_getLogoType(f.readInt8('little'),feed) - feed=nacp.par_getLogoHandling(f.readInt8('little'),feed) - feed=nacp.par_getRuntimeAddOnContentInstall(f.readInt8('little'),feed) - feed=nacp.par_getCrashReport(f.readInt8('little'),feed) - feed=nacp.par_getHdcp(f.readInt8('little'),feed) - feed=nacp.par_getSeedForPseudoDeviceId(f.readInt64('little'),feed) - f.seek(offset+0x3100) - feed=nacp.par_getBcatPassphrase(f.read(0x40),feed) - f.seek(offset+0x3148) - feed=nacp.par_UserAccountSaveDataSizeMax(f.readInt64('little'),feed) - f.seek(offset+0x3150) - feed=nacp.par_UserAccountSaveDataJournalSizeMax(f.readInt64('little'),feed) - f.seek(offset+0x3158) - feed=nacp.par_getDeviceSaveDataSizeMax(f.readInt64('little'),feed) - f.seek(offset+0x3160) - feed=nacp.par_getDeviceSaveDataJournalSizeMax(f.readInt64('little'),feed) - f.seek(offset+0x3168) - feed=nacp.par_getTemporaryStorageSize(f.readInt64('little'),feed) - feed=nacp.par_getCacheStorageSize(f.readInt64('little'),feed) - f.seek(offset+0x3178) - feed=nacp.par_getCacheStorageJournalSize(f.readInt64('little'),feed) - feed=nacp.par_getCacheStorageDataAndJournalSizeMax(f.readInt64('little'),feed) - f.seek(offset+0x3188) - feed=nacp.par_getCacheStorageIndexMax(f.readInt64('little'),feed) - feed=nacp.par_getPlayLogQueryableApplicationId(f.readInt64('little'),feed) - f.seek(offset+0x3210) - feed=nacp.par_getPlayLogQueryCapability(f.readInt8('little'),feed) - feed=nacp.par_getRepair(f.readInt8('little'),feed) - feed=nacp.par_getProgramIndex(f.readInt8('little'),feed) - feed=nacp.par_getRequiredNetworkServiceLicenseOnLaunch(f.readInt8('little'),feed) - #f.seek(offset+0x3000) - #nacp.open(MemoryFile(f.read(),32768*2)) - #nacp.printInfo() - #Hex.dump(offset) + message='...............................';print(message);feed+=message+'\n' + try: + feed=nacp.par_getDisplayVersion(f.read(0xF),feed) + f.seek(offset+0x3070) + feed=nacp.par_getAddOnContentBaseId(f.readInt64('little'),feed) + f.seek(offset+0x3078) + feed=nacp.par_getSaveDataOwnerId(f.readInt64('little'),feed) + f.seek(offset+0x3080) + feed=nacp.par_getUserAccountSaveDataSize(f.readInt64('little'),feed) + f.seek(offset+0x3088) + feed=nacp.par_getUserAccountSaveDataJournalSize(f.readInt64('little'),feed) + f.seek(offset+0x3090) + feed=nacp.par_getDeviceSaveDataSize(f.readInt64('little'),feed) + f.seek(offset+0x3098) + feed=nacp.par_getDeviceSaveDataJournalSize(f.readInt64('little'),feed) + f.seek(offset+0x30A0) + feed=nacp.par_getBcatDeliveryCacheStorageSize(f.readInt64('little'),feed) + f.seek(offset+0x30A8) + feed=nacp.par_getApplicationErrorCodeCategory(f.read(0x07),feed) + f.seek(offset+0x30B0) + feed=nacp.par_getLocalCommunicationId(f.readInt64('little'),feed) + f.seek(offset+0x30F0) + feed=nacp.par_getLogoType(f.readInt8('little'),feed) + feed=nacp.par_getLogoHandling(f.readInt8('little'),feed) + feed=nacp.par_getRuntimeAddOnContentInstall(f.readInt8('little'),feed) + feed=nacp.par_getCrashReport(f.readInt8('little'),feed) + feed=nacp.par_getHdcp(f.readInt8('little'),feed) + feed=nacp.par_getSeedForPseudoDeviceId(f.readInt64('little'),feed) + f.seek(offset+0x3100) + feed=nacp.par_getBcatPassphrase(f.read(0x40),feed) + f.seek(offset+0x3148) + feed=nacp.par_UserAccountSaveDataSizeMax(f.readInt64('little'),feed) + f.seek(offset+0x3150) + feed=nacp.par_UserAccountSaveDataJournalSizeMax(f.readInt64('little'),feed) + f.seek(offset+0x3158) + feed=nacp.par_getDeviceSaveDataSizeMax(f.readInt64('little'),feed) + f.seek(offset+0x3160) + feed=nacp.par_getDeviceSaveDataJournalSizeMax(f.readInt64('little'),feed) + f.seek(offset+0x3168) + feed=nacp.par_getTemporaryStorageSize(f.readInt64('little'),feed) + feed=nacp.par_getCacheStorageSize(f.readInt64('little'),feed) + f.seek(offset+0x3178) + feed=nacp.par_getCacheStorageJournalSize(f.readInt64('little'),feed) + feed=nacp.par_getCacheStorageDataAndJournalSizeMax(f.readInt64('little'),feed) + f.seek(offset+0x3188) + feed=nacp.par_getCacheStorageIndexMax(f.readInt64('little'),feed) + feed=nacp.par_getPlayLogQueryableApplicationId(f.readInt64('little'),feed) + f.seek(offset+0x3210) + feed=nacp.par_getPlayLogQueryCapability(f.readInt8('little'),feed) + feed=nacp.par_getRepair(f.readInt8('little'),feed) + feed=nacp.par_getProgramIndex(f.readInt8('little'),feed) + feed=nacp.par_getRequiredNetworkServiceLicenseOnLaunch(f.readInt8('little'),feed) + except:continue return feed + def read_npdm(self,files_list): + for nspF in self.hfs0: + if str(nspF._path)=="secure": + for nca in nspF: + if type(nca) == Fs.Nca and nca.header.getRightsId() == 0: + if str(nca.header.contentType) == 'Content.PROGRAM': + for i in range(len(files_list)): + if str(nca._path) == files_list[i][0]: + offset=files_list[i][1] + #print(offset) + break + decKey=nca.header.titleKeyDec + try: + fp=open(str(self._path), 'rb') + nca3=NCA3(fp,int(offset),str(nca._path),decKey) + feed=nca3.print_npdm() + fp.close(); + except BaseException as e: + #Print.error('Exception: ' + str(e)) + nca.rewind() + for f in nca: + for g in f: + if str(g._path)=='main.npdm': + inmemoryfile = io.BytesIO(g.read()) + npdm = NPDM(inmemoryfile) + n=npdm.__str__() + print(n) + if type(nca) == Fs.Nca and nca.header.getRightsId() != 0: + if str(nca.header.contentType) == 'Content.PROGRAM': + correct, tkey = self.verify_nca_key(str(nca._path)) + if correct == True: + crypto1=nca.header.getCryptoType() + crypto2=nca.header.getCryptoType2() + if crypto2>crypto1: + masterKeyRev=crypto2 + if crypto2<=crypto1: + masterKeyRev=crypto1 + decKey = Keys.decryptTitleKey(tkey, Keys.getMasterKeyIndex(int(masterKeyRev))) + for i in range(len(files_list)): + if str(nca._path) == files_list[i][0]: + offset=files_list[i][1] + #print(offset) + break + try: + fp=open(str(self._path), 'rb') + nca3=NCA3(fp,int(offset),str(nca._path),decKey) + feed=nca3.print_npdm() + fp.close(); + except BaseException as e: + #Print.error('Exception: ' + str(e)) + nca.rewind() + for fs in nca.sectionFilesystems: + #print(fs.fsType) + #print(fs.cryptoType) + if fs.fsType == Type.Fs.PFS0 and fs.cryptoType == Type.Crypto.CTR: + nca.seek(0) + ncaHeader = NcaHeader() + ncaHeader.open(MemoryFile(nca.read(0x400), Type.Crypto.XTS, uhx(Keys.get('header_key')))) + ncaHeader.seek(0) + fs.rewind() + pfs0=fs + sectionHeaderBlock = fs.buffer + nca.seek(fs.offset) + pfs0Offset=fs.offset + pfs0Header = nca.read(0x10*14) + mem = MemoryFile(pfs0Header, Type.Crypto.CTR, decKey, pfs0.cryptoCounter, offset = pfs0Offset) + data = mem.read(); + #Hex.dump(data) + head=data[0:4] + n_files=(data[4:8]) + n_files=int.from_bytes(n_files, byteorder='little') + st_size=(data[8:12]) + st_size=int.from_bytes(st_size, byteorder='little') + junk=(data[12:16]) + offset=(0x10 + n_files * 0x18) + stringTable=(data[offset:offset+st_size]) + stringEndOffset = st_size + headerSize = 0x10 + 0x18 * n_files + st_size + #print(head) + #print(str(n_files)) + #print(str(st_size)) + #print(str((stringTable))) + files_list=list() + for i in range(n_files): + i = n_files - i - 1 + pos=0x10 + i * 0x18 + offset = data[pos:pos+8] + offset=int.from_bytes(offset, byteorder='little') + size = data[pos+8:pos+16] + size=int.from_bytes(size, byteorder='little') + nameOffset = data[pos+16:pos+20] # just the offset + nameOffset=int.from_bytes(nameOffset, byteorder='little') + name = stringTable[nameOffset:stringEndOffset].decode('utf-8').rstrip(' \t\r\n\0') + stringEndOffset = nameOffset + junk2 = data[pos+20:pos+24] # junk data + #print(name) + #print(offset) + #print(size) + files_list.append([name,offset,size]) + files_list.reverse() + #print(files_list) + for i in range(len(files_list)): + if files_list[i][0] == 'main.npdm': + off1=files_list[i][1]+pfs0Offset+headerSize + nca.seek(off1) + np=nca.read(files_list[i][2]) + mem = MemoryFile(np, Type.Crypto.CTR, decKey, pfs0.cryptoCounter, offset = off1) + data = mem.read(); + #Hex.dump(data) + inmemoryfile = io.BytesIO(data) + npdm = NPDM(inmemoryfile) + n=npdm.__str__() + print(n) + break + break + return feed + + def copy_as_plaintext(self,ofolder,files_list,buffer=32768): + for nspF in self.hfs0: + if str(nspF._path)=="secure": + for nca in nspF: + tk=None;skip=False + if type(nca) == Nca: + for fs in nca.sectionFilesystems: + if fs.cryptoType == Type.Crypto.BKTR: + skip=True + break + if nca.header.getRightsId() != 0: + correct, tkey = self.verify_nca_key(str(nca._path)) + if correct == True: + crypto1=nca.header.getCryptoType() + crypto2=nca.header.getCryptoType2() + if crypto2>crypto1: + masterKeyRev=crypto2 + if crypto2<=crypto1: + masterKeyRev=crypto1 + tk = Keys.decryptTitleKey(tkey, Keys.getMasterKeyIndex(int(masterKeyRev))) + else: + tk=nca.header.titleKeyDec + if skip == False: + ncaname = str(nca._path) + PN = os.path.join(ofolder,ncaname) + if not os.path.exists(ofolder): + os.makedirs(ofolder) + for i in range(len(files_list)): + if str(nca._path) == files_list[i][0]: + offset=files_list[i][1] + #print(offset) + break + #print(nca.size) + #print(str(nca._path)[-9:]) + lon=0;test=str(nca._path)[-9:] + if test=='.cnmt.nca': + ext='.plain.cnmt.nca' + else: + ext='.plain.nca' + lon=(-1)*len(ext) + try: + fp=open(str(self._path), 'rb') + nca3=NCA3(fp,int(offset),str(nca._path),tk) + nca3.decrypt_to_plaintext(PN.replace(str(nca._path)[lon:], ext)) + fp.close(); + except BaseException as e: + #Print.error('Exception: ' + str(e)) + if nca.sizecrypto1: + masterKeyRev=crypto2 + if crypto2<=crypto1: + masterKeyRev=crypto1 + tk = Keys.decryptTitleKey(tkey, Keys.getMasterKeyIndex(int(masterKeyRev))) + else: + tk=nca.header.titleKeyDec + if skip == False: + if type(nca) == Nca: + ncaname = str(nca._path)[:-4]+'_nca' + ncafolder = os.path.join(ofolder,ncaname) + ncaname2 = str(nca._path) + PN = os.path.join(ofolder,ncaname2) + if not os.path.exists(ncafolder): + os.makedirs(ncafolder) + for i in range(len(files_list)): + if str(nca._path) == files_list[i][0]: + offset=files_list[i][1] + break + #t = tqdm(total=nca.size, unit='B', unit_scale=True, leave=False) + try: + fp=open(str(self._path), 'rb') + nca3=NCA3(fp,int(offset),str(nca._path),tk,buffer) + nca3.extract_conts(ncafolder, disp=True) + fp.close() + except: + #Print.error('Exception: ' + str(e)) + if nca.size no RSV to patch\n');print(message);feed+=message+'\n' return feed - def inf_get_title(self,target,offset,content_entries,original_ID): + def inf_get_title(self,target,offset,content_entries,original_ID,roman=True): content_type='' token='secure' for nspF in self.hfs0: @@ -2340,7 +2703,7 @@ def inf_get_title(self,target,offset,content_entries,original_ID): if type(nca) == Nca: if nca_name == str(nca._path): if str(nca.header.contentType) == 'Content.CONTROL': - title,editor,ediver,SupLg,regionstr,isdemo=nca.get_langueblock(title) + title,editor,ediver,SupLg,regionstr,isdemo=nca.get_langueblock(title,roman) return(title,editor,ediver,SupLg,regionstr,isdemo) regionstr="0|0|0|0|0|0|0|0|0|0|0|0|0|0" return(title,"","","",regionstr,"") @@ -2707,6 +3070,7 @@ def supertrim(self,buffer,outfile,ofolder,fat): masterKeyRev = ticket.getMasterKeyRevision() titleKeyDec = Keys.decryptTitleKey(ticket.getTitleKeyBlock().to_bytes(16, byteorder='big'), Keys.getMasterKeyIndex(masterKeyRev)) rightsId = ticket.getRightsId() + tik=ticket for nca in nspF: if type(nca) == Nca: if nca.header.getRightsId() != 0: @@ -2720,7 +3084,7 @@ def supertrim(self,buffer,outfile,ofolder,fat): if nca.header.getCryptoType2() == 0: if nca.header.getCryptoType() == 2: masterKeyRev = 2 - titleKeyDec = Keys.decryptTitleKey(ticket.getTitleKeyBlock().to_bytes(16, byteorder='big'), Keys.getMasterKeyIndex(masterKeyRev)) + titleKeyDec = Keys.decryptTitleKey(tik.getTitleKeyBlock().to_bytes(16, byteorder='big'), Keys.getMasterKeyIndex(masterKeyRev)) break Print.info('Generating XCI:') @@ -3126,7 +3490,8 @@ def c_nsp_direct(self,buffer,outfile,ofolder,fat,fx,delta,metapatch,RSV_cap,keyp if type(file) == Ticket: masterKeyRev = file.getMasterKeyRevision() titleKeyDec = Keys.decryptTitleKey(file.getTitleKeyBlock().to_bytes(16, byteorder='big'), Keys.getMasterKeyIndex(masterKeyRev)) - rightsId = file.getRightsId() + rightsId = file.getRightsId() + ticket=file for nspF in self.hfs0: if str(nspF._path)=="secure": for nca in nspF: @@ -3335,7 +3700,9 @@ def c_nsp_direct(self,buffer,outfile,ofolder,fat,fx,delta,metapatch,RSV_cap,keyp if fat=="fat32" and (c+len(newheader))>block: n2=block-c c=0 - dat2=newheader[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO(newheader) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -3344,7 +3711,9 @@ def c_nsp_direct(self,buffer,outfile,ofolder,fat,fx,delta,metapatch,RSV_cap,keyp outfile=outfile[0:-1] outfile=outfile+str(index) outf = open(outfile, 'wb') - dat2=newheader[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(newheader)-n2) + inmemoryfile.close() outf.write(dat2) t.update(len(dat2)) outf.flush() @@ -3355,14 +3724,13 @@ def c_nsp_direct(self,buffer,outfile,ofolder,fat,fx,delta,metapatch,RSV_cap,keyp nca.seek(0xC00) i+=1 else: - outf.write(data) - t.update(len(data)) - c=c+len(data) - outf.flush() if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=nca.read(int(n2)) + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -3370,7 +3738,18 @@ def c_nsp_direct(self,buffer,outfile,ofolder,fat,fx,delta,metapatch,RSV_cap,keyp index=index+1 outfile=outfile[0:-1] outfile=outfile+str(index) - outf = open(outfile, 'wb') + outf = open(outfile, 'wb') + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() + outf.write(dat2) + t.update(len(dat2)) + outf.flush() + else: + outf.write(data) + t.update(len(data)) + c=c+len(data) + outf.flush() if not data: nca.close() break @@ -3383,14 +3762,13 @@ def c_nsp_direct(self,buffer,outfile,ofolder,fat,fx,delta,metapatch,RSV_cap,keyp size=os.path.getsize(filepath) t.write(tabs+'- Appending: ' + str(nca._path)) for data in iter(lambda: target.read(int(size)), ""): - outf.write(data) - t.update(len(data)) - c=c+len(data) - outf.flush() if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=nca.read(int(n2)) + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -3398,12 +3776,18 @@ def c_nsp_direct(self,buffer,outfile,ofolder,fat,fx,delta,metapatch,RSV_cap,keyp index=index+1 outfile=outfile[0:-1] outfile=outfile+str(index) - outf = open(outfile, 'wb') - if totSize>(4294934528+int(buffer)): - dat2=nca.read(int(buffer)) - outf.write(dat2) - t.update(len(dat2)) - outf.flush() + outf = open(outfile, 'wb') + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() + outf.write(dat2) + t.update(len(dat2)) + outf.flush() + else: + outf.write(data) + t.update(len(data)) + c=c+len(data) + outf.flush() if not data: target.close() break @@ -3418,14 +3802,13 @@ def c_nsp_direct(self,buffer,outfile,ofolder,fat,fx,delta,metapatch,RSV_cap,keyp t.write(tabs+'- Appending: ' + xmlname) xmlf.seek(0x00) for data in iter(lambda: xmlf.read(int(buffer)), ""): - outf.write(data) - t.update(len(data)) - c=c+len(data) - outf.flush() if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=nca.read(int(n2)) + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -3433,12 +3816,18 @@ def c_nsp_direct(self,buffer,outfile,ofolder,fat,fx,delta,metapatch,RSV_cap,keyp index=index+1 outfile=outfile[0:-1] outfile=outfile+str(index) - outf = open(outfile, 'wb') - if totSize>(4294934528+int(buffer)): - dat2=nca.read(int(buffer)) - outf.write(dat2) - t.update(len(dat2)) - outf.flush() + outf = open(outfile, 'wb') + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() + outf.write(dat2) + t.update(len(dat2)) + outf.flush() + else: + outf.write(data) + t.update(len(data)) + c=c+len(data) + outf.flush() if not data: xmlf.close() break @@ -3479,6 +3868,7 @@ def c_xci_direct(self,buffer,outfile,ofolder,fat,delta,metapatch,RSV_cap,keypatc masterKeyRev = file.getMasterKeyRevision() titleKeyDec = Keys.decryptTitleKey(file.getTitleKeyBlock().to_bytes(16, byteorder='big'), Keys.getMasterKeyIndex(masterKeyRev)) rightsId = file.getRightsId() + ticket=file for nspF in self.hfs0: if str(nspF._path)=="secure": for file in nspF: @@ -3637,7 +4027,9 @@ def c_xci_direct(self,buffer,outfile,ofolder,fat,delta,metapatch,RSV_cap,keypatc if fat=="fat32" and (c+len(newheader))>block: n2=block-c c=0 - dat2=newheader[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO(newheader) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -3646,25 +4038,26 @@ def c_xci_direct(self,buffer,outfile,ofolder,fat,delta,metapatch,RSV_cap,keypatc outfile=outfile[0:-1] outfile=outfile+str(index) outf = open(outfile, 'wb') - dat2=newheader[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(newheader)-n2) + inmemoryfile.close() outf.write(dat2) t.update(len(dat2)) outf.flush() else: outf.write(newheader) t.update(len(newheader)) - c=c+len(newheader) + c=c+len(newheader) nca.seek(0xC00) i+=1 else: - outf.write(data) - t.update(len(data)) - c=c+len(data) - outf.flush() if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=nca.read(int(n2)) + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -3672,12 +4065,18 @@ def c_xci_direct(self,buffer,outfile,ofolder,fat,delta,metapatch,RSV_cap,keypatc index=index+1 outfile=outfile[0:-1] outfile=outfile+str(index) - outf = open(outfile, 'wb') - if totSize>(4294934528+int(buffer)): - dat2=nca.read(int(buffer)) - outf.write(dat2) - t.update(len(dat2)) - outf.flush() + outf = open(outfile, 'wb') + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() + outf.write(dat2) + t.update(len(dat2)) + outf.flush() + else: + outf.write(data) + t.update(len(data)) + c=c+len(data) + outf.flush() if not data: break if type(nca) == Nca and str(nca.header.contentType) == 'Content.META': @@ -3747,14 +4146,13 @@ def c_xci_direct(self,buffer,outfile,ofolder,fat,delta,metapatch,RSV_cap,keypatc size=os.path.getsize(filepath) t.write(tabs+'- Appending: ' + str(nca._path)) for data in iter(lambda: target.read(int(size)), ""): - outf.write(data) - t.update(len(data)) - c=c+len(data) - outf.flush() if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=nca.read(int(n2)) + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -3762,12 +4160,18 @@ def c_xci_direct(self,buffer,outfile,ofolder,fat,delta,metapatch,RSV_cap,keypatc index=index+1 outfile=outfile[0:-1] outfile=outfile+str(index) - outf = open(outfile, 'wb') - if totSize>(4294934528+int(buffer)): - dat2=nca.read(int(buffer)) - outf.write(dat2) - t.update(len(dat2)) - outf.flush() + outf = open(outfile, 'wb') + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() + outf.write(dat2) + t.update(len(dat2)) + outf.flush() + else: + outf.write(data) + t.update(len(data)) + c=c+len(data) + outf.flush() if not data: break target.close() @@ -4293,6 +4697,7 @@ def cd_spl_nsp(self,buffer,outfile,ofolder,filelist,fat,fx): splitnumb=math.ceil(totSize/4294901760) index=0 filepath=filepath[:-1]+str(index) + if fx=="folder" and fat=="fat32": output_folder ="archfolder" output_folder = os.path.join(ofolder, output_folder) @@ -4307,6 +4712,7 @@ def cd_spl_nsp(self,buffer,outfile,ofolder,filelist,fat,fx): t = tqdm(total=totSize, unit='B', unit_scale=True, leave=False) t.write(tabs+'- Writing header...') outf = open(str(filepath), 'w+b') + outfile=str(filepath) outf.write(hd) t.update(len(hd)) c=c+len(hd) @@ -4335,7 +4741,10 @@ def cd_spl_nsp(self,buffer,outfile,ofolder,filelist,fat,fx): if fat=="fat32" and (c+len(newheader))>block: n2=block-c c=0 - dat2=newheader[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(newheader) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -4344,7 +4753,9 @@ def cd_spl_nsp(self,buffer,outfile,ofolder,filelist,fat,fx): outfile=outfile[0:-1] outfile=outfile+str(index) outf = open(outfile, 'wb') - dat2=newheader[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(newheader)-n2) + inmemoryfile.close() outf.write(dat2) t.update(len(dat2)) outf.flush() @@ -4355,14 +4766,13 @@ def cd_spl_nsp(self,buffer,outfile,ofolder,filelist,fat,fx): file.seek(0xC00) i+=1 else: - outf.write(data) - t.update(len(data)) - c=c+len(data) - outf.flush() if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=file.read(int(n2)) + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -4370,21 +4780,31 @@ def cd_spl_nsp(self,buffer,outfile,ofolder,filelist,fat,fx): index=index+1 outfile=outfile[0:-1] outfile=outfile+str(index) - outf = open(outfile, 'wb') + outf = open(outfile, 'wb') + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() + outf.write(dat2) + t.update(len(dat2)) + outf.flush() + else: + outf.write(data) + t.update(len(data)) + c=c+len(data) + outf.flush() if not data: break if type(file) != Nca and file._path in contentlist: file.rewind() t.write(tabs+'- Appending: ' + str(file._path)) for data in iter(lambda: file.read(int(buffer)), ""): - outf.write(data) - t.update(len(data)) - c=c+len(data) - outf.flush() if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=file.read(int(n2)) + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -4393,11 +4813,17 @@ def cd_spl_nsp(self,buffer,outfile,ofolder,filelist,fat,fx): outfile=outfile[0:-1] outfile=outfile+str(index) outf = open(outfile, 'wb') - if totSize>(4294934528+int(buffer)): - dat2=file.read(int(buffer)) - outf.write(dat2) - t.update(len(dat2)) - outf.flush() + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() + outf.write(dat2) + t.update(len(dat2)) + outf.flush() + else: + outf.write(data) + t.update(len(data)) + c=c+len(data) + outf.flush() if not data: break t.close() @@ -4467,6 +4893,7 @@ def cd_spl_xci(self,buffer,outfile,ofolder,filelist,fat,fx): masterKeyRev = file.getMasterKeyRevision() titleKeyDec = Keys.decryptTitleKey(file.getTitleKeyBlock().to_bytes(16, byteorder='big'), Keys.getMasterKeyIndex(masterKeyRev)) rightsId = file.getRightsId() + ticket=file for nspF in self.hfs0: if str(nspF._path)=="secure": for file in nspF: @@ -4523,10 +4950,13 @@ def cd_spl_xci(self,buffer,outfile,ofolder,filelist,fat,fx): Print.info('masterKeyRev =\t' + hex(masterKeyRev)) print("") + if totSize <= 4294934528: + fat="exfat" if fat=="fat32": splitnumb=math.ceil(totSize/4294934528) index=0 filepath=filepath[:-1]+str(index) + c=0 t = tqdm(total=totSize, unit='B', unit_scale=True, leave=False) t.write(tabs+'- Writing XCI header...') @@ -4565,7 +4995,7 @@ def cd_spl_xci(self,buffer,outfile,ofolder,filelist,fat,fx): outf.write(sec_header) t.update(len(sec_header)) c=c+len(sec_header) - + outfile=str(filepath) block=4294934528 for nspF in self.hfs0: if str(nspF._path)=="secure": @@ -4609,7 +5039,10 @@ def cd_spl_xci(self,buffer,outfile,ofolder,filelist,fat,fx): if fat=="fat32" and (c+len(newheader))>block: n2=block-c c=0 - dat2=newheader[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(newheader) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -4618,7 +5051,9 @@ def cd_spl_xci(self,buffer,outfile,ofolder,filelist,fat,fx): outfile=outfile[0:-1] outfile=outfile+str(index) outf = open(outfile, 'wb') - dat2=newheader[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(newheader)-n2) + inmemoryfile.close() outf.write(dat2) t.update(len(dat2)) outf.flush() @@ -4629,14 +5064,13 @@ def cd_spl_xci(self,buffer,outfile,ofolder,filelist,fat,fx): nca.seek(0xC00) i+=1 else: - outf.write(data) - t.update(len(data)) - c=c+len(data) - outf.flush() if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=nca.read(int(n2)) + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) outf.write(dat2) outf.flush() outf.close() @@ -4645,11 +5079,17 @@ def cd_spl_xci(self,buffer,outfile,ofolder,filelist,fat,fx): outfile=outfile[0:-1] outfile=outfile+str(index) outf = open(outfile, 'wb') - if totSize>(4294934528+int(buffer)): - dat2=nca.read(int(buffer)) - outf.write(dat2) - t.update(len(dat2)) - outf.flush() + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() + outf.write(dat2) + t.update(len(dat2)) + outf.flush() + else: + outf.write(data) + t.update(len(data)) + c=c+len(data) + outf.flush() if not data: break t.close() @@ -5247,7 +5687,10 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R if fat=="fat32" and (c+len(newheader))>block: n2=block-c c=0 - dat2=newheader[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(newheader) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) fp.write(dat2) fp.flush() fp.close() @@ -5256,7 +5699,9 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R outf=outf[0:-1] outf=outf+str(index) fp = open(outf, 'wb') - dat2=newheader[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(newheader)-n2) + inmemoryfile.close() fp.write(dat2) t.update(len(dat2)) c=c+len(dat2) @@ -5273,7 +5718,10 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=data[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) fp.write(dat2) fp.flush() fp.close() @@ -5282,7 +5730,9 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R outf=outf[0:-1] outf=outf+str(index) fp = open(outf, 'wb') - dat2=data[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() fp.write(dat2) t.update(len(dat2)) c=c+len(dat2) @@ -5319,7 +5769,10 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R if fat=="fat32" and (c+len(newheader))>block: n2=block-c c=0 - dat2=newheader[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(newheader) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) fp.write(dat2) fp.flush() fp.close() @@ -5328,7 +5781,9 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R outf=outf[0:-1] outf=outf+str(index) fp = open(outf, 'wb') - dat2=newheader[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(newheader)-n2) + inmemoryfile.close() fp.write(dat2) t.update(len(dat2)) fp.flush() @@ -5344,7 +5799,10 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=data[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) fp.write(dat2) fp.flush() fp.close() @@ -5353,7 +5811,9 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R outf=outf[0:-1] outf=outf+str(index) fp = open(outf, 'wb') - dat2=data[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() fp.write(dat2) t.update(len(dat2)) c=c+len(dat2) @@ -5393,7 +5853,10 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=data[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) fp.write(dat2) fp.flush() fp.close() @@ -5402,7 +5865,9 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R outf=outf[0:-1] outf=outf+str(index) fp = open(outf, 'wb') - dat2=data[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() fp.write(dat2) t.update(len(dat2)) c=c+len(dat2) @@ -5435,7 +5900,10 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=data[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) fp.write(dat2) fp.flush() fp.close() @@ -5444,7 +5912,9 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R outf=outf[0:-1] outf=outf+str(index) fp = open(outf, 'wb') - dat2=data[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() fp.write(dat2) t.update(len(dat2)) c=c+len(dat2) @@ -5467,7 +5937,10 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R if fat=="fat32" and (c+len(data))>block: n2=block-c c=0 - dat2=data[0x00:0x00+int(n2)] + inmemoryfile = io.BytesIO() + inmemoryfile.write(data) + inmemoryfile.seek(0) + dat2=inmemoryfile.read(n2) fp.write(dat2) fp.flush() fp.close() @@ -5476,7 +5949,9 @@ def append_clean_content(self,outf,target,buffer,t,gamecard,keypatch,metapatch,R outf=outf[0:-1] outf=outf+str(index) fp = open(outf, 'wb') - dat2=data[0x00+int(n2)+1:] + inmemoryfile.seek(n2) + dat2=inmemoryfile.read(len(data)-n2) + inmemoryfile.close() fp.write(dat2) t.update(len(dat2)) c=c+len(dat2) @@ -5553,7 +6028,7 @@ def cnmt_get_baseids(self): #/////////////////////////////////////////////////// #ADD TO DATABASE #/////////////////////////////////////////////////// - def addtodb(self,ofile,dbtype): + def addtodb(self,ofile,dbtype,roman=True): for nspF in self.hfs0: if str(nspF._path)=="secure": for nca in nspF: @@ -5603,7 +6078,7 @@ def addtodb(self,ofile,dbtype): if ckey == '0': ckey=self.getdbkey(titlerights) target=str(nca._path) - tit_name,editor,ediver,SupLg,regionstr,isdemo = self.inf_get_title(target,offset,content_entries,original_ID) + tit_name,editor,ediver,SupLg,regionstr,isdemo = self.inf_get_title(target,offset,content_entries,original_ID,roman) if tit_name=='DLC' and (str(titleid2).endswith('000') or str(titleid2).endswith('800')): tit_name='-' editor='-' @@ -5994,6 +6469,11 @@ def verify(self): message=(str(f.header.titleId)+' - '+str(f.header.contentType));print(message);feed+=message+'\n' if str(f.header.contentType) != 'Content.PROGRAM': correct = self.verify_enforcer(file) + if correct == True: + if str(f.header.contentType) == 'Content.PUBLIC_DATA' and f.header.getRightsId() == 0: + correct = f.pr_noenc_check_dlc() + if correct == False: + baddec=True else: for nf in f: nf.rewind() @@ -6008,11 +6488,11 @@ def verify(self): if correct == False and f.header.getRightsId() == 0: correct = f.pr_noenc_check() if correct == False and f.header.getRightsId() != 0: - correct = self.verify_nca_key(file) + correct,tk = self.verify_nca_key(file) if correct == True and f.header.getRightsId() == 0: - correct = f.pr_noenc_check() + correct = f.pr_noenc_check() if correct == False: - baddec=True + baddec=True elif file.endswith('.tik'): tikfile=str(file) checktik == False @@ -6146,7 +6626,7 @@ def verify_sig(self,feed,tmpfolder): for f in nspF: if type(f) == Nca and f.header.contentType != Type.Content.META: message=(str(f.header.titleId)+' - '+str(f.header.contentType));print(message);feed+=message+'\n' - verify,origheader,ncaname,feed,origkg=f.verify(feed) + verify,origheader,ncaname,feed,origkg,tr,tkey,iGC=f.verify(feed) headerlist.append([ncaname,origheader,hlisthash]) keygenerationlist.append([ncaname,origkg]) if verdict == True: @@ -6160,7 +6640,7 @@ def verify_sig(self,feed,tmpfolder): f.rewind();meta_dat=f.read() message=(str(f.header.titleId)+' - '+str(f.header.contentType));print(message);feed+=message+'\n' targetkg,minrsv=self.find_addecuatekg(meta_nca,keygenerationlist) - verify,origheader,ncaname,feed,origkg=f.verify(feed) + verify,origheader,ncaname,feed,origkg,tr,tkey,iGC=f.verify(feed) #print(targetkg) if verify == False: tempfile=os.path.join(tmpfolder,meta_nca) @@ -6224,7 +6704,7 @@ def verify_sig(self,feed,tmpfolder): fp.close() fp = Fs.Nca(tempfile, 'r+b') progress=True - verify,origheader,ncapath,feed,origkg=fp.verify(feed,targetkg,rsv_endcheck,progress,t) + verify,origheader,ncapath,feed,origkg,tr,tkey,iGC=fp.verify(feed,targetkg,rsv_endcheck,progress,t) fp.close() t.update(1) if verify == True: @@ -6253,7 +6733,7 @@ def verify_sig(self,feed,tmpfolder): if hlisthash == True: sha0=sha0.hexdigest() hlisthash=sha0 - headerlist.append([ncaname,origheader,hlisthash]) + headerlist.append([ncaname,origheader,hlisthash,tr,tkey,iGC]) message='';print(message);feed+=message+'\n' try: shutil.rmtree(tmpfolder) @@ -6440,15 +6920,16 @@ def verify_enforcer(self,nca): else: return False def verify_nca_key(self,nca): - check=False + check=False;titleKey=0 for nspF in self.hfs0: if str(nspF._path)=="secure": for file in nspF: if (file._path).endswith('.tik'): + titleKey = file.getTitleKeyBlock().to_bytes(16, byteorder='big') check=self.verify_key(nca,str(file._path)) if check==True: break - return check + return check,titleKey def verify_key(self,nca,ticket): for nspF in self.hfs0: @@ -6463,9 +6944,9 @@ def verify_key(self,nca,ticket): else: masterKeyRev=file.header.getCryptoType2() else: - masterKeyRev=file.header.getCryptoType2() - if str(file._path) == nca: - break + masterKeyRev=file.header.getCryptoType2() + break + for nspF in self.hfs0: if str(nspF._path)=="secure": @@ -6488,6 +6969,8 @@ def verify_key(self,nca,ticket): if str(f._path) == nca: if type(f) == Fs.Nca and f.header.getRightsId() != 0: for fs in f.sectionFilesystems: + #print(fs.fsType) + #print(fs.cryptoType) if fs.fsType == Type.Fs.PFS0 and fs.cryptoType == Type.Crypto.CTR: f.seek(0) ncaHeader = NcaHeader() @@ -6504,8 +6987,8 @@ def verify_key(self,nca,ticket): mem = MemoryFile(pfs0Header, Type.Crypto.CTR, decKey, pfs0.cryptoCounter, offset = pfs0Offset) data = mem.read(); #Hex.dump(data) - #print('hash = %s' % str(_sha256(data))) - if hx(sectionHeaderBlock[0xc8:0xc8+0x20]).decode('utf-8') == str(_sha256(data)): + #print('hash = %s' % str(sha256(data).hexdigest())) + if hx(sectionHeaderBlock[0xc8:0xc8+0x20]).decode('utf-8') == str(sha256(data).hexdigest()): return True else: return False @@ -6518,21 +7001,43 @@ def verify_key(self,nca,ticket): if magic != b'PFS0': return False else: - return True + return True if fs.fsType == Type.Fs.ROMFS and fs.cryptoType == Type.Crypto.CTR: f.seek(0) ncaHeader = NcaHeader() ncaHeader.open(MemoryFile(f.read(0x400), Type.Crypto.XTS, uhx(Keys.get('header_key')))) - romfs=fs + ncaHeader = f.read(0x400) + pfs0=fs sectionHeaderBlock = fs.buffer - f.seek(fs.offset) - romfsOffset=fs.offset - romfsHeader = f.read(0x10) - mem = MemoryFile(romfsHeader, Type.Crypto.CTR, decKey, romfs.cryptoCounter, offset = romfsOffset) - magic = mem.read()[0:4] - return True - else: - return False + + levelOffset = int.from_bytes(sectionHeaderBlock[0x18:0x20], byteorder='little', signed=False) + levelSize = int.from_bytes(sectionHeaderBlock[0x20:0x28], byteorder='little', signed=False) + + pfs0Offset = fs.offset + levelOffset + f.seek(pfs0Offset) + pfs0Header = f.read(levelSize) + #print(sectionHeaderBlock[8:12] == b'IVFC') + if sectionHeaderBlock[8:12] == b'IVFC': + #Hex.dump(self.sectionHeaderBlock) + #Print.info(hx(self.sectionHeaderBlock[0xc8:0xc8+0x20]).decode('utf-8')) + mem = MemoryFile(pfs0Header, Type.Crypto.CTR, decKey, pfs0.cryptoCounter, offset = pfs0Offset) + data = mem.read(); + #Hex.dump(data) + #print('hash = %s' % str(sha256(data).hexdigest())) + if hx(sectionHeaderBlock[0xc8:0xc8+0x20]).decode('utf-8') == str(sha256(data).hexdigest()): + return True + else: + return False + else: + mem = MemoryFile(pfs0Header, Type.Crypto.CTR, decKey, pfs0.cryptoCounter, offset = pfs0Offset) + data = mem.read(); + #Hex.dump(data) + magic = mem.read()[0:4] + #print(magic) + if magic != b'PFS0': + return False + else: + return True \ No newline at end of file diff --git a/py/ztools/Fs/pyNCA3.py b/py/ztools/Fs/pyNCA3.py new file mode 100644 index 00000000..14b28e46 --- /dev/null +++ b/py/ztools/Fs/pyNCA3.py @@ -0,0 +1,438 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +import os +import io +from struct import pack as pk +from binascii import hexlify as hx, unhexlify as uhx +from Crypto.Cipher import AES +from Crypto.Util import Counter +from Utils import ( + read_at, read_u8, read_u32, read_u64, pk_u32, pk_u64, + memdump, check_tkey, bytes2human, align_to, FileInContainer +) +from CryptoUtils import validate_rsa2048_pss_sig, gen_aes_kek, AESXTSN +from Fs.pyPFS0 import PFS0Superblock, HashTableWrappedPFS0 +from Fs.pyRomFS import IVFCSuperblock, HashTreeWrappedRomFS +from Fs.pyNPDM import NPDM +from NXKeys import ProdKeys, TitleKeys +from tqdm import tqdm +import Keys + +keys = ProdKeys() +tkeys = list() + +RSA_PUBLIC_EXPONENT = 0x10001 +FS_HEADER_LENGTH = 0x200 +nca_header_fixed_key_modulus = 0xBFBE406CF4A780E9F07D0C99611D772F96BC4B9E58381B03ABB175499F2B4D5834B005A37522BE1A3F0373AC7068D116B904465EB707912F078B26DEF60007B2B451F80D0A5E58ADEBBC9AD649B964EFA782B5CF6D7013B00F85F6A908AA4D676687FA89FF7590181E6B3DE98A68C92604D980CE3F5E92CE01FF063BF2C1A90CCE026F16BC92420A4164CD52B6344DAEC02EDEA4DF27683CC1A060AD43F3FC86C13E6C46F77C299FFAFDF0E3CE64E735F2F656566F6DF1E242B08340A5C3202BCC9AAECAED4D7030A8701C70FD1363290279EAD2A7AF3528321C7BE62F1AAA407E328C2742FE8278EC0DEBE6834B6D8104401A9E9A67F67229FA04F09DE4F403 +class SectionTable: + MEDIA_SIZE = 0x200 + def __init__(self, raw_section_table): + media_start_offset = read_u32(raw_section_table, 0x0) + media_end_offset = read_u32(raw_section_table, 0x4) + self.start_offset = media_start_offset * self.MEDIA_SIZE + self.end_offset = media_end_offset * self.MEDIA_SIZE + + def gen(self): + table = b'' + table += pk_u32(self.start_offset / self.MEDIA_SIZE) + table += pk_u32(self.end_offset / self.MEDIA_SIZE) + table += 0x8 * b'\xFF' + return table + +class SectionHeader: + SECTION_HEADER_LENGTH = 0x138 + part_types = { + 0: 'RomFS', + 1: 'PFS0' + } + fs_types = { + 2: 'PFS0', + 3: 'RomFS' + } + crypto_types = { + 1: 'None', + 2: 'XTS', + 3: 'CTR', + 4: 'BKTR' + } + def __init__(self, raw_section_header, section_table): + self.offset = section_table.start_offset + self.size = section_table.end_offset - self.offset + + self.part_type = self.part_types[read_u8(raw_section_header, 0x2)] + self.fs_type = self.fs_types[read_u8(raw_section_header, 0x3)] + self.crypto_type = self.crypto_types[read_u8(raw_section_header, 0x4)] + raw_superblock = io.BytesIO(read_at(raw_section_header, 0x8, self.SECTION_HEADER_LENGTH)) + self.section_ctr = read_u64(raw_section_header, 0x140) + + if self.fs_type == 'PFS0': + self.superblock = PFS0Superblock(raw_superblock) + self.fs_offset = self.superblock.offset + self.fs_size = self.superblock.size + elif self.fs_type == 'RomFS': + self.superblock = IVFCSuperblock(raw_superblock) + self.fs_offset = self.superblock.lvls[-1].offset + self.fs_size = self.superblock.lvls[-1].size + +class NCAHeader: + content_types = { + 0: 'Program', + 1: 'Meta', + 2: 'Control', + 3: 'Manual', + 4: 'Data', + 5: 'PublicData' + } + def __init__(self, raw_header): + self.rsa_sig_1 = read_at(raw_header, 0x0, 0x100) + self.rsa_sig_2 = read_at(raw_header, 0x100, 0x100) + self.magic = read_at(raw_header, 0x200, 0x4) + self.is_game_card = bool(read_u8(raw_header, 0x204)) + self.content_type = self.content_types[read_u8(raw_header, 0x205)] + self.crypto_type = read_u8(raw_header, 0x206) + self.kaek_ind = read_u8(raw_header, 0x207) + self.size = read_u64(raw_header, 0x208) + self.tid = read_u64(raw_header, 0x210) + self.sdk_rev = '.'.join(str(read_u8(raw_header, 0x21C + n)) for n in range(4))[::-1] + self.crypto_type_2 = read_u8(raw_header, 0x220) + self.rights_id = read_at(raw_header, 0x230, 0x10) + + self.section_tables = [] + for n in range(4): + raw_section_table = io.BytesIO(read_at(raw_header, 0x240 + 0x10 * n, 0x10)) + self.section_tables.append(SectionTable(raw_section_table)) + + self.hash_table = [] + for n in range(4): + self.hash_table.append(read_at(raw_header, 0x280 + n * 0x20, 0x20)) + self.enc_key_area = read_at(raw_header, 0x300, 0x40) + + self.section_headers = [] + for n in range(4): + if self.section_tables[n].start_offset: # Sections exists + raw_section_header = io.BytesIO(read_at(raw_header, 0x400 + n * FS_HEADER_LENGTH, FS_HEADER_LENGTH)) + self.section_headers.append(SectionHeader(raw_section_header, self.section_tables[n])) + +class NCA3: + '''Class for manipulating NCA3 files''' + kaeks = { + 0: keys['key_area_key_application_source'], + 1: keys['key_area_key_ocean_source'], + 2: keys['key_area_key_system_source'] + } + def __init__(self, fp,ifo=0,name='nca',titlekey=None,buffer=65536,verify=False): + # print(str(fp)) + # print(str(ifo)) + # print(str(name)) + self.f = fp + self.ifo=int(ifo) + self.buffer=buffer + if name=='nca': + self.name=self.f.name + else: + self.name=name + + self.is_valid = True + + self._parse(titlekey=titlekey, verify=verify) + + def __str__(self): + string = '%s:\n' % os.path.basename(self.name) + string += ' Size: %s\n' % bytes2human(self.header.size) + string += ' Content type: %s\n' % self.header.content_type + string += ' Title ID: %016x\n' % self.header.tid + if self.has_rights_id: + string += ' Rights ID: %032x\n' % self.rights_id + string += ' Master key revision: %d\n' % self.crypto_type + string += ' Encryption type: %s\n' % ('Titlekey' if self.has_rights_id else 'Standard') + if self.has_rights_id and not self.has_tkey: + string += 'Titlekey not available' + return string + string += ' Body key: %s\n\n' % hx(self.body_key).decode() + for n, sec in enumerate(self.sections): + string += 'Section %d: %s (%s)\n' % ( + n, + ('ExeFS' if sec.is_exefs else sec.fs_type), + bytes2human(sec.size) + ) + if sec.section_header.fs_type == 'RomFS': + string += ' Directories: %d\n' % sec.fs.dir_nb + string += ' Files: %d\n' % sec.fs.file_nb + return string + + def print_more(self): + print('%s:' % os.path.basename(self.name)) + print(' Content type: %s' % self.header.content_type) + print(' Size: %s' % bytes2human(self.header.size)) + print(' Distribution Type: %s' % ('Gamecard' if self.header.is_game_card else 'Digital')) + print(' Title ID: %016x' % self.header.tid) + if self.has_rights_id: + print(' Rights ID: %032x' % self.rights_id) + print(' Master key revision: %d' % self.crypto_type) + print(' Encryption type: %s' % ('Titlekey' if self.has_rights_id else 'Standard')) + if self.has_rights_id and not self.has_tkey: + print('Titlekey not available') + return + print(' Body key: %s' % hx(self.body_key).decode()) + print(memdump(self.header.rsa_sig_1, message=' Fixed key signature: ', length=32)) + print(memdump(self.header.rsa_sig_2, message=' NPDM signature: ', length=32)) + print() + for n, sec in enumerate(self.sections): + print('Section %d (%s):' % (n, bytes2human(sec.size))) + print(' Type: %s' % ('ExeFS' if sec.is_exefs else sec.fs_type)) + print(' Counter: %032x' % ((int.from_bytes(sec.nonce, byteorder='big') << 64) + (sec.section_offset >> 4))) + print(' Offset: 0x%x' % sec.offset_in_cont) + if sec.section_header.fs_type == 'RomFS': + print(' Block Size: 0x%x' % sec.section_header.superblock.lvls[0].block_size) + print(' Directories: %d' % sec.fs.dir_nb) + else: + print(' Block Size: 0x%x' % sec.section_header.superblock.block_size) + print(' Files: %d' % sec.fs.file_nb) + print(str(sec.fs)) + print() + + def _decrypt_keyarea(self, enc_key_area): + area_key = gen_aes_kek(self.kaek, self.mkey, keys['aes_kek_generation_source'], keys['aes_key_generation_source']) + return AES.new(area_key, AES.MODE_ECB).decrypt(enc_key_area) + + def _decrypt_tkey(self, enc_tkey): + return AES.new(self.tkek, AES.MODE_ECB).decrypt(enc_tkey) + + def _decrypt_header(self): + self.f.seek(self.ifo+0) + header_keys = keys['nca_header_key'][:0x10], keys['nca_header_key'][0x10:] + cipher = AESXTSN(header_keys) + return cipher.decrypt(self.f.read(0xC00)) + + def _parse(self, titlekey=None, verify=False): + raw_header = self._decrypt_header() + self.header = NCAHeader(io.BytesIO(raw_header)) + + if self.header.magic != b'NCA3': + raise ValueError('Invalid NCA3 magic') + + if self.header.crypto_type_2 > self.header.crypto_type: + self.crypto_type = self.header.crypto_type_2 + else: + self.crypto_type = self.header.crypto_type + if self.crypto_type > 0: + self.crypto_type -= 1 + self.mkey = keys['master_key_%02x' % self.crypto_type] + + if titlekey is not None: + #print(hx(titlekey)) + self.body_key = titlekey + if self.header.rights_id != 0x10 * b'\0': + self.has_rights_id = True + else: + self.has_rights_id = False + self.kaek = self.kaeks[self.header.kaek_ind] + keyblob = self._decrypt_keyarea(self.header.enc_key_area) + self.key_area = b''.join(keyblob[0x10 * n : 0x10 * (n + 1)] for n in range(4)) + elif self.header.rights_id == 0x10 * b'\0': + self.has_rights_id = False + self.kaek = self.kaeks[self.header.kaek_ind] + keyblob = self._decrypt_keyarea(self.header.enc_key_area) + self.key_area = b''.join(keyblob[0x10 * n : 0x10 * (n + 1)] for n in range(4)) + self.body_key = self.key_area[0x20:0x30] + #print(hx(keyblob)) + #print(hx(self.key_area)) + #print(hx(self.body_key)) + else: + if self.header.rights_id != 0x10 * b'\0': + self.has_rights_id = True + self.rights_id = int.from_bytes(self.header.rights_id, byteorder='big') + self.tkek = keys['titlekek_%02x' % self.crypto_type] + if titlekey is not None: + self.enc_tkey = uhx(titlekey) + else: + try: + self.enc_tkey = tkeys['%032x' % self.rights_id] + except KeyError: + self.has_tkey = False + return + self.has_tkey = True + self.body_key = self._decrypt_tkey(self.enc_tkey) + self.sections = [] + if verify: + self.is_valid = True + for n, section_header in enumerate(self.header.section_headers): + self.sections.append(self.SectionInNCA3(self, section_header, verify=verify,ifo=self.ifo)) + if verify and not self.sections[n].fs.is_valid: + self.is_valid = False + + if verify: + mod = int(hx(keys['nca_header_fixed_key_modulus']), 16) + if not validate_rsa2048_pss_sig(mod, RSA_PUBLIC_EXPONENT, raw_header[0x200:0x400], self.header.rsa_sig_1): + self.is_valid = False + print('[WARN] Invalid fixed key signature') + for sec in self.sections: + if sec.is_exefs: + npdm = NPDM(sec.fs.open('main.npdm')) + mod = int.from_bytes(npdm.acid.rsa_pubk, byteorder='big') + if not validate_rsa2048_pss_sig(mod, RSA_PUBLIC_EXPONENT, raw_header[0x200:0x400], self.header.rsa_sig_2): + self.is_valid = False + print('[WARN] Invalid ACID signature') + + def decrypt_to_plaintext(self, out_f, disp=True): + if disp: + print('Decrypting %s to plaintext...' % os.path.basename(self.name)) + t = tqdm(total=self.header.size, unit='B', unit_scale=True, leave=False) + out = open(out_f, 'wb') + out.write(self._decrypt_header()) + t.update(int(0x300)) + if not self.has_rights_id: # Write the decrypted key area + out.seek(self.ifo+0x300) + out.write(self.key_area) + t.update(len(self.key_area)) + t.write(' > Parsing nca this may take some time...') + for _, sec in enumerate(self.sections): + out.seek(self.ifo+sec.offset_in_cont) + for buf in sec.decrypt_raw(): + out.write(buf) + t.update(len(buf)) + t.close() + out.close() + if disp: + print('Decrypted to %s' % out.name) + + def ret_npdm(self): + for _, sec in enumerate(self.sections): + if sec.is_exefs: + npdm = NPDM(sec.fs.open('main.npdm')) + n=npdm.ret + return n + + def print_npdm(self): + for _, sec in enumerate(self.sections): + #print(_) + #print(sec.fs_type) + if sec.is_exefs: + npdm = NPDM(sec.fs.open('main.npdm')) + n=npdm.__str__() + print(n) + return n + + def decrypt_raw_sections(self, out_dir, disp=True): + os.makedirs(out_dir, exist_ok=True) + for n, sec in enumerate(self.sections): + dest = os.path.join(out_dir, '.'.join((str(n), 'sec'))) + if disp: + print('Decrypting raw section %d to %s...' % (n, dest)) + with open(dest, 'wb') as out: + for buf in sec.decrypt_raw(): + out.write(buf) + if disp: + print('Decrypted to %s.' % out_dir) + + def decrypt_raw_conts(self, out_dir, disp=True): + os.makedirs(out_dir, exist_ok=True) + for n, sec in enumerate(self.sections): + dest = os.path.join(out_dir, '.'.join((str(n), sec.fs_type.lower()))) + if disp: + print('Decrypting raw %s partition %d to %s...' % (sec.fs_type.lower(), n, dest)) + with open(dest, 'wb') as out: + for buf in sec.decrypt_raw_cont(): + out.write(buf) + if disp: + print('Decrypted to %s.' % out_dir) + + def extract_conts(self, out_dir, disp=True): + + for n, sec in enumerate(self.sections): + try: + dest = os.path.join(out_dir, '%d [%s]' % (n, sec.fs_type.lower())) + if not os.path.exists(dest): + os.makedirs(dest) + if disp: + print('Extracting files of %s partition %d to %s...' % (sec.fs_type.lower(), n, dest)) + + sec.extract_cont(dest) + except:continue + if disp: + print('Extracted to %s.' % out_dir) + + class SectionInNCA3(FileInContainer): + def __init__(self, nca3, section_header, verify=False,ifo=0,buffer=65536): + self.nca = nca3 + self.section_header = section_header + + self.key = self.nca.body_key + self.nonce = pk_u64(self.section_header.section_ctr, endianness='>') + + self.section_offset = self.section_header.offset + self.section_size = self.section_header.size + self.fs_type = self.section_header.fs_type + self.crypto = self.section_header.crypto_type + self.ifo=ifo + self.buffer=buffer + + if self.crypto in ('BTKR', 'XTS'): + raise NotImplementedError('BTKR/XTS not implemented') + + super(NCA3.SectionInNCA3, self).__init__(self.nca.f, self.section_offset, self.section_size) + + self.is_exefs = False + if self.fs_type == 'PFS0': + self.cont_offset = self.section_header.superblock.offset + self.cont_size = self.section_header.superblock.size + self.fs = HashTableWrappedPFS0(self.section_header.superblock, self, verify=verify) + if 'main.npdm' in self.fs.files: + self.is_exefs = True + elif self.fs_type == 'RomFS': + self.cont_offset = self.section_header.superblock.lvls[IVFCSuperblock.IVFC_MAX_LEVEL-1].offset + self.cont_size = self.section_header.superblock.lvls[IVFCSuperblock.IVFC_MAX_LEVEL-1].size + self.fs = HashTreeWrappedRomFS(self.section_header.superblock, self, verify=verify) + + def update_ctr(self, off): + self.ctr = Counter.new(64, prefix=self.nonce, initial_value=((self.section_offset + off) >> 4)) + self.cipher = AES.new(self.key, AES.MODE_CTR, counter=self.ctr) + + def seek(self, off): + if self.crypto == 'None' or self.crypto == 'BTKR' or self.crypto == 'XTS': + super(NCA3.SectionInNCA3, self).seek(self.ifo+off) + elif self.crypto == 'CTR': + block_offset = off & ~0xF + self.sector_offset = off & 0xF + super(NCA3.SectionInNCA3, self).seek(self.ifo+block_offset) + self.update_ctr(block_offset) + + def read(self, length=None): + if self.crypto == 'None' or self.crypto == 'BTKR' or self.crypto == 'XTS': + data = super(NCA3.SectionInNCA3, self).read(length) + return data + elif self.crypto == 'CTR': + aligned_length = align_to(length + self.sector_offset, 0x10) + dec_data = self.cipher.decrypt(super(NCA3.SectionInNCA3, self).read(aligned_length)) + if aligned_length != length: + try: + return dec_data[self.sector_offset : length+self.sector_offset] + finally: # Seek to what the offset should be if not for the crypto + self.seek(self.ifo+self.tell() - aligned_length + length) + else: + return dec_data[:length] + + def _decrypt_from_offset(self, off, size=None): + self.seek(off+self.ifo) + read = 0 + while True: + buf = self.read(self.buffer) + if not buf: + break + if size and (read + len(buf) >= size): + yield buf[:size - read] + break + yield buf + read += len(buf) + + def decrypt_raw(self): + for buf in self._decrypt_from_offset(0): + yield buf + + def decrypt_raw_cont(self): + for buf in self._decrypt_from_offset(self.cont_offset, self.cont_size): + yield buf + + def extract_cont(self, dest=None): + self.fs.extract(dest, disp=False) diff --git a/py/ztools/Fs/pyNPDM.py b/py/ztools/Fs/pyNPDM.py new file mode 100644 index 00000000..cbd141ee --- /dev/null +++ b/py/ztools/Fs/pyNPDM.py @@ -0,0 +1,170 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +import io +import re +from Utils import read_at, read_u8, read_u32, read_u64, memdump + +class FsAccessControl: + def __init__(self, fp): + self.f = fp + self._parse() + + def __str__(self): + string = ' FS Access Control:\n' + string += ' Version: %d\n' % self.version + string += ' Permission bitmask: %016x\n' % self.permissions + return string + + def _parse(self): + self.version = read_u8(self.f, 0x0) + self.permissions = read_u64(self.f, 0x4) + +class ServiceAccessControl: + def __init__(self, fp): + self.f = fp + self._parse() + + def __str__(self): + string = ' Service Access Control:\n' + string += ' ' + '\n '.join('%02x - %s' % (int.from_bytes(b, byteorder='little'), s) + for s, b in self.services.items()) + '\n' + return string + + def _parse(self): + serv = re.compile(b'([\x02-\x07])(([a-z0-9-]+:?)+)', re.I) + self.services = {r[2].decode(): r[1] for r in re.finditer(serv, self.f.read())} + +class KernelAccessControl: + def __init__(self, fp): + self.f = fp + self._parse() + + def __str__(self): + string = ' Kernel Access Control:\n' + string += ' Placeholder\n' + return string + + def _parse(self): + # TODO + pass + +class ACID: + def __init__(self, fp): + self.f = fp + self._parse() + + def __str__(self): + string = ' ACID:\n' + string += ' ACID flags: %d\n' % self.flags + string += ' TitleID range: %016x-%016x\n' % (self.tid_min, self.tid_max) + string += memdump(self.rsa_sig, message=' RSA signature: ') + '\n' + string += memdump(self.rsa_pubk, message=' RSA public key: ') + '\n' + string += '\n' + str(self.fs_access_control) + string += '\n' + str(self.service_access_control) + string += '\n' + str(self.kernel_access_control) + return string + + def _parse(self): + self.rsa_sig = read_at(self.f, 0x0, 0x100) + self.rsa_pubk = read_at(self.f, 0x100, 0x100) + if read_at(self.f, 0x200, 0x4) != b'ACID': + raise ValueError('Invalid ACID magic') + self.flags = read_u32(self.f, 0x20C) + self.tid_min = read_u64(self.f, 0x210) + self.tid_max = read_u64(self.f, 0x218) + + fs_access_control_offset = read_u32(self.f, 0x220) + fs_access_control_size = read_u32(self.f, 0x224) + service_access_control_offset = read_u32(self.f, 0x228) + service_access_control_size = read_u32(self.f, 0x22C) + kernel_access_control_offset = read_u32(self.f, 0x230) + kernel_access_control_size = read_u32(self.f, 0x234) + + self.fs_access_control = FsAccessControl(io.BytesIO(read_at(self.f, + fs_access_control_offset, fs_access_control_size))) + self.service_access_control = ServiceAccessControl(io.BytesIO(read_at(self.f, + service_access_control_offset, service_access_control_size))) + self.kernel_access_control = KernelAccessControl(io.BytesIO(read_at(self.f, + kernel_access_control_offset, kernel_access_control_size))) + +class ACI0: + def __init__(self, fp): + self.f = fp + self._parse() + + def __str__(self): + string = ' ACI0:\n' + string += ' TitleID: %016x\n' % self.tid + string += '\n' + str(self.fs_access_control) + string += '\n' + str(self.service_access_control) + string += '\n' + str(self.kernel_access_control) + return string + + def _parse(self): + if read_at(self.f, 0x0, 0x4) != b'ACI0': + raise ValueError('Invalid ACI0 magic') + self.tid = read_u64(self.f, 0x10) + + fs_access_control_offset = read_u32(self.f, 0x20) + fs_access_control_size = read_u32(self.f, 0x24) + service_access_control_offset = read_u32(self.f, 0x28) + service_access_control_size = read_u32(self.f, 0x2C) + kernel_access_control_offset = read_u32(self.f, 0x30) + kernel_access_control_size = read_u32(self.f, 0x34) + + self.fs_access_control = FsAccessControl(io.BytesIO(read_at(self.f, + fs_access_control_offset, fs_access_control_size))) + self.service_access_control = ServiceAccessControl(io.BytesIO(read_at(self.f, + service_access_control_offset, service_access_control_size))) + self.kernel_access_control = KernelAccessControl(io.BytesIO(read_at(self.f, + kernel_access_control_offset, kernel_access_control_size))) + +class NPDM: + process_categories = { + 0: 'Regular title', + 1: 'Kernel built-in' + } + def __init__(self, fp): + self.f = fp + self._parse() + + def __str__(self): + string = '' + #string += 'NPDM:\n' + string += ' Title name: %s\n' % self.title_name + string += ' Process category: %s\n' % self.process_category + string += ' MMU flags: %d\n' % self.mmu_flags + string += ' Main stack thread priority: %d\n' % self.main_thread_priority + string += ' Main thread stack size: 0x%x\n' % self.main_thread_stack_size + string += ' Default CPU ID: %d\n' % self.default_cpu_id + string += '\n' + string += str(self.acid) + string += '\n' + string += str(self.aci0) + return string + + def _parse(self): + if read_at(self.f, 0x0, 0x4) != b'META': + raise ValueError('Invalid META magic') + self.mmu_flags = read_u8(self.f, 0xC) + self.main_thread_priority = read_u8(self.f, 0xE) + self.default_cpu_id = read_u8(self.f, 0xF) + self.process_category = self.process_categories[read_u32(self.f, 0x18)] + self.main_thread_stack_size = read_u32(self.f, 0x1C) + self.title_name = read_at(self.f, 0x20, 0x50).strip(b'\0').decode() + + aci0_offset = read_u32(self.f, 0x70) + aci0_size = read_u32(self.f, 0x74) + acid_offset = read_u32(self.f, 0x78) + acid_size = read_u32(self.f, 0x7C) + + self.acid = ACID(io.BytesIO(read_at(self.f, acid_offset, acid_size))) + self.aci0 = ACI0(io.BytesIO(read_at(self.f, aci0_offset, aci0_size))) + + def ret(self): + aci0_offset = read_u32(self.f, 0x70) + aci0_size = read_u32(self.f, 0x74) + acid_offset = read_u32(self.f, 0x78) + acid_size = read_u32(self.f, 0x7C) + return read_at(self.f, 0x0, acid_offset+acid_size) \ No newline at end of file diff --git a/py/ztools/Fs/pyPFS0.py b/py/ztools/Fs/pyPFS0.py new file mode 100644 index 00000000..8387f6b9 --- /dev/null +++ b/py/ztools/Fs/pyPFS0.py @@ -0,0 +1,258 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +import os +from math import ceil +from Utils import ( + read_at, read_u32, read_u64, pk_u32, pk_u64, + align_to, pad_to, bytes2human, FileInContainer +) +from CryptoUtils import sha256 + +PFS0_HEADER_LENGTH = 0x10 +PFS0_FILE_ENTRY_LENGTH = 0x18 + +class PFS0: + '''Class to manipulate PFS0 files''' + def __init__(self, fp): + self.f = fp + self._parse() + + def __str__(self): + return 'pfs0:\n ' +\ + '\n '.join('%s\t%s' % (bytes2human(d['Size']), name) for name, d in self.files.items()) + + def _parse(self): + if read_at(self.f, 0x0, 0x4) != b'PFS0': + raise ValueError('Invalid PFS0 magic!') + + self.files = {} + self.file_nb = read_u32(self.f, 0x4) + string_table_size = read_u32(self.f, 0x8) + string_table_offset = PFS0_HEADER_LENGTH + self.file_nb * PFS0_FILE_ENTRY_LENGTH + header_size = string_table_offset + string_table_size + names = [name.decode() for name in read_at(self.f, string_table_offset, string_table_size).split(b'\x00')][:self.file_nb] + + for n in range(self.file_nb): + self.files[names[n]] = { + 'Offset': read_u64(self.f, PFS0_HEADER_LENGTH + n * PFS0_FILE_ENTRY_LENGTH) + header_size, + 'Size': read_u64(self.f, PFS0_HEADER_LENGTH + n * PFS0_FILE_ENTRY_LENGTH + 0x8), + 'IsRepacked': True + } + self.filedata_size = sum(self.files[file]['Size'] for file in self.files) + self.size = self.filedata_size + PFS0_HEADER_LENGTH + + @classmethod + def new(cls, *files): + pfs0 = super().__new__(cls) + pfs0.f = None + pfs0.file_nb = 0 + pfs0.files = {} + pfs0.filedata_size = 0 + pfs0.add_files(*files) + return pfs0 + + def add_files(self, *args): + for f in args: + if f in self.files: + self.filedata_size -= self.files[f]['SIze'] + self.file_nb -= 1 + self.files[os.path.basename(f)] = { + 'Path': f, + 'Size': os.path.getsize(f), + 'Offset': self.filedata_size, + 'IsRepacked': False + } + self.filedata_size += os.path.getsize(f) + self.file_nb += 1 + + def open(self, fn): + assert fn in self.files, 'Couldn\'t find %s in PFS0' % fn + if self.files[fn]['IsRepacked']: + return self.FileinPFS0(self, fn) + else: + return open(self.files[fn]['Path'], 'rb') + + def extract(self, dest=None, disp=True): + if dest is None: + dest = os.path.join(os.path.dirname(__file__), os.path.splitext(self.f.name)[0]) + assert not os.path.isfile(dest), 'Output path is a file' + os.makedirs(dest, exist_ok=True) + + for file in self.files: + if disp: + print('Extracting %s...' % file) + inf = self.open(file) + outf = open(os.path.join(dest, file), 'wb') + while True: + buf = inf.read(0x10000) + if not buf: + break + outf.write(buf) + inf.close() + outf.close() + if disp: + print('Extracted to %s' % dest) + + def _gen_header(self): + file_nb = len(self.files) + string_table = b'\x00'.join(file.encode() for file in self.files) + header_length = align_to(PFS0_HEADER_LENGTH + file_nb * PFS0_FILE_ENTRY_LENGTH + len(string_table), 0x10) + string_table_length = header_length - (PFS0_HEADER_LENGTH + file_nb * PFS0_FILE_ENTRY_LENGTH) + + file_sizes = [self.files[file]['Size'] for file in self.files] + file_offsets = [sum(file_sizes[:n]) for n in range(file_nb)] + + file_names_lengths = [len(file) + 1 for file in self.files] + string_table_offsets = [sum(file_names_lengths[:n]) for n in range(file_nb)] + + header = b'' + header += b'PFS0' + header += pk_u32(file_nb) + header += pk_u32(string_table_length) + header += b'\x00\x00\x00\x00' + for n in range(file_nb): + header += pk_u64(file_offsets[n]) + header += pk_u64(file_sizes[n]) + header += pk_u32(string_table_offsets[n]) + header += b'\x00\x00\x00\x00' + header += string_table + header = pad_to(header, length=header_length) + return header + + def _buffered_repack(self, disp=True): + yield self._gen_header() + for file in self.files: + if disp: + print('Appending %s...' % file) + inf = self.open(file) + while True: + buf = inf.read(0x10000) + if not buf: + break + yield buf + inf.close() + + def repack(self, dest, disp=True): + if (self.f is not None) and (dest == self.f.name): + raise FileExistsError('Can\'t repack PFS0 to its original location') + with open(dest, 'wb') as outf: + for data in self._buffered_repack(disp=disp): + outf.write(data) + if disp: + print('Repacked to %s' % dest) + + class FileinPFS0(FileInContainer): + def __init__(self, pfs0, name): + offset = pfs0.files[name]['Offset'] + size = pfs0.files[name]['Size'] + super(PFS0.FileinPFS0, self).__init__(pfs0.f, offset, size) + + +class PFS0Superblock: + def __init__(self, block): + self.master_hash = read_at(block, 0x0, 0x20) + self.block_size = read_u32(block, 0x20) + if read_u32(block, 0x24) != 2: # Always 2 + print('[WARN] %d should be 2' % read_u32(block, 0x24)) + self.hash_table_offset = read_u64(block, 0x28) + self.hash_table_size = read_u64(block, 0x30) + self.offset = read_u64(block, 0x38) + self.size = read_u64(block, 0x40) + + def gen(self): + block = b'' + block += self.master_hash + block += pk_u32(self.block_size) + block += pk_u32(0x2) + block += pk_u64(self.hash_table_offset) + block += pk_u64(self.hash_table_size) + block += pk_u64(self.offset) + block += pk_u64(self.size) + block += 0xF0 * b'\0' + return block + +class HashTableWrappedPFS0(PFS0): + def __init__(self, superblock, fp, verify=False): + self.superblock = superblock + self.f = fp + + self.master_hash = self.superblock.master_hash + self.hash_table_offset = self.superblock.hash_table_offset + self.hash_table_size = self.superblock.hash_table_size + self.block_size = self.superblock.block_size + self.pfs0_offset = self.superblock.offset + self.pfs0_size = self.superblock.size + + if verify: + if self.verify(): + self.is_valid = True + else: + self.is_valid = False + print('[WARN] Invalid PFS0 hash table') + + super(HashTableWrappedPFS0, self).__init__(FileInContainer(self.f, self.pfs0_offset, self.pfs0_size)) + + def _get_hash(self, block_nb): + self.f.seek(0x20 * block_nb) + return self.f.read(0x20) + + def _verify_block_hash(self, ref_hash, block): + return ref_hash == sha256(block) + + def verify(self): + self.f.seek(self.hash_table_offset) + hash_table = self.f.read(self.hash_table_size) + if not self._verify_block_hash(self.master_hash, hash_table): + return False + + read = 0 + block_offsets = (self.pfs0_offset + self.block_size * _ for _ in range(ceil(self.pfs0_size / self.block_size))) + for i, block_offset in enumerate(block_offsets): + self.f.seek(block_offset) + if self.pfs0_size - read > self.block_size: + block = self.f.read(self.block_size) + else: + block = self.f.read(self.pfs0_size - read) + read += len(block) + + ref_hash = self._get_hash(i) + if not self._verify_block_hash(ref_hash, block): + return False + return True + + def _gen_hash_table(self): + hash_table = b'' + data_to_hash = b'' + for buf in super(HashTableWrappedPFS0, self)._buffered_repack(disp=False): + data_to_hash += buf + while len(data_to_hash) >= self.block_size: + hash_table += sha256(data_to_hash[:self.block_size]) + data_to_hash = data_to_hash[self.block_size:] + if data_to_hash: + hash_table += sha256(data_to_hash) + self.hash_table_size = len(hash_table) + return hash_table + + def _get_tot_size(self): + pfs0_size = align_to(self.size, self.block_size) + return pfs0_size + align_to(pfs0_size // self.block_size * 0x20, self.block_size) + + def _buffered_repack(self, disp=True): + yield self._gen_hash_table() + yield (align_to(self.hash_table_size, self.block_size) - self.hash_table_size) * b'\0' + written = 0 + for buf in super(HashTableWrappedPFS0, self)._buffered_repack(disp=disp): + written += len(buf) + yield buf + yield (align_to(written, self.block_size) - written) * b'\0' + + def repack(self, dest=None, disp=True): + if dest == self.f.name: + raise FileExistsError('Can\'t repack PFS0 to its original location') + with open(dest, 'wb') as outf: + for data in self._buffered_repack(disp=disp): + outf.write(data) + if disp: + print('Repacked to %s' % dest) + \ No newline at end of file diff --git a/py/ztools/Fs/pyRomFS.py b/py/ztools/Fs/pyRomFS.py new file mode 100644 index 00000000..94cf8d19 --- /dev/null +++ b/py/ztools/Fs/pyRomFS.py @@ -0,0 +1,648 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +import os +import io +from math import ceil +from Crypto.Hash import SHA256 +from Utils import ( + read_at, read_u32, read_u64, pk_u32, pk_u64, align_to, + bytes2human, pad_to, FileInContainer +) +from CryptoUtils import sha256 + +ROMFS_HEADER_LENGTH = 0x50 +ROMFS_FILEPARTITION_OFS = 0x200 +ROMFS_ENTRY_EMPTY = 0xFFFFFFFF + +class HashTable: + def __init__(self, entry_count): + self.entry_count = entry_count + self.data = entry_count * [pk_u32(ROMFS_ENTRY_EMPTY)] + + def add(self, entry, entry_table): + assert isinstance(entry, DirEntry) or isinstance(entry, FileEntry), 'Invalid entry type' + index = entry.hash % self.entry_count + if self.data[index] != ROMFS_ENTRY_EMPTY: + entry_table.data[-1]['PreviousHash'] = self.data[index] + self.data[index] = pk_u32(entry.offsets['Self']) + + def get_table(self): + return b''.join(_ for _ in self.data) + +class EntryTable: + def __init__(self, entry_type, entry_nb): + if (entry_type != DirEntry) and (entry_type != FileEntry): + raise TypeError('Invalid entry type') + self.entry_type = entry_type + self.entry_nb = entry_nb + self.data = [] + + def add(self, entry): + assert isinstance(entry, self.entry_type), 'Invalid entry type' + if (len(self.data) <= self.entry_nb) and (self.entry_type is DirEntry): + self.data.append({ + 'Parent': pk_u32(entry.offsets['Parent']), + 'Sibling': pk_u32(entry.offsets['Sibling']), + 'Child': pk_u32(entry.offsets['Child']), + 'File': pk_u32(entry.offsets['File']), + 'PreviousHash': pk_u32(ROMFS_ENTRY_EMPTY), + 'NameLength': pk_u32(len(entry.name)), + 'Name': pad_to(entry.name.encode(), multiple=4) + }) + elif len(self.data) <= self.entry_nb: + self.data.append({ + 'Parent': pk_u32(entry.offsets['Parent']), + 'Sibling': pk_u32(entry.offsets['Sibling']), + 'DataOffset': pk_u64(entry.offsets['Data']), + 'Size': pk_u64(entry.offsets['Size']), + 'PreviousHash': pk_u32(ROMFS_ENTRY_EMPTY), + 'NameLength': pk_u32(len(entry.name)), + 'Name': pad_to(entry.name.encode(), multiple=4) + }) + else: + raise ValueError('Table is already filled up') + + def get_table(self): + table = b'' + for d in self.data: + table += b''.join(d[_] for _ in d) + return table + +class DirEntry: + ROMFS_DIRENTRY_LENGTH = 0x18 + def __init__(self, parent, name): + if isinstance(name, bytes): + name = name.decode() + self.parent = parent + self.prev = None + self.next = None + self.name = name + self.hash = 0 + if parent is None: + self.path_in_romfs = 'romfs:' + else: + self.path_in_romfs = '/'.join((parent.path_in_romfs, name)) + self.offsets = { + 'Self': 0, + 'Parent': 0, + 'Sibling': ROMFS_ENTRY_EMPTY, + 'Child': ROMFS_ENTRY_EMPTY, + 'File': ROMFS_ENTRY_EMPTY + } + self.childs = [] + self.files = [] + + def __getattribute__(self, attr): + return object.__getattribute__(self, attr) + +class FileEntry: + ROMFS_FILEENTRY_LENGTH = 0x20 + def __init__(self, parent, name, size): + if isinstance(name, bytes): + name = name.decode() + self.parent = parent + self.prev = None + self.next = None + self.name = name + self.path_in_romfs = '/'.join((parent.path_in_romfs, name)) + self.size = size + self.hash = 0 + self.offsets = { + 'Self': 0, + 'Parent': 0, + 'Sibling': ROMFS_ENTRY_EMPTY, + 'Data': 0, + 'Size': 0 + } + self.offset_in_romfs_data = 0 + self.is_repacked = False + + def __getattribute__(self, attr): + return object.__getattribute__(self, attr) + +class RomFS: + '''Class to manipulate RomFS files''' + def __init__(self, fp): + self.f = fp + self._parse() + + def __str__(self): + return 'romfs:' + self._print_dir(self.root, lvl=1) + + def _print_dir(self, dir, lvl=0, char=' '): + tab = lvl * char + string = '' + for child in dir.childs: + string += '\n' + tab + child.name + string += self._print_dir(child, lvl + 1, char) + for file in dir.files: + string += '\n' + tab + bytes2human(file.size) + '\t' + file.name + return string + + def _parse(self): + if read_u64(self.f, 0x0) != 0x50: + print('[WARN] RomFS haader size isn\'t 0x50') + self.dir_table = io.BytesIO(read_at(self.f, read_u64(self.f, 0x18), read_u64(self.f, 0x20))) + self.file_table = io.BytesIO(read_at(self.f, read_u64(self.f, 0x38), read_u64(self.f, 0x40))) + self.data_offset = read_u64(self.f, 0x48) + if self.data_offset != ROMFS_FILEPARTITION_OFS: + print('[WARN] Data offset isn\'t 0x200') + + self.first_file = None + _, self.root = self._parse_dir(0, None) + self._gen_metadata() + + def _parse_dir(self, offset, parent): + cur_dir_dict = { + 'Parent': read_u32(self.dir_table, offset + 0x0), + 'Sibling': read_u32(self.dir_table, offset + 0x4), + 'Child': read_u32(self.dir_table, offset + 0x8), + 'File': read_u32(self.dir_table, offset + 0xC), + 'Name': read_at(self.dir_table, offset + 0x18, read_u32(self.dir_table, offset + 0x14)).decode() + } + dir_entry = DirEntry(parent, cur_dir_dict['Name']) + + if cur_dir_dict['Child'] != ROMFS_ENTRY_EMPTY: # if not cur_dir_dict['Child'] + 1 & 1 << 32: + child_dict, child_entry = self._parse_dir(cur_dir_dict['Child'], dir_entry) + dir_entry.childs.append(child_entry) + while child_dict['Sibling'] != ROMFS_ENTRY_EMPTY: + child_dict, child_entry = self._parse_dir(child_dict['Sibling'], dir_entry) + dir_entry.childs.append(child_entry) + if cur_dir_dict['File'] != ROMFS_ENTRY_EMPTY: + file_dict, file_entry = self._parse_file(cur_dir_dict['File'], dir_entry) + dir_entry.files.append(file_entry) + while file_dict['Sibling'] != ROMFS_ENTRY_EMPTY: + file_dict, file_entry = self._parse_file(file_dict['Sibling'], dir_entry) + dir_entry.files.append(file_entry) + return cur_dir_dict, dir_entry + + def _parse_file(self, offset, parent): + file_dict = { + 'Parent': read_u32(self.file_table, offset + 0x0), + 'Sibling': read_u32(self.file_table, offset + 0x4), + 'DataOffset': read_u64(self.file_table, offset + 0x8), + 'Size': read_u32(self.file_table, offset + 0x10), + 'Name': read_at(self.file_table, offset + 0x20, read_u32(self.file_table, offset + 0x1C)).decode() + } + file_entry = FileEntry(parent, file_dict['Name'], file_dict['Size']) + file_entry.offset_in_romfs_data = file_dict['DataOffset'] + file_entry.is_repacked = True + return file_dict, file_entry + + def _gen_metadata(self): + self.dir_table_size = 0x18 # root + self.dir_nb = 1 # root + self.file_nb = 0 + self.data_size = self.file_table_size = self.dir_table_size = 0 + self.prev_dir = self.prev_file = None + self._gen_dir_metadata(self.root) + del self.prev_dir, self.prev_file + + self.first_file = self._find_first_file() + self.dir_hash_table_entry_count = self._calc_hash_table_entry_count(self.dir_nb) + self.file_hash_table_entry_count = self._calc_hash_table_entry_count(self.file_nb) + self.size = self.data_size + self.file_table_size + self.dir_table_size +\ + 0x4 * (self.dir_hash_table_entry_count + self.file_hash_table_entry_count) + + + def _gen_dir_metadata(self, dir_entry): + if self.prev_dir is not None: + self.prev_dir.next = dir_entry + dir_entry.prev, self.prev_dir = self.prev_dir, dir_entry + + dir_entry.hash = self._calc_path_hash(dir_entry.offsets['Parent'], dir_entry.name.encode()) + + for child in dir_entry.childs: + self.dir_nb += 1 + if dir_entry.offsets['Child'] == ROMFS_ENTRY_EMPTY: + dir_entry.offsets['Child'] = self.dir_table_size + child.offsets['Parent'] = dir_entry.offsets['Self'] + child.offsets['Self'] = self.dir_table_size + self.dir_table_size += DirEntry.ROMFS_DIRENTRY_LENGTH + align_to(len(child.name), 4) + self._gen_dir_metadata(child) + if child != dir_entry.childs[-1]: + child.offsets['Sibling'] = self.dir_table_size + else: + child.offsets['Sibling'] = ROMFS_ENTRY_EMPTY + for file_entry in dir_entry.files: + self.file_nb += 1 + if self.prev_file is not None: + self.prev_file.next = file_entry + file_entry.prev, self.prev_file = self.prev_file, file_entry + + if dir_entry.offsets['File'] == ROMFS_ENTRY_EMPTY: + dir_entry.offsets['File'] = self.file_table_size + file_entry.offsets['Parent'] = dir_entry.offsets['Self'] + file_entry.offsets['Self'] = self.file_table_size + self.file_table_size += FileEntry.ROMFS_FILEENTRY_LENGTH + align_to(len(file_entry.name), 4) + if file_entry != dir_entry.files[-1]: + file_entry.offsets['Sibling'] = self.file_table_size + else: + file_entry.offsets['Sibling'] = ROMFS_ENTRY_EMPTY + + file_entry.hash = self._calc_path_hash(file_entry.offsets['Parent'], file_entry.name.encode()) + + file_entry.offsets['Size'] = file_entry.size + file_entry.offsets['Data'] = self.data_size + self.data_size += align_to(file_entry.size, 0x10) + + def _calc_path_hash(self, parent_offset, name): + # https://www.3dbrew.org/wiki/RomFS#Hash_Table_Structure + hash = parent_offset ^ 123456789 + for c in name: + hash = (hash >> 5) | (hash << 27) + hash &= (1 << 32) - 1 # Reproduce uint32 overflow + hash ^= c + return hash + + def _find_first_file(self): + cur_dir = self.root + while cur_dir.childs: + cur_dir = cur_dir.childs[0] + while not cur_dir.files: + cur_dir = cur_dir.next + return cur_dir.files[0] + + @classmethod + def new(cls, *dirs): + romfs = super().__new__(cls) + romfs.f = None + romfs.data_offset = 0x200 + romfs.root = DirEntry(None, '') + romfs.add_dirs(*dirs) + return romfs + + def add_dirs(self, *args): + for arg in args: + self._add_dir(arg, self.root) + self._gen_metadata() + + def _add_dir(self, parent, parent_entry): + if not (os.path.exists(parent) and os.path.isdir(parent)): + raise ValueError('Wrong argument %s (doesn\'t exist, or is not a directory)' % parent) + if not os.listdir(parent): + raise Exception('Directory %s is empty' % parent) + for item in os.listdir(parent): + if os.path.isdir(os.path.join(parent, item)): + child_entry = DirEntry(parent_entry, item) + if parent_entry.childs: + for child in parent_entry.childs: + if child.name == item: + child_entry = child # Merge the existing dir with the new one + break + elif child == parent_entry.childs[-1]: + parent_entry.childs = self._ordered_insertion(child_entry, parent_entry.childs) + break # List is modified, need to break out of it + else: + parent_entry.childs = self._ordered_insertion(child_entry, parent_entry.childs) + self._add_dir(os.path.join(parent, item), child_entry) + + elif os.path.isfile(os.path.join(parent, item)): + file_entry = FileEntry(parent_entry, item, os.path.getsize(os.path.join(parent, item))) + file_entry.path = os.path.join(parent, item) + if parent_entry.files: + for file in parent_entry.files: + if file.name == item: + index = parent_entry.files.index(file) + parent_entry.files[index] = file_entry # Replace the old file with the new one + break + elif file == parent_entry.files[-1]: + parent_entry.files = self._ordered_insertion(file_entry, parent_entry.files) + break + else: + parent_entry.files = self._ordered_insertion(file_entry, parent_entry.files) + + def _ordered_insertion(self, item, list): + list.append(item) + return sorted(list, key=lambda x: x.name) + + def open(self, arg): + if isinstance(arg, FileEntry): # Entry is provided + entry = arg + else: # Find the entry given its path + arg = arg.replace('\\', '/') + cur_file = self.first_file + while cur_file.path_in_romfs != arg: + if cur_file.next is None: + raise FileNotFoundError('Can\'t find file in romfs') + cur_file = cur_file.next + entry = cur_file + + if entry.is_repacked: + return self.FileInRomFS(self, entry) + elif entry: + return open(entry.path, 'rb') + else: + raise ValueError('Nothing to open') + + def extract(self, dest=None, disp=True): + if dest is None: + dest = os.path.join(os.path.dirname(__file__), os.path.splitext(self.f.name)[0]) + assert not os.path.isfile(dest), 'Output path is a file' + os.makedirs(dest, exist_ok=True) + + cur_file = self.first_file + while cur_file is not None: + if disp: + print('Extracting %s...' % cur_file.path_in_romfs) + path = os.path.join(dest, cur_file.path_in_romfs.replace('romfs:/', '')) + os.makedirs(os.path.dirname(path), exist_ok=True) + inf = self.open(cur_file) + outf = open(path, 'wb') + while True: + buf = inf.read(0x10000) + if not buf: + break + outf.write(buf) + inf.close() + outf.close() + cur_file = cur_file.next + if disp: + print('Extracted to %s' % dest) + + def _calc_hash_table_entry_count(self, entries_nb): + # https://www.3dbrew.org/wiki/RomFS#Hash_Table_Structure + count = entries_nb + if entries_nb < 3: + count = 3 + elif entries_nb < 19: + count |= 1 + else: + while (count % 2 == 0) or (count % 3 == 0) or (count % 5 == 0) or (count % 7 == 0)\ + or (count % 11 == 0) or (count % 13 == 0) or (count % 17 == 0): + count += 1 + return count + + def _gen_footer(self): + dir_hash_table = HashTable(self.dir_hash_table_entry_count) + dir_table = EntryTable(DirEntry, self.dir_nb) + cur_dir = self.root + while cur_dir is not None: + dir_table.add(cur_dir) + dir_hash_table.add(cur_dir, dir_table) + cur_dir = cur_dir.next + dir_hash_table = dir_hash_table.get_table() + self.dir_table = dir_table.get_table() + + file_hash_table = HashTable(self.file_hash_table_entry_count) + file_table = EntryTable(FileEntry, self.file_nb) + cur_file = self.first_file + while cur_file is not None: + file_table.add(cur_file) + file_hash_table.add(cur_file, file_table) + cur_file = cur_file.next + file_hash_table = file_hash_table.get_table() + self.file_table = file_table.get_table() + return dir_hash_table + self.dir_table + file_hash_table + self.file_table + + def _gen_header(self): + dir_hash_table_offset = ROMFS_FILEPARTITION_OFS + self.data_size + dir_hash_table_length = 4 * self.dir_hash_table_entry_count + dir_table_offset = dir_hash_table_offset + dir_hash_table_length + dir_table_length = len(self.dir_table) + file_hash_table_offset = dir_table_offset + len(self.dir_table) + file_hash_table_length = 4 * self.file_hash_table_entry_count + file_table_offset = file_hash_table_offset + file_hash_table_length + file_table_length = len(self.file_table) + + header = b'' + header += pk_u64(ROMFS_HEADER_LENGTH) + header += pk_u64(dir_hash_table_offset) + header += pk_u64(dir_hash_table_length) + header += pk_u64(dir_table_offset) + header += pk_u64(dir_table_length) + header += pk_u64(file_hash_table_offset) + header += pk_u64(file_hash_table_length) + header += pk_u64(file_table_offset) + header += pk_u64(file_table_length) + header += pk_u64(ROMFS_FILEPARTITION_OFS) + header = pad_to(header, length=ROMFS_FILEPARTITION_OFS) + return header + + def _get_metadata(self): + # In that order + footer = self._gen_footer() + header = self._gen_header() + return header, footer + + def _buffered_repack(self, disp): + header, footer = self._get_metadata() + + yield header + cur_file = self.first_file + while cur_file is not None: + if disp: + print('Appending %s...' % cur_file.path_in_romfs) + inf = self.open(cur_file) + while True: + buf = inf.read(0x10000) + if not buf: + break + yield pad_to(buf, multiple=0x10) + inf.close() + cur_file = cur_file.next + yield footer + + def repack(self, dest, disp=True): + if (self.f is not None) and (dest == self.f.name): + raise FileExistsError('Can\'t repack RomFS to its original location') + with open(dest, 'wb') as outf: + for data in self._buffered_repack(disp=disp): + outf.write(data) + if disp: + print('Repacked to %s' % dest) + + class FileInRomFS(FileInContainer): + def __init__(self, romfs, file_entry): + offset = file_entry.offset_in_romfs_data + ROMFS_FILEPARTITION_OFS + size = file_entry.size + super(RomFS.FileInRomFS, self).__init__(romfs.f, offset, size) + + + +class IVFCSuperblock: + IVFC_MAX_LEVEL = 6 + def __init__(self, block): + if read_at(block, 0x0, 0x4) != b'IVFC': + print('[WARN] Invalid IVFC magic') + if read_u32(block, 0x4) != 0x20000: + print('[WARN] Invalid 0x20000 IVFC magic') + self.master_hash_size = read_u32(block, 0x8) + self.nb_lvl = read_u32(block, 0xC) + + self.lvls = [] + for n in range(self.IVFC_MAX_LEVEL): + lvl = io.BytesIO(read_at(block, 0x10 + n*0x18, 0x18)) + self.lvls.append(self.IVFClvl(lvl)) + self.master_hash = read_at(block, 0xC0, self.master_hash_size) + + def gen(self): + block = b'' + block += b'IVFC' + block += pk_u32(0x20000) + block += pk_u32(self.master_hash_size) + block += pk_u32(self.IVFC_MAX_LEVEL+1) # Nb of levels + block += b''.join(lvl.gen() for lvl in self.lvls) + block += 0x20 * b'\0' + block += self.master_hash + block += 0x58 * b'\0' + return block + + class IVFClvl: + def __init__(self, block): + self.offset = read_u64(block, 0x0) + self.size = read_u64(block, 0x8) + self.block_size = 1 << read_u32(block, 0x10) # In log2 in the header + + def gen(self): + from math import log2 + lvl = b'' + lvl += pk_u64(self.offset) + lvl += pk_u64(self.size) + lvl += pk_u32(int(log2(self.block_size))) + lvl += 0x4 * b'\0' + return lvl + +class HashTreeWrappedRomFS(RomFS): + def __init__(self, section_header, fp, verify=False): + self.section_header = section_header + self.f = fp + + self.romfs_offset = self.section_header.lvls[IVFCSuperblock.IVFC_MAX_LEVEL-1].offset + self.romfs_size = self.section_header.lvls[IVFCSuperblock.IVFC_MAX_LEVEL-1].size + + if verify: + if self.verify(): + self.is_valid = True + else: + self.is_valid = False + print('[WARN] Invalid IVFC hash tree') + + super(HashTreeWrappedRomFS, self).__init__(FileInContainer(self.f, self.romfs_offset, self.romfs_size)) + + def _get_tot_size(self): + tot = cur_lvl_size = align_to(self.size, self.section_header.lvls[-1].block_size) + for n in reversed(range(IVFCSuperblock.IVFC_MAX_LEVEL-1)): + cur_lvl_size = align_to(cur_lvl_size // self.section_header.lvls[n].block_size * 0x20, self.section_header.lvls[n-1].block_size) + tot += cur_lvl_size + return tot + + def _get_hash(self, lvl, block_nb): + self.f.seek(self.section_header.lvls[lvl].offset + 0x20 * block_nb) + return self.f.read(0x20) + + def _verify_block_hash(self, ref_hash, block, block_size): + h = SHA256.new(block) + if len(block) != block_size: + h.update((block_size - len(block)) * b'\0') + return ref_hash == h.digest() + + def verify(self): + for lvl in range(IVFCSuperblock.IVFC_MAX_LEVEL): + lvl_offset = self.section_header.lvls[lvl].offset + lvl_size = self.section_header.lvls[lvl].size + block_size = self.section_header.lvls[lvl].block_size + if lvl == 0: + self.f.seek(lvl_offset) + block = self.f.read(block_size) + + ref_hash = self.section_header.master_hash + if not self._verify_block_hash(ref_hash, block, block_size): + return False + continue + + read = 0 + block_offsets = (lvl_offset + block_size * _ for _ in range(ceil(lvl_size / block_size))) + for i, block_offset in enumerate(block_offsets): + self.f.seek(block_offset) + if lvl_size - read > block_size: + block = self.f.read(block_size) + else: + block = self.f.read(lvl_size - read) + read += len(block) + + ref_hash = self._get_hash(lvl-1, i) + if not self._verify_block_hash(ref_hash, block, block_size): + return False + return True + + def _gen_hash_table(self, data_to_hash, block_size=0x4000): + hash_table = b'' + n = 0 + while True: + block = data_to_hash[n * block_size : (n + 1) * block_size] + if not block: + break + hash_table += sha256(pad_to(block, multiple=block_size)) + n += 1 + return hash_table + + def _gen_hash_tree(self, disp=True): + # If we want to yield the section in the right order, + # we have no choice but to completely hash the romfs beforehand + if disp: + print('Hashing RomFS...') + lvl_5 = b'' + data_to_hash = b'' + block_size = self.section_header.lvls[5].block_size + for buf in super(HashTreeWrappedRomFS, self)._buffered_repack(disp=False): + data_to_hash += buf + while len(data_to_hash) >= block_size: + lvl_5 += sha256(data_to_hash[:block_size]) + data_to_hash = data_to_hash[block_size:] + if data_to_hash: + lvl_5 += sha256(pad_to(data_to_hash, multiple=block_size)) + self.lvl_5_size = len(lvl_5) + + if disp: + print('Hashing levels...') + lvl_4 = self._gen_hash_table(lvl_5, block_size=self.section_header.lvls[4].block_size) + self.lvl_4_size = len(lvl_4) + lvl_3 = self._gen_hash_table(lvl_4, block_size=self.section_header.lvls[3].block_size) + self.lvl_3_size = len(lvl_3) + lvl_2 = self._gen_hash_table(lvl_3, block_size=self.section_header.lvls[2].block_size) + self.lvl_2_size = len(lvl_2) + lvl_1 = self._gen_hash_table(lvl_2, block_size=self.section_header.lvls[1].block_size) + self.lvl_1_size = len(lvl_1) + self.master_hash = self._gen_hash_table(lvl_1, block_size=blself.section_header.lvls[0].block_sizeock_size) + + return lvl_1, lvl_2, lvl_3, lvl_4, lvl_5 + + @classmethod + def new(cls, *files): + super().new(*files) + self.size = self._get_size() + + def _buffered_repack(self, block_size=0x4000, disp=True): + lvl_1, lvl_2, lvl_3, lvl_4, lvl_5 = self._gen_hash_tree(block_size=block_size, disp=disp) + + if disp: + print('Writing IVFC levels...') + yield lvl_1 + yield (align_to(self.lvl_1_size, block_size) - self.lvl_1_size) * b'\0' + yield lvl_2 + yield (align_to(self.lvl_2_size, block_size) - self.lvl_2_size) * b'\0' + yield lvl_3 + yield (align_to(self.lvl_3_size, block_size) - self.lvl_3_size) * b'\0' + yield lvl_4 + yield (align_to(self.lvl_4_size, block_size) - self.lvl_4_size) * b'\0' + yield lvl_5 + yield (align_to(self.lvl_5_size, block_size) - self.lvl_5_size) * b'\0' + + if disp: + print('Writing RomFS...') + written = 0 + for buf in super(HashTreeWrappedRomFS, self)._buffered_repack(disp=disp): + written += len(buf) + yield buf + yield (align_to(written, block_size) - written) * b'\0' + + def repack(self, dest, disp=True): + if dest == self.f.name: + raise FileExistsError('Can\'t repack RomFS to its original location') + with open(dest, 'wb') as outf: + for data in self._buffered_repack(disp=disp): + outf.write(data) + if disp: + print('Repacked to %s' % dest) diff --git a/py/ztools/JOINER.bat b/py/ztools/JOINER.bat new file mode 100644 index 00000000..185663fb --- /dev/null +++ b/py/ztools/JOINER.bat @@ -0,0 +1,297 @@ +@ECHO OFF +:TOP_INIT +CD /d "%prog_dir%" + +REM ////////////////////////////////////////////////// +REM ///////////////////////////////////////////////// +REM FILE JOINER +REM ///////////////////////////////////////////////// +REM //////////////////////////////////////////////// +:normalmode +cls +call :program_logo +echo ------------------------------------------------- +echo FILE JOINER ACTIVATED +echo ------------------------------------------------- +if exist "joinlist.txt" goto prevlist +goto manual_INIT +:prevlist +set conta=0 +for /f "tokens=*" %%f in (joinlist.txt) do ( +echo %%f +) >NUL 2>&1 +setlocal enabledelayedexpansion +for /f "tokens=*" %%f in (joinlist.txt) do ( +set /a conta=!conta! + 1 +) >NUL 2>&1 +if !conta! LEQ 0 ( del joinlist.txt ) +endlocal +if not exist "joinlist.txt" goto manual_INIT +ECHO ....................................................... +ECHO A PREVIOUS LIST WAS FOUND. WHAT DO YOU WANT TO DO? +:prevlist0 +ECHO ....................................................... +echo Input "1" to auto-start processing from the previous list +echo Input "2" to erase list and make a new one. +echo Input "3" to continue building the previous list +echo ....................................................... +echo NOTE: By pressing 3 you'll see the previous list +echo before starting the processing the files and you will +echo be able to add and delete items from the list +echo. +ECHO ************************************************* +echo Or Input "0" to return to the MODE SELECTION MENU +ECHO ************************************************* +echo. +set /p bs="Enter your choice: " +set bs=%bs:"=% +if /i "%bs%"=="3" goto showlist +if /i "%bs%"=="2" goto delist +if /i "%bs%"=="1" goto start_cleaning +if /i "%bs%"=="0" exit /B +echo. +echo BAD CHOICE +goto prevlist0 +:delist +del joinlist.txt +cls +call :program_logo +echo ------------------------------------------------- +echo FILE JOINER ACTIVATED +echo ------------------------------------------------- +echo .................................. +echo YOU'VE DECIDED TO START A NEW LIST +echo .................................. + +:manual_INIT +endlocal +ECHO *********************************************** +echo Input "0" to return to the MODE SELECTION MENU +ECHO *********************************************** +echo. +%pycommand% "%nut%" -t ns0 xc0 00 -tfile "%prog_dir%joinlist.txt" -uin "%uinput%" -ff "uinput" +set /p eval=<"%uinput%" +set eval=%eval:"=% +setlocal enabledelayedexpansion +echo+ >"%uinput%" +endlocal +if /i "%eval%"=="0" exit /B +goto checkagain +echo. +:checkagain +echo WHAT DO YOU WANT TO DO? +echo ...................................................................... +echo "DRAG ANOTHER FILE OR FOLDER AND PRESS ENTER TO ADD ITEMS TO THE LIST" +echo. +echo Input "1" to start processing +echo Input "e" to exit +echo Input "i" to see list of files to process +echo Input "r" to remove some files (counting from bottom) +echo Input "z" to remove the whole list +echo ...................................................................... +ECHO ************************************************* +echo Or Input "0" to return to the MODE SELECTION MENU +ECHO ************************************************* +echo. +%pycommand% "%nut%" -t nsp xci -tfile "%prog_dir%joinlist.txt" -uin "%uinput%" -ff "uinput" +set /p eval=<"%uinput%" +set eval=%eval:"=% +setlocal enabledelayedexpansion +echo+ >"%uinput%" +endlocal + +if /i "%eval%"=="0" exit /B +if /i "%eval%"=="1" goto start_cleaning +if /i "%eval%"=="e" goto salida +if /i "%eval%"=="i" goto showlist +if /i "%eval%"=="r" goto r_files +if /i "%eval%"=="z" del joinlist.txt + +goto checkagain + +:r_files +set /p bs="Input the number of files you want to remove (from bottom): " +set bs=%bs:"=% + +setlocal enabledelayedexpansion +set conta= +for /f "tokens=*" %%f in (joinlist.txt) do ( +set /a conta=!conta! + 1 +) + +set /a pos1=!conta!-!bs! +set /a pos2=!conta! +set string= + +:update_list1 +if !pos1! GTR !pos2! ( goto :update_list2 ) else ( set /a pos1+=1 ) +set string=%string%,%pos1% +goto :update_list1 +:update_list2 +set string=%string%, +set skiplist=%string% +Set "skip=%skiplist%" +setlocal DisableDelayedExpansion +(for /f "tokens=1,*delims=:" %%a in (' findstr /n "^" ^&1>NUL ||Echo=%%b +)>joinlist.txt.new +endlocal +move /y "joinlist.txt.new" "joinlist.txt" >nul +endlocal + +:showlist +cls +call :program_logo +echo ------------------------------------------------- +echo FILE JOINER ACTIVATED +echo ------------------------------------------------- +ECHO ------------------------------------------------- +ECHO FILES TO PROCESS +ECHO ------------------------------------------------- +for /f "tokens=*" %%f in (joinlist.txt) do ( +echo %%f +) +setlocal enabledelayedexpansion +set conta= +for /f "tokens=*" %%f in (joinlist.txt) do ( +set /a conta=!conta! + 1 +) +echo ................................................. +echo YOU'VE ADDED !conta! FILES TO PROCESS +echo ................................................. +endlocal + +goto exit /B + +:s_cl_wrongchoice +echo wrong choice +echo ............ +:start_cleaning +echo ******************************************************* +echo CHOOSE HOW TO PROCESS THE FILES +echo ******************************************************* +echo Input "1" to join .xc*,.ns*,.0* files +echo. +ECHO ****************************************** +echo Or Input "b" to return to the list options +ECHO ****************************************** +echo. +set /p bs="Enter your choice: " +set bs=%bs:"=% +set vrepack=none +if /i "%bs%"=="b" goto checkagain +if /i "%bs%"=="1" goto joinfiles +if %vrepack%=="none" goto s_cl_wrongchoice + +:joinfiles +cls +call :program_logo +CD /d "%prog_dir%" +for /f "tokens=*" %%f in (joinlist.txt) do ( + +%pycommand% "%nut%" %buffer% -o "%fold_output%" -tfile "%prog_dir%joinlist.txt" --joinfile "" +if exist "%fold_output%output.nsp" ( %pycommand% "%nut%" -renf "%fold_output%output.nsp">"%prog_dir%nn") +if exist "%fold_output%output.xci" ( %pycommand% "%nut%" -renf "%fold_output%output.xci">"%prog_dir%nn") +if exist "%prog_dir%nn" del "%prog_dir%nn" +more +1 "joinlist.txt">"joinlist.txt.new" +move /y "joinlist.txt.new" "joinlist.txt" >nul +call :contador_NF +) +ECHO --------------------------------------------------- +ECHO *********** ALL FILES WERE PROCESSED! ************* +ECHO --------------------------------------------------- +goto s_exit_choice + +:s_exit_choice +if exist joinlist.txt del joinlist.txt +if /i "%va_exit%"=="true" echo PROGRAM WILL CLOSE NOW +if /i "%va_exit%"=="true" ( PING -n 2 127.0.0.1 >NUL 2>&1 ) +if /i "%va_exit%"=="true" goto salida +echo. +echo Input "0" to go back to the mode selection +echo Input "1" to exit the program +echo. +set /p bs="Enter your choice: " +set bs=%bs:"=% +if /i "%bs%"=="0" goto manual_Reentry +if /i "%bs%"=="1" goto salida +goto s_exit_choice + +:contador_NF +setlocal enabledelayedexpansion +set /a conta=0 +for /f "tokens=*" %%f in (joinlist.txt) do ( +set /a conta=!conta! + 1 +) +echo ................................................... +echo STILL !conta! FILES TO PROCESS +echo ................................................... +PING -n 2 127.0.0.1 >NUL 2>&1 +set /a conta=0 +endlocal +exit /B + + +:://///////////////////////////////////////////////// +::SUBROUTINES +:://///////////////////////////////////////////////// + +:squirrell +echo ,;:;;, +echo ;;;;; +echo .=', ;:;;:, +echo /_', "=. ';:;:; +echo @=:__, \,;:;:' +echo _(\.= ;:;;' +echo `"_( _/="` +echo `"' +exit /B + +:program_logo + +ECHO __ _ __ __ +ECHO ____ _____ ____ / /_ __ __(_) /___/ /__ _____ +ECHO / __ \/ ___/ ___/ / __ \/ / / / / / __ / _ \/ ___/ +ECHO / / / (__ ) /__ / /_/ / /_/ / / / /_/ / __/ / +ECHO /_/ /_/____/\___/____/_.___/\__,_/_/_/\__,_/\___/_/ +ECHO /_____/ +ECHO ------------------------------------------------------------------------------------- +ECHO NINTENDO SWITCH CLEANER AND BUILDER +ECHO (THE XCI MULTI CONTENT BUILDER AND MORE) +ECHO ------------------------------------------------------------------------------------- +ECHO ============================= BY JULESONTHEROAD ============================= +ECHO ------------------------------------------------------------------------------------- +ECHO " POWERED BY SQUIRREL " +ECHO " BASED IN THE WORK OF BLAWAR AND LUCA FRAGA " +ECHO VERSION 0.88 +ECHO ------------------------------------------------------------------------------------- +ECHO Program's github: https://github.com/julesontheroad/NSC_BUILDER +ECHO Blawar's github: https://github.com/blawar +ECHO Blawar's tinfoil: https://github.com/digableinc/tinfoil +ECHO Luca Fraga's github: https://github.com/LucaFraga +ECHO ------------------------------------------------------------------------------------- +exit /B + +:delay +PING -n 2 127.0.0.1 >NUL 2>&1 +exit /B + +:thumbup +echo. +echo /@ +echo \ \ +echo ___\ \ +echo (__O) \ +echo (____@) \ +echo (____@) \ +echo (__o)_ \ +echo \ \ +echo. +echo HOPE YOU HAVE A FUN TIME +exit /B + + +:salida +exit /B + + diff --git a/py/ztools/LEGACY.bat b/py/ztools/LEGACY.bat index a85fe960..209b568c 100644 --- a/py/ztools/LEGACY.bat +++ b/py/ztools/LEGACY.bat @@ -2,7 +2,7 @@ :TOP_INIT CD /d "%prog_dir%" set "bat_name=%~n0" -Title NSC_Builder v0.87.c -- Profile: %ofile_name% -- by JulesOnTheRoad +Title NSC_Builder v0.88 -- Profile: %ofile_name% -- by JulesOnTheRoad ::Check if user is dragging a folder or a file if "%~1"=="" goto manual @@ -2247,7 +2247,7 @@ ECHO ============================= BY JULESONTHEROAD =================== ECHO ------------------------------------------------------------------------------------- ECHO " POWERED BY SQUIRREL " ECHO " BASED IN THE WORK OF BLAWAR AND LUCA FRAGA " -ECHO VERSION 0.87 (LEGACY) +ECHO VERSION 0.88 (LEGACY) ECHO ------------------------------------------------------------------------------------- ECHO Program's github: https://github.com/julesontheroad/NSC_BUILDER ECHO Blawar's github: https://github.com/blawar diff --git a/py/ztools/NSCB_config.bat b/py/ztools/NSCB_config.bat index 93e23bdd..bd3de935 100644 --- a/py/ztools/NSCB_config.bat +++ b/py/ztools/NSCB_config.bat @@ -987,7 +987,7 @@ set skipRSVprompt="%skipRSVprompt%" %pycommand% "%listmanager%" -rl "%op_file%" -ln "108" -nl "Line in config was changed to: " REM buffer -set "v_buffer=buffer=-b 65536" +set "v_buffer=-b 65536" set v_buffer="buffer=%v_buffer%" set v_buffer="%v_buffer%" %pycommand% "%listmanager%" -cl "%op_file%" -ln "32" -nl "set %v_buffer%" @@ -1132,7 +1132,7 @@ ECHO ============================= BY JULESONTHEROAD =================== ECHO ------------------------------------------------------------------------------------- ECHO " POWERED BY SQUIRREL " ECHO " BASED IN THE WORK OF BLAWAR AND LUCA FRAGA " -ECHO VERSION 0.87 +ECHO VERSION 0.88 ECHO ------------------------------------------------------------------------------------- ECHO Program's github: https://github.com/julesontheroad/NSC_BUILDER ECHO Blawar's github: https://github.com/blawar diff --git a/py/ztools/hactool.exe b/py/ztools/hactool.exe new file mode 100644 index 00000000..87eb8355 Binary files /dev/null and b/py/ztools/hactool.exe differ diff --git a/py/ztools/info.bat b/py/ztools/info.bat index db8d46c7..f18d3f1b 100644 --- a/py/ztools/info.bat +++ b/py/ztools/info.bat @@ -29,9 +29,10 @@ echo Input "1" to get FILE LIST of the xci\nsp echo Input "2" to get CONTENT LIST of the xci\nsp echo Input "3" to get NUT-INFO of the xci\nsp echo Input "4" to get GAME-INFO and FW requirements -echo Input "5" to READ the CNMT of the xci\nsp -echo Input "6" to READ the NACP of the xci\nsp -echo Input "7" to VERIFY file (xci\nsp\nsx\nca) +echo Input "5" to READ the CNMT from the xci\nsp +echo Input "6" to READ the NACP from the xci\nsp +echo Input "7" to READ the main.NPDM from the xci\nsp +echo Input "8" to VERIFY file (xci\nsp\nsx\nca) echo. echo Input "b" to go back to FILE LOADING echo Input "0" to go back to the MAIN PROGRAM @@ -54,7 +55,8 @@ if /i "%bs%"=="3" goto n_info if /i "%bs%"=="4" goto f_info if /i "%bs%"=="5" goto r_cnmt if /i "%bs%"=="6" goto r_nacp -if /i "%bs%"=="7" goto verify +if /i "%bs%"=="7" goto r_npdm +if /i "%bs%"=="8" goto verify if /i "%bs%"=="b" goto sc1 if /i "%bs%"=="0" goto salida @@ -145,23 +147,35 @@ echo IMPLEMENTATION OF 0LIAM'S NACP LIBRARY %pycommand% "%nut%" -o "%info_dir%" --Read_nacp "%targt%" goto sc2 +:r_npdm +cls +call :logo +echo ******************************************************** +echo SHOW MAIN.NPDM DATA FROM PROGRAM NCA IN NSP\XCI +echo ******************************************************** +%pycommand% "%nut%" -o "%info_dir%" --Read_npdm "%targt%" +goto sc2 + + :verify cls call :logo echo ******************************************************** echo VERIFY A NSP\XCI\NCA echo ******************************************************** -%pycommand% "%nut%" -b %buffer% -o "%info_dir%" -v "%targt%" +%pycommand% "%nut%" %buffer% -o "%info_dir%" -v "%targt%" + goto sc2 :sc3 cls call :logo echo ....................................................... -echo Input "1" to get NUT-INFO of the xci\nsp -echo Input "2" to READ the CNMT of the xci\nsp -echo Input "3" to READ the NACP of the xci\nsp -echo Input "4" to VERIFY file (xci\nsp\nsx\nca) +echo Input "1" to get NUT-INFO of the NCA +echo Input "2" to READ the CNMT of a meta NCA +echo Input "3" to READ the NACP of a control NCA +echo Input "4" to READ the NPDM of a program NCA +echo Input "5" to VERIFY the NCA echo. echo Input "b" to go back to FILE LOADING echo Input "0" to go back to the MAIN PROGRAM @@ -181,7 +195,8 @@ if "%Extension%" EQU ".xci" ( goto snfi ) if /i "%bs%"=="1" goto n_info_nca if /i "%bs%"=="2" goto r_cnmt_nca if /i "%bs%"=="3" goto r_nacp_nca -if /i "%bs%"=="4" goto verify_nca +if /i "%bs%"=="4" goto r_npdm_nca +if /i "%bs%"=="5" goto verify_nca if /i "%bs%"=="b" goto sc1 if /i "%bs%"=="0" goto salida @@ -245,13 +260,22 @@ echo IMPLEMENTATION OF 0LIAM'S NACP LIBRARY %pycommand% "%nut%" -o "%info_dir%" --Read_nacp "%targt%" goto sc3 +:r_npdm_nca +cls +call :logo +echo ******************************************************** +echo SHOW MAIN.NPDM DATA FROM PROGRAM NCA IN NSP\XCI +echo ******************************************************** +%pycommand% "%nut%" -o "%info_dir%" --Read_npdm "%targt%" +goto sc3 + :verify_nca cls call :logo echo ******************************************************** echo VERIFY A NSP\XCI\NCA echo ******************************************************** -%pycommand% "%nut%" -b %buffer% -o "%info_dir%" -v "%targt%" +%pycommand% "%nut%" %buffer% -o "%info_dir%" -v "%targt%" goto sc3 @@ -273,7 +297,7 @@ ECHO ============================= BY JULESONTHEROAD =================== ECHO ------------------------------------------------------------------------------------- ECHO " POWERED BY SQUIRREL " ECHO " BASED IN THE WORK OF BLAWAR AND LUCA FRAGA " -ECHO VERSION 0.87 +ECHO VERSION 0.88 ECHO ------------------------------------------------------------------------------------- ECHO Program's github: https://github.com/julesontheroad/NSC_BUILDER ECHO Blawar's github: https://github.com/blawar diff --git a/py/ztools/lib/CryptoUtils.py b/py/ztools/lib/CryptoUtils.py new file mode 100644 index 00000000..4e9613c4 --- /dev/null +++ b/py/ztools/lib/CryptoUtils.py @@ -0,0 +1,130 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +from binascii import hexlify as hx, unhexlify as uhx +from Utils import pk_u8 +from Crypto.Hash import SHA256 +from Crypto.Cipher import AES +from Crypto.Util import Counter +from Crypto.PublicKey import RSA +from Crypto.Signature import PKCS1_v1_5, PKCS1_PSS + +def sha256(data): + return SHA256.new(data).digest() + +def sha256_file(fp): + hash = SHA256.new() + fp.seek(0) + while True: + buf = fp.read(0x10000) + if not buf: + break + hash.update(buf) + return hash.digest() + +def validate_rsa2048_pkcs1_sig(n, e, msg, sig): + cipher = PKCS1_v1_5.new(RSA.RsaKey(n=n, e=e)) + digest = SHA256.new(msg) # DRM'd to use their impl + return cipher.verify(digest, sig) + +def validate_rsa2048_pss_sig(n, e, msg, sig): + cipher = PKCS1_PSS.new(RSA.RsaKey(n=n, e=e)) + digest = SHA256.new(msg) + return cipher.verify(digest, sig) + +def sxor(s1, s2): + assert len(s1) == len(s2), 'Strings need to be of the same size' + return b''.join(pk_u8(x ^ y) for x, y in zip(s1, s2)) + +def gen_aes_kek(src, mkey, kek_seed, key_seed): + key = AES.new(mkey, AES.MODE_ECB).decrypt(kek_seed) + kek = AES.new(key, AES.MODE_ECB).decrypt(src) + return AES.new(kek, AES.MODE_ECB).decrypt(key_seed) + +def hex2ctr(x): + return Counter.new(128, initial_value=int(x, 16)) + +def b2ctr(x): + return hex2ctr(hx(x)) + +# From: https://gist.github.com/SciresM/a4b9ae50a9ae89e6119c4de9def22435 +# Pure python AES128 implementation +# SciresM, 2017 +# Ported to Python 3, and modded to use the pycryptodome module for AES-ECB operations +class AESXTSN: + def __init__(self, keys, sector=0): + if not (type(keys) is tuple and len(keys) == 2): + raise ValueError('Key must be 32 bytes long') + self.K1 = AES.new(keys[0], mode=AES.MODE_ECB) + self.K2 = AES.new(keys[1], mode=AES.MODE_ECB) + self.keys = keys + self.sector = sector + self.sector_size = 0x200 + self.block_size = 0x10 + + def encrypt(self, data, sector=None): + if sector is None: + sector = self.sector + if len(data) % self.block_size: + raise ValueError('Data is not aligned to block size!') + out = b'' + while data: + tweak = self.get_tweak(sector) + out += self.encrypt_sector(data[:self.sector_size], tweak) + data = data[self.sector_size:] + sector += 1 + return out + + def encrypt_sector(self, data, tweak): + if len(data) % self.block_size: + raise ValueError('Data is not aligned to block size!') + out = b'' + tweak = self.K2.encrypt(uhx('%032X' % tweak)) + while data: + out += sxor(tweak, self.K1.encrypt(sxor(data[:0x10], tweak))) + _t = int(hx(tweak[::-1]), 16) + _t <<= 1 + if _t & (1 << 128): + _t ^= ((1 << 128) | (0x87)) + tweak = uhx('%032X' % _t)[::-1] + data = data[0x10:] + return out + + def decrypt(self, data, sector=None): + if sector is None: + sector = self.sector + if len(data) % self.block_size: + raise ValueError('Data is not aligned to block size!') + out = b'' + while data: + tweak = self.get_tweak(sector) + out += self.decrypt_sector(data[:self.sector_size], tweak) + data = data[self.sector_size:] + sector += 1 + return out + + def decrypt_sector(self, data, tweak): + if len(data) % self.block_size: + raise ValueError('Data is not aligned to block size!') + out = b'' + tweak = self.K2.encrypt(uhx('%032X' % tweak)) + while data: + a = self.K1.decrypt(sxor(data[:0x10], tweak)) + out += sxor(tweak, a) + _t = int(hx(tweak[::-1]), 16) + _t <<= 1 + if _t & (1 << 128): + _t ^= ((1 << 128) | (0x87)) + tweak = uhx('%032X' % _t)[::-1] + data = data[0x10:] + return out + + def get_tweak(self, sector=None): + if sector is None: + sector = self.sector + tweak = 0 + for i in range(self.block_size): + tweak |= (sector & 0xFF) << (i * 8) + sector >>= 8 + return tweak + \ No newline at end of file diff --git a/py/ztools/lib/NXKeys.py b/py/ztools/lib/NXKeys.py new file mode 100644 index 00000000..de8518ae --- /dev/null +++ b/py/ztools/lib/NXKeys.py @@ -0,0 +1,49 @@ +import os.path as path +import re +from binascii import hexlify as hx, unhexlify as uhx +from pathlib import Path +my_file = Path('keys.txt') +my_file2 = Path('ztools\\keys.txt') + +class Keys(dict): + def __init__(self, keys_type): + self.keys_type = keys_type + is_key = re.compile(r'''\s*([a-zA-Z0-9_]*)\s* # name + = + \s*([a-fA-F0-9]*)\s* # key''', re.X) + try: + if my_file.is_file(): + f = open('keys.txt', 'r') + if my_file2.is_file(): + f = open('ztools\\keys.txt', 'r') + except FileNotFoundError: + try: + f = open(path.join(path.dirname(path.abspath(__file__)), '%s' % self.keys_type), 'r') + except FileNotFoundError: + raise FileNotFoundError('Need key file %s.keys in either %s or %s' % (self.keys_type, + path.expanduser('~/.switch'), path.dirname(path.abspath(__file__)))) + iterator = (re.search(is_key, l) for l in f) + super(Keys, self).__init__({r[1]: uhx(r[2]) for r in iterator if r is not None}) + f.close() + + def __getitem__(self, item): + try: + return dict.__getitem__(self, item) + except KeyError: + raise KeyError('Missing key %s in %s' % (item, self.keys_type)) + +class ProdKeys(Keys): + def __init__(self): + super(ProdKeys, self).__init__('keys.txt') + if 'header_key' in self: + self['nca_header_key'] = self.pop('header_key') + +class DevKeys(Keys): + def __init__(self): + super(DevKeys, self).__init__('dev') + +class TitleKeys(Keys): + def __init__(self): + super(TitleKeys, self).__init__('title') + if 'header_key' in self: + self['nca_header_key'] = self.pop('header_key') diff --git a/py/ztools/lib/Utils.py b/py/ztools/lib/Utils.py new file mode 100644 index 00000000..212ee242 --- /dev/null +++ b/py/ztools/lib/Utils.py @@ -0,0 +1,137 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +import os, sys +import io +import re +from binascii import hexlify as hx, unhexlify as uhx +from struct import pack as pk, unpack as upk + +def memdump(data, length=16, message=''): + assert data, 'Nothing to dump' + dump = [] + first = True + while data: + dump.append(message + ' '.join('%02x' % data[n] for n in range(min(length, len(data))))) + data = data[length:] + if first: + message = len(message) * ' ' + first = False + return '\n'.join(dump) + +def check_tkey(tkey): + return re.match('[a-fA-F0-9]{32}', tkey) + +def check_tid(tid): + return re.match('0100[a-fA-F0-9]{12}', tid) + +def read_at(fp, off, len): + fp.seek(off) + return fp.read(len) + +def read_u8(fp, off): + return upk('= 0, 'Can\'t convert negative input to byte units' + symbols = ('B', 'KB', 'MB', 'GB', 'TB') + prefix = {} + for i, s in enumerate(symbols[1:]): + prefix[s] = 10**(3 * i) * 1000 + for symbol in reversed(symbols[1:]): + if n >= prefix[symbol]: + value = float(n) / prefix[symbol] + return f % locals() + return f % dict(symbol=symbols[0], value=n) + +class FileInContainer(io.BufferedReader): + '''Hacky class to redirect read operations to the container, instead of loading everything in memory''' + def __init__(self, fp, offset_in_container, size): + super(FileInContainer, self).__init__(fp) + self.f = fp + self.offset_in_cont = offset_in_container + self.size = size + self.seek(0) # Doesn't default there + + def seek(self, offset, whence=0): + if whence == 0: + self._pos = offset + self.f.seek(self.offset_in_cont + offset) + elif whence == 1: + self._pos += offset + self.f.seek(self.offset_in_cont + self._pos + offset) + elif whence == 2: + if offset > 0: + offset *= -1 + self._pos = self.size + offset + self.f.seek(self.offset_in_cont + self.size + offset, whence) + + def tell(self): + return self._pos + + def read(self, size=None): + if self.offset_in_cont + self._pos != self.f.tell(): + self.seek(self._pos) + if self.tell() > self.size: + data = b'' + elif size is None or size + self.tell() > self.size: + data = self.f.read(self.size - self.tell()) + self._pos = self.size + else: + data = self.f.read(size) + self._pos += size + return data + + def write(self, data): + raise NotImplementedError + + def close(self): + # File of container is closed along with this object + return diff --git a/py/ztools/lib/sq_tools.py b/py/ztools/lib/sq_tools.py index 03217a62..0fb476b9 100644 --- a/py/ztools/lib/sq_tools.py +++ b/py/ztools/lib/sq_tools.py @@ -9,6 +9,7 @@ import Fs import aes128 import sq_tools +import io indent = 1 tabs = '\t' * indent ''' @@ -1089,4 +1090,113 @@ def get_xciheader(oflist,osizelist,sec_hashlist): return header,enc_info,sig_padding,fake_CERT,root_header,upd_header,norm_header,sec_header,rootSize,upd_multiplier,norm_multiplier,sec_multiplier +def ret_nsp_offsets(filepath): + files_list=list() + try: + with open(filepath, 'r+b') as f: + data=f.read(int(8*1024)) + try: + head=data[0:4] + n_files=(data[4:8]) + n_files=int.from_bytes(n_files, byteorder='little') + st_size=(data[8:12]) + st_size=int.from_bytes(st_size, byteorder='little') + junk=(data[12:16]) + offset=(0x10 + n_files * 0x18) + stringTable=(data[offset:offset+st_size]) + stringEndOffset = st_size + headerSize = 0x10 + 0x18 * n_files + st_size + #print(head) + #print(str(n_files)) + #print(str(st_size)) + #print(str((stringTable))) + for i in range(n_files): + i = n_files - i - 1 + pos=0x10 + i * 0x18 + offset = data[pos:pos+8] + offset=int.from_bytes(offset, byteorder='little') + size = data[pos+8:pos+16] + size=int.from_bytes(size, byteorder='little') + nameOffset = data[pos+16:pos+20] # just the offset + nameOffset=int.from_bytes(nameOffset, byteorder='little') + name = stringTable[nameOffset:stringEndOffset].decode('utf-8').rstrip(' \t\r\n\0') + stringEndOffset = nameOffset + junk2 = data[pos+20:pos+24] # junk data + #print(name) + #print(offset) + #print(size) + off1=offset+headerSize + off2=off1+size + files_list.append([name,off1,off2,size]) + files_list.reverse() + #print(files_list) + except BaseException as e: + Print.error('Exception: ' + str(e)) + #print(files_list) + except BaseException as e: + Print.error('Exception: ' + str(e)) + return files_list +def ret_xci_offsets(filepath): + files_list=list() + try: + with open(filepath, 'r+b') as f: + rawhead = io.BytesIO(f.read(int(0x200))) + data=rawhead.read() + #print(hx(data)) + try: + rawhead.seek(0x100) + magic=rawhead.read(0x4) + if magic==b'HEAD': + #print(magic) + secureOffset=int.from_bytes(rawhead.read(4), byteorder='little') + secureOffset=secureOffset*0x200 + with open(filepath, 'r+b') as f: + f.seek(secureOffset) + data=f.read(int(8*1024)) + rawhead = io.BytesIO(data) + rmagic=rawhead.read(0x4) + if rmagic==b'HFS0': + #print(rmagic) + head=data[0:4] + n_files=(data[4:8]) + n_files=int.from_bytes(n_files, byteorder='little') + st_size=(data[8:12]) + st_size=int.from_bytes(st_size, byteorder='little') + junk=(data[12:16]) + offset=(0x10 + n_files * 0x40) + stringTable=(data[offset:offset+st_size]) + stringEndOffset = st_size + headerSize = 0x10 + 0x40 * n_files + st_size + #print(head) + #print(str(n_files)) + #print(str(st_size)) + #print(str((stringTable))) + for i in range(n_files): + i = n_files - i - 1 + pos=0x10 + i * 0x40 + offset = data[pos:pos+8] + offset=int.from_bytes(offset, byteorder='little') + size = data[pos+8:pos+16] + size=int.from_bytes(size, byteorder='little') + nameOffset = data[pos+16:pos+20] # just the offset + nameOffset=int.from_bytes(nameOffset, byteorder='little') + name = stringTable[nameOffset:stringEndOffset].decode('utf-8').rstrip(' \t\r\n\0') + stringEndOffset = nameOffset + junk2 = data[pos+20:pos+24] # junk data + #print(name) + #print(offset) + #print(size) + off1=offset+headerSize+secureOffset + off2=off1+size + files_list.append([name,off1,off2,size]) + # with open(filename, 'r+b') as f: + # f.seek(off1) + # print(f.read(0x4)) + files_list.reverse() + #print(files_list) + except BaseException as e: + Print.error('Exception: ' + str(e)) + except BaseException as e: + Print.error('Exception: ' + str(e)) + return files_list \ No newline at end of file diff --git a/py/ztools/lib/title.keys b/py/ztools/lib/title.keys new file mode 100644 index 00000000..e69de29b diff --git a/py/ztools/squirrel.py b/py/ztools/squirrel.py index 62a242b0..e2c9f901 100644 --- a/py/ztools/squirrel.py +++ b/py/ztools/squirrel.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + ''' _____ _ __ / ___/____ ___ __(_)____________ / / @@ -23,11 +25,11 @@ standalone program with many functions, some of them not being used currently in NSC_Builder. ''' -# -*- coding: utf-8 -*- import argparse import sys import os import re +import io import pathlib import urllib3 import json @@ -53,7 +55,7 @@ from datetime import datetime import math import pykakasi - +from Fs.pyNCA3 import NCA3 if __name__ == '__main__': try: @@ -63,13 +65,14 @@ # INFORMATION parser.add_argument('-i', '--info', help='show info about title or file') - parser.add_argument('--filelist', nargs='+', help='Prints file list from NSP\XCI secure partition') - parser.add_argument('--ADVfilelist', nargs='+', help='Prints ADVANCED file list from NSP\XCI secure partition') - parser.add_argument('--ADVcontentlist', nargs='+', help='Prints ADVANCED content list from NSP\XCI arranged by base titleid') - parser.add_argument('--Read_cnmt', nargs='+', help='Read cnmt file inside NSP\XCI') - parser.add_argument('--Read_nacp', nargs='+', help='Read ncap file inside NSP\XCI') + parser.add_argument('--filelist', nargs='+', help='Prints file list from NSP/XCI secure partition') + parser.add_argument('--ADVfilelist', nargs='+', help='Prints ADVANCED file list from NSP/XCI secure partition') + parser.add_argument('--ADVcontentlist', nargs='+', help='Prints ADVANCED content list from NSP/XCI arranged by base titleid') + parser.add_argument('--Read_cnmt', nargs='+', help='Read cnmt file inside NSP/XCI') + parser.add_argument('--Read_nacp', nargs='+', help='Read nacp file inside NSP/XCI') + parser.add_argument('--Read_npdm', nargs='+', help='Read npdm file inside NSP/XCI') parser.add_argument('--Read_hfs0', nargs='+', help='Read hfs0') - parser.add_argument('--fw_req', nargs='+', help='Get information about fw requirements for NSP\XCI') + parser.add_argument('--fw_req', nargs='+', help='Get information about fw requirements for NSP/XCI') parser.add_argument('--Read_xci_head', nargs='+', help='Get information about xci header and cert') parser.add_argument('-nscdb', '--addtodb', nargs='+', help='Adds content to database') parser.add_argument('-v', '--verify', nargs='+', help='Verify nsp or xci file') @@ -89,7 +92,8 @@ parser.add_argument('-dc', '--direct_creation', nargs='+', help='Create directly a nsp or xci') parser.add_argument('-dmul', '--direct_multi', nargs='+', help='Create directly a multi nsp or xci') parser.add_argument('-ed', '--erase_deltas', nargs='+', help='Take of deltas from updates') - parser.add_argument('-rbnsp', '--rebuild_nsp', nargs='+', help='Rebuild nsp by cnmt order') + parser.add_argument('-rbnsp', '--rebuild_nsp', nargs='+', help='Rebuild nsp by cnmt order') + parser.add_argument('-rst', '--restore', nargs='+', help='Rebuild nsp by cnmt order') # nca/nsp identification parser.add_argument('--ncatitleid', nargs='+', help='Returns titleid from a nca input') @@ -122,6 +126,8 @@ # NSP Copy functions parser.add_argument('-x', '--extract', nargs='+', help='Extracts all files from nsp or xci') parser.add_argument('-raw_x', '--raw_extraction', nargs='+', help='Extracts files without checking readability, useful when there is bad files') + parser.add_argument('-nfx', '--nca_file_extraction', nargs='+', help='Extracts files files within nca files from nsp/xci\nca file') + parser.add_argument('-plx', '--extract_plain_nca', nargs='+', help='Extracts nca files as plaintext or generate a plaintext file from a nca file') parser.add_argument('--NSP_copy_ticket', nargs='+', help='Extracts ticket from target nsp') parser.add_argument('--NSP_copy_nca', nargs='+', help='Extracts all nca files from target nsp') parser.add_argument('--NSP_copy_other', nargs='+', help='Extracts all kinds of files different from nca or ticket from target nsp') @@ -130,7 +136,7 @@ parser.add_argument('--NSP_copy_jpg', nargs='+', help='Extracts jpg files from target nsp') parser.add_argument('--NSP_copy_cnmt', nargs='+', help='Extracts cnmt files from target nsp') parser.add_argument('--copy_pfs0_meta', nargs='+', help='Extracts meta pfs0 from target nsp') - parser.add_argument('--NSP_copy_ncap', nargs='+', help='Extracts ncap files from target nsp') + parser.add_argument('--copy_nacp', nargs='+', help='Extracts nacp files from target nsp') # XCI Copy functions parser.add_argument('--XCI_copy_hfs0', nargs='+', help='Extracts hfs0 partition files from target xci') @@ -206,7 +212,7 @@ if sys.platform == 'win32': parser.add_argument('-archive','--archive', help='Archive to folder') parser.add_argument('-zippy','--zippy', help='Zip a file') - parser.add_argument('-joinfile','--joinfile', help='Join split file') + parser.add_argument('-joinfile','--joinfile', nargs='+', help='Join split file') # OTHER parser.add_argument('-nint_keys','--nint_keys', help='Verify NS keys') parser.add_argument('-renf','--renamef', help='Rename file with proper name') @@ -544,69 +550,71 @@ except BaseException as e: Print.error('Exception: ' + str(e)) # ................................................... - # Copy all NCA from NSP file + # Copy all FILES from NSP\XCI file # ................................................... if args.extract: - if args.ofolder: - for input in args.ofolder: - try: - ofolder = input - except BaseException as e: - Print.error('Exception: ' + str(e)) - else: - if args.text_file: - tfile=args.text_file - with open(tfile,"r+", encoding='utf8') as filelist: - filename = filelist.readline() - filename=os.path.abspath(filename.rstrip('\n')) - dir=os.path.dirname(os.path.abspath(filename)) - basename=str(os.path.basename(os.path.abspath(filepath))) - ofolder =os.path.join(dir, 'output') - ofolder =os.path.join(dir, 'basename') - else: - for filename in args.extract: - dir=os.path.dirname(os.path.abspath(filename)) - ofolder =os.path.join(dir, 'output') - if not os.path.exists(ofolder): - os.makedirs(ofolder) if args.buffer: - for input in args.buffer: + for var in args.buffer: try: - buffer = input + buffer = var except BaseException as e: Print.error('Exception: ' + str(e)) else: buffer = 32768 - - if args.extract: - if args.text_file: - tfile=args.text_file - with open(tfile,"r+", encoding='utf8') as filelist: - filename = filelist.readline() - filename=os.path.abspath(filename.rstrip('\n')) - else: - for filename in args.extract: - filename=filename - test=filename.lower() - if test.endswith('.nsp') or test.endswith('.nsx'): + ofolder=False + if args.ofolder: + for input in args.ofolder: try: - f = Fs.Nsp(filename, 'rb') - f.open(filename, 'rb') - f.extract_all(ofolder,buffer) - f.flush() - f.close() + ofolder = input except BaseException as e: Print.error('Exception: ' + str(e)) - elif test.endswith('.xci'): - try: - f = Fs.factory(filename) - f.open(filename, 'rb') - f.extract_all(ofolder,buffer) - f.flush() - f.close() - except BaseException as e: - Print.error('Exception: ' + str(e)) - + if not os.path.exists(ofolder): + os.makedirs(ofolder) + if args.text_file: + tfile=args.text_file + with open(tfile,"r+", encoding='utf8') as filelist: + filename = filelist.readline() + filename=os.path.abspath(filename.rstrip('\n')) + if ofolder != False: + dir=ofolder + else: + dir=os.path.dirname(os.path.abspath(filename)) + basename=str(os.path.basename(os.path.abspath(filename))) + basename=basename[:-4] + ofolder =os.path.join(dir, basename) + else: + for filename in args.extract: + if ofolder != False: + dir=ofolder + else: + dir=os.path.dirname(os.path.abspath(filename)) + basename=str(os.path.basename(os.path.abspath(filename))) + basename=basename[:-4] + ofolder =os.path.join(dir, basename) + if not os.path.exists(ofolder): + os.makedirs(ofolder) + test=filename.lower() + if test.endswith('.nsp') or test.endswith('.nsx'): + try: + f = Fs.Nsp(filename, 'rb') + f.open(filename, 'rb') + f.extract_all(ofolder,buffer) + f.flush() + f.close() + except BaseException as e: + Print.error('Exception: ' + str(e)) + elif test.endswith('.xci'): + try: + f = Fs.factory(filename) + f.open(filename, 'rb') + f.extract_all(ofolder,buffer) + f.flush() + f.close() + except BaseException as e: + Print.error('Exception: ' + str(e)) + # ................................................... + # Copy all NCA from NSP file + # ................................................... if args.NSP_copy_nca: if args.ofolder: for input in args.ofolder: @@ -1168,9 +1176,9 @@ Print.error('Exception: ' + str(e)) # ................................................... - # Copy control ncap files from NSP file + # Copy control nacp files from NSP file # ................................................... - if args.NSP_copy_ncap: + if args.copy_nacp: if args.ofolder: for input in args.ofolder: try: @@ -1178,7 +1186,7 @@ except BaseException as e: Print.error('Exception: ' + str(e)) else: - for filename in args.NSP_copy_ncap: + for filename in args.copy_nacp: dir=os.path.dirname(os.path.abspath(filename)) ofolder =os.path.join(dir, 'output') @@ -1190,15 +1198,25 @@ Print.error('Exception: ' + str(e)) else: buffer = 32768 - for filename in args.NSP_copy_ncap: - try: - f = Fs.Nsp(filename, 'rb') - f.copy_ncap(ofolder,buffer) - f.flush() - f.close() - except BaseException as e: - Print.error('Exception: ' + str(e)) - + for filename in args.copy_nacp: + if filename.endswith(".nsp"): + try: + f = Fs.Nsp(filename, 'rb') + f.copy_nacp(ofolder,buffer) + f.flush() + f.close() + except BaseException as e: + Print.error('Exception: ' + str(e)) + ''' + if filename.endswith(".nca"): + try: + f = Fs.Nca(filename, 'rb') + f.extract(ofolder,buffer) + f.flush() + f.close() + except BaseException as e: + Print.error('Exception: ' + str(e)) + ''' # DEDICATED COPY FUNCTIONS. NCA TYPES. # ................................................... # Copy all META NCA from NSP file @@ -3412,7 +3430,7 @@ f = Fs.Nsp(filepath) for file in oflist: if not file.endswith('xml'): - outf,index,c = f.append_content(endfile,file,buffer,t,fat,fx,c,index) + endfile,index,c = f.append_content(endfile,file,buffer,t,fat,fx,c,index) f.flush() f.close() except BaseException as e: @@ -3528,7 +3546,7 @@ for i in range(len(GClist)): if GClist[i][0] == file: GC=GClist[i][1] - outf,index,c = f.append_clean_content(endfile,file,buffer,t,GC,vkeypatch,metapatch,RSV_cap,fat,fx,c,index) + endfile,index,c = f.append_clean_content(endfile,file,buffer,t,GC,vkeypatch,metapatch,RSV_cap,fat,fx,c,index) f.flush() f.close() except BaseException as e: @@ -3542,7 +3560,7 @@ for i in range(len(GClist)): if GClist[i][0] == file: GC=GClist[i][1] - outf,index,c = f.append_clean_content(endfile,file,buffer,t,GC,vkeypatch,metapatch,RSV_cap,fat,fx,c,index) + endfile,index,c = f.append_clean_content(endfile,file,buffer,t,GC,vkeypatch,metapatch,RSV_cap,fat,fx,c,index) f.flush() f.close() except BaseException as e: @@ -3592,7 +3610,7 @@ shutil.rmtree(afolder, ignore_errors=True) #print(str(totSize)) t = tqdm(total=totSize, unit='B', unit_scale=True, leave=False) - outf = open(endfile, 'w+b') + endfile = open(endfile, 'w+b') t.write(tabs+'- Writing NSP header...') outf.write(nspheader) t.update(len(nspheader)) @@ -3604,7 +3622,7 @@ f = Fs.Nsp(filepath) for file in oflist: if not file.endswith('xml'): - outf,index,c = f.append_clean_content(endfile,file,buffer,t,False,vkeypatch,metapatch,RSV_cap,fat,fx,c,index) + endfile,index,c = f.append_clean_content(endfile,file,buffer,t,False,vkeypatch,metapatch,RSV_cap,fat,fx,c,index) f.flush() f.close() except BaseException as e: @@ -3614,7 +3632,7 @@ f = Fs.Xci(filepath) for file in oflist: if not file.endswith('xml'): - outf,index,c = f.append_clean_content(endfile,file,buffer,t,False,vkeypatch,metapatch,RSV_cap,fat,fx,c,index) + endfile,index,c = f.append_clean_content(endfile,file,buffer,t,False,vkeypatch,metapatch,RSV_cap,fat,fx,c,index) f.flush() f.close() except BaseException as e: @@ -3783,15 +3801,26 @@ Print.error('Exception: ' + str(e)) else: buffer = 32768 - filepath = args.joinfile + if args.text_file: + tfile=args.text_file + with open(tfile,"r+", encoding='utf8') as filelist: + filepath = filelist.readline() + filepath=os.path.abspath(filepath.rstrip('\n')) + else: + for filepath in args.joinfile: + filepath=filepath + print(filepath) file_list=list() - #print (filepath) try: + bname=os.path.basename(os.path.abspath(filepath)) + bn='' + if bname != '00': + bn=bname[:-4] if filepath.endswith(".xc0"): - outname = "output.xci" + outname = bn+".xci" ender=".xc" elif filepath.endswith(".ns0"): - outname = "output.nsp" + outname = bn+".nsp" ender=".ns" elif filepath[-2:]=="00": outname = "output.nsp" @@ -3800,13 +3829,25 @@ print ("Not valid file") outfile = os.path.join(ofolder, outname) #print (outfile) - ruta=os.path.dirname(os.path.abspath(args.joinfile)) + ruta=os.path.dirname(os.path.abspath(filepath)) + #print(ruta) for dirpath, dnames, fnames in os.walk(ruta): for f in fnames: - check=f[-3:-1] - if check==ender: - fp = os.path.join(ruta, f) - file_list.append(fp) + check=f[-4:-1] + #print(check) + #print(ender) + #print(bname[:-1]) + #print(f[:-1]) + if check==ender and bname[:-1]==f[:-1]: + n=bname[-1];n=int(n) + try: + n=f[-1];n=int(n) + n+=1 + fp = os.path.join(ruta, f) + file_list.append(fp) + except: continue + file_list.sort() + #print(file_list) except BaseException as e: Print.error('Exception: ' + str(e)) totSize = sum(os.path.getsize(file) for file in file_list) @@ -4148,6 +4189,15 @@ # ADD CONTENT TO DATABASE # ................................................... if args.addtodb: + if args.romanize: + for input in args.ofolder: + roman=str(input).upper() + if roman == "FALSE": + roman = False + else: + roman = True + else: + roman = True if args.db_file: outfile=args.db_file dir=os.path.dirname(os.path.abspath(outfile)) @@ -4191,7 +4241,7 @@ infile=r'' infile+=filename f = Fs.Nsp(filename, 'rb') - f.addtodb(outfile,outdb) + f.addtodb(outfile,outdb,roman) f.flush() f.close() except BaseException as e: @@ -4208,7 +4258,7 @@ infile+=filename f = Fs.factory(filename) f.open(filename, 'rb') - f.addtodb(outfile,outdb) + f.addtodb(outfile,outdb,roman) f.flush() f.close() except BaseException as e: @@ -4367,86 +4417,353 @@ # ...................................................................... if args.raw_extraction: - for filename in args.raw_extraction: - if filename.endswith('.nsp'): - dir=os.path.dirname(os.path.abspath(filename)) - ofolder =os.path.join(dir, 'output') - if not os.path.exists(ofolder): - os.makedirs(ofolder) + if args.buffer: + for var in args.buffer: try: - with open(filename, 'r+b') as f: - data=f.read(int(8*1024)) - try: - head=data[0:4] - n_files=(data[4:8]) - n_files=int.from_bytes(n_files, byteorder='little') - st_size=(data[8:12]) - st_size=int.from_bytes(st_size, byteorder='little') - junk=(data[12:16]) - offset=(0x10 + n_files * 0x18) - stringTable=(data[offset:offset+st_size]) - stringEndOffset = st_size - headerSize = 0x10 + 0x18 * n_files + st_size - #print(head) - #print(str(n_files)) - #print(str(st_size)) - #print(str((stringTable))) - files_list=list() - for i in range(n_files): - i = n_files - i - 1 - pos=0x10 + i * 0x18 - offset = data[pos:pos+8] - offset=int.from_bytes(offset, byteorder='little') - size = data[pos+8:pos+16] - size=int.from_bytes(size, byteorder='little') - nameOffset = data[pos+16:pos+20] # just the offset - nameOffset=int.from_bytes(nameOffset, byteorder='little') - name = stringTable[nameOffset:stringEndOffset].decode('utf-8').rstrip(' \t\r\n\0') - stringEndOffset = nameOffset - junk2 = data[pos+20:pos+24] # junk data - #print(name) - #print(offset) - #print(size) - files_list.append([name,offset,size]) - files_list.reverse() - except IOError as e: - print(e, file=sys.stderr) - #print(files_list) - for i in range(len(files_list)): - #print(files_list[i][0]) - #print(files_list[i][1]) - #print(files_list[i][2]) - off1=headerSize+files_list[i][1] - off2=off1+files_list[i][2] - filepath = os.path.join(ofolder, files_list[i][0]) - fp = open(filepath, 'w+b') - s=0 - for j in range(len(files_list)): - s=s+files_list[j][2] - #print(filepath) - t = tqdm(total=s, unit='B', unit_scale=True, leave=False) - with open(filename, 'r+b') as f: - f.seek(off1) - c=0;buffer=32768 - t.write(tabs+'Copying: ' + str(files_list[i][0])) - for data in iter(lambda: f.read(int(buffer)), ""): + buffer = var + except BaseException as e: + Print.error('Exception: ' + str(e)) + else: + buffer = 32768 + ofolder=False + if args.ofolder: + for input in args.ofolder: + try: + ofolder = input + except BaseException as e: + Print.error('Exception: ' + str(e)) + if not os.path.exists(ofolder): + os.makedirs(ofolder) + if args.text_file: + tfile=args.text_file + with open(tfile,"r+", encoding='utf8') as filelist: + filename = filelist.readline() + filename=os.path.abspath(filename.rstrip('\n')) + if ofolder != False: + dir=ofolder + else: + dir=os.path.dirname(os.path.abspath(filename)) + basename=str(os.path.basename(os.path.abspath(filename))) + basename=basename[:-4] + ofolder =os.path.join(dir, basename) + else: + for filename in args.raw_extraction: + if ofolder != False: + dir=ofolder + else: + dir=os.path.dirname(os.path.abspath(filename)) + basename=str(os.path.basename(os.path.abspath(filename))) + basename=basename[:-4] + ofolder =os.path.join(dir, basename) + if not os.path.exists(ofolder): + os.makedirs(ofolder) + test=filename.lower() + if test.endswith('.nsp') or test.endswith('.nsx'): + try: + files_list=sq_tools.ret_nsp_offsets(filename) + for i in range(len(files_list)): + #print(files_list[i][0]) + #print(files_list[i][1]) + #print(files_list[i][2]) + off1=files_list[i][1] + off2=files_list[i][2] + filepath = os.path.join(ofolder, files_list[i][0]) + fp = open(filepath, 'w+b') + s=files_list[i][3] + if buffer>s: + buf=s + else: + buf=buffer + #print(filepath) + t = tqdm(total=s, unit='B', unit_scale=True, leave=False) + with open(filename, 'r+b') as f: + f.seek(off1) + c=0 + t.write(tabs+'Copying: ' + str(files_list[i][0])) + for data in iter(lambda: f.read(int(buf)), ""): + fp.write(data) + fp.flush() + c=len(data)+c + t.update(len(data)) + if c+int(buf)>s: + if (s-c)<0: + t.close() + fp.close() + break + data=f.read(s-c) fp.write(data) - fp.flush() - c=len(data)+c t.update(len(data)) - if c+len(data)>off2: - data=f.read(off2-c) - t.update(len(data)) - t.close() - fp.close() - break - if not data: + t.close() + fp.close() + break + if not data: + t.close() + fp.close() + break + except BaseException as e: + Print.error('Exception: ' + str(e)) + elif test.endswith('.xci'): + try: + files_list=sq_tools.ret_xci_offsets(filename) + #print(files_list) + for i in range(len(files_list)): + #print(files_list[i][0]) + #print(files_list[i][1]) + #print(files_list[i][2]) + off1=files_list[i][1] + off2=files_list[i][2] + filepath = os.path.join(ofolder, files_list[i][0]) + fp = open(filepath, 'w+b') + s=files_list[i][3] + if buffer>s: + buf=s + else: + buf=buffer + #print(filepath) + t = tqdm(total=s, unit='B', unit_scale=True, leave=False) + with open(filename, 'r+b') as f: + f.seek(off1) + c=0 + t.write(tabs+'Copying: ' + str(files_list[i][0])) + for data in iter(lambda: f.read(int(buf)), ""): + fp.write(data) + fp.flush() + c=len(data)+c + t.update(len(data)) + if c+int(buf)>s: + if (s-c)<0: t.close() - fp.close() - break + fp.close() + break + data=f.read(s-c) + fp.write(data) + t.update(len(data)) + t.close() + fp.close() + break + if not data: + t.close() + fp.close() + break + except BaseException as e: + Print.error('Exception: ' + str(e)) + + # .......................................................................... + # NCA_FILE_EXTACTION. EXTRACT FILES PACKED IN NCA FROM NSP\XCI\NCA + # .......................................................................... + + if args.nca_file_extraction: + if args.buffer: + for var in args.buffer: + try: + buffer = var except BaseException as e: - Print.error('Exception: ' + str(e)) + Print.error('Exception: ' + str(e)) + else: + buffer = 32768 + ofolder=False + if args.ofolder: + for input in args.ofolder: + try: + ofolder = input + except BaseException as e: + Print.error('Exception: ' + str(e)) + if not os.path.exists(ofolder): + os.makedirs(ofolder) + if args.text_file: + tfile=args.text_file + with open(tfile,"r+", encoding='utf8') as filelist: + filename = filelist.readline() + filename=os.path.abspath(filename.rstrip('\n')) + if ofolder != False: + dir=ofolder + else: + dir=os.path.dirname(os.path.abspath(filename)) + basename=str(os.path.basename(os.path.abspath(filename))) + basename=basename[:-4] + ofolder =os.path.join(dir, basename) + else: + for filename in args.nca_file_extraction: + if ofolder != False: + dir=ofolder + else: + dir=os.path.dirname(os.path.abspath(filename)) + basename=str(os.path.basename(os.path.abspath(filename))) + basename=basename[:-4] + ofolder =os.path.join(dir, basename) + if not os.path.exists(ofolder): + os.makedirs(ofolder) + if filename.endswith('.nsp'): + try: + files_list=sq_tools.ret_nsp_offsets(filename) + f = Fs.Nsp(filename, 'rb') + f.extract_nca(ofolder,files_list,buffer) + f.flush() + f.close() + except BaseException as e: + Print.error('Exception: ' + str(e)) + if filename.endswith('.xci'): + try: + files_list=sq_tools.ret_xci_offsets(filename) + f = Fs.Xci(filename) + f.extract_nca(ofolder,files_list,buffer) + f.flush() + f.close() + except BaseException as e: + Print.error('Exception: ' + str(e)) + # ........................................................................... + # NCA_2_PLAINTEXT. EXTRACT OR CONVERT NCA FILES TO PLAINTEXT FROM NSP\XCI\NCA + # ........................................................................... + + if args.extract_plain_nca: + if args.buffer: + for var in args.buffer: + try: + buffer = var + except BaseException as e: + Print.error('Exception: ' + str(e)) + else: + buffer = 32768 + ofolder=False + if args.ofolder: + for input in args.ofolder: + try: + ofolder = input + except BaseException as e: + Print.error('Exception: ' + str(e)) + if not os.path.exists(ofolder): + os.makedirs(ofolder) + if args.text_file: + tfile=args.text_file + with open(tfile,"r+", encoding='utf8') as filelist: + filename = filelist.readline() + filename=os.path.abspath(filename.rstrip('\n')) + if ofolder != False: + dir=ofolder + else: + dir=os.path.dirname(os.path.abspath(filename)) + basename=str(os.path.basename(os.path.abspath(filename))) + basename=basename[:-4] + ofolder =os.path.join(dir, basename) + else: + for filename in args.extract_plain_nca: + if ofolder != False: + dir=ofolder + else: + dir=os.path.dirname(os.path.abspath(filename)) + basename=str(os.path.basename(os.path.abspath(filename))) + basename=basename[:-4] + ofolder =os.path.join(dir, basename) + if not os.path.exists(ofolder): + os.makedirs(ofolder) + if filename.endswith('.nsp'): + try: + files_list=sq_tools.ret_nsp_offsets(filename) + f = Fs.Nsp(filename, 'rb') + f.copy_as_plaintext(ofolder,files_list,buffer) + f.flush() + f.close() + except BaseException as e: + Print.error('Exception: ' + str(e)) + if filename.endswith('.xci'): + try: + files_list=sq_tools.ret_xci_offsets(filename) + #print(files_list) + f = Fs.Xci(filename) + f.copy_as_plaintext(ofolder,files_list,buffer) + f.flush() + f.close() + except BaseException as e: + Print.error('Exception: ' + str(e)) + + # ........................................................................... + # Read npdm from inside nsp or xci + # ........................................................................... + if args.Read_npdm: + if args.ofolder: + for var in args.ofolder: + try: + ofolder = var + except BaseException as e: + Print.error('Exception: ' + str(e)) + else: + for filename in args.Read_npdm: + dir=os.path.dirname(os.path.abspath(filename)) + info='INFO' + ofolder =os.path.join(dir,info) + if not os.path.exists(ofolder): + os.makedirs(ofolder) + if args.text_file: + tfile=args.text_file + dir=os.path.dirname(os.path.abspath(tfile)) + if not os.path.exists(dir): + os.makedirs(dir) + err='badfiles.txt' + errfile = os.path.join(dir, err) + with open(tfile,"r+", encoding='utf8') as filelist: + filename = filelist.readline() + filename=os.path.abspath(filename.rstrip('\n')) + else: + for filename in args.Read_npdm: + filename=filename + basename=str(os.path.basename(os.path.abspath(filename))) + ofile=basename[:-4]+'-npdm.txt' + infotext=os.path.join(ofolder, ofile) + if filename.endswith(".nsp"): + try: + files_list=sq_tools.ret_nsp_offsets(filename) + f = Fs.Nsp(filename, 'rb') + feed=f.read_npdm(files_list) + f.flush() + f.close() + if not args.text_file: + print('\n********************************************************') + print('Do you want to print the information to a text file') + print('********************************************************') + i=0 + while i==0: + print('Input "1" to print to text file') + print('Input "2" to NOT print to text file\n') + ck=input('Input your answer: ') + if ck ==str(1): + with open(infotext, 'w') as info: + info.write(feed) + i=1 + elif ck ==str(2): + i=1 + else: + print('WRONG CHOICE\n') + except BaseException as e: + Print.error('Exception: ' + str(e)) + if filename.endswith(".xci"): + try: + files_list=sq_tools.ret_xci_offsets(filename) + f = Fs.Xci(filename) + feed=f.read_npdm(files_list) + f.flush() + f.close() + if not args.text_file: + print('\n********************************************************') + print('Do you want to print the information to a text file') + print('********************************************************') + i=0 + while i==0: + print('Input "1" to print to text file') + print('Input "2" to NOT print to text file\n') + ck=input('Input your answer: ') + if ck ==str(1): + with open(infotext, 'w') as info: + info.write(feed) + i=1 + elif ck ==str(2): + i=1 + else: + print('WRONG CHOICE\n') + except BaseException as e: + Print.error('Exception: ' + str(e)) + + # ................................................... # Read cnmt inside nsp or xci # ................................................... @@ -6016,8 +6333,13 @@ try: f = Fs.Nsp(filename, 'rb') check,feed=f.verify() + f.flush() + f.close() if not args.text_file: + f = Fs.Nsp(filename, 'rb') verdict,headerlist,feed=f.verify_sig(feed,tmpfolder) + f.flush() + f.close() i=0 print('\n********************************************************') print('Do you want to verify the hash of the nca files?') @@ -6028,6 +6350,7 @@ ck=input('Input your answer: ') if ck ==str(1): print('') + f = Fs.Nsp(filename, 'rb') verdict,feed=f.verify_hash_nca(buffer,headerlist,verdict,feed) f.flush() f.close() @@ -6056,14 +6379,23 @@ print('WRONG CHOICE\n') elif args.text_file: if vertype == "lv2": + f = Fs.Nsp(filename, 'rb') verdict,headerlist,feed=f.verify_sig(feed,tmpfolder) + f.flush() + f.close() if check == True: check=verdict elif vertype == "lv3": + f = Fs.Nsp(filename, 'rb') verdict,headerlist,feed=f.verify_sig(feed,tmpfolder) + f.flush() + f.close() if check == True: - check=verdict + check=verdict + f = Fs.Nsp(filename, 'rb') verdict,feed=f.verify_hash_nca(buffer,headerlist,verdict,feed) + f.flush() + f.close() if check == True: check=verdict if check == False: @@ -6097,8 +6429,14 @@ f = Fs.factory(filename) f.open(filename, 'rb') check,feed=f.verify() + f.flush() + f.close() if not args.text_file: + f = Fs.factory(filename) + f.open(filename, 'rb') verdict,headerlist,feed=f.verify_sig(feed,tmpfolder) + f.flush() + f.close() i=0 print('\n********************************************************') print('Do you want to verify the hash of the nca files?') @@ -6109,6 +6447,8 @@ check=input('Input your answer: ') if check ==str(1): print('') + f = Fs.factory(filename) + f.open(filename, 'rb') verdict,feed=f.verify_hash_nca(buffer,headerlist,verdict,feed) f.flush() f.close() @@ -6137,14 +6477,26 @@ print('WRONG CHOICE\n') elif args.text_file: if vertype == "lv2": - verdict,headerlist,feed=f.verify_sig(feed,tmpfolder) + f = Fs.factory(filename) + f.open(filename, 'rb') + verdict,headerlist,feed=f.verify_sig(feed,tmpfolder) + f.flush() + f.close() if check == True: check=verdict elif vertype == "lv3": - verdict,headerlist,feed=f.verify_sig(feed,tmpfolder) + f = Fs.factory(filename) + f.open(filename, 'rb') + verdict,headerlist,feed=f.verify_sig(feed,tmpfolder) + f.flush() + f.close() if check == True: - check=verdict + check=verdict + f = Fs.factory(filename) + f.open(filename, 'rb') verdict,feed=f.verify_hash_nca(buffer,headerlist,verdict,feed) + f.flush() + f.close() if check == True: check=verdict if check == False: @@ -6384,7 +6736,45 @@ tfile.write(line+"\n") except: continue - + + # ................................................... + # Restore. File Restoration + # ................................................... + if args.restore: + feed='' + if args.buffer: + for var in args.buffer: + try: + buffer = var + except BaseException as e: + Print.error('Exception: ' + str(e)) + else: + buffer = 32768 + if not os.path.exists(ofolder): + os.makedirs(ofolder) + if args.text_file: + tfile=args.text_file + dir=os.path.dirname(os.path.abspath(tfile)) + if not os.path.exists(dir): + os.makedirs(dir) + err='badfiles.txt' + errfile = os.path.join(dir, err) + with open(tfile,"r+", encoding='utf8') as filelist: + filename = filelist.readline() + filename=os.path.abspath(filename.rstrip('\n')) + else: + for filename in args.verify: + filename=filename + if filename.endswith('.nsp') or filename.endswith('.nsx'): + try: + f = Fs.Nsp(filename, 'rb') + check,feed=f.verify() + verdict,headerlist,feed=f.verify_sig(feed,tmpfolder) + if verdict == True: + pass + except BaseException as e: + Print.error('Exception: ' + str(e)) + Status.close()