Small fixes, minor updates.
This commit is contained in:
parent
f058905e2f
commit
51398a6ea9
11 changed files with 373 additions and 106 deletions
11
HOWTO.txt
11
HOWTO.txt
|
@ -6,4 +6,13 @@ mvn install:install-file \
|
|||
-DartifactId=libKonogonka \
|
||||
-Dversion=0.1 \
|
||||
-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'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, String> keys) throws Exception{
|
||||
this(file, keys, 0);
|
||||
}
|
||||
|
||||
public NCAProvider (File file, HashMap<String, String> keys, long offsetPosition) throws Exception{
|
||||
public NCAProvider(File file, HashMap<String, String> 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) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue