Small fixes, minor updates.

This commit is contained in:
Dmitry Isaenko 2022-12-07 04:22:23 +03:00
parent f058905e2f
commit 51398a6ea9
11 changed files with 373 additions and 106 deletions

View file

@ -7,3 +7,12 @@ mvn install:install-file \
-Dversion=0.1 \ -Dversion=0.1 \
-Dpackaging=jar \ -Dpackaging=jar \
-DgeneratePom=true -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'

View file

@ -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) ![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 ### License

View file

@ -18,11 +18,18 @@
*/ */
package libKonogonka; package libKonogonka;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
public class Converter { public class Converter {
private final static Logger log = LogManager.getLogger(Converter.class);
public static int getLEint(byte[] bytes, int fromOffset){ 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(); return ByteBuffer.wrap(bytes, fromOffset, 0x4).order(ByteOrder.LITTLE_ENDIAN).getInt();
} }

View file

@ -18,12 +18,17 @@
*/ */
package libKonogonka; package libKonogonka;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
/** /**
* Debug tool like hexdump <3 * Debug tool like hexdump <3
*/ */
public class RainbowDump { 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_RESET = "\u001B[0m";
private static final String ANSI_BLACK = "\u001B[30m"; private static final String ANSI_BLACK = "\u001B[30m";
private static final String ANSI_RED = "\u001B[31m"; 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_CYAN = "\u001B[36m";
private static final String ANSI_WHITE = "\u001B[37m"; private static final String ANSI_WHITE = "\u001B[37m";
private static StringBuilder stringBuilder;
public static void hexDumpUTF8(byte[] byteArray){ public static void hexDumpUTF8(byte[] byteArray){
stringBuilder = new StringBuilder();
if (byteArray == null || byteArray.length == 0) if (byteArray == null || byteArray.length == 0)
return; return;
int k = 0; 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++) { for (int i = 0; i < byteArray.length; i++) {
if (k == 8) if (k == 8)
System.out.print(" "); stringBuilder.append(" ");
if (k == 16){ if (k == 16){
System.out.print(ANSI_GREEN+"| "+ANSI_RESET); stringBuilder.append(ANSI_GREEN+"| "+ANSI_RESET);
printChars(byteArray, i); printChars(byteArray, i);
System.out.println(); stringBuilder.append("\n")
System.out.printf("%s%08x %s", ANSI_BLUE, i, ANSI_RESET); .append(String.format("%s%08x %s", ANSI_BLUE, i, ANSI_RESET));
k = 0; k = 0;
} }
System.out.printf("%02x ", byteArray[i]); stringBuilder.append(String.format("%02x ", byteArray[i]));
k++; k++;
} }
int paddingSize = 16 - (byteArray.length % 16); int paddingSize = 16 - (byteArray.length % 16);
if (paddingSize != 16) { if (paddingSize != 16) {
for (int i = 0; i < paddingSize; i++) { for (int i = 0; i < paddingSize; i++) {
System.out.print(" "); stringBuilder.append(" ");
} }
if (paddingSize > 7) { 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); printChars(byteArray, byteArray.length);
System.out.println(); stringBuilder.append("\n")
System.out.print(ANSI_RESET+new String(byteArray, StandardCharsets.UTF_8)+"\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){ private static void printChars(byte[] byteArray, int pointer){
for (int j = pointer-16; j < pointer; j++){ for (int j = pointer-16; j < pointer; j++){
if ((byteArray[j] > 21) && (byteArray[j] < 126)) // man ascii 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) else if (byteArray[j] == 0x0a)
System.out.print(""); //"" stringBuilder.append(""); //""
else if (byteArray[j] == 0x0d) else if (byteArray[j] == 0x0d)
System.out.print(""); // "" stringBuilder.append(""); // ""
else else
System.out.print("."); stringBuilder.append(".");
} }
} }
@ -107,4 +117,7 @@ public class RainbowDump {
public static String formatDecHexString(long value){ public static String formatDecHexString(long value){
return String.format("%-20d 0x%x", value, value); return String.format("%-20d 0x%x", value, value);
} }
public static String formatDecHexString(int value){
return String.format("%-20d 0x%x", value, value);
}
} }

View file

@ -18,6 +18,7 @@
*/ */
package libKonogonka.Tools.NCA; package libKonogonka.Tools.NCA;
import libKonogonka.Converter;
import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader; import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader;
import libKonogonka.exceptions.EmptySectionException; import libKonogonka.exceptions.EmptySectionException;
import libKonogonka.xtsaes.XTSAESCipher; import libKonogonka.xtsaes.XTSAESCipher;
@ -45,7 +46,7 @@ public class NCAProvider {
// Header // Header
private byte[] rsa2048one; private byte[] rsa2048one;
private byte[] rsa2048two; private byte[] rsa2048two;
private String magicnum; private String magicNumber;
private byte systemOrGcIndicator; private byte systemOrGcIndicator;
private byte contentType; private byte contentType;
private byte cryptoType1; // keyblob index. Considering as number within application/ocean/system private byte cryptoType1; // keyblob index. Considering as number within application/ocean/system
@ -61,35 +62,12 @@ public class NCAProvider {
private byte cryptoTypeReal; private byte cryptoTypeReal;
private byte[] sha256hash0; private byte[] sha256hash0, sha256hash1, sha256hash2, sha256hash3,
private byte[] sha256hash1; encryptedKey0, encryptedKey1, encryptedKey2, encryptedKey3,
private byte[] sha256hash2; decryptedKey0, decryptedKey1, decryptedKey2, decryptedKey3;
private byte[] sha256hash3; private NCAHeaderTableEntry tableEntry0, tableEntry1, tableEntry2, tableEntry3;
private NcaFsHeader sectionBlock0, sectionBlock1, sectionBlock2, sectionBlock3;
private byte[] encryptedKey0; private NCAContent ncaContent0, ncaContent1, ncaContent2, ncaContent3;
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;
public NCAProvider(File file, HashMap<String, String> keys) throws Exception{ public NCAProvider(File file, HashMap<String, String> keys) throws Exception{
this(file, keys, 0); this(file, keys, 0);
@ -128,11 +106,11 @@ public class NCAProvider {
System.arraycopy(decryptedSequence, 0, decryptedHeader, i * 0x200, 0x200); System.arraycopy(decryptedSequence, 0, decryptedHeader, i * 0x200, 0x200);
} }
getHeader(decryptedHeader); setupHeader(decryptedHeader);
raf.close(); raf.close();
getNCAContent(); setupNCAContent();
/*//--------------------------------------------------------------------- /*//---------------------------------------------------------------------
FileInputStream fis = new FileInputStream(file); FileInputStream fis = new FileInputStream(file);
try (BufferedOutputStream bos = new BufferedOutputStream(Files.newOutputStream(Paths.get("/tmp/decrypted.nca")))){ try (BufferedOutputStream bos = new BufferedOutputStream(Files.newOutputStream(Paths.get("/tmp/decrypted.nca")))){
@ -159,16 +137,16 @@ public class NCAProvider {
} }
return data; return data;
} }
private void getHeader(byte[] decryptedData) throws Exception{ private void setupHeader(byte[] decryptedData) throws Exception{
rsa2048one = Arrays.copyOfRange(decryptedData, 0, 0x100); rsa2048one = Arrays.copyOfRange(decryptedData, 0, 0x100);
rsa2048two = Arrays.copyOfRange(decryptedData, 0x100, 0x200); 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]; systemOrGcIndicator = decryptedData[0x204];
contentType = decryptedData[0x205]; contentType = decryptedData[0x205];
cryptoType1 = decryptedData[0x206]; cryptoType1 = decryptedData[0x206];
keyIndex = decryptedData[0x207]; keyIndex = decryptedData[0x207];
ncaSize = getLElong(decryptedData, 0x208); ncaSize = getLElong(decryptedData, 0x208);
titleId = Arrays.copyOfRange(decryptedData, 0x210, 0x218); titleId = Converter.flip(Arrays.copyOfRange(decryptedData, 0x210, 0x218));
contentIndx = Arrays.copyOfRange(decryptedData, 0x218, 0x21C); contentIndx = Arrays.copyOfRange(decryptedData, 0x218, 0x21C);
sdkVersion = Arrays.copyOfRange(decryptedData, 0x21c, 0x220); sdkVersion = Arrays.copyOfRange(decryptedData, 0x21c, 0x220);
cryptoType2 = decryptedData[0x220]; cryptoType2 = decryptedData[0x220];
@ -198,8 +176,8 @@ public class NCAProvider {
cryptoTypeReal -= 1; cryptoTypeReal -= 1;
//If nca3 proceed //If nca3 proceed
if (! magicnum.equalsIgnoreCase("NCA3")) if (! magicNumber.equalsIgnoreCase("NCA3"))
throw new Exception("Not supported data type: "+magicnum+". Only NCA3 supported"); throw new Exception("Not supported data type: "+ magicNumber +". Only NCA3 supported");
// Decrypt keys if encrypted // Decrypt keys if encrypted
if (Arrays.equals(rightsId, new byte[0x10])) { if (Arrays.equals(rightsId, new byte[0x10])) {
String keyAreaKey; String keyAreaKey;
@ -262,35 +240,34 @@ public class NCAProvider {
throw new Exception(exceptionStringBuilder.toString()); throw new Exception(exceptionStringBuilder.toString());
} }
private void getNCAContent() throws Exception{ private void setupNCAContent() throws Exception{
byte[] key; byte[] key = calculateKey();
// If empty Rights ID setupNcaContentByNumber(0, key);
if (Arrays.equals(rightsId, new byte[0x10])) { setupNcaContentByNumber(1, key);
key = decryptedKey2; // NOTE: Just remember this dumb hack setupNcaContentByNumber(2, key);
setupNcaContentByNumber(3, key);
} }
else { private byte[] calculateKey() throws Exception{
try { try {
byte[] rightsIDkey = hexStrToByteArray(keys.get(byteArrToHexString(rightsId))); // throws NullPointerException 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( SecretKeySpec skSpec = new SecretKeySpec(
hexStrToByteArray(keys.get(String.format("titlekek_%02x", cryptoTypeReal)) hexStrToByteArray(keys.get(String.format("titlekek_%02x", cryptoTypeReal))
), "AES"); ), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding"); Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, skSpec); cipher.init(Cipher.DECRYPT_MODE, skSpec);
key = cipher.doFinal(rightsIDkey); return cipher.doFinal(rightsIdKey);
} }
catch (Exception e){ catch (Exception e){
throw new Exception("No title.keys loaded for '"+ throw new Exception("No title.keys loaded for '"+
String.format("titlekek_%02x", cryptoTypeReal)+"' or '"+byteArrToHexString(rightsId)+"'? ("+e+")", e); String.format("titlekek_%02x", cryptoTypeReal)+"' or '"+byteArrToHexString(rightsId)+"'? ("+e+")", e);
} }
} }
getNcaContentByNumber(0, key); private void setupNcaContentByNumber(int number, byte[] key){
getNcaContentByNumber(1, key);
getNcaContentByNumber(2, key);
getNcaContentByNumber(3, key);
}
private void getNcaContentByNumber(int number, byte[] key){
try { try {
switch (number) { switch (number) {
case 0: case 0:
@ -313,9 +290,11 @@ public class NCAProvider {
} }
} }
// -======================= API =======================-
public byte[] getRsa2048one() { return rsa2048one; } public byte[] getRsa2048one() { return rsa2048one; }
public byte[] getRsa2048two() { return rsa2048two; } public byte[] getRsa2048two() { return rsa2048two; }
public String getMagicnum() { return magicnum; } public String getMagicnum() { return magicNumber; }
public byte getSystemOrGcIndicator() { return systemOrGcIndicator; } public byte getSystemOrGcIndicator() { return systemOrGcIndicator; }
public byte getContentType() { return contentType; } public byte getContentType() { return contentType; }
public byte getCryptoType1() { return cryptoType1; } public byte getCryptoType1() { return cryptoType1; }
@ -345,7 +324,7 @@ public class NCAProvider {
public byte[] getDecryptedKey3() { return decryptedKey3; } public byte[] getDecryptedKey3() { return decryptedKey3; }
/** /**
* Get NCA Hedaer Table Entry for selected id * 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{ public NCAHeaderTableEntry getTableEntry(int id) throws Exception{
switch (id) { switch (id) {
@ -367,7 +346,7 @@ public class NCAProvider {
public NCAHeaderTableEntry getTableEntry3() { return tableEntry3; } public NCAHeaderTableEntry getTableEntry3() { return tableEntry3; }
/** /**
* Get NCA Section Block for selected section * 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{ public NcaFsHeader getSectionBlock(int id) throws Exception{
switch (id) { switch (id) {
@ -396,7 +375,7 @@ public class NCAProvider {
} }
/** /**
* Get content for the selected section * 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{ public NCAContent getNCAContentProvider(int sectionNumber) throws Exception{
switch (sectionNumber) { switch (sectionNumber) {

View file

@ -20,7 +20,6 @@
package libKonogonka.Tools.RomFs; package libKonogonka.Tools.RomFs;
import libKonogonka.Converter; import libKonogonka.Converter;
import libKonogonka.Tools.NCA.NCAContent;
import libKonogonka.Tools.RomFs.view.FileSystemTreeViewMaker; import libKonogonka.Tools.RomFs.view.FileSystemTreeViewMaker;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -32,7 +31,7 @@ import java.util.Comparator;
import java.util.List; import java.util.List;
public class FileSystemEntry { 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 boolean directoryFlag;
private String name; private String name;
@ -137,7 +136,7 @@ public class FileSystemEntry {
nextHashTableBucketDirectoryOffset = Converter.getLEint(dirsMetadataTable, i); nextHashTableBucketDirectoryOffset = Converter.getLEint(dirsMetadataTable, i);
/* /*
if (nextHashTableBucketDirectoryOffset < 0) { if (nextHashTableBucketDirectoryOffset < 0) {
System.out.println("nextHashTableBucketDirectoryOffset: "+ nextHashTableBucketDirectoryOffset); log.debug("nextHashTableBucketDirectoryOffset: "+ nextHashTableBucketDirectoryOffset);
} }
//*/ //*/
i += 4; i += 4;
@ -149,7 +148,7 @@ public class FileSystemEntry {
} }
else { else {
dirName = ""; dirName = "";
System.out.println("dirName: "+dirNameLength); // log.debug("Dir Name Length: "+dirNameLength);
} }
//i += getRealNameSize(dirNameLength); //i += getRealNameSize(dirNameLength);
} }
@ -197,12 +196,12 @@ public class FileSystemEntry {
fileName = new String(Arrays.copyOfRange(filesMetadataTable, i, i + fileNameLength), StandardCharsets.UTF_8); fileName = new String(Arrays.copyOfRange(filesMetadataTable, i, i + fileNameLength), StandardCharsets.UTF_8);
} }
catch (Exception e){ 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 { else {
fileName = ""; fileName = "";
System.out.println("fileName: "+fileNameLength); //log.debug("File Name Length: "+fileNameLength);
} }
//i += getRealNameSize(fileNameLength); //i += getRealNameSize(fileNameLength);
} }
@ -214,5 +213,4 @@ public class FileSystemEntry {
public void printTreeForDebug(){ public void printTreeForDebug(){
log.debug(FileSystemTreeViewMaker.make(content, 100)); log.debug(FileSystemTreeViewMaker.make(content, 100));
} }
} }

View file

@ -21,15 +21,13 @@ package libKonogonka.Tools.RomFs;
import libKonogonka.Converter; import libKonogonka.Converter;
import libKonogonka.ctraes.AesCtrBufferedInputStream; import libKonogonka.ctraes.AesCtrBufferedInputStream;
import libKonogonka.ctraes.AesCtrDecryptSimple; import libKonogonka.ctraes.AesCtrDecryptSimple;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.File; import java.io.File;
import java.nio.file.Files; import java.nio.file.Files;
public class RomFsConstruct { public class RomFsConstruct {
private final static Logger log = LogManager.getLogger(RomFsConstruct.class); //private final static Logger log = LogManager.getLogger(RomFsConstruct.class);
private Level6Header header; private Level6Header header;
@ -66,8 +64,6 @@ public class RomFsConstruct {
this.file = file; this.file = file;
this.level6Offset = level6Offset; this.level6Offset = level6Offset;
this.offsetPositionInFile = ncaOffset + (mediaStartOffset * 0x200); this.offsetPositionInFile = ncaOffset + (mediaStartOffset * 0x200);
// In 512-blocks
// In 512-blocks
this.stream = new AesCtrBufferedInputStream( this.stream = new AesCtrBufferedInputStream(
decryptor, decryptor,
ncaOffset, ncaOffset,
@ -141,8 +137,13 @@ public class RomFsConstruct {
} }
private void constructRootFilesystemEntry() throws Exception{ private void constructRootFilesystemEntry() throws Exception{
try {
rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable); rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable);
} }
catch (Exception e){
throw new Exception("File: " + file.getName(), e);
}
}
private void skipBytes(long size) throws Exception{ private void skipBytes(long size) throws Exception{
long mustSkip = size; long mustSkip = size;

View file

@ -23,7 +23,8 @@ rootLogger.appenderRef.stdout.ref = consoleLogger
appender.console.type = Console appender.console.type = Console
appender.console.name = consoleLogger appender.console.name = consoleLogger
appender.console.layout.type = PatternLayout 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 # # Enable log to files
# rootLogger.appenderRef.rolling.ref = fileLogger # rootLogger.appenderRef.rolling.ref = fileLogger

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String, String> 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();
}
}

View file

@ -21,8 +21,8 @@ package libKonogonka.RomFsDecrypted;
import libKonogonka.KeyChainHolder; import libKonogonka.KeyChainHolder;
import libKonogonka.RainbowDump; import libKonogonka.RainbowDump;
import libKonogonka.Tools.NCA.NCAProvider; import libKonogonka.Tools.NCA.NCAProvider;
import libKonogonka.Tools.PFS0.IPFS0Provider;
import libKonogonka.Tools.PFS0.PFS0subFile; import libKonogonka.Tools.PFS0.PFS0subFile;
import libKonogonka.Tools.RomFs.FileSystemEntry;
import libKonogonka.ctraes.AesCtrBufferedInputStream; import libKonogonka.ctraes.AesCtrBufferedInputStream;
import libKonogonka.ctraes.AesCtrDecryptSimple; import libKonogonka.ctraes.AesCtrDecryptSimple;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
@ -107,13 +107,17 @@ public class Pfs0EncryptedTest {
ncaProvider.getDecryptedKey2(), ncaProvider.getDecryptedKey2(),
ncaProvider.getSectionBlock0().getSectionCTR(), ncaProvider.getSectionBlock0().getSectionCTR(),
ncaProvider.getTableEntry0().getMediaStartOffset()*0x200); ncaProvider.getTableEntry0().getMediaStartOffset()*0x200);
/*
for (PFS0subFile subFile : subfiles){ for (PFS0subFile subFile : subfiles){
exportContentLegacy(subFile, "/tmp/legacy_PFS0"); exportContentLegacy(subFile, "/tmp/legacy_PFS0");
} }
*/
IPFS0Provider pfs0Provider = ncaProvider.getNCAContentProvider(0).getPfs0();
//---------------------------------------------------------------------- //----------------------------------------------------------------------
for (PFS0subFile subFile : subfiles) { 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()));
} }
} }

View file

@ -18,6 +18,10 @@
*/ */
package libKonogonka.RomFsDecrypted; 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 libKonogonka.Tools.XCI.XCIProvider;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName; 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")); // log.fatal("Configuration File Defined To Be :: "+System.getProperty("log4j.configurationFile"));
public class XciTest { 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 xci_header_keyFileLocation = "./FilesForTests/xci_header_key.txt";
private static final String decryptedFileAbsolutePath = "./FilesForTests/sample.xci"; private static final String decryptedFileAbsolutePath = "./FilesForTests/sample.xci";
private File xciFile; private File xciFile;
XCIProvider provider; XCIProvider provider;
String xci_header_key; String xci_header_key;
HFS0Provider hfs0Provider;
private static KeyChainHolder keyChainHolder;
@Disabled @Disabled
@DisplayName("RomFsDecryptedProvider: tests") @DisplayName("XciTest")
@Test @Test
void romFsValidation() throws Exception{ void romFsValidation() throws Exception{
makeFile(); makeFile();
getXciHeaderKey(); getXciHeaderKey();
keyChainHolder = new KeyChainHolder(keysFileLocation, xci_header_key);
makeProvider(); makeProvider();
getHfsSecure();
getFirstNca3();
} }
void getXciHeaderKey() throws Exception{ void getXciHeaderKey() throws Exception{
@ -63,4 +76,20 @@ public class XciTest {
void makeProvider() throws Exception{ void makeProvider() throws Exception{
provider = new XCIProvider(xciFile, xci_header_key); 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.
}
} }