From 7add08c196f92ab3268de667cf40139b5e3ec690 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko Date: Sat, 14 Jan 2023 15:07:32 +0300 Subject: [PATCH] Ass stream support into System2 related classes. --- src/main/java/libKonogonka/IProducer.java | 3 +- .../java/libKonogonka/KeyChainHolder.java | 35 +---- .../Tools/RomFs/RomFsProvider.java | 13 +- .../Tools/other/System2/System2Header.java | 3 + .../Tools/other/System2/System2Provider.java | 98 +++++++----- .../other/System2/ini1/Ini1Provider.java | 16 +- .../ctraes/AesCtrBufferedInputStream.java | 41 ++--- .../AesCtrClassicBufferedInputStream.java | 3 +- .../ctraesclassic/AesCtrStream.java | 1 + .../InFileStreamClassicProducer.java | 123 ++++++++++++--- .../RomFsDecrypted/Package2UnpackedTest.java | 143 ++++++++++++++++-- 11 files changed, 350 insertions(+), 129 deletions(-) diff --git a/src/main/java/libKonogonka/IProducer.java b/src/main/java/libKonogonka/IProducer.java index eb162a3..7710e83 100644 --- a/src/main/java/libKonogonka/IProducer.java +++ b/src/main/java/libKonogonka/IProducer.java @@ -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(); } diff --git a/src/main/java/libKonogonka/KeyChainHolder.java b/src/main/java/libKonogonka/KeyChainHolder.java index c3c3bfb..a430984 100644 --- a/src/main/java/libKonogonka/KeyChainHolder.java +++ b/src/main/java/libKonogonka/KeyChainHolder.java @@ -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 getRawKeySet() { - return rawKeySet; - } - - public HashMap getKey_area_key_application() { - return key_area_key_application; - } - - public HashMap getKey_area_key_ocean() { - return key_area_key_ocean; - } - - public HashMap getKey_area_key_system() { - return key_area_key_system; - } - - public HashMap getTitlekek() { - return titlekek; - } + public String getXci_header_key() { return xci_header_key; } + public String getHeader_key() { return rawKeySet.get("header_key"); } + public HashMap getRawKeySet() { return rawKeySet; } + public HashMap getKey_area_key_application() { return key_area_key_application; } + public HashMap getKey_area_key_ocean() { return key_area_key_ocean; } + public HashMap getKey_area_key_system() { return key_area_key_system; } + public HashMap getTitlekek() { return titlekek; } } diff --git a/src/main/java/libKonogonka/Tools/RomFs/RomFsProvider.java b/src/main/java/libKonogonka/Tools/RomFs/RomFsProvider.java index 8ce3107..b90fefd 100644 --- a/src/main/java/libKonogonka/Tools/RomFs/RomFsProvider.java +++ b/src/main/java/libKonogonka/Tools/RomFs/RomFsProvider.java @@ -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{ diff --git a/src/main/java/libKonogonka/Tools/other/System2/System2Header.java b/src/main/java/libKonogonka/Tools/other/System2/System2Header.java index e523ce6..817913e 100644 --- a/src/main/java/libKonogonka/Tools/other/System2/System2Header.java +++ b/src/main/java/libKonogonka/Tools/other/System2/System2Header.java @@ -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; } diff --git a/src/main/java/libKonogonka/Tools/other/System2/System2Provider.java b/src/main/java/libKonogonka/Tools/other/System2/System2Provider.java index 3c5eb64..58a1bcb 100644 --- a/src/main/java/libKonogonka/Tools/other/System2/System2Provider.java +++ b/src/main/java/libKonogonka/Tools/other/System2/System2Provider.java @@ -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; } } diff --git a/src/main/java/libKonogonka/Tools/other/System2/ini1/Ini1Provider.java b/src/main/java/libKonogonka/Tools/other/System2/ini1/Ini1Provider.java index 0f31a9f..ab8bb6d 100644 --- a/src/main/java/libKonogonka/Tools/other/System2/ini1/Ini1Provider.java +++ b/src/main/java/libKonogonka/Tools/other/System2/ini1/Ini1Provider.java @@ -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{ diff --git a/src/main/java/libKonogonka/ctraes/AesCtrBufferedInputStream.java b/src/main/java/libKonogonka/ctraes/AesCtrBufferedInputStream.java index e89f31f..757f31c 100644 --- a/src/main/java/libKonogonka/ctraes/AesCtrBufferedInputStream.java +++ b/src/main/java/libKonogonka/ctraes/AesCtrBufferedInputStream.java @@ -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; } diff --git a/src/main/java/libKonogonka/ctraesclassic/AesCtrClassicBufferedInputStream.java b/src/main/java/libKonogonka/ctraesclassic/AesCtrClassicBufferedInputStream.java index e9de976..43d4091 100644 --- a/src/main/java/libKonogonka/ctraesclassic/AesCtrClassicBufferedInputStream.java +++ b/src/main/java/libKonogonka/ctraesclassic/AesCtrClassicBufferedInputStream.java @@ -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; } diff --git a/src/main/java/libKonogonka/ctraesclassic/AesCtrStream.java b/src/main/java/libKonogonka/ctraesclassic/AesCtrStream.java index 48598df..9c3b35e 100644 --- a/src/main/java/libKonogonka/ctraesclassic/AesCtrStream.java +++ b/src/main/java/libKonogonka/ctraesclassic/AesCtrStream.java @@ -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; diff --git a/src/main/java/libKonogonka/ctraesclassic/InFileStreamClassicProducer.java b/src/main/java/libKonogonka/ctraesclassic/InFileStreamClassicProducer.java index 342af11..d697190 100644 --- a/src/main/java/libKonogonka/ctraesclassic/InFileStreamClassicProducer.java +++ b/src/main/java/libKonogonka/ctraesclassic/InFileStreamClassicProducer.java @@ -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(); } } diff --git a/src/test/java/libKonogonka/RomFsDecrypted/Package2UnpackedTest.java b/src/test/java/libKonogonka/RomFsDecrypted/Package2UnpackedTest.java index a78cb70..effc24f 100644 --- a/src/test/java/libKonogonka/RomFsDecrypted/Package2UnpackedTest.java +++ b/src/test/java/libKonogonka/RomFsDecrypted/Package2UnpackedTest.java @@ -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 ncaFilesList = Arrays.asList(Objects.requireNonNull(fileNamesArray)); + if (ncaFilesList.size() == 0) + throw new Exception("No NCA files found in firmware folder"); + + List 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"); + } }