Ass stream support into System2 related classes.
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Dmitry Isaenko 2023-01-14 15:07:32 +03:00
parent 045d195f91
commit 7add08c196
11 changed files with 350 additions and 129 deletions

View file

@ -4,9 +4,8 @@ import java.io.BufferedInputStream;
import java.io.File; import java.io.File;
public interface IProducer { public interface IProducer {
BufferedInputStream produce() throws Exception; BufferedInputStream produce() throws Exception;
IProducer getSuccessor(long subOffset); IProducer getSuccessor(long subOffset) throws Exception;
boolean isEncrypted(); boolean isEncrypted();
File getFile(); File getFile();
} }

View file

@ -22,6 +22,7 @@ import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.util.HashMap; import java.util.HashMap;
// NOTE: Probably it would be better to make it singleton which creates along with 'library initialization'
public class KeyChainHolder { public class KeyChainHolder {
@ -72,31 +73,11 @@ public class KeyChainHolder {
return tempKeySet; return tempKeySet;
} }
public String getXci_header_key() { public String getXci_header_key() { return xci_header_key; }
return xci_header_key; public String getHeader_key() { return rawKeySet.get("header_key"); }
} public HashMap<String, String> getRawKeySet() { return rawKeySet; }
public HashMap<String, String> getKey_area_key_application() { return key_area_key_application; }
public String getHeader_key() { public HashMap<String, String> getKey_area_key_ocean() { return key_area_key_ocean; }
return rawKeySet.get("header_key"); public HashMap<String, String> getKey_area_key_system() { return key_area_key_system; }
} public HashMap<String, String> getTitlekek() { return titlekek; }
public HashMap<String, String> getRawKeySet() {
return rawKeySet;
}
public HashMap<String, String> getKey_area_key_application() {
return key_area_key_application;
}
public HashMap<String, String> getKey_area_key_ocean() {
return key_area_key_ocean;
}
public HashMap<String, String> getKey_area_key_system() {
return key_area_key_system;
}
public HashMap<String, String> getTitlekek() {
return titlekek;
}
} }

View file

@ -79,7 +79,7 @@ public class RomFsProvider extends ExportAble {
exportSingleFile(entry, saveToLocation); exportSingleFile(entry, saveToLocation);
} }
catch (Exception e){ catch (Exception e){
log.error("File export failure", e); log.error(getFile().getName()+" export failure ", e);
return false; return false;
} }
return true; return true;
@ -98,9 +98,14 @@ public class RomFsProvider extends ExportAble {
} }
private void exportSingleFile(FileSystemEntry entry, String saveToLocation) throws Exception { private void exportSingleFile(FileSystemEntry entry, String saveToLocation) throws Exception {
stream = producer.produce(); try {
long skipBytes = entry.getOffset() + mediaStartOffset * 0x200 + level6Header.getFileDataOffset() + level6Offset; stream = producer.produce();
export(saveToLocation, entry.getName(), skipBytes, entry.getSize()); long skipBytes = entry.getOffset() + mediaStartOffset * 0x200 + level6Header.getFileDataOffset() + level6Offset;
export(saveToLocation, entry.getName(), skipBytes, entry.getSize());
}
catch (Exception e){
throw new Exception(entry.getName()+": "+e.getLocalizedMessage(), e);
}
} }
public InFileStreamProducer getStreamProducer(FileSystemEntry entry) throws Exception{ public InFileStreamProducer getStreamProducer(FileSystemEntry entry) throws Exception{

View file

@ -115,6 +115,9 @@ public class System2Header {
sha256overEncryptedSection1 = Arrays.copyOfRange(decodedHeaderBytes, 0xa0, 0xc0); sha256overEncryptedSection1 = Arrays.copyOfRange(decodedHeaderBytes, 0xa0, 0xc0);
sha256overEncryptedSection2 = Arrays.copyOfRange(decodedHeaderBytes, 0xc0, 0xe0); sha256overEncryptedSection2 = Arrays.copyOfRange(decodedHeaderBytes, 0xc0, 0xe0);
sha256overEncryptedSection3 = Arrays.copyOfRange(decodedHeaderBytes, 0xe0, 0x100); sha256overEncryptedSection3 = Arrays.copyOfRange(decodedHeaderBytes, 0xe0, 0x100);
if (packageSize != 0x200 + section0size)
log.error("'Package size' doesn't match 'Header Size' + 'Section 0 size'!");
} }
public byte[] getHeaderCtr() { return headerCtr; } public byte[] getHeaderCtr() { return headerCtr; }

View file

@ -21,11 +21,9 @@ package libKonogonka.Tools.other.System2;
import libKonogonka.KeyChainHolder; import libKonogonka.KeyChainHolder;
import libKonogonka.Tools.ExportAble; import libKonogonka.Tools.ExportAble;
import libKonogonka.Tools.other.System2.ini1.Ini1Provider; import libKonogonka.Tools.other.System2.ini1.Ini1Provider;
import libKonogonka.ctraesclassic.AesCtrClassicBufferedInputStream; import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.ctraesclassic.AesCtrDecryptClassic; import libKonogonka.ctraesclassic.InFileStreamClassicProducer;
import libKonogonka.ctraesclassic.AesCtrStream;
import javax.crypto.CipherInputStream;
import java.io.*; import java.io.*;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.file.Files; import java.nio.file.Files;
@ -39,16 +37,30 @@ public class System2Provider extends ExportAble {
private KernelMap kernelMap; private KernelMap kernelMap;
private Ini1Provider ini1Provider; private Ini1Provider ini1Provider;
private final String pathToFile;
private final KeyChainHolder keyChainHolder; private final KeyChainHolder keyChainHolder;
private InFileStreamClassicProducer producer;
public System2Provider(String pathToFile, KeyChainHolder keyChainHolder) throws Exception{ public System2Provider(String pathToFile, KeyChainHolder keyChainHolder) throws Exception{
this.pathToFile = pathToFile;
this.keyChainHolder = keyChainHolder; this.keyChainHolder = keyChainHolder;
this.stream = new BufferedInputStream(Files.newInputStream(Paths.get(pathToFile))); Path filePath = Paths.get(pathToFile);
this.stream = new BufferedInputStream(Files.newInputStream(filePath));
readSignatures(); readSignatures();
readHeader(); readHeader();
this.stream.close();
createProducerOfFile(filePath);
findIni1KernelMap();
this.stream.close();
}
public System2Provider(InFileStreamProducer producer, KeyChainHolder keyChainHolder) throws Exception{
this.keyChainHolder = keyChainHolder;
this.stream = producer.produce();
readSignatures();
readHeader();
this.stream.close();
createProducerOfStream(producer);
findIni1KernelMap(); findIni1KernelMap();
this.stream.close(); this.stream.close();
} }
@ -66,43 +78,52 @@ public class System2Provider extends ExportAble {
this.header = new System2Header(headerBytes, keyChainHolder.getRawKeySet()); this.header = new System2Header(headerBytes, keyChainHolder.getRawKeySet());
} }
private void createProducerOfFile(Path filePath) throws Exception{
this.producer = new InFileStreamClassicProducer(filePath,
0,
0x200,
header.getPackageSize(),
header.getKey(),
header.getSection0Ctr());
this.stream = producer.produce();
}
private void createProducerOfStream(InFileStreamProducer parentProducer) throws Exception{
producer = new InFileStreamClassicProducer(parentProducer,
0,
0x200,
header.getPackageSize(),
header.getKey(),
header.getSection0Ctr(),
header.getPackageSize());
this.stream = producer.produce();
}
private void findIni1KernelMap() throws Exception{ private void findIni1KernelMap() throws Exception{
try (InputStream fis = Files.newInputStream(Paths.get(pathToFile))){ if (0x200 != stream.skip(0x200))
// Encrypted section comes next throw new Exception("Unable to skip offset of 0x200");
long toSkip = 0x200;
if (toSkip != fis.skip(toSkip))
throw new Exception("Unable to skip offset: " + toSkip);
ByteBuffer byteBuffer = ByteBuffer.allocate(0x1000); ByteBuffer byteBuffer = ByteBuffer.allocate(0x1000);
try (CipherInputStream cipherInputStream = AesCtrStream.getStream(header.getKey(), header.getSection0Ctr(), fis);) {
for (int j = 0; j < 8; j++) {
byte[] block = new byte[0x200];
int actuallyRead;
if ((actuallyRead = cipherInputStream.read(block)) != 0x200)
throw new Exception("Read failure " + actuallyRead);
byteBuffer.put(block);
}
}
byte[] searchField = byteBuffer.array(); for (int j = 0; j < 8; j++) {
for (int i = 0; i < 1024; i += 4) { byte[] block = new byte[0x200];
kernelMap = new KernelMap(searchField, i); int actuallyRead;
if (kernelMap.isValid(header.getSection0size())) if ((actuallyRead = stream.read(block)) != 0x200)
return; throw new Exception("Read failure " + actuallyRead);
} byteBuffer.put(block);
throw new Exception("Kernel map not found");
} }
byte[] searchField = byteBuffer.array();
for (int i = 0; i < 1024; i += 4) {
kernelMap = new KernelMap(searchField, i);
if (kernelMap.isValid(header.getSection0size()))
return;
}
throw new Exception("Kernel map not found");
} }
public boolean exportKernel(String saveTo) throws Exception{ public boolean exportKernel(String saveTo) throws Exception{
Path filePath = Paths.get(pathToFile); stream = producer.produce();
AesCtrDecryptClassic decryptor = new AesCtrDecryptClassic(header.getKey(), header.getSection0Ctr());
stream = new AesCtrClassicBufferedInputStream(decryptor,
0x200,
Files.size(filePath), // size of system2
Files.newInputStream(filePath),
Files.size(filePath));
return export(saveTo, "Kernel.bin", 0x200, header.getSection0size()); return export(saveTo, "Kernel.bin", 0x200, header.getSection0size());
} }
@ -111,7 +132,8 @@ public class System2Provider extends ExportAble {
public KernelMap getKernelMap() { return kernelMap; } public KernelMap getKernelMap() { return kernelMap; }
public Ini1Provider getIni1Provider() throws Exception{ public Ini1Provider getIni1Provider() throws Exception{
if (ini1Provider == null) if (ini1Provider == null)
ini1Provider = new Ini1Provider(header, pathToFile, kernelMap); ini1Provider = new Ini1Provider(
producer.getSuccessor(0x200 + kernelMap.getIni1Offset(), true));
return ini1Provider; return ini1Provider;
} }
} }

View file

@ -19,7 +19,6 @@
package libKonogonka.Tools.other.System2.ini1; package libKonogonka.Tools.other.System2.ini1;
import libKonogonka.Tools.ExportAble; import libKonogonka.Tools.ExportAble;
import libKonogonka.Tools.other.System2.KernelMap;
import libKonogonka.Tools.other.System2.System2Header; import libKonogonka.Tools.other.System2.System2Header;
import libKonogonka.ctraesclassic.InFileStreamClassicProducer; import libKonogonka.ctraesclassic.InFileStreamClassicProducer;
@ -35,17 +34,26 @@ public class Ini1Provider extends ExportAble {
private final InFileStreamClassicProducer producer; private final InFileStreamClassicProducer producer;
public Ini1Provider(System2Header system2Header, String pathToFile, KernelMap kernelMap) throws Exception{ public Ini1Provider(InFileStreamClassicProducer producer) throws Exception{
this.producer = producer;
this.stream = producer.produce();
makeHeader();
collectKips();
this.stream.close();
}
public Ini1Provider(System2Header system2Header, String pathToFile, int kernelMapIni1Offset) throws Exception{
Path filePath = Paths.get(pathToFile); Path filePath = Paths.get(pathToFile);
this.producer = new InFileStreamClassicProducer(filePath, this.producer = new InFileStreamClassicProducer(filePath,
0x200 + kernelMap.getIni1Offset(), 0x200 + kernelMapIni1Offset,
0x200, 0x200,
Files.size(filePath), // size of system2 Files.size(filePath), // size of system2
system2Header.getKey(), system2Header.getKey(),
system2Header.getSection0Ctr()); system2Header.getSection0Ctr());
stream = producer.produce(); this.stream = producer.produce();
makeHeader(); makeHeader();
collectKips(); collectKips();
this.stream.close();
} }
private void makeHeader() throws Exception{ private void makeHeader() throws Exception{

View file

@ -54,23 +54,25 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
private int pointerInsideDecryptedSection; private int pointerInsideDecryptedSection;
@Override @Override
public synchronized int read(byte[] b) throws IOException{ public int read(byte[] b, int off, int len) throws IOException {
int bytesToRead = b.length; if (off != 0 || len != b.length)
throw new IOException("Not supported");
if (isPointerInsideEncryptedSection()){ if (isPointerInsideEncryptedSection()){
int bytesFromFirstBlock = 0x200 - pointerInsideDecryptedSection; int bytesFromFirstBlock = 0x200 - pointerInsideDecryptedSection;
if (bytesFromFirstBlock > bytesToRead){ if (bytesFromFirstBlock > len){
log.trace("1.2. Pointer Inside + End Position Inside (Decrypted) Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")"); log.trace("1.2. Pointer Inside + End Position Inside (Decrypted) Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesToRead); System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, len);
pseudoPos += bytesToRead; pseudoPos += len;
pointerInsideDecryptedSection += bytesToRead; pointerInsideDecryptedSection += len;
return b.length; return b.length;
} }
if (isEndPositionInsideEncryptedSection(b.length)) { if (isEndPositionInsideEncryptedSection(b.length)) {
log.trace("1.1. Pointer Inside + End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")"); log.trace("1.1. Pointer Inside + End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
int middleBlocksCount = (bytesToRead - bytesFromFirstBlock) / 0x200; int middleBlocksCount = (len - bytesFromFirstBlock) / 0x200;
int bytesFromLastBlock = (bytesToRead - bytesFromFirstBlock) % 0x200; int bytesFromLastBlock = (len - bytesFromFirstBlock) % 0x200;
//1 //1
System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesFromFirstBlock); System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesFromFirstBlock);
//2 //2
@ -79,17 +81,17 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
System.arraycopy(decryptedBytes, 0, b, bytesFromFirstBlock+i*0x200, 0x200); System.arraycopy(decryptedBytes, 0, b, bytesFromFirstBlock+i*0x200, 0x200);
} }
//3 //3
if(fileSize > (pseudoPos+bytesToRead)) { if(fileSize > (pseudoPos+ len)) {
fillDecryptedCache(); fillDecryptedCache();
System.arraycopy(decryptedBytes, 0, b, bytesFromFirstBlock + middleBlocksCount * 0x200, bytesFromLastBlock); System.arraycopy(decryptedBytes, 0, b, bytesFromFirstBlock + middleBlocksCount * 0x200, bytesFromLastBlock);
} }
pseudoPos += bytesToRead; pseudoPos += len;
pointerInsideDecryptedSection = bytesFromLastBlock; pointerInsideDecryptedSection = bytesFromLastBlock;
return b.length; return b.length;
} }
log.trace("1. Pointer Inside + End Position Outside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")"); log.trace("1. Pointer Inside + End Position Outside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
int middleBlocksCount = (int) ((mediaOffsetPositionEnd - (pseudoPos+bytesFromFirstBlock)) / 0x200); int middleBlocksCount = (int) ((mediaOffsetPositionEnd - (pseudoPos+bytesFromFirstBlock)) / 0x200);
int bytesFromEnd = bytesToRead - bytesFromFirstBlock - middleBlocksCount * 0x200; int bytesFromEnd = len - bytesFromFirstBlock - middleBlocksCount * 0x200;
//1 //1
System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesFromFirstBlock); System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesFromFirstBlock);
//2 //2
@ -100,15 +102,15 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
} }
//3 // TODO: if it's zero? //3 // TODO: if it's zero?
System.arraycopy(readChunk(bytesFromEnd), 0, b, bytesFromFirstBlock+middleBlocksCount*0x200, bytesFromEnd); System.arraycopy(readChunk(bytesFromEnd), 0, b, bytesFromFirstBlock+middleBlocksCount*0x200, bytesFromEnd);
pseudoPos += bytesToRead; pseudoPos += len;
pointerInsideDecryptedSection = 0; pointerInsideDecryptedSection = 0;
return b.length; return b.length;
} }
if (isEndPositionInsideEncryptedSection(bytesToRead)) { if (isEndPositionInsideEncryptedSection(len)) {
log.trace("2. End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")"); log.trace("2. End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
int bytesTillEncrypted = (int) (mediaOffsetPositionStart - pseudoPos); int bytesTillEncrypted = (int) (mediaOffsetPositionStart - pseudoPos);
int fullEncryptedBlocks = (bytesToRead - bytesTillEncrypted) / 0x200; int fullEncryptedBlocks = (len - bytesTillEncrypted) / 0x200;
int incompleteEncryptedBytes = (bytesToRead - bytesTillEncrypted) % 0x200; int incompleteEncryptedBytes = (len - bytesTillEncrypted) % 0x200;
System.arraycopy(readChunk(bytesTillEncrypted), 0, b, 0, bytesTillEncrypted); System.arraycopy(readChunk(bytesTillEncrypted), 0, b, 0, bytesTillEncrypted);
//2 //2
for (int i = 0; i < fullEncryptedBlocks; i++) { for (int i = 0; i < fullEncryptedBlocks; i++) {
@ -118,12 +120,12 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
//3 //3
fillDecryptedCache(); fillDecryptedCache();
System.arraycopy(decryptedBytes, 0, b, bytesTillEncrypted+fullEncryptedBlocks*0x200, incompleteEncryptedBytes); System.arraycopy(decryptedBytes, 0, b, bytesTillEncrypted+fullEncryptedBlocks*0x200, incompleteEncryptedBytes);
pseudoPos += bytesToRead; pseudoPos += len;
pointerInsideDecryptedSection = incompleteEncryptedBytes; pointerInsideDecryptedSection = incompleteEncryptedBytes;
return b.length; return b.length;
} }
log.trace("3. Not encrypted ("+pseudoPos+"-"+(pseudoPos+b.length)+")"); log.trace("3. Not encrypted ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
pseudoPos += bytesToRead; pseudoPos += len;
pointerInsideDecryptedSection = 0; pointerInsideDecryptedSection = 0;
return super.read(b); return super.read(b);
} }
@ -146,10 +148,9 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
} }
private byte[] readChunk(int bytes) throws IOException{ private byte[] readChunk(int bytes) throws IOException{
byte[] chunkBytes = new byte[bytes]; byte[] chunkBytes = new byte[bytes];
long actuallyRead = super.read(chunkBytes); long actuallyRead = super.read(chunkBytes, 0, bytes);
if (actuallyRead != bytes) if (actuallyRead != bytes)
throw new IOException("Can't read. Need block of "+ bytes +" while only " + throw new IOException("Can't read: " + actuallyRead + "/"+ bytes);
actuallyRead + " bytes.");
return chunkBytes; return chunkBytes;
} }

View file

@ -138,8 +138,7 @@ public class AesCtrClassicBufferedInputStream extends BufferedInputStream {
byte[] chunkBytes = new byte[bytes]; byte[] chunkBytes = new byte[bytes];
long actuallyRead = super.read(chunkBytes); long actuallyRead = super.read(chunkBytes);
if (actuallyRead != bytes) if (actuallyRead != bytes)
throw new IOException("Can't read. Need block of "+ bytes +" while only " + throw new IOException("Can't read. "+ bytes +"/" + actuallyRead);
actuallyRead + " bytes.");
return chunkBytes; return chunkBytes;
} }

View file

@ -27,6 +27,7 @@ import javax.crypto.spec.SecretKeySpec;
import java.io.InputStream; import java.io.InputStream;
import java.security.Security; import java.security.Security;
@Deprecated
public class AesCtrStream { public class AesCtrStream {
private static boolean BCinitialized = false; private static boolean BCinitialized = false;

View file

@ -19,21 +19,36 @@
package libKonogonka.ctraesclassic; package libKonogonka.ctraesclassic;
import libKonogonka.IProducer; import libKonogonka.IProducer;
import libKonogonka.RainbowDump;
import libKonogonka.ctraes.InFileStreamProducer;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.File; import java.io.File;
import java.io.FilterInputStream;
import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
public class InFileStreamClassicProducer implements IProducer { public class InFileStreamClassicProducer implements IProducer {
private boolean encrypted; private boolean encrypted;
private final Path filePath; private Path filePath;
private InFileStreamProducer parentProducer;
private long offset; private long offset;
private long encryptedStartOffset; private long encryptedStartOffset;
private long encryptedEndOffset; private long encryptedEndOffset;
private AesCtrDecryptClassic decryptor; private AesCtrDecryptClassic decryptor;
private final long fileSize;
/** Reference AES-CTR stream producer.
* @param filePath Path to file-container
* @param offset Offset to skip (since file beginning).
* @param encryptedStartOffset Offset since file beginning where encrypted section starts
* @param encryptedEndOffset Offset since file beginning where encrypted section ends
* @param key AES-CTR Key
* @param iv CTR / IV (counter)
*/
public InFileStreamClassicProducer(Path filePath, public InFileStreamClassicProducer(Path filePath,
long offset, long offset,
long encryptedStartOffset, long encryptedStartOffset,
@ -46,43 +61,94 @@ public class InFileStreamClassicProducer implements IProducer {
this.encryptedStartOffset = encryptedStartOffset; this.encryptedStartOffset = encryptedStartOffset;
this.encryptedEndOffset = encryptedEndOffset; this.encryptedEndOffset = encryptedEndOffset;
this.decryptor = new AesCtrDecryptClassic(key, iv); this.decryptor = new AesCtrDecryptClassic(key, iv);
this.fileSize = Files.size(filePath);
} }
public InFileStreamClassicProducer(Path filePath, private InFileStreamClassicProducer(Path filePath,
long offset, long offset,
long encryptedStartOffset, long encryptedStartOffset,
long encryptedEndOffset, //Files.size(filePath) long encryptedEndOffset, //Files.size(filePath)
AesCtrDecryptClassic decryptor){ AesCtrDecryptClassic decryptor) throws Exception{
this.encrypted = true; this.encrypted = true;
this.filePath = filePath; this.filePath = filePath;
this.offset = offset; this.offset = offset;
this.encryptedStartOffset = encryptedStartOffset; this.encryptedStartOffset = encryptedStartOffset;
this.encryptedEndOffset = encryptedEndOffset; this.encryptedEndOffset = encryptedEndOffset;
this.decryptor = decryptor; this.decryptor = decryptor;
this.fileSize = Files.size(filePath);
} }
/** Stream producer for non-encrypted files.
public InFileStreamClassicProducer(Path filePath){ * @param filePath Path to file-container
* */
public InFileStreamClassicProducer(Path filePath) throws Exception{
this.filePath = filePath; this.filePath = filePath;
this.fileSize = Files.size(filePath);
} }
public InFileStreamClassicProducer(Path filePath, long offset){ /** Stream producer for non-encrypted files.
* @param filePath Path to file-container
* @param offset Offset to skip (since file beginning).
* */
public InFileStreamClassicProducer(Path filePath, long offset) throws Exception{
this.filePath = filePath; this.filePath = filePath;
this.offset = offset; this.offset = offset;
this.fileSize = Files.size(filePath);
}
/** Reference AES-CTR stream producer that utilizes InFileStreamProducer instead of file.
* @param parentProducer Producer of the stream
* @param offset Offset to skip at parent stream
* @param encryptedStartOffset Offset since parent stream start at stream where encrypted section starts
* @param encryptedEndOffset Offset since parent stream start at stream where encrypted section ends
* @param key AES-CTR Key
* @param iv CTR / IV (counter)
*/
public InFileStreamClassicProducer(InFileStreamProducer parentProducer,
long offset,
long encryptedStartOffset,
long encryptedEndOffset,
String key,
byte[] iv,
long fileSize) throws Exception{
this.parentProducer = parentProducer;
this.encrypted = true;
this.offset = offset;
this.encryptedStartOffset = encryptedStartOffset;
this.encryptedEndOffset = encryptedEndOffset;
this.decryptor = new AesCtrDecryptClassic(key, iv);
this.fileSize = fileSize;
}
private InFileStreamClassicProducer(InFileStreamProducer parentProducer,
long offset,
long encryptedStartOffset,
long encryptedEndOffset,
AesCtrDecryptClassic decryptor,
long fileSize){
this.parentProducer = parentProducer;
this.encrypted = true;
this.offset = offset;
this.encryptedStartOffset = encryptedStartOffset;
this.encryptedEndOffset = encryptedEndOffset;
this.decryptor = decryptor;
this.fileSize = fileSize;
} }
@Override @Override
public BufferedInputStream produce() throws Exception{ public BufferedInputStream produce() throws Exception{
if (encrypted) if (encrypted)
return produceAesCtr(); return produceAesCtr();
else return produceNotEncrypted();
return produceNotEncrypted();
} }
private BufferedInputStream produceAesCtr() throws Exception{ private BufferedInputStream produceAesCtr() throws Exception{
decryptor.reset(); decryptor.reset();
AesCtrClassicBufferedInputStream stream = new AesCtrClassicBufferedInputStream(decryptor,
encryptedStartOffset, InputStream is;
encryptedEndOffset,
Files.newInputStream(filePath), if (filePath == null)
Files.size(filePath)); is = parentProducer.produce();
else
is = Files.newInputStream(filePath);
AesCtrClassicBufferedInputStream stream = new AesCtrClassicBufferedInputStream(
decryptor, encryptedStartOffset, encryptedEndOffset, is, fileSize);
if (offset != stream.skip(offset)) if (offset != stream.skip(offset))
throw new Exception("Unable to skip offset: "+offset); throw new Exception("Unable to skip offset: "+offset);
@ -91,18 +157,29 @@ public class InFileStreamClassicProducer implements IProducer {
} }
private BufferedInputStream produceNotEncrypted() throws Exception{ private BufferedInputStream produceNotEncrypted() throws Exception{
BufferedInputStream stream = new BufferedInputStream(Files.newInputStream(filePath)); BufferedInputStream stream;
if (filePath == null)
stream = new BufferedInputStream(parentProducer.produce());
else
stream = new BufferedInputStream(Files.newInputStream(filePath));
if (offset != stream.skip(offset)) if (offset != stream.skip(offset))
throw new Exception("Unable to skip offset: "+offset); throw new Exception("Unable to skip offset: "+offset);
return stream; return stream;
} }
@Override @Override
public InFileStreamClassicProducer getSuccessor(long offset){ public InFileStreamClassicProducer getSuccessor(long offset) throws Exception{
if (encrypted) if (! encrypted)
return new InFileStreamClassicProducer(filePath, offset, encryptedStartOffset, encryptedEndOffset, decryptor); return new InFileStreamClassicProducer(filePath, offset);
return new InFileStreamClassicProducer(filePath, offset);
if (filePath == null)
return new InFileStreamClassicProducer(parentProducer, offset, encryptedStartOffset, encryptedEndOffset, decryptor, fileSize);
return new InFileStreamClassicProducer(filePath, offset, encryptedStartOffset, encryptedEndOffset, decryptor);
} }
public InFileStreamClassicProducer getSuccessor(long offset, boolean incrementExisting){
public InFileStreamClassicProducer getSuccessor(long offset, boolean incrementExisting) throws Exception{
if (incrementExisting) if (incrementExisting)
return getSuccessor(this.offset + offset); return getSuccessor(this.offset + offset);
return getSuccessor(offset); return getSuccessor(offset);
@ -113,9 +190,15 @@ public class InFileStreamClassicProducer implements IProducer {
return encrypted; return encrypted;
} }
@Override @Override
public File getFile(){ return filePath.toFile(); } public File getFile(){
if (filePath == null)
return parentProducer.getFile();
return filePath.toFile();
}
@Override @Override
public String toString(){ public String toString(){
if (filePath == null)
return parentProducer.getFile().getAbsolutePath();
return filePath.toString(); return filePath.toString();
} }
} }

View file

@ -18,10 +18,15 @@
*/ */
package libKonogonka.RomFsDecrypted; package libKonogonka.RomFsDecrypted;
import libKonogonka.Converter;
import libKonogonka.KeyChainHolder; import libKonogonka.KeyChainHolder;
import libKonogonka.Tools.NCA.NCAProvider;
import libKonogonka.Tools.RomFs.FileSystemEntry;
import libKonogonka.Tools.RomFs.RomFsProvider;
import libKonogonka.Tools.other.System2.System2Provider; import libKonogonka.Tools.other.System2.System2Provider;
import libKonogonka.Tools.other.System2.ini1.Ini1Provider; import libKonogonka.Tools.other.System2.ini1.Ini1Provider;
import libKonogonka.Tools.other.System2.ini1.KIP1Provider; import libKonogonka.Tools.other.System2.ini1.KIP1Provider;
import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.ctraesclassic.AesCtrDecryptClassic; import libKonogonka.ctraesclassic.AesCtrDecryptClassic;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
@ -32,9 +37,8 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Arrays; import java.util.*;
import java.util.HashMap; import java.util.stream.Collectors;
import java.util.Map;
public class Package2UnpackedTest { public class Package2UnpackedTest {
@ -44,6 +48,8 @@ public class Package2UnpackedTest {
private static final String fileLocation = "/home/loper/Projects/libKonogonka/FilesForTests/6b7abe7efa17ad065b18e62d1c87a5cc.nca_extracted/ROOT/nx/package2"; private static final String fileLocation = "/home/loper/Projects/libKonogonka/FilesForTests/6b7abe7efa17ad065b18e62d1c87a5cc.nca_extracted/ROOT/nx/package2";
final String pathToFirmware = "/home/loper/Загрузки/patchesPlayground/nintendo-switch-global-firmwares/Firmware 14.1.0";
@DisplayName("Package2 unpacked test") @DisplayName("Package2 unpacked test")
@Test @Test
void discover() throws Exception{ void discover() throws Exception{
@ -118,17 +124,130 @@ public class Package2UnpackedTest {
@DisplayName("KIP1 unpack test") @DisplayName("KIP1 unpack test")
@Test @Test
void unpackKip1() throws Exception{ void unpackKip1FromNca() throws Exception{
keyChainHolder = new KeyChainHolder(keysFileLocation, null); keyChainHolder = new KeyChainHolder(keysFileLocation, null);
System2Provider provider = new System2Provider(fileLocation, keyChainHolder); // ------------------------------------------------------------------------------------------------------------
Ini1Provider ini1Provider = provider.getIni1Provider(); File firmware = new File(pathToFirmware);
for (KIP1Provider kip1Provider : ini1Provider.getKip1List())
if (kip1Provider.getHeader().getName().startsWith("FS"))
kip1Provider.printDebug();
for (KIP1Provider kip1Provider : ini1Provider.getKip1List()) { if (! firmware.exists())
if (kip1Provider.getHeader().getName().startsWith("FS")) throw new Exception("Firmware directory does not exist " + pathToFirmware);
kip1Provider.exportAsDecompressed("/tmp");
String[] fileNamesArray = firmware.list((File directory, String file) -> ( ! file.endsWith(".cnmt.nca") && file.endsWith(".nca")));
List<String> ncaFilesList = Arrays.asList(Objects.requireNonNull(fileNamesArray));
if (ncaFilesList.size() == 0)
throw new Exception("No NCA files found in firmware folder");
List<NCAProvider> ncaProviders = new ArrayList<>();
for (String ncaFileName : fileNamesArray){
File nca = new File(firmware.getAbsolutePath()+File.separator+ncaFileName);
NCAProvider provider = new NCAProvider(nca, keyChainHolder.getRawKeySet());
ncaProviders.add(provider);
}
// ------------------------------------------------------------------------------------------------------------
NCAProvider system2FatNcaProvider = null;
NCAProvider system2ExFatNcaProvider = null;
for (NCAProvider ncaProvider : ncaProviders) {
String titleId = Converter.byteArrToHexStringAsLE(ncaProvider.getTitleId());
if (titleId.equals("0100000000000819"))
system2FatNcaProvider = ncaProvider;
if (titleId.equals("010000000000081b"))
system2ExFatNcaProvider = ncaProvider;
}
System.out.println("FAT " + (system2FatNcaProvider == null ? "NOT FOUND": "FOUND"));
System.out.println("ExFAT " + (system2ExFatNcaProvider == null ? "NOT FOUND": "FOUND"));
RomFsProvider romFsExFatProvider = null;
FileSystemEntry exFatPackage2Content = null;
InFileStreamProducer producerExFat = null;
if (system2ExFatNcaProvider != null){
romFsExFatProvider = system2ExFatNcaProvider.getNCAContentProvider(0).getRomfs();
exFatPackage2Content = romFsExFatProvider.getRootEntry().getContent()
.stream()
.filter(e -> e.getName().equals("nx"))
.collect(Collectors.toList())
.get(0)
.getContent()
.stream()
.filter(e -> e.getName().equals("package2"))
.collect(Collectors.toList())
.get(0);
producerExFat = romFsExFatProvider.getStreamProducer(exFatPackage2Content);
system2ExFatNcaProvider.getNCAContentProvider(0).getRomfs().getRootEntry().printTreeForDebug();
romFsExFatProvider.exportContent("/tmp/exported_ExFat", exFatPackage2Content);
System2Provider provider = new System2Provider(producerExFat, keyChainHolder);
provider.getKernelMap().printDebug();
Ini1Provider ini1Provider = provider.getIni1Provider();
KIP1Provider fsProvider = null;
for (KIP1Provider kip1Provider : ini1Provider.getKip1List())
if (kip1Provider.getHeader().getName().startsWith("FS"))
fsProvider = kip1Provider;
if (fsProvider != null) {
fsProvider.printDebug();
fsProvider.exportAsDecompressed("/tmp/FAT_kip1");
}
else
System.out.println("FS KIP1 NOT FOUND");
}
RomFsProvider romFsFatProvider = null;
FileSystemEntry fatPackage2Content = null;
InFileStreamProducer producerFat;
if (system2FatNcaProvider != null){
romFsFatProvider = system2FatNcaProvider.getNCAContentProvider(0).getRomfs();
fatPackage2Content = romFsFatProvider.getRootEntry().getContent()
.stream()
.filter(e -> e.getName().equals("nx"))
.collect(Collectors.toList())
.get(0)
.getContent()
.stream()
.filter(e -> e.getName().equals("package2"))
.collect(Collectors.toList())
.get(0);
producerFat = romFsFatProvider.getStreamProducer(fatPackage2Content);
System2Provider provider = new System2Provider(producerFat, keyChainHolder);
provider.getKernelMap().printDebug();
Ini1Provider ini1Provider = provider.getIni1Provider();
KIP1Provider fsProvider = null;
for (KIP1Provider kip1Provider : ini1Provider.getKip1List())
if (kip1Provider.getHeader().getName().startsWith("FS"))
fsProvider = kip1Provider;
if (fsProvider != null) {
fsProvider.printDebug();
fsProvider.exportAsDecompressed("/tmp/FAT_kip1");
}
else
System.out.println("FS KIP1 NOT FOUND");
} }
} }
@DisplayName("KIP1 unpack test")
@Test
void unpackKip1() throws Exception{
System2Provider provider = new System2Provider(fileLocation, keyChainHolder);
provider.getKernelMap().printDebug();
Ini1Provider ini1Provider = provider.getIni1Provider();
KIP1Provider fsProvider = null;
for (KIP1Provider kip1Provider : ini1Provider.getKip1List())
if (kip1Provider.getHeader().getName().startsWith("FS"))
fsProvider = kip1Provider;
if (fsProvider != null) {
fsProvider.printDebug();
fsProvider.exportAsDecompressed("/tmp");
}
else
System.out.println("FS KIP1 NOT FOUND");
}
} }