From 51398a6ea9a5483567c45531e7b3a5f50730db84 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko Date: Wed, 7 Dec 2022 04:22:23 +0300 Subject: [PATCH] Small fixes, minor updates. --- HOWTO.txt | 11 +- README.md | 6 +- src/main/java/libKonogonka/Converter.java | 7 + src/main/java/libKonogonka/RainbowDump.java | 45 ++-- .../libKonogonka/Tools/NCA/NCAProvider.java | 119 ++++------ .../Tools/RomFs/FileSystemEntry.java | 12 +- .../Tools/RomFs/RomFsConstruct.java | 13 +- src/main/resources/log4j2.properties | 3 +- .../RomFsDecrypted/NSPpfs0EncryptedTest.java | 222 ++++++++++++++++++ .../RomFsDecrypted/Pfs0EncryptedTest.java | 10 +- .../libKonogonka/RomFsDecrypted/XciTest.java | 31 ++- 11 files changed, 373 insertions(+), 106 deletions(-) create mode 100644 src/test/java/libKonogonka/RomFsDecrypted/NSPpfs0EncryptedTest.java diff --git a/HOWTO.txt b/HOWTO.txt index f093d27..88e56ea 100644 --- a/HOWTO.txt +++ b/HOWTO.txt @@ -6,4 +6,13 @@ mvn install:install-file \ -DartifactId=libKonogonka \ -Dversion=0.1 \ -Dpackaging=jar \ - -DgeneratePom=true \ No newline at end of file + -DgeneratePom=true + + +-Dlog4j.configurationFile=/home/loper/Projects/libKonogonka/src/main/resources/log4j2.properties + +- NCA flow: +NCAProvider -> NCAContent -> RomFsEncryptedProvider (needs to know location of the lv6header location) + Reads Section Block to understand boundaries Reads lv6Header via \RomFsEncryptedConstruct' + Reads Header Table Entry to get meta info 4 section Provides stream/streams with requested files via 'RomFsEncrytedContenRetrieve' + diff --git a/README.md b/README.md index 8d09e32..45eaa7f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,11 @@ ![License](https://img.shields.io/badge/License-GPLv3-blue.svg) [![Build Status](https://ci.redrise.ru/api/badges/desu/libKonogonka/status.svg)](https://ci.redrise.ru/desu/libKonogonka) -Library to work with NS-specific files / filesystem images. Ex-backend of [konogonka](https://github.com/developersu/konogonka) ([independent src location](https://git.redrise.ru/desu/konogonka)) +Library to work with NS-specific files / filesystem images. Ex-backend of [konogonka](https://github.com/developersu/konogonka) ([independent source location](https://git.redrise.ru/desu/konogonka)) + +### Let's stay in touch + +You can get this application from independent source location: [https://git.redrise.ru/desu/libKonogonka](https://git.redrise.ru/desu/libKonogonka) ### License diff --git a/src/main/java/libKonogonka/Converter.java b/src/main/java/libKonogonka/Converter.java index dcf79d1..8672384 100644 --- a/src/main/java/libKonogonka/Converter.java +++ b/src/main/java/libKonogonka/Converter.java @@ -18,11 +18,18 @@ */ package libKonogonka; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import java.nio.ByteBuffer; import java.nio.ByteOrder; public class Converter { + private final static Logger log = LogManager.getLogger(Converter.class); + public static int getLEint(byte[] bytes, int fromOffset){ + if (fromOffset < 0 || fromOffset >= bytes.length) + log.debug("\tLen =" + bytes.length + "\tFrom =" + fromOffset); return ByteBuffer.wrap(bytes, fromOffset, 0x4).order(ByteOrder.LITTLE_ENDIAN).getInt(); } diff --git a/src/main/java/libKonogonka/RainbowDump.java b/src/main/java/libKonogonka/RainbowDump.java index 997e680..3a812db 100644 --- a/src/main/java/libKonogonka/RainbowDump.java +++ b/src/main/java/libKonogonka/RainbowDump.java @@ -18,12 +18,17 @@ */ package libKonogonka; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import java.nio.charset.StandardCharsets; /** * Debug tool like hexdump <3 */ public class RainbowDump { + private final static Logger log = LogManager.getLogger(Converter.class); + private static final String ANSI_RESET = "\u001B[0m"; private static final String ANSI_BLACK = "\u001B[30m"; private static final String ANSI_RED = "\u001B[31m"; @@ -34,51 +39,56 @@ public class RainbowDump { private static final String ANSI_CYAN = "\u001B[36m"; private static final String ANSI_WHITE = "\u001B[37m"; - + private static StringBuilder stringBuilder; public static void hexDumpUTF8(byte[] byteArray){ + stringBuilder = new StringBuilder(); if (byteArray == null || byteArray.length == 0) return; int k = 0; - System.out.printf("%s%08x %s", ANSI_BLUE, 0, ANSI_RESET); + stringBuilder.append(String.format("%s%08x %s", ANSI_BLUE, 0, ANSI_RESET)); for (int i = 0; i < byteArray.length; i++) { if (k == 8) - System.out.print(" "); + stringBuilder.append(" "); if (k == 16){ - System.out.print(ANSI_GREEN+"| "+ANSI_RESET); + stringBuilder.append(ANSI_GREEN+"| "+ANSI_RESET); printChars(byteArray, i); - System.out.println(); - System.out.printf("%s%08x %s", ANSI_BLUE, i, ANSI_RESET); + stringBuilder.append("\n") + .append(String.format("%s%08x %s", ANSI_BLUE, i, ANSI_RESET)); k = 0; } - System.out.printf("%02x ", byteArray[i]); + stringBuilder.append(String.format("%02x ", byteArray[i])); k++; } int paddingSize = 16 - (byteArray.length % 16); if (paddingSize != 16) { for (int i = 0; i < paddingSize; i++) { - System.out.print(" "); + stringBuilder.append(" "); } if (paddingSize > 7) { - System.out.print(" "); + stringBuilder.append(" "); } } - System.out.print(ANSI_GREEN+"| "+ANSI_RESET); + stringBuilder.append(ANSI_GREEN+"| "+ANSI_RESET); printChars(byteArray, byteArray.length); - System.out.println(); - System.out.print(ANSI_RESET+new String(byteArray, StandardCharsets.UTF_8)+"\n"); + stringBuilder.append("\n") + .append(ANSI_RESET) + .append(new String(byteArray, StandardCharsets.UTF_8)) + .append("\n"); + + log.debug(stringBuilder.toString()); } private static void printChars(byte[] byteArray, int pointer){ for (int j = pointer-16; j < pointer; j++){ if ((byteArray[j] > 21) && (byteArray[j] < 126)) // man ascii - System.out.print((char) byteArray[j]); + stringBuilder.append((char) byteArray[j]); else if (byteArray[j] == 0x0a) - System.out.print("↲"); //"␤" + stringBuilder.append("↲"); //"␤" else if (byteArray[j] == 0x0d) - System.out.print("←"); // "␍" + stringBuilder.append("←"); // "␍" else - System.out.print("."); + stringBuilder.append("."); } } @@ -107,4 +117,7 @@ public class RainbowDump { public static String formatDecHexString(long value){ return String.format("%-20d 0x%x", value, value); } + public static String formatDecHexString(int value){ + return String.format("%-20d 0x%x", value, value); + } } diff --git a/src/main/java/libKonogonka/Tools/NCA/NCAProvider.java b/src/main/java/libKonogonka/Tools/NCA/NCAProvider.java index f515e92..3eaf6da 100644 --- a/src/main/java/libKonogonka/Tools/NCA/NCAProvider.java +++ b/src/main/java/libKonogonka/Tools/NCA/NCAProvider.java @@ -18,6 +18,7 @@ */ package libKonogonka.Tools.NCA; +import libKonogonka.Converter; import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader; import libKonogonka.exceptions.EmptySectionException; import libKonogonka.xtsaes.XTSAESCipher; @@ -45,7 +46,7 @@ public class NCAProvider { // Header private byte[] rsa2048one; private byte[] rsa2048two; - private String magicnum; + private String magicNumber; private byte systemOrGcIndicator; private byte contentType; private byte cryptoType1; // keyblob index. Considering as number within application/ocean/system @@ -61,41 +62,18 @@ public class NCAProvider { private byte cryptoTypeReal; - private byte[] sha256hash0; - private byte[] sha256hash1; - private byte[] sha256hash2; - private byte[] sha256hash3; - - private byte[] encryptedKey0; - private byte[] encryptedKey1; - private byte[] encryptedKey2; - private byte[] encryptedKey3; - - private byte[] decryptedKey0; - private byte[] decryptedKey1; - private byte[] decryptedKey2; - private byte[] decryptedKey3; - - private NCAHeaderTableEntry tableEntry0; - private NCAHeaderTableEntry tableEntry1; - private NCAHeaderTableEntry tableEntry2; - private NCAHeaderTableEntry tableEntry3; - - private NcaFsHeader sectionBlock0; - private NcaFsHeader sectionBlock1; - private NcaFsHeader sectionBlock2; - private NcaFsHeader sectionBlock3; - - private NCAContent ncaContent0; - private NCAContent ncaContent1; - private NCAContent ncaContent2; - private NCAContent ncaContent3; + private byte[] sha256hash0, sha256hash1, sha256hash2, sha256hash3, + encryptedKey0, encryptedKey1, encryptedKey2, encryptedKey3, + decryptedKey0, decryptedKey1, decryptedKey2, decryptedKey3; + private NCAHeaderTableEntry tableEntry0, tableEntry1, tableEntry2, tableEntry3; + private NcaFsHeader sectionBlock0, sectionBlock1, sectionBlock2, sectionBlock3; + private NCAContent ncaContent0, ncaContent1, ncaContent2, ncaContent3; public NCAProvider(File file, HashMap keys) throws Exception{ this(file, keys, 0); } - public NCAProvider (File file, HashMap keys, long offsetPosition) throws Exception{ + public NCAProvider(File file, HashMap keys, long offsetPosition) throws Exception{ this.file = file; this.keys = keys; String header_key = keys.get("header_key"); @@ -128,11 +106,11 @@ public class NCAProvider { System.arraycopy(decryptedSequence, 0, decryptedHeader, i * 0x200, 0x200); } - getHeader(decryptedHeader); + setupHeader(decryptedHeader); raf.close(); - getNCAContent(); + setupNCAContent(); /*//--------------------------------------------------------------------- FileInputStream fis = new FileInputStream(file); try (BufferedOutputStream bos = new BufferedOutputStream(Files.newOutputStream(Paths.get("/tmp/decrypted.nca")))){ @@ -159,16 +137,16 @@ public class NCAProvider { } return data; } - private void getHeader(byte[] decryptedData) throws Exception{ + private void setupHeader(byte[] decryptedData) throws Exception{ rsa2048one = Arrays.copyOfRange(decryptedData, 0, 0x100); rsa2048two = Arrays.copyOfRange(decryptedData, 0x100, 0x200); - magicnum = new String(decryptedData, 0x200, 0x4, StandardCharsets.US_ASCII); + magicNumber = new String(decryptedData, 0x200, 0x4, StandardCharsets.US_ASCII); systemOrGcIndicator = decryptedData[0x204]; contentType = decryptedData[0x205]; cryptoType1 = decryptedData[0x206]; keyIndex = decryptedData[0x207]; ncaSize = getLElong(decryptedData, 0x208); - titleId = Arrays.copyOfRange(decryptedData, 0x210, 0x218); + titleId = Converter.flip(Arrays.copyOfRange(decryptedData, 0x210, 0x218)); contentIndx = Arrays.copyOfRange(decryptedData, 0x218, 0x21C); sdkVersion = Arrays.copyOfRange(decryptedData, 0x21c, 0x220); cryptoType2 = decryptedData[0x220]; @@ -198,8 +176,8 @@ public class NCAProvider { cryptoTypeReal -= 1; //If nca3 proceed - if (! magicnum.equalsIgnoreCase("NCA3")) - throw new Exception("Not supported data type: "+magicnum+". Only NCA3 supported"); + if (! magicNumber.equalsIgnoreCase("NCA3")) + throw new Exception("Not supported data type: "+ magicNumber +". Only NCA3 supported"); // Decrypt keys if encrypted if (Arrays.equals(rightsId, new byte[0x10])) { String keyAreaKey; @@ -262,35 +240,34 @@ public class NCAProvider { throw new Exception(exceptionStringBuilder.toString()); } - private void getNCAContent() throws Exception{ - byte[] key; - - // If empty Rights ID - if (Arrays.equals(rightsId, new byte[0x10])) { - key = decryptedKey2; // NOTE: Just remember this dumb hack - } - else { - try { - byte[] rightsIDkey = hexStrToByteArray(keys.get(byteArrToHexString(rightsId))); // throws NullPointerException - - SecretKeySpec skSpec = new SecretKeySpec( - hexStrToByteArray(keys.get(String.format("titlekek_%02x", cryptoTypeReal)) - ), "AES"); - Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding"); - cipher.init(Cipher.DECRYPT_MODE, skSpec); - key = cipher.doFinal(rightsIDkey); - } - catch (Exception e){ - throw new Exception("No title.keys loaded for '"+ - String.format("titlekek_%02x", cryptoTypeReal)+"' or '"+byteArrToHexString(rightsId)+"'? ("+e+")", e); - } - } - getNcaContentByNumber(0, key); - getNcaContentByNumber(1, key); - getNcaContentByNumber(2, key); - getNcaContentByNumber(3, key); + private void setupNCAContent() throws Exception{ + byte[] key = calculateKey(); + + setupNcaContentByNumber(0, key); + setupNcaContentByNumber(1, key); + setupNcaContentByNumber(2, key); + setupNcaContentByNumber(3, key); } - private void getNcaContentByNumber(int number, byte[] key){ + private byte[] calculateKey() throws Exception{ + try { + if (Arrays.equals(rightsId, new byte[0x10])) // If empty Rights ID + return decryptedKey2; // NOTE: Just remember this dumb hack + + byte[] rightsIdKey = hexStrToByteArray(keys.get(byteArrToHexString(rightsId))); // throws NullPointerException + + SecretKeySpec skSpec = new SecretKeySpec( + hexStrToByteArray(keys.get(String.format("titlekek_%02x", cryptoTypeReal)) + ), "AES"); + Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding"); + cipher.init(Cipher.DECRYPT_MODE, skSpec); + return cipher.doFinal(rightsIdKey); + } + catch (Exception e){ + throw new Exception("No title.keys loaded for '"+ + String.format("titlekek_%02x", cryptoTypeReal)+"' or '"+byteArrToHexString(rightsId)+"'? ("+e+")", e); + } + } + private void setupNcaContentByNumber(int number, byte[] key){ try { switch (number) { case 0: @@ -313,9 +290,11 @@ public class NCAProvider { } } + // -======================= API =======================- + public byte[] getRsa2048one() { return rsa2048one; } public byte[] getRsa2048two() { return rsa2048two; } - public String getMagicnum() { return magicnum; } + public String getMagicnum() { return magicNumber; } public byte getSystemOrGcIndicator() { return systemOrGcIndicator; } public byte getContentType() { return contentType; } public byte getCryptoType1() { return cryptoType1; } @@ -345,7 +324,7 @@ public class NCAProvider { public byte[] getDecryptedKey3() { return decryptedKey3; } /** * Get NCA Hedaer Table Entry for selected id - * @param id should be 0-3 + * @param id must be 0-3 * */ public NCAHeaderTableEntry getTableEntry(int id) throws Exception{ switch (id) { @@ -367,7 +346,7 @@ public class NCAProvider { public NCAHeaderTableEntry getTableEntry3() { return tableEntry3; } /** * Get NCA Section Block for selected section - * @param id should be 0-3 + * @param id must be 0-3 * */ public NcaFsHeader getSectionBlock(int id) throws Exception{ switch (id) { @@ -396,7 +375,7 @@ public class NCAProvider { } /** * Get content for the selected section - * @param sectionNumber should be 0-3 + * @param sectionNumber must be 0-3 * */ public NCAContent getNCAContentProvider(int sectionNumber) throws Exception{ switch (sectionNumber) { diff --git a/src/main/java/libKonogonka/Tools/RomFs/FileSystemEntry.java b/src/main/java/libKonogonka/Tools/RomFs/FileSystemEntry.java index 8909666..b2f5e14 100644 --- a/src/main/java/libKonogonka/Tools/RomFs/FileSystemEntry.java +++ b/src/main/java/libKonogonka/Tools/RomFs/FileSystemEntry.java @@ -20,7 +20,6 @@ package libKonogonka.Tools.RomFs; import libKonogonka.Converter; -import libKonogonka.Tools.NCA.NCAContent; import libKonogonka.Tools.RomFs.view.FileSystemTreeViewMaker; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -32,7 +31,7 @@ import java.util.Comparator; import java.util.List; public class FileSystemEntry { - private final static Logger log = LogManager.getLogger(NCAContent.class); + private final static Logger log = LogManager.getLogger(FileSystemEntry.class); private boolean directoryFlag; private String name; @@ -137,7 +136,7 @@ public class FileSystemEntry { nextHashTableBucketDirectoryOffset = Converter.getLEint(dirsMetadataTable, i); /* if (nextHashTableBucketDirectoryOffset < 0) { - System.out.println("nextHashTableBucketDirectoryOffset: "+ nextHashTableBucketDirectoryOffset); + log.debug("nextHashTableBucketDirectoryOffset: "+ nextHashTableBucketDirectoryOffset); } //*/ i += 4; @@ -149,7 +148,7 @@ public class FileSystemEntry { } else { dirName = ""; - System.out.println("dirName: "+dirNameLength); + // log.debug("Dir Name Length: "+dirNameLength); } //i += getRealNameSize(dirNameLength); } @@ -197,12 +196,12 @@ public class FileSystemEntry { fileName = new String(Arrays.copyOfRange(filesMetadataTable, i, i + fileNameLength), StandardCharsets.UTF_8); } catch (Exception e){ - System.out.println("fileName sizes are: "+filesMetadataTable.length+"\t"+i+"\t"+i + fileNameLength+"\t\t"+nextHashTableBucketFileOffset); + log.debug("fileName sizes are: "+filesMetadataTable.length+"\t"+i+"\t"+i + fileNameLength+"\t\t"+nextHashTableBucketFileOffset); } } else { fileName = ""; - System.out.println("fileName: "+fileNameLength); + //log.debug("File Name Length: "+fileNameLength); } //i += getRealNameSize(fileNameLength); } @@ -214,5 +213,4 @@ public class FileSystemEntry { public void printTreeForDebug(){ log.debug(FileSystemTreeViewMaker.make(content, 100)); } - } diff --git a/src/main/java/libKonogonka/Tools/RomFs/RomFsConstruct.java b/src/main/java/libKonogonka/Tools/RomFs/RomFsConstruct.java index 347e504..0cc329a 100644 --- a/src/main/java/libKonogonka/Tools/RomFs/RomFsConstruct.java +++ b/src/main/java/libKonogonka/Tools/RomFs/RomFsConstruct.java @@ -21,15 +21,13 @@ package libKonogonka.Tools.RomFs; import libKonogonka.Converter; import libKonogonka.ctraes.AesCtrBufferedInputStream; import libKonogonka.ctraes.AesCtrDecryptSimple; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import java.io.BufferedInputStream; import java.io.File; import java.nio.file.Files; public class RomFsConstruct { - private final static Logger log = LogManager.getLogger(RomFsConstruct.class); + //private final static Logger log = LogManager.getLogger(RomFsConstruct.class); private Level6Header header; @@ -66,8 +64,6 @@ public class RomFsConstruct { this.file = file; this.level6Offset = level6Offset; this.offsetPositionInFile = ncaOffset + (mediaStartOffset * 0x200); - // In 512-blocks - // In 512-blocks this.stream = new AesCtrBufferedInputStream( decryptor, ncaOffset, @@ -141,7 +137,12 @@ public class RomFsConstruct { } private void constructRootFilesystemEntry() throws Exception{ - rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable); + try { + rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable); + } + catch (Exception e){ + throw new Exception("File: " + file.getName(), e); + } } private void skipBytes(long size) throws Exception{ diff --git a/src/main/resources/log4j2.properties b/src/main/resources/log4j2.properties index 48ea019..d631910 100644 --- a/src/main/resources/log4j2.properties +++ b/src/main/resources/log4j2.properties @@ -23,7 +23,8 @@ rootLogger.appenderRef.stdout.ref = consoleLogger appender.console.type = Console appender.console.name = consoleLogger appender.console.layout.type = PatternLayout -appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n +#appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n +appender.console.layout.pattern = %d{HH:mm:ss.SSS} libKonogonka %-5p %c{1}:%L - %m%n ################################################## # # Enable log to files # rootLogger.appenderRef.rolling.ref = fileLogger diff --git a/src/test/java/libKonogonka/RomFsDecrypted/NSPpfs0EncryptedTest.java b/src/test/java/libKonogonka/RomFsDecrypted/NSPpfs0EncryptedTest.java new file mode 100644 index 0000000..35f4598 --- /dev/null +++ b/src/test/java/libKonogonka/RomFsDecrypted/NSPpfs0EncryptedTest.java @@ -0,0 +1,222 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libKonogonka is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libKonogonka. If not, see . + */ +package libKonogonka.RomFsDecrypted; + +import libKonogonka.KeyChainHolder; +import libKonogonka.RainbowDump; +import libKonogonka.TitleKeyChainHolder; +import libKonogonka.Tools.NCA.NCAProvider; +import libKonogonka.Tools.PFS0.PFS0Provider; +import libKonogonka.Tools.PFS0.PFS0subFile; +import libKonogonka.ctraes.AesCtrBufferedInputStream; +import libKonogonka.ctraes.AesCtrDecryptSimple; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.*; +import java.nio.file.Files; +import java.util.HashMap; + +public class NSPpfs0EncryptedTest { + private static final String keysFileLocation = "./FilesForTests/prod.keys"; + private static final String titleFileLocation = "./FilesForTests/simple_nsp.title_key"; + private static final String xci_header_keyFileLocation = "./FilesForTests/xci_header_key.txt"; + private static final String nspFileLocation = "./FilesForTests/sample.nsp"; + private static KeyChainHolder keyChainHolder; + private static PFS0Provider pfs0Provider; + private static NCAProvider ncaProvider; + + @Disabled + @DisplayName("NSP PFS0 Encrypted test") + @Test + void pfs0test() throws Exception{ + BufferedReader br = new BufferedReader(new FileReader(xci_header_keyFileLocation)); + String keyValue = br.readLine(); + br.close(); + + if (keyValue == null) + throw new Exception("Unable to retrieve xci_header_key"); + + keyValue = keyValue.trim(); + keyChainHolder = new KeyChainHolder(keysFileLocation, keyValue); + + TitleKeyChainHolder titleKeyChainHolder = new TitleKeyChainHolder(titleFileLocation); + + HashMap finalKeysSet = keyChainHolder.getRawKeySet(); + finalKeysSet.putAll(titleKeyChainHolder.getKeySet()); + /* + for (Map.Entry e: titleKeyChainHolder.getKeySet().entrySet()){ + System.out.println(e.getKey()+" = "+e.getValue()); + } + for (Map.Entry e: finalKeysSet.entrySet()){ + System.out.println(e.getKey()+" = "+e.getValue()); + } + */ + File nspFile = new File(nspFileLocation); + + pfs0Provider = new PFS0Provider(nspFile); + + for (PFS0subFile subFile : pfs0Provider.getPfs0subFiles()) { + if (subFile.getName().endsWith("890.nca")) { + System.out.println("File found: "+subFile.getName()); + ncaProvider = new NCAProvider(nspFile, finalKeysSet, pfs0Provider.getRawFileDataStart()+subFile.getOffset()); + break; + } + } + //ncaProvider = new NCAProvider(new File(ncaFileLocation), keyChainHolder.getRawKeySet()); + + pfs0Validation(); + + AesCtrBufferedInputStreamTest(); + } + + void pfs0Validation() throws Exception{ + for (byte i = 0; i < 4; i++){ + System.out.println("..:: TEST SECTION #"+i+" ::.."); + if (ncaProvider.getSectionBlock(i).getFsType() == 1 && + ncaProvider.getSectionBlock(i).getHashType() == 2 && + ncaProvider.getSectionBlock(i).getCryptoType() == 3){ + //ncaProvider.getNCAContentProvider(i).getPfs0().printDebug(); + //ncaProvider.getSectionBlock(i).printDebug(); + return; + } + } + } + + private AesCtrDecryptSimple decryptSimple; + long ACBISoffsetPosition; + long ACBISmediaStartOffset; + long ACBISmediaEndOffset; + + long offsetPosition; + + void AesCtrBufferedInputStreamTest() throws Exception { + File nca = new File(nspFileLocation); // TODO:NOTICE + PFS0subFile[] subfiles = ncaProvider.getNCAContentProvider(0).getPfs0().getPfs0subFiles(); + + offsetPosition = ncaProvider.getTableEntry0().getMediaStartOffset()*0x200 + + ncaProvider.getNCAContentProvider(0).getPfs0().getRawFileDataStart(); + System.out.println("\t============================================================="); + System.out.println("\tNCA SIZE: "+ RainbowDump.formatDecHexString(nca.length())); + System.out.println("\tPFS0 Offset(get) "+RainbowDump.formatDecHexString(ncaProvider.getSectionBlock0().getSuperBlockPFS0().getPfs0offset())); + System.out.println("\tPFS0 MediaStart (* 0x200) "+RainbowDump.formatDecHexString(ncaProvider.getTableEntry0().getMediaStartOffset()*0x200)); + System.out.println("\tPFS0 MediaEnd (* 0x200) "+RainbowDump.formatDecHexString(ncaProvider.getTableEntry0().getMediaEndOffset()*0x200)); + System.out.println("\tPFS0 Offset+MediaBlockStart: "+RainbowDump.formatDecHexString(offsetPosition)); + System.out.println("\tRAW Offset: "+RainbowDump.formatDecHexString(ncaProvider.getNCAContentProvider(0).getPfs0().getRawFileDataStart())); + System.out.println("\tHashTableSize: "+RainbowDump.formatDecHexString(ncaProvider.getSectionBlock0().getSuperBlockPFS0().getHashTableSize())); + for (PFS0subFile subFile : subfiles){ + System.out.println("\n\tEntry Name: "+subFile.getName()); + System.out.println("\tEntry Offset: "+RainbowDump.formatDecHexString(subFile.getOffset())); + System.out.println("\tEntry Size: "+RainbowDump.formatDecHexString(subFile.getSize())); + } + System.out.println("\t============================================================="); + + ACBISoffsetPosition = 0; + ACBISmediaStartOffset = ncaProvider.getTableEntry0().getMediaStartOffset(); + ACBISmediaEndOffset = ncaProvider.getTableEntry0().getMediaEndOffset(); +/* + decryptSimple = new AesCtrDecryptSimple( + ncaProvider.getDecryptedKey2(), + ncaProvider.getSectionBlock0().getSectionCTR(), + ncaProvider.getTableEntry0().getMediaStartOffset()*0x200); + + for (PFS0subFile subFile : subfiles){ + exportContentLegacy(subFile, "/tmp/legacy_NSP_PFS0"); + } + + */ + //---------------------------------------------------------------------- + for (PFS0subFile subFile : subfiles) { + pfs0Provider.exportContent("/tmp/2_brandnew_NSP_PFS0", subFile.getName()); + } + } + + private void exportContent(PFS0subFile entry, String saveToLocation) throws Exception{ + File contentFile = new File(saveToLocation + entry.getName()); + + BufferedOutputStream extractedFileBOS = new BufferedOutputStream(Files.newOutputStream(contentFile.toPath())); + //--- + InputStream is = Files.newInputStream(new File(nspFileLocation).toPath()); //TODO: NOTICE + + AesCtrBufferedInputStream aesCtrBufferedInputStream = new AesCtrBufferedInputStream( + decryptSimple, + ACBISoffsetPosition, + ACBISmediaStartOffset, + ACBISmediaEndOffset, + is); + + //long offsetToSkip = entry.getOffset() + ncaProvider.getNCAContentProvider(0).getPfs0().getRawFileDataStart(); + long offsetToSkip = offsetPosition+entry.getOffset(); + System.out.println("\nOffsets"+ + "\nRAW: "+ncaProvider.getNCAContentProvider(0).getPfs0().getRawFileDataStart()+ + "\nPfs0 offset: "+offsetPosition+ + "\nentry.getOffset(): "+entry.getOffset()+ + "\n"); + + if (offsetToSkip != aesCtrBufferedInputStream.skip(offsetToSkip)) + throw new Exception("Can't skip "+ + ncaProvider.getSectionBlock0().getSuperBlockPFS0().getPfs0offset()+ + "("+entry.getOffset()+ + " + "+ + ncaProvider.getNCAContentProvider(0).getPfs0().getRawFileDataStart()+")"); + + + int blockSize = 0x200; + if (entry.getSize() < 0x200) + blockSize = (int) entry.getSize(); + + long i = 0; + byte[] block = new byte[blockSize]; + + int actuallyRead; + + while (true) { + if ((actuallyRead = aesCtrBufferedInputStream.read(block)) != blockSize) + throw new Exception("Read failure. Block Size: "+blockSize+", actuallyRead: "+actuallyRead); + extractedFileBOS.write(block); + i += blockSize; + if ((i + blockSize) > entry.getSize()) { + blockSize = (int) (entry.getSize() - i); + if (blockSize == 0) + break; + block = new byte[blockSize]; + } + } + //--- + extractedFileBOS.close(); + } + + private void exportContentLegacy(PFS0subFile entry, String saveToLocation) throws Exception { + File contentFile = new File(saveToLocation + entry.getName()); + + BufferedOutputStream extractedFileBOS = new BufferedOutputStream(new FileOutputStream(contentFile)); + PipedInputStream pis = ncaProvider.getNCAContentProvider(0).getPfs0().getProviderSubFilePipedInpStream(entry.getName()); + + byte[] readBuf = new byte[0x200]; // 8mb NOTE: consider switching to 1mb 1048576 + int readSize; + + while ((readSize = pis.read(readBuf)) > -1) { + extractedFileBOS.write(readBuf, 0, readSize); + readBuf = new byte[0x200]; + } + + extractedFileBOS.close(); + } +} diff --git a/src/test/java/libKonogonka/RomFsDecrypted/Pfs0EncryptedTest.java b/src/test/java/libKonogonka/RomFsDecrypted/Pfs0EncryptedTest.java index 7f76449..0858a85 100644 --- a/src/test/java/libKonogonka/RomFsDecrypted/Pfs0EncryptedTest.java +++ b/src/test/java/libKonogonka/RomFsDecrypted/Pfs0EncryptedTest.java @@ -21,8 +21,8 @@ package libKonogonka.RomFsDecrypted; import libKonogonka.KeyChainHolder; import libKonogonka.RainbowDump; import libKonogonka.Tools.NCA.NCAProvider; +import libKonogonka.Tools.PFS0.IPFS0Provider; import libKonogonka.Tools.PFS0.PFS0subFile; -import libKonogonka.Tools.RomFs.FileSystemEntry; import libKonogonka.ctraes.AesCtrBufferedInputStream; import libKonogonka.ctraes.AesCtrDecryptSimple; import org.junit.jupiter.api.*; @@ -107,13 +107,17 @@ public class Pfs0EncryptedTest { ncaProvider.getDecryptedKey2(), ncaProvider.getSectionBlock0().getSectionCTR(), ncaProvider.getTableEntry0().getMediaStartOffset()*0x200); - +/* for (PFS0subFile subFile : subfiles){ exportContentLegacy(subFile, "/tmp/legacy_PFS0"); } + + */ + IPFS0Provider pfs0Provider = ncaProvider.getNCAContentProvider(0).getPfs0(); //---------------------------------------------------------------------- for (PFS0subFile subFile : subfiles) { - exportContent(subFile, "/tmp/brandnew_PFS0"); + System.out.println("Exporting "+subFile.getName()); + System.out.println("Result: "+pfs0Provider.exportContent("/tmp/1_brandnew_PFS0", subFile.getName())); } } diff --git a/src/test/java/libKonogonka/RomFsDecrypted/XciTest.java b/src/test/java/libKonogonka/RomFsDecrypted/XciTest.java index 479b907..ef042c3 100644 --- a/src/test/java/libKonogonka/RomFsDecrypted/XciTest.java +++ b/src/test/java/libKonogonka/RomFsDecrypted/XciTest.java @@ -18,6 +18,10 @@ */ package libKonogonka.RomFsDecrypted; +import libKonogonka.KeyChainHolder; +import libKonogonka.Tools.NCA.NCAProvider; +import libKonogonka.Tools.XCI.HFS0File; +import libKonogonka.Tools.XCI.HFS0Provider; import libKonogonka.Tools.XCI.XCIProvider; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; @@ -30,19 +34,28 @@ import java.io.FileReader; // log.fatal("Configuration File Defined To Be :: "+System.getProperty("log4j.configurationFile")); public class XciTest { + private static final String keysFileLocation = "./FilesForTests/prod.keys"; + private static final String xci_header_keyFileLocation = "./FilesForTests/xci_header_key.txt"; private static final String decryptedFileAbsolutePath = "./FilesForTests/sample.xci"; private File xciFile; XCIProvider provider; String xci_header_key; + HFS0Provider hfs0Provider; + + private static KeyChainHolder keyChainHolder; @Disabled - @DisplayName("RomFsDecryptedProvider: tests") + @DisplayName("XciTest") @Test void romFsValidation() throws Exception{ makeFile(); getXciHeaderKey(); + keyChainHolder = new KeyChainHolder(keysFileLocation, xci_header_key); makeProvider(); + + getHfsSecure(); + getFirstNca3(); } void getXciHeaderKey() throws Exception{ @@ -63,4 +76,20 @@ public class XciTest { void makeProvider() throws Exception{ provider = new XCIProvider(xciFile, xci_header_key); } + + void getHfsSecure(){ + hfs0Provider = provider.getHfs0ProviderSecure(); + } + + void getFirstNca3() throws Exception{ + HFS0File hfs0File = hfs0Provider.getHfs0Files()[0]; + + System.out.println(hfs0File.getName() +" "+ hfs0Provider.getRawFileDataStart()+" "+hfs0File.getOffset()+ " " + +(hfs0Provider.getRawFileDataStart() + hfs0File.getOffset())); + + NCAProvider ncaProvider = new NCAProvider(xciFile, keyChainHolder.getRawKeySet(), + hfs0Provider.getRawFileDataStart() + + hfs0File.getOffset()); + //ncaProvider. + } } \ No newline at end of file