diff --git a/src/main/java/konogonka/Tools/NCA/NCAContentPFS0.java b/src/main/java/konogonka/Tools/NCA/NCAContentPFS0.java index 29c56e1..44c222a 100644 --- a/src/main/java/konogonka/Tools/NCA/NCAContentPFS0.java +++ b/src/main/java/konogonka/Tools/NCA/NCAContentPFS0.java @@ -1,12 +1,10 @@ package konogonka.Tools.NCA; -import konogonka.LoperConverter; -import konogonka.RainbowHexDump; import konogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock; import konogonka.Tools.PFS0.IPFS0Provider; import konogonka.Tools.PFS0.PFS0EncryptedProvider; import konogonka.Tools.PFS0.PFS0Provider; -import konogonka.ctraes.AesCtr; +import konogonka.ctraes.AesCtrDecryptSimple; import java.io.*; import java.util.LinkedList; @@ -94,7 +92,7 @@ public class NCAContentPFS0 { long abosluteOffsetPosition = offsetPosition + (mediaStartOffset * 0x200); raf.seek(abosluteOffsetPosition); - AesCtrDecryptor decryptor = new AesCtrDecryptor(decryptedKey, ncaSectionBlock.getSectionCTR(), mediaStartOffset * 0x200); + AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey, ncaSectionBlock.getSectionCTR(), mediaStartOffset * 0x200); byte[] encryptedBlock; byte[] dectyptedBlock; @@ -106,7 +104,6 @@ public class NCAContentPFS0 { Thread pThread = new Thread(new ParseThread( streamInp, ncaSectionBlock.getSuperBlockPFS0().getPfs0offset(), - ncaSectionBlock.getSuperBlockPFS0().getPfs0size(), ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset(), ncaSectionBlock.getSuperBlockPFS0().getHashTableSize() )); @@ -129,39 +126,46 @@ public class NCAContentPFS0 { pThread.join(); streamOut.close(); raf.close(); - } - /* - * Simplify decryption of the CTR - * */ - private class AesCtrDecryptor{ - - private long realMediaOffset; - byte[] IVarray; - private AesCtr aesCtr; - - AesCtrDecryptor(byte[] decryptedKey, byte[] sectionCTR, long realMediaOffset) throws Exception{ - this.realMediaOffset = realMediaOffset; - aesCtr = new AesCtr(decryptedKey); - // IV for CTR == 16 bytes - IVarray = new byte[0x10]; - // Populate first 8 bytes taken from Header's section Block CTR - System.arraycopy(LoperConverter.flip(sectionCTR), 0x0, IVarray, 0x0, 0x8); + // TODO: re-write + if (pfs0 != null){ + PFS0EncryptedProvider pfs0enc = (PFS0EncryptedProvider)pfs0; + pfs0enc.setMeta( + offsetPosition, + file, + decryptedKey, + ncaSectionBlock.getSectionCTR(), + mediaStartOffset, + mediaEndOffset + ); } + //****************************************___DEBUG___******************************************************* + //* + File contentFile = new File("/tmp/decryptedNCA0block.pfs0"); + BufferedOutputStream extractedFileOS = new BufferedOutputStream(new FileOutputStream(contentFile)); - public byte[] dectyptNext(byte[] enctyptedBlock) throws Exception{ - updateIV(realMediaOffset); - byte[] decryptedBlock = aesCtr.decrypt(enctyptedBlock, IVarray); - realMediaOffset += 0x200; - return decryptedBlock; - } - // Populate last 8 bytes calculated. Thanks hactool project! - private void updateIV(long offset){ - offset >>= 4; - for (int i = 0; i < 0x8; i++){ - IVarray[0x10-i-1] = (byte)(offset & 0xff); // Note: issues could be here - offset >>= 8; + raf = new RandomAccessFile(file, "r"); + raf.seek(abosluteOffsetPosition); + decryptor = new AesCtrDecryptSimple(decryptedKey, ncaSectionBlock.getSectionCTR(), mediaStartOffset * 0x200); + + for (int i = 0; i < mediaBlockSize; i++){ + encryptedBlock = new byte[0x200]; + if (raf.read(encryptedBlock) != -1){ + //dectyptedBlock = aesCtr.decrypt(encryptedBlock); + dectyptedBlock = decryptor.dectyptNext(encryptedBlock); + // Writing decrypted data to pipe + try { + extractedFileOS.write(dectyptedBlock); + } + catch (IOException e){ + System.out.println("Exception @extract"); + break; + } } } + extractedFileOS.close(); + raf.close(); + System.out.println("@extract done"); + //*//******************************************************************************************************/ } /* * Since we representing decrypted data as stream (it's easier to look on it this way), @@ -175,15 +179,14 @@ public class NCAContentPFS0 { long hashTableSize; long hashTableRecordsCount; long pfs0offset; - long pfs0size; - ParseThread(PipedInputStream pipedInputStream, long pfs0offset, long pfs0size, long hashTableOffset, long hashTableSize){ + + ParseThread(PipedInputStream pipedInputStream, long pfs0offset, long hashTableOffset, long hashTableSize){ this.pipedInputStream = pipedInputStream; this.hashTableOffset = hashTableOffset; this.hashTableSize = hashTableSize; this.hashTableRecordsCount = hashTableSize / 0x20; this.pfs0offset = pfs0offset; - this.pfs0size = pfs0size; } @Override @@ -220,7 +223,7 @@ public class NCAContentPFS0 { counter += toSkip; } //--------------------------------------------------------- - pfs0 = new PFS0EncryptedProvider(pipedInputStream); + pfs0 = new PFS0EncryptedProvider(pipedInputStream, counter); pipedInputStream.close(); } catch (Exception e){ diff --git a/src/main/java/konogonka/Tools/PFS0/IPFS0Provider.java b/src/main/java/konogonka/Tools/PFS0/IPFS0Provider.java index a4dcd23..e4d8cd5 100644 --- a/src/main/java/konogonka/Tools/PFS0/IPFS0Provider.java +++ b/src/main/java/konogonka/Tools/PFS0/IPFS0Provider.java @@ -1,6 +1,7 @@ package konogonka.Tools.PFS0; public interface IPFS0Provider { + boolean isEncrypted(); String getMagic(); int getFilesCount(); int getStringTableSize(); diff --git a/src/main/java/konogonka/Tools/PFS0/PFS0EncryptedProvider.java b/src/main/java/konogonka/Tools/PFS0/PFS0EncryptedProvider.java index 46baf4b..429e592 100644 --- a/src/main/java/konogonka/Tools/PFS0/PFS0EncryptedProvider.java +++ b/src/main/java/konogonka/Tools/PFS0/PFS0EncryptedProvider.java @@ -1,14 +1,15 @@ package konogonka.Tools.PFS0; -import java.io.PipedInputStream; +import konogonka.ctraes.AesCtrDecryptSimple; + +import java.io.*; import java.nio.charset.StandardCharsets; import java.util.Arrays; -import static konogonka.LoperConverter.getLEint; -import static konogonka.LoperConverter.getLElong; +import static konogonka.LoperConverter.*; public class PFS0EncryptedProvider implements IPFS0Provider{ - private long rawFileDataStart; // If -1 then this PFS0 located @ encrypted region + private long rawFileDataStart; // For this class is pointing to data start position relative to media block start private String magic; private int filesCount; @@ -17,15 +18,145 @@ public class PFS0EncryptedProvider implements IPFS0Provider{ private PFS0subFile[] pfs0subFiles; //--------------------------------------- - /* - absOffsetPosOfMediaBlock - Counter - PFS0 Position - mediaBlockSize - PFS0 Subsustem Size - * */ + private long rawBlockDataStart; + + private PFS0DecryptedStreamProvider pfs0DecryptedStreamProvider; + // Let's do some fuck + private class PFS0DecryptedStreamProvider{ + private long mediaStartOffset; // * 0x200 + private long mediaEndOffset; + + + private RandomAccessFile raf; + private PipedOutputStream streamOut; + private PipedInputStream streamInp; + private AesCtrDecryptSimple aesCtrDecryptSimple; // if null, then exception happened. + + public PipedInputStream getPipedInputStream(){ return streamInp; } + + + public void getStarted(PFS0subFile subFile) throws Exception{ + + System.out.println("rawBlockDataStart: "+rawBlockDataStart); + System.out.println("Skip blocks: "+rawBlockDataStart/0x200); // aesCtrDecryptSimple.skip(THIS) + System.out.println("Skip bytes: "+ (rawBlockDataStart-(rawBlockDataStart/0x200)*0x200)); // write to stream after skiping THIS + + // DBG START + File contentFile = new File("/tmp/pfs0-NCA0block.pfs0"); + BufferedOutputStream extractedFileOS = new BufferedOutputStream(new FileOutputStream(contentFile)); + // DBG END + long mediaBlockSize = mediaEndOffset - mediaStartOffset; + byte[] encryptedBlock; + byte[] dectyptedBlock; + + // Skip full-size blocks of 512 bytes that we don't need and start decryption from required one + long startFromBlock = rawBlockDataStart/0x200; + if (startFromBlock > 0) { + aesCtrDecryptSimple.skipNext(startFromBlock); + raf.seek(raf.getFilePointer() + (startFromBlock*0x200)); + } + // Since our data could be located in position with some offset from the decrypted block, let's skip bytes left + int skipBytes = (int)(rawBlockDataStart-(rawBlockDataStart/0x200)*0x200); + if (skipBytes > 0){ + encryptedBlock = new byte[0x200]; + if (raf.read(encryptedBlock) != -1){ + dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock); + try { + // DBG START + extractedFileOS.write(dectyptedBlock, skipBytes, 0x200-skipBytes); + // DBG END + //streamOut.write(dectyptedBlock, skipBytes, 0x200); + } + catch (IOException e){ + System.out.println("Exception @extract 1st bock"); + } + } + startFromBlock++; + } + + for (long i = startFromBlock; i < mediaBlockSize; i++){ + encryptedBlock = new byte[0x200]; + if (raf.read(encryptedBlock) != -1){ + //dectyptedBlock = aesCtr.decrypt(encryptedBlock); + dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock); + // Writing decrypted data to pipe + try { + // DBG START + extractedFileOS.write(dectyptedBlock); + // DBG END + //streamOut.write(dectyptedBlock); + } + catch (IOException e){ + System.out.println("Exception @extract"); + break; + } + } + } + + // DBG START + extractedFileOS.close(); + // DBG END + } + + PFS0DecryptedStreamProvider(File file, + long rawBlockDataStart, + long offsetPositionInFile, + byte[] key, + byte[] sectionCTR, + long mediaStartOffset, + long mediaEndOffset + ){ + this.mediaStartOffset = mediaStartOffset; + this.mediaEndOffset = mediaEndOffset; + + try { + this.raf = new RandomAccessFile(file, "r"); + this.raf.seek(offsetPositionInFile + (mediaStartOffset * 0x200)); + this.aesCtrDecryptSimple = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200); + + this.streamOut = new PipedOutputStream(); + this.streamInp = new PipedInputStream(streamOut); + } + catch (Exception e) { + e.printStackTrace(); + } + } + } + // TODO: simplify + public void setMeta( + long offsetPositionInFile, + File fileWithEncPFS0, + byte[] key, + byte[] sectionCTR, + long mediaStartOffset, + long mediaEndOffset + ){ + this.pfs0DecryptedStreamProvider = new PFS0DecryptedStreamProvider( + fileWithEncPFS0, + rawBlockDataStart, + offsetPositionInFile, + key, + sectionCTR, + mediaStartOffset, + mediaEndOffset + ); + try{ + pfs0DecryptedStreamProvider.getStarted(pfs0subFiles[0]); + } + catch (Exception e){ + e.printStackTrace(); + } + } //--------------------------------------- + public PFS0EncryptedProvider(PipedInputStream pipedInputStream, + long pfs0offsetPosition + ) throws Exception{ + // pfs0offsetPosition is a position relative to Media block. Lets add pfs0 'header's' bytes count and get raw data start position in media block + rawFileDataStart = -1; // Set -1 for PFS0EncryptedProvider + // Detect raw data start position using next var + rawBlockDataStart = pfs0offsetPosition; - public PFS0EncryptedProvider(PipedInputStream pipedInputStream) throws Exception{ byte[] fileStartingBytes = new byte[0x10]; // Read PFS0Provider, files count, header, padding (4 zero bytes) @@ -36,6 +167,8 @@ public class PFS0EncryptedProvider implements IPFS0Provider{ } fileStartingBytes[i] = (byte)currentByte; } + // Update position + rawBlockDataStart += 0x10; // Check PFS0Provider magic = new String(fileStartingBytes, 0x0, 0x4, StandardCharsets.US_ASCII); if (! magic.equals("PFS0")){ @@ -73,6 +206,8 @@ public class PFS0EncryptedProvider implements IPFS0Provider{ sizesSubFiles[i] = getLElong(fileEntryTable, 0x8); strTableOffsets[i] = getLEint(fileEntryTable, 0x10); zeroBytes[i] = Arrays.copyOfRange(fileEntryTable, 0x14, 0x18); + // Update position + rawBlockDataStart += 0x18; } //********************************************************************************************************** // In here pointer in front of String table @@ -86,6 +221,8 @@ public class PFS0EncryptedProvider implements IPFS0Provider{ } stringTbl[i] = (byte)currentByte; } + // Update position + rawBlockDataStart += stringTableSize; for (int i=0; i < filesCount; i++){ int j = 0; @@ -101,14 +238,21 @@ public class PFS0EncryptedProvider implements IPFS0Provider{ zeroBytes[i] ); } - rawFileDataStart = -1; + } + @Override + public boolean isEncrypted() { return true; } + @Override public String getMagic() { return magic; } + @Override public int getFilesCount() { return filesCount; } + @Override public int getStringTableSize() { return stringTableSize; } + @Override public byte[] getPadding() { return padding; } - + @Override public long getRawFileDataStart() { return rawFileDataStart; } + @Override public PFS0subFile[] getPfs0subFiles() { return pfs0subFiles; } } diff --git a/src/main/java/konogonka/Tools/PFS0/PFS0Provider.java b/src/main/java/konogonka/Tools/PFS0/PFS0Provider.java index 1bcfea7..a89cc7c 100644 --- a/src/main/java/konogonka/Tools/PFS0/PFS0Provider.java +++ b/src/main/java/konogonka/Tools/PFS0/PFS0Provider.java @@ -93,11 +93,18 @@ public class PFS0Provider implements IPFS0Provider{ raf.close(); } + @Override + public boolean isEncrypted() { return false; } + @Override public String getMagic() { return magic; } + @Override public int getFilesCount() { return filesCount; } + @Override public int getStringTableSize() { return stringTableSize; } + @Override public byte[] getPadding() { return padding; } - + @Override public long getRawFileDataStart() { return rawFileDataStart; } + @Override public PFS0subFile[] getPfs0subFiles() { return pfs0subFiles; } } diff --git a/src/main/java/konogonka/ctraes/AesCtrDecryptSimple.java b/src/main/java/konogonka/ctraes/AesCtrDecryptSimple.java new file mode 100644 index 0000000..de2067b --- /dev/null +++ b/src/main/java/konogonka/ctraes/AesCtrDecryptSimple.java @@ -0,0 +1,45 @@ +package konogonka.ctraes; + +import konogonka.LoperConverter; +/** + * Simplify decryption of the CTR + */ +public class AesCtrDecryptSimple { + + private long realMediaOffset; + private byte[] IVarray; + private AesCtr aesCtr; + + public AesCtrDecryptSimple(byte[] key, byte[] sectionCTR, long realMediaOffset) throws Exception{ + this.realMediaOffset = realMediaOffset; + aesCtr = new AesCtr(key); + // IV for CTR == 16 bytes + IVarray = new byte[0x10]; + // Populate first 8 bytes taken from Header's section Block CTR + System.arraycopy(LoperConverter.flip(sectionCTR), 0x0, IVarray, 0x0, 0x8); + } + + public void skipNext(){ + realMediaOffset += 0x200; + } + + public void skipNext(long blocksNum){ + if (blocksNum > 0) + realMediaOffset += blocksNum * 0x200; + } + + public byte[] dectyptNext(byte[] enctyptedBlock) throws Exception{ + updateIV(realMediaOffset); + byte[] decryptedBlock = aesCtr.decrypt(enctyptedBlock, IVarray); + realMediaOffset += 0x200; + return decryptedBlock; + } + // Populate last 8 bytes calculated. Thanks hactool project! + private void updateIV(long offset){ + offset >>= 4; + for (int i = 0; i < 0x8; i++){ + IVarray[0x10-i-1] = (byte)(offset & 0xff); // Note: issues could be here + offset >>= 8; + } + } +} diff --git a/src/main/resources/FXML/NCA/NCATab.fxml b/src/main/resources/FXML/NCA/NCATab.fxml index a9989fe..6cb6ada 100644 --- a/src/main/resources/FXML/NCA/NCATab.fxml +++ b/src/main/resources/FXML/NCA/NCATab.fxml @@ -407,7 +407,7 @@ - diff --git a/src/main/resources/FXML/Settings/SettingsLayout.fxml b/src/main/resources/FXML/Settings/SettingsLayout.fxml index 6dd613d..e870201 100644 --- a/src/main/resources/FXML/Settings/SettingsLayout.fxml +++ b/src/main/resources/FXML/Settings/SettingsLayout.fxml @@ -112,7 +112,7 @@