diff --git a/src/main/java/libKonogonka/ctraes/AesCtrBufferedInputStream.java b/src/main/java/libKonogonka/ctraes/AesCtrBufferedInputStream.java new file mode 100644 index 0000000..09f1c5c --- /dev/null +++ b/src/main/java/libKonogonka/ctraes/AesCtrBufferedInputStream.java @@ -0,0 +1,246 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libKonogonka is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libKonogonka. If not, see . + */ +package libKonogonka.ctraes; + +import libKonogonka.RainbowDump; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.*; + +public class AesCtrBufferedInputStream extends BufferedInputStream { + private final static Logger log = LogManager.getLogger(AesCtrBufferedInputStream.class); + + private final AesCtrDecryptSimple decryptor; + private final long mediaOffsetPositionStart; + private final long mediaOffsetPositionEnd; + + public AesCtrBufferedInputStream(AesCtrDecryptSimple decryptor, + long offsetPosition, + long mediaStartOffset, + long mediaEndOffset, + InputStream inputStream){ + super(inputStream); + this.decryptor = decryptor; + this.mediaOffsetPositionStart = offsetPosition + (mediaStartOffset * 0x200); + this.mediaOffsetPositionEnd = offsetPosition + (mediaEndOffset * 0x200); + + log.debug("\nOffset Position "+offsetPosition+ + "\nMediaOffsetPositionStart "+RainbowDump.formatDecHexString(mediaOffsetPositionStart)+ + "\nMediaOffsetPositionEnd "+RainbowDump.formatDecHexString(mediaOffsetPositionEnd)); + } + + private byte[] decryptedBytes; + private long pseudoPos; + private int pointerInsideDecryptedSection; + + @Override + public synchronized int read(byte[] b) throws IOException { + int bytesToRead = b.length; + if (isPointerInsideEncryptedSection()){ + int bytesFromFirstBlock = 0x200 - pointerInsideDecryptedSection; + if (bytesFromFirstBlock > bytesToRead){ + log.trace("1.2. Pointer Inside + End Position Inside (Decrypted) Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")"); + System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesToRead); + + log.error("!Pointer Inside Decrypted Section "+pointerInsideDecryptedSection+" "+(pointerInsideDecryptedSection+bytesToRead)); + pseudoPos += bytesToRead; + pointerInsideDecryptedSection += bytesToRead; + 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; + //1 + System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesFromFirstBlock); + //2 + for (int i = 0; i < middleBlocksCount; i++) { + fillDecryptedCache(); + System.arraycopy(decryptedBytes, 0, b, bytesFromFirstBlock+i*0x200, 0x200); + } + //3 // TODO: ONLY IF NON-NULL?? + fillDecryptedCache(); + System.arraycopy(decryptedBytes, 0, b, bytesFromFirstBlock+middleBlocksCount*0x200, bytesFromLastBlock); + pseudoPos += bytesToRead; + pointerInsideDecryptedSection = bytesFromLastBlock; + return b.length; + } + log.trace("1. Pointer Inside + End Position Outside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")"); + int middleBlocksCount = (int) ((mediaOffsetPositionEnd - pseudoPos) / 0x200); + int bytesFromEnd = b.length - bytesFromFirstBlock - middleBlocksCount * 0x200; + //1 + System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesFromFirstBlock); + //2 + //System.out.println("\n"+bytesFromFirstBlock+"\n"+ middleBlocksCount+" - "+(middleBlocksCount*0x200)+"\n"+ bytesFromEnd+"\n"); + for (int i = 0; i < middleBlocksCount; i++) { + fillDecryptedCache(); + System.arraycopy(decryptedBytes, 0, b, bytesFromFirstBlock+i*0x200, 0x200); + } + //3 // TODO: if it's zero? + System.arraycopy(readChunk(bytesFromEnd), 0, b, bytesFromFirstBlock+middleBlocksCount*0x200, bytesFromEnd); + pseudoPos += bytesToRead; + pointerInsideDecryptedSection = 0; + return b.length; + } + if (isEndPositionInsideEncryptedSection(bytesToRead)) { + log.trace("2. End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")"); + int bytesTillEncrypted = (int) (mediaOffsetPositionStart - pos); + int fullEncryptedBlocks = (bytesToRead - bytesTillEncrypted) / 0x200; + int incompleteEncryptedBytes = (bytesToRead - bytesTillEncrypted) % 0x200; + System.arraycopy(readChunk(bytesTillEncrypted), 0, b, 0, bytesTillEncrypted); + //2 + for (int i = 0; i < fullEncryptedBlocks; i++) { + fillDecryptedCache(); + System.arraycopy(decryptedBytes, 0, b, fullEncryptedBlocks+i*0x200, 0x200); + } + //3 + fillDecryptedCache(); + System.arraycopy(decryptedBytes, 0, b, bytesTillEncrypted+fullEncryptedBlocks*0x200, incompleteEncryptedBytes); + pseudoPos += bytesToRead; + pointerInsideDecryptedSection = incompleteEncryptedBytes; + return b.length; + } + log.trace("3. Not encrypted ("+pseudoPos+"-"+(pseudoPos+b.length)+")"); + pseudoPos += bytesToRead; + pointerInsideDecryptedSection = 0; + return super.read(b); + } + private void fillDecryptedCache() throws IOException{ + try{ + decryptedBytes = decryptor.decryptNext(readChunk(0x200)); + } + catch (Exception e){ + throw new IOException(e); + } + } + private void resetAndSkip(long blockSum) throws IOException{ + try{ + decryptor.reset(); + decryptor.skipNext(blockSum); // recalculate + } + catch (Exception e){ + throw new IOException(e); + } + } + private byte[] readChunk(int bytes) throws IOException{ + 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."); + return chunkBytes; + } + + private boolean isPointerInsideEncryptedSection(){ + return (pseudoPos >= mediaOffsetPositionStart) && (pseudoPos < mediaOffsetPositionEnd); + } + private boolean isEndPositionInsideEncryptedSection(long requestedBytesCount){ + return ((pseudoPos + requestedBytesCount) >= mediaOffsetPositionStart) && ((pseudoPos + requestedBytesCount) < mediaOffsetPositionEnd); + } + + @Override + public synchronized long skip(long n) throws IOException { + if (isPointerInsideEncryptedSection()){ + long realCountOfBytesToSkip = n - (0x200 - pointerInsideDecryptedSection); + if (realCountOfBytesToSkip <= 0){ + pseudoPos += n; + pointerInsideDecryptedSection += n; + return n; + } + + if (isEndPositionInsideEncryptedSection(n)){ // If we need to move somewhere out of the encrypted section + log.trace("4.1. Pointer Inside + End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+n)+")"); + long blocksToSkipCountingFromStart = (pseudoPos+n - mediaOffsetPositionStart) / 0x200; // always positive + resetAndSkip(blocksToSkipCountingFromStart); + + long leftovers = realCountOfBytesToSkip % 0x200; // most likely will be 0; TODO: a lot of tests + long bytesToSkipTillRequiredBlock = realCountOfBytesToSkip - leftovers; + long skipped = super.skip(bytesToSkipTillRequiredBlock); + if (bytesToSkipTillRequiredBlock != skipped) + throw new IOException("Can't skip bytes. To skip: " + + bytesToSkipTillRequiredBlock + + ".\nActually skipped: " + skipped + + ".\nLeftovers inside encrypted section: " + leftovers); + fillDecryptedCache(); + pseudoPos += n; + pointerInsideDecryptedSection = (int) leftovers; + return n; + } + log.trace("4. Pointer Inside + End Position Outside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+n)+")"); + long skipped = super.skip(realCountOfBytesToSkip); + if (realCountOfBytesToSkip != skipped) + throw new IOException("Can't skip bytes. To skip: " + + realCountOfBytesToSkip + + ".\nActually skipped: " + skipped); + pseudoPos += n; + pointerInsideDecryptedSection = 0; + return n; + // just fast-forward to position we need and flush caches + } + + if (isEndPositionInsideEncryptedSection(n)) { //pointer will be inside Encrypted Section, but now outside + log.trace("5. End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+n)+")"); + //skip to start if the block we need + long bytesToSkipTillEncryptedBlock = mediaOffsetPositionStart - pseudoPos; //TODO:FIX + long blocksToSkipCountingFromStart = (n - bytesToSkipTillEncryptedBlock) / 0x200; // always positive + long bytesToSkipTillRequiredBlock = bytesToSkipTillEncryptedBlock + blocksToSkipCountingFromStart * 0x200; + long leftovers = n - bytesToSkipTillRequiredBlock; // most likely will be 0; + + long skipped = super.skip(bytesToSkipTillRequiredBlock); + if (bytesToSkipTillRequiredBlock != skipped) + throw new IOException("Can't skip bytes. To skip: " + + bytesToSkipTillEncryptedBlock + + ".\nActually skipped: " + skipped + + ".\nLeftovers inside encrypted section: " + leftovers); + resetAndSkip(blocksToSkipCountingFromStart); + fillDecryptedCache(); + pseudoPos += n; + pointerInsideDecryptedSection = (int) leftovers; + return n; + } + log.trace("6. Not encrypted ("+pseudoPos+"-"+(pseudoPos+n)+")"); + long skipped = super.skip(n); + pseudoPos += n; + pointerInsideDecryptedSection = 0; + return skipped; + } + + @Override + public synchronized int read() throws IOException { + byte[] b = new byte[1]; + if (read(b) != -1) + return b[0]; + return -1; + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public synchronized void mark(int readlimit) {} + + @Override + public synchronized void reset() throws IOException { + throw new IOException("Not supported"); + } +} \ No newline at end of file