Ass stream support into System2 related classes.
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
045d195f91
commit
7add08c196
11 changed files with 350 additions and 129 deletions
|
@ -4,9 +4,8 @@ import java.io.BufferedInputStream;
|
|||
import java.io.File;
|
||||
|
||||
public interface IProducer {
|
||||
|
||||
BufferedInputStream produce() throws Exception;
|
||||
IProducer getSuccessor(long subOffset);
|
||||
IProducer getSuccessor(long subOffset) throws Exception;
|
||||
boolean isEncrypted();
|
||||
File getFile();
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.io.BufferedReader;
|
|||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.util.HashMap;
|
||||
// NOTE: Probably it would be better to make it singleton which creates along with 'library initialization'
|
||||
|
||||
public class KeyChainHolder {
|
||||
|
||||
|
@ -72,31 +73,11 @@ public class KeyChainHolder {
|
|||
return tempKeySet;
|
||||
}
|
||||
|
||||
public String getXci_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 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;
|
||||
}
|
||||
public String getXci_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 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; }
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ public class RomFsProvider extends ExportAble {
|
|||
exportSingleFile(entry, saveToLocation);
|
||||
}
|
||||
catch (Exception e){
|
||||
log.error("File export failure", e);
|
||||
log.error(getFile().getName()+" export failure ", e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -98,9 +98,14 @@ public class RomFsProvider extends ExportAble {
|
|||
}
|
||||
|
||||
private void exportSingleFile(FileSystemEntry entry, String saveToLocation) throws Exception {
|
||||
stream = producer.produce();
|
||||
long skipBytes = entry.getOffset() + mediaStartOffset * 0x200 + level6Header.getFileDataOffset() + level6Offset;
|
||||
export(saveToLocation, entry.getName(), skipBytes, entry.getSize());
|
||||
try {
|
||||
stream = producer.produce();
|
||||
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{
|
||||
|
|
|
@ -115,6 +115,9 @@ public class System2Header {
|
|||
sha256overEncryptedSection1 = Arrays.copyOfRange(decodedHeaderBytes, 0xa0, 0xc0);
|
||||
sha256overEncryptedSection2 = Arrays.copyOfRange(decodedHeaderBytes, 0xc0, 0xe0);
|
||||
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; }
|
||||
|
|
|
@ -21,11 +21,9 @@ package libKonogonka.Tools.other.System2;
|
|||
import libKonogonka.KeyChainHolder;
|
||||
import libKonogonka.Tools.ExportAble;
|
||||
import libKonogonka.Tools.other.System2.ini1.Ini1Provider;
|
||||
import libKonogonka.ctraesclassic.AesCtrClassicBufferedInputStream;
|
||||
import libKonogonka.ctraesclassic.AesCtrDecryptClassic;
|
||||
import libKonogonka.ctraesclassic.AesCtrStream;
|
||||
import libKonogonka.ctraes.InFileStreamProducer;
|
||||
import libKonogonka.ctraesclassic.InFileStreamClassicProducer;
|
||||
|
||||
import javax.crypto.CipherInputStream;
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
|
@ -39,16 +37,30 @@ public class System2Provider extends ExportAble {
|
|||
private KernelMap kernelMap;
|
||||
private Ini1Provider ini1Provider;
|
||||
|
||||
private final String pathToFile;
|
||||
private final KeyChainHolder keyChainHolder;
|
||||
private InFileStreamClassicProducer producer;
|
||||
|
||||
public System2Provider(String pathToFile, KeyChainHolder keyChainHolder) throws Exception{
|
||||
this.pathToFile = pathToFile;
|
||||
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();
|
||||
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();
|
||||
this.stream.close();
|
||||
}
|
||||
|
@ -66,43 +78,52 @@ public class System2Provider extends ExportAble {
|
|||
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{
|
||||
try (InputStream fis = Files.newInputStream(Paths.get(pathToFile))){
|
||||
// Encrypted section comes next
|
||||
long toSkip = 0x200;
|
||||
if (toSkip != fis.skip(toSkip))
|
||||
throw new Exception("Unable to skip offset: " + toSkip);
|
||||
if (0x200 != stream.skip(0x200))
|
||||
throw new Exception("Unable to skip offset of 0x200");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(0x1000);
|
||||
|
||||
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");
|
||||
for (int j = 0; j < 8; j++) {
|
||||
byte[] block = new byte[0x200];
|
||||
int actuallyRead;
|
||||
if ((actuallyRead = stream.read(block)) != 0x200)
|
||||
throw new Exception("Read failure " + actuallyRead);
|
||||
byteBuffer.put(block);
|
||||
}
|
||||
|
||||
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{
|
||||
Path filePath = Paths.get(pathToFile);
|
||||
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));
|
||||
|
||||
stream = producer.produce();
|
||||
return export(saveTo, "Kernel.bin", 0x200, header.getSection0size());
|
||||
}
|
||||
|
||||
|
@ -111,7 +132,8 @@ public class System2Provider extends ExportAble {
|
|||
public KernelMap getKernelMap() { return kernelMap; }
|
||||
public Ini1Provider getIni1Provider() throws Exception{
|
||||
if (ini1Provider == null)
|
||||
ini1Provider = new Ini1Provider(header, pathToFile, kernelMap);
|
||||
ini1Provider = new Ini1Provider(
|
||||
producer.getSuccessor(0x200 + kernelMap.getIni1Offset(), true));
|
||||
return ini1Provider;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
package libKonogonka.Tools.other.System2.ini1;
|
||||
|
||||
import libKonogonka.Tools.ExportAble;
|
||||
import libKonogonka.Tools.other.System2.KernelMap;
|
||||
import libKonogonka.Tools.other.System2.System2Header;
|
||||
import libKonogonka.ctraesclassic.InFileStreamClassicProducer;
|
||||
|
||||
|
@ -35,17 +34,26 @@ public class Ini1Provider extends ExportAble {
|
|||
|
||||
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);
|
||||
this.producer = new InFileStreamClassicProducer(filePath,
|
||||
0x200 + kernelMap.getIni1Offset(),
|
||||
0x200 + kernelMapIni1Offset,
|
||||
0x200,
|
||||
Files.size(filePath), // size of system2
|
||||
system2Header.getKey(),
|
||||
system2Header.getSection0Ctr());
|
||||
stream = producer.produce();
|
||||
this.stream = producer.produce();
|
||||
makeHeader();
|
||||
collectKips();
|
||||
this.stream.close();
|
||||
}
|
||||
|
||||
private void makeHeader() throws Exception{
|
||||
|
|
|
@ -54,23 +54,25 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
|
|||
private int pointerInsideDecryptedSection;
|
||||
|
||||
@Override
|
||||
public synchronized int read(byte[] b) throws IOException{
|
||||
int bytesToRead = b.length;
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if (off != 0 || len != b.length)
|
||||
throw new IOException("Not supported");
|
||||
|
||||
if (isPointerInsideEncryptedSection()){
|
||||
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)+")");
|
||||
System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesToRead);
|
||||
System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, len);
|
||||
|
||||
pseudoPos += bytesToRead;
|
||||
pointerInsideDecryptedSection += bytesToRead;
|
||||
pseudoPos += len;
|
||||
pointerInsideDecryptedSection += len;
|
||||
return b.length;
|
||||
}
|
||||
|
||||
if (isEndPositionInsideEncryptedSection(b.length)) {
|
||||
log.trace("1.1. Pointer Inside + End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
|
||||
int middleBlocksCount = (bytesToRead - bytesFromFirstBlock) / 0x200;
|
||||
int bytesFromLastBlock = (bytesToRead - bytesFromFirstBlock) % 0x200;
|
||||
int middleBlocksCount = (len - bytesFromFirstBlock) / 0x200;
|
||||
int bytesFromLastBlock = (len - bytesFromFirstBlock) % 0x200;
|
||||
//1
|
||||
System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesFromFirstBlock);
|
||||
//2
|
||||
|
@ -79,17 +81,17 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
|
|||
System.arraycopy(decryptedBytes, 0, b, bytesFromFirstBlock+i*0x200, 0x200);
|
||||
}
|
||||
//3
|
||||
if(fileSize > (pseudoPos+bytesToRead)) {
|
||||
if(fileSize > (pseudoPos+ len)) {
|
||||
fillDecryptedCache();
|
||||
System.arraycopy(decryptedBytes, 0, b, bytesFromFirstBlock + middleBlocksCount * 0x200, bytesFromLastBlock);
|
||||
}
|
||||
pseudoPos += bytesToRead;
|
||||
pseudoPos += len;
|
||||
pointerInsideDecryptedSection = bytesFromLastBlock;
|
||||
return b.length;
|
||||
}
|
||||
log.trace("1. Pointer Inside + End Position Outside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
|
||||
int middleBlocksCount = (int) ((mediaOffsetPositionEnd - (pseudoPos+bytesFromFirstBlock)) / 0x200);
|
||||
int bytesFromEnd = bytesToRead - bytesFromFirstBlock - middleBlocksCount * 0x200;
|
||||
int bytesFromEnd = len - bytesFromFirstBlock - middleBlocksCount * 0x200;
|
||||
//1
|
||||
System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesFromFirstBlock);
|
||||
//2
|
||||
|
@ -100,15 +102,15 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
|
|||
}
|
||||
//3 // TODO: if it's zero?
|
||||
System.arraycopy(readChunk(bytesFromEnd), 0, b, bytesFromFirstBlock+middleBlocksCount*0x200, bytesFromEnd);
|
||||
pseudoPos += bytesToRead;
|
||||
pseudoPos += len;
|
||||
pointerInsideDecryptedSection = 0;
|
||||
return b.length;
|
||||
}
|
||||
if (isEndPositionInsideEncryptedSection(bytesToRead)) {
|
||||
if (isEndPositionInsideEncryptedSection(len)) {
|
||||
log.trace("2. End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
|
||||
int bytesTillEncrypted = (int) (mediaOffsetPositionStart - pseudoPos);
|
||||
int fullEncryptedBlocks = (bytesToRead - bytesTillEncrypted) / 0x200;
|
||||
int incompleteEncryptedBytes = (bytesToRead - bytesTillEncrypted) % 0x200;
|
||||
int fullEncryptedBlocks = (len - bytesTillEncrypted) / 0x200;
|
||||
int incompleteEncryptedBytes = (len - bytesTillEncrypted) % 0x200;
|
||||
System.arraycopy(readChunk(bytesTillEncrypted), 0, b, 0, bytesTillEncrypted);
|
||||
//2
|
||||
for (int i = 0; i < fullEncryptedBlocks; i++) {
|
||||
|
@ -118,12 +120,12 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
|
|||
//3
|
||||
fillDecryptedCache();
|
||||
System.arraycopy(decryptedBytes, 0, b, bytesTillEncrypted+fullEncryptedBlocks*0x200, incompleteEncryptedBytes);
|
||||
pseudoPos += bytesToRead;
|
||||
pseudoPos += len;
|
||||
pointerInsideDecryptedSection = incompleteEncryptedBytes;
|
||||
return b.length;
|
||||
}
|
||||
log.trace("3. Not encrypted ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
|
||||
pseudoPos += bytesToRead;
|
||||
pseudoPos += len;
|
||||
pointerInsideDecryptedSection = 0;
|
||||
return super.read(b);
|
||||
}
|
||||
|
@ -146,10 +148,9 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
|
|||
}
|
||||
private byte[] readChunk(int bytes) throws IOException{
|
||||
byte[] chunkBytes = new byte[bytes];
|
||||
long actuallyRead = super.read(chunkBytes);
|
||||
long actuallyRead = super.read(chunkBytes, 0, bytes);
|
||||
if (actuallyRead != bytes)
|
||||
throw new IOException("Can't read. Need block of "+ bytes +" while only " +
|
||||
actuallyRead + " bytes.");
|
||||
throw new IOException("Can't read: " + actuallyRead + "/"+ bytes);
|
||||
return chunkBytes;
|
||||
}
|
||||
|
||||
|
|
|
@ -138,8 +138,7 @@ public class AesCtrClassicBufferedInputStream extends BufferedInputStream {
|
|||
byte[] chunkBytes = new byte[bytes];
|
||||
long actuallyRead = super.read(chunkBytes);
|
||||
if (actuallyRead != bytes)
|
||||
throw new IOException("Can't read. Need block of "+ bytes +" while only " +
|
||||
actuallyRead + " bytes.");
|
||||
throw new IOException("Can't read. "+ bytes +"/" + actuallyRead);
|
||||
return chunkBytes;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import javax.crypto.spec.SecretKeySpec;
|
|||
import java.io.InputStream;
|
||||
import java.security.Security;
|
||||
|
||||
@Deprecated
|
||||
public class AesCtrStream {
|
||||
private static boolean BCinitialized = false;
|
||||
|
||||
|
|
|
@ -19,21 +19,36 @@
|
|||
package libKonogonka.ctraesclassic;
|
||||
|
||||
import libKonogonka.IProducer;
|
||||
import libKonogonka.RainbowDump;
|
||||
import libKonogonka.ctraes.InFileStreamProducer;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class InFileStreamClassicProducer implements IProducer {
|
||||
private boolean encrypted;
|
||||
|
||||
private final Path filePath;
|
||||
private Path filePath;
|
||||
private InFileStreamProducer parentProducer;
|
||||
private long offset;
|
||||
private long encryptedStartOffset;
|
||||
private long encryptedEndOffset;
|
||||
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,
|
||||
long offset,
|
||||
long encryptedStartOffset,
|
||||
|
@ -46,43 +61,94 @@ public class InFileStreamClassicProducer implements IProducer {
|
|||
this.encryptedStartOffset = encryptedStartOffset;
|
||||
this.encryptedEndOffset = encryptedEndOffset;
|
||||
this.decryptor = new AesCtrDecryptClassic(key, iv);
|
||||
this.fileSize = Files.size(filePath);
|
||||
}
|
||||
public InFileStreamClassicProducer(Path filePath,
|
||||
private InFileStreamClassicProducer(Path filePath,
|
||||
long offset,
|
||||
long encryptedStartOffset,
|
||||
long encryptedEndOffset, //Files.size(filePath)
|
||||
AesCtrDecryptClassic decryptor){
|
||||
AesCtrDecryptClassic decryptor) throws Exception{
|
||||
this.encrypted = true;
|
||||
this.filePath = filePath;
|
||||
this.offset = offset;
|
||||
this.encryptedStartOffset = encryptedStartOffset;
|
||||
this.encryptedEndOffset = encryptedEndOffset;
|
||||
this.decryptor = decryptor;
|
||||
this.fileSize = Files.size(filePath);
|
||||
}
|
||||
|
||||
public InFileStreamClassicProducer(Path filePath){
|
||||
/** Stream producer for non-encrypted files.
|
||||
* @param filePath Path to file-container
|
||||
* */
|
||||
public InFileStreamClassicProducer(Path filePath) throws Exception{
|
||||
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.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
|
||||
public BufferedInputStream produce() throws Exception{
|
||||
if (encrypted)
|
||||
return produceAesCtr();
|
||||
else
|
||||
return produceNotEncrypted();
|
||||
return produceNotEncrypted();
|
||||
}
|
||||
|
||||
private BufferedInputStream produceAesCtr() throws Exception{
|
||||
decryptor.reset();
|
||||
AesCtrClassicBufferedInputStream stream = new AesCtrClassicBufferedInputStream(decryptor,
|
||||
encryptedStartOffset,
|
||||
encryptedEndOffset,
|
||||
Files.newInputStream(filePath),
|
||||
Files.size(filePath));
|
||||
|
||||
InputStream is;
|
||||
|
||||
if (filePath == null)
|
||||
is = parentProducer.produce();
|
||||
else
|
||||
is = Files.newInputStream(filePath);
|
||||
|
||||
AesCtrClassicBufferedInputStream stream = new AesCtrClassicBufferedInputStream(
|
||||
decryptor, encryptedStartOffset, encryptedEndOffset, is, fileSize);
|
||||
|
||||
if (offset != stream.skip(offset))
|
||||
throw new Exception("Unable to skip offset: "+offset);
|
||||
|
@ -91,18 +157,29 @@ public class InFileStreamClassicProducer implements IProducer {
|
|||
}
|
||||
|
||||
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))
|
||||
throw new Exception("Unable to skip offset: "+offset);
|
||||
|
||||
return stream;
|
||||
}
|
||||
@Override
|
||||
public InFileStreamClassicProducer getSuccessor(long offset){
|
||||
if (encrypted)
|
||||
return new InFileStreamClassicProducer(filePath, offset, encryptedStartOffset, encryptedEndOffset, decryptor);
|
||||
return new InFileStreamClassicProducer(filePath, offset);
|
||||
public InFileStreamClassicProducer getSuccessor(long offset) throws Exception{
|
||||
if (! encrypted)
|
||||
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)
|
||||
return getSuccessor(this.offset + offset);
|
||||
return getSuccessor(offset);
|
||||
|
@ -113,9 +190,15 @@ public class InFileStreamClassicProducer implements IProducer {
|
|||
return encrypted;
|
||||
}
|
||||
@Override
|
||||
public File getFile(){ return filePath.toFile(); }
|
||||
public File getFile(){
|
||||
if (filePath == null)
|
||||
return parentProducer.getFile();
|
||||
return filePath.toFile();
|
||||
}
|
||||
@Override
|
||||
public String toString(){
|
||||
if (filePath == null)
|
||||
return parentProducer.getFile().getAbsolutePath();
|
||||
return filePath.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,15 @@
|
|||
*/
|
||||
package libKonogonka.RomFsDecrypted;
|
||||
|
||||
import libKonogonka.Converter;
|
||||
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.ini1.Ini1Provider;
|
||||
import libKonogonka.Tools.other.System2.ini1.KIP1Provider;
|
||||
import libKonogonka.ctraes.InFileStreamProducer;
|
||||
import libKonogonka.ctraesclassic.AesCtrDecryptClassic;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
|
@ -32,9 +37,8 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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";
|
||||
|
||||
final String pathToFirmware = "/home/loper/Загрузки/patchesPlayground/nintendo-switch-global-firmwares/Firmware 14.1.0";
|
||||
|
||||
@DisplayName("Package2 unpacked test")
|
||||
@Test
|
||||
void discover() throws Exception{
|
||||
|
@ -118,17 +124,130 @@ public class Package2UnpackedTest {
|
|||
|
||||
@DisplayName("KIP1 unpack test")
|
||||
@Test
|
||||
void unpackKip1() throws Exception{
|
||||
void unpackKip1FromNca() throws Exception{
|
||||
keyChainHolder = new KeyChainHolder(keysFileLocation, null);
|
||||
System2Provider provider = new System2Provider(fileLocation, keyChainHolder);
|
||||
Ini1Provider ini1Provider = provider.getIni1Provider();
|
||||
for (KIP1Provider kip1Provider : ini1Provider.getKip1List())
|
||||
if (kip1Provider.getHeader().getName().startsWith("FS"))
|
||||
kip1Provider.printDebug();
|
||||
// ------------------------------------------------------------------------------------------------------------
|
||||
File firmware = new File(pathToFirmware);
|
||||
|
||||
for (KIP1Provider kip1Provider : ini1Provider.getKip1List()) {
|
||||
if (kip1Provider.getHeader().getName().startsWith("FS"))
|
||||
kip1Provider.exportAsDecompressed("/tmp");
|
||||
if (! firmware.exists())
|
||||
throw new Exception("Firmware directory does not exist " + pathToFirmware);
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue