diff --git a/src/main/java/libKonogonka/TitleKeyChainHolder.java b/src/main/java/libKonogonka/TitleKeyChainHolder.java
new file mode 100644
index 0000000..5970f2d
--- /dev/null
+++ b/src/main/java/libKonogonka/TitleKeyChainHolder.java
@@ -0,0 +1,58 @@
+/*
+ Copyright 2019-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;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.util.HashMap;
+
+public class TitleKeyChainHolder {
+ private final File keysFile;
+ private HashMap rawKeySet;
+
+ public TitleKeyChainHolder(String pathToKeysFile) throws Exception{
+ this(new File(pathToKeysFile));
+ }
+
+ public TitleKeyChainHolder(File keysFile) throws Exception{
+ this.keysFile = keysFile;
+ collectEverything();
+ }
+
+ private void collectEverything() throws Exception{
+ rawKeySet = new HashMap<>();
+
+ BufferedReader br = new BufferedReader(new FileReader(keysFile));
+
+ String fileLine;
+ String[] keyValue;
+
+ while ((fileLine = br.readLine()) != null){
+ keyValue = fileLine.trim().split("\\s*=\\s*", 2);
+ if (keyValue.length == 2 && keyValue[0].length() > 16 && ! (keyValue[0].length() > 32) && keyValue[1].length() == 32){
+ rawKeySet.put(keyValue[0], keyValue[1]);
+ }
+ }
+ }
+
+ public HashMap getKeySet() {
+ return rawKeySet;
+ }
+}
diff --git a/src/main/java/libKonogonka/Tools/ISuperProvider.java b/src/main/java/libKonogonka/Tools/ISuperProvider.java
index 55b6678..6aa50d7 100644
--- a/src/main/java/libKonogonka/Tools/ISuperProvider.java
+++ b/src/main/java/libKonogonka/Tools/ISuperProvider.java
@@ -26,7 +26,8 @@ import java.io.PipedInputStream;
public interface ISuperProvider {
PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception;
PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception;
-
+ boolean exportContent(String saveToLocation, String subFileName) throws Exception;
+ boolean exportContent(String saveToLocation, int subFileNumber) throws Exception;
File getFile();
long getRawFileDataStart();
}
diff --git a/src/main/java/libKonogonka/Tools/NCA/NCAContent.java b/src/main/java/libKonogonka/Tools/NCA/NCAContent.java
index f962da7..1a34c6f 100644
--- a/src/main/java/libKonogonka/Tools/NCA/NCAContent.java
+++ b/src/main/java/libKonogonka/Tools/NCA/NCAContent.java
@@ -19,10 +19,8 @@
package libKonogonka.Tools.NCA;
import libKonogonka.Converter;
-import libKonogonka.RainbowDump;
import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader;
import libKonogonka.Tools.PFS0.IPFS0Provider;
-import libKonogonka.Tools.PFS0.PFS0EncryptedProvider;
import libKonogonka.Tools.PFS0.PFS0Provider;
import libKonogonka.Tools.RomFs.IRomFsProvider;
import libKonogonka.Tools.RomFs.RomFsEncryptedProvider;
@@ -41,28 +39,28 @@ public class NCAContent {
private final static Logger log = LogManager.getLogger(NCAContent.class);
private final File file;
- private final long offsetPosition;
+ private final long ncaOffsetPosition;
private final NcaFsHeader ncaFsHeader;
private final NCAHeaderTableEntry ncaHeaderTableEntry;
private final byte[] decryptedKey;
- private final LinkedList Pfs0SHA256hashes;
+ private LinkedList Pfs0SHA256hashes;
private IPFS0Provider pfs0;
private IRomFsProvider romfs;
// TODO: if decryptedKey is empty, throw exception ??
public NCAContent(File file,
- long offsetPosition,
+ long ncaOffsetPosition,
NcaFsHeader ncaFsHeader,
NCAHeaderTableEntry ncaHeaderTableEntry,
byte[] decryptedKey) throws Exception
{
this.file = file;
- this.offsetPosition = offsetPosition;
+ this.ncaOffsetPosition = ncaOffsetPosition;
this.ncaFsHeader = ncaFsHeader;
this.ncaHeaderTableEntry = ncaHeaderTableEntry;
this.decryptedKey = decryptedKey;
-
+ System.out.println("NCAContent pfs0offsetPosition: "+ncaOffsetPosition);
Pfs0SHA256hashes = new LinkedList<>();
// If nothing to do
if (ncaHeaderTableEntry.getMediaEndOffset() == 0)
@@ -89,60 +87,24 @@ public class NCAContent {
}
}
private void proceedPFS0NotEncrypted() throws Exception{
- RandomAccessFile raf = new RandomAccessFile(file, "r");
- long thisMediaLocation = offsetPosition + (ncaHeaderTableEntry.getMediaStartOffset() * 0x200);// TODO: NOTE already defined inside PFS0
- long hashTableLocation = thisMediaLocation + ncaFsHeader.getSuperBlockPFS0().getHashTableOffset();
- long pfs0Location = thisMediaLocation + ncaFsHeader.getSuperBlockPFS0().getPfs0offset();
-
- raf.seek(hashTableLocation);
-
- byte[] rawData;
- long sha256recordsNumber = ncaFsHeader.getSuperBlockPFS0().getHashTableSize() / 0x20;
- // Collect hashes
- for (int i = 0; i < sha256recordsNumber; i++){
- rawData = new byte[0x20]; // 32 bytes - size of SHA256 hash
- if (raf.read(rawData) != -1)
- Pfs0SHA256hashes.add(rawData);
- else {
- raf.close();
- return; // TODO: fix
- }
- }
- raf.close();
- // Get pfs0
- pfs0 = new PFS0Provider(file, pfs0Location);
- }
- private void proceedPFS0Encrypted() throws Exception{/*
- RandomAccessFile raf = new RandomAccessFile(file, "r");
- long thisMediaLocation = offsetPosition + (ncaHeaderTableEntry.getMediaStartOffset() * 0x200);
- long hashTableLocation = thisMediaLocation + ncaFsHeader.getSuperBlockPFS0().getHashTableOffset();
- long pfs0Location = thisMediaLocation + ncaFsHeader.getSuperBlockPFS0().getPfs0offset();
-
- raf.seek(hashTableLocation);
-
- byte[] rawData;
- long sha256recordsNumber = ncaFsHeader.getSuperBlockPFS0().getHashTableSize() / 0x20;
- // Collect hashes
- for (int i = 0; i < sha256recordsNumber; i++){
- rawData = new byte[0x20]; // 32 bytes - size of SHA256 hash
- if (raf.read(rawData) != -1)
- Pfs0SHA256hashes.add(rawData);
- else {
- raf.close();
- return; // TODO: fix
- }
- }
- raf.close();
- // Get pfs0
- pfs0 = new PFS0Provider(file, pfs0Location);
- /*/
- new CryptoSection03Pfs0(file,
- offsetPosition,
- decryptedKey,
- ncaFsHeader,
+ pfs0 = new PFS0Provider(file,
+ ncaOffsetPosition,
+ ncaFsHeader.getSuperBlockPFS0(),
ncaHeaderTableEntry.getMediaStartOffset(),
ncaHeaderTableEntry.getMediaEndOffset());
- //*/
+ Pfs0SHA256hashes = pfs0.getPfs0SHA256hashes();
+ }
+
+ private void proceedPFS0Encrypted() throws Exception{
+ AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey, ncaFsHeader.getSectionCTR(),
+ ncaHeaderTableEntry.getMediaStartOffset() * 0x200);
+ pfs0 = new PFS0Provider(file,
+ ncaOffsetPosition,
+ ncaFsHeader.getSuperBlockPFS0(),
+ decryptor,
+ ncaHeaderTableEntry.getMediaStartOffset(),
+ ncaHeaderTableEntry.getMediaEndOffset());
+ Pfs0SHA256hashes = pfs0.getPfs0SHA256hashes();
}
private void proceedRomFs() throws Exception{
@@ -167,7 +129,7 @@ public class NCAContent {
this.romfs = new RomFsEncryptedProvider(
ncaFsHeader.getSuperBlockIVFC().getLvl6Offset(),
file,
- offsetPosition,
+ ncaOffsetPosition,
decryptedKey,
ncaFsHeader.getSectionCTR(),
ncaHeaderTableEntry.getMediaStartOffset(),
@@ -178,174 +140,6 @@ public class NCAContent {
public IPFS0Provider getPfs0() { return pfs0; }
public IRomFsProvider getRomfs() { return romfs; }
- private class CryptoSection03Pfs0 {
- CryptoSection03Pfs0(File file,
- long offsetPosition,
- byte[] decryptedKey,
- NcaFsHeader ncaFsHeader,
- long mediaStartBlocksOffset,
- long mediaEndBlocksOffset) throws Exception
- {
- log.debug( "-== Crypto Section 03 PFS0 ==-\n" +
- "Media start location: " + RainbowDump.formatDecHexString(mediaStartBlocksOffset) + "\n" +
- "Media end location: " + RainbowDump.formatDecHexString(mediaEndBlocksOffset) + "\n" +
- "Media size: " + RainbowDump.formatDecHexString((mediaEndBlocksOffset-mediaStartBlocksOffset)) + "\n" +
- "Media actual location: " + RainbowDump.formatDecHexString((offsetPosition + (mediaStartBlocksOffset * 0x200))) + "\n" +
- "SHA256 hash table size: " + RainbowDump.formatDecHexString(ncaFsHeader.getSuperBlockPFS0().getHashTableSize()) + "\n" +
- "SHA256 hash table offs: " + RainbowDump.formatDecHexString(ncaFsHeader.getSuperBlockPFS0().getHashTableOffset()) + "\n" +
- "PFS0 Offset: " + RainbowDump.formatDecHexString(ncaFsHeader.getSuperBlockPFS0().getPfs0offset()) + "\n" +
- "SHA256 records: " + RainbowDump.formatDecHexString(ncaFsHeader.getSuperBlockPFS0().getHashTableSize() / 0x20) + "\n" +
- "KEY (decrypted): " + Converter.byteArrToHexString(decryptedKey) + "\n" +
- "CTR: " + Converter.byteArrToHexString(ncaFsHeader.getSectionCTR()) + "\n" +
- "-----------------------------------------------------------\n");
- if (decryptedKey == null)
- throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided.");
-
- RandomAccessFile raf = new RandomAccessFile(file, "r");
- long absoluteOffsetPosition = offsetPosition + (mediaStartBlocksOffset * 0x200);
- raf.seek(absoluteOffsetPosition);
-
- AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey, ncaFsHeader.getSectionCTR(),
- mediaStartBlocksOffset * 0x200);
-
- byte[] encryptedBlock;
- byte[] decryptedBlock;
- long mediaBlocksSize = mediaEndBlocksOffset - mediaStartBlocksOffset;
- // Prepare thread to parse encrypted data
- PipedOutputStream streamOut = new PipedOutputStream();
- PipedInputStream streamInp = new PipedInputStream(streamOut);
-
- Thread pThread = new Thread(new ParseThread(
- streamInp,
- ncaFsHeader.getSuperBlockPFS0().getPfs0offset(),
- ncaFsHeader.getSuperBlockPFS0().getHashTableOffset(),
- ncaFsHeader.getSuperBlockPFS0().getHashTableSize(),
- offsetPosition,
- file,
- decryptedKey,
- ncaFsHeader.getSectionCTR(),
- mediaStartBlocksOffset,
- mediaEndBlocksOffset
- ));
- pThread.start();
- // Decrypt data
- for (int i = 0; i < mediaBlocksSize; i++){
- encryptedBlock = new byte[0x200];
- if (raf.read(encryptedBlock) != -1){
- //decryptedBlock = aesCtr.decrypt(encryptedBlock);
- decryptedBlock = decryptor.decryptNext(encryptedBlock);
- // Writing decrypted data to pipe
- try {
- streamOut.write(decryptedBlock);
- }
- catch (IOException e){
- break;
- }
- }
- }
- pThread.join();
- streamOut.close();
- raf.close();
- }
- /**
- * Since we're representing decrypted data as stream (it's easier to look on it this way),
- * this thread will be parsing it.
- * */
- private class ParseThread implements Runnable{
-
- PipedInputStream pipedInputStream;
-
- long hashTableOffset;
- long hashTableSize;
- long hashTableRecordsCount;
- long pfs0offset;
-
- private final long MetaOffsetPositionInFile;
- private final File MetaFileWithEncPFS0;
- private final byte[] MetaKey;
- private final byte[] MetaSectionCTR;
- private final long MetaMediaStartOffset;
- private final long MetaMediaEndOffset;
-
-
- ParseThread(PipedInputStream pipedInputStream,
- long pfs0offset,
- long hashTableOffset,
- long hashTableSize,
-
- long MetaOffsetPositionInFile,
- File MetaFileWithEncPFS0,
- byte[] MetaKey,
- byte[] MetaSectionCTR,
- long MetaMediaStartOffset,
- long MetaMediaEndOffset
- ){
- this.pipedInputStream = pipedInputStream;
- this.hashTableOffset = hashTableOffset;
- this.hashTableSize = hashTableSize;
- this.hashTableRecordsCount = hashTableSize / 0x20;
- this.pfs0offset = pfs0offset;
-
- this.MetaOffsetPositionInFile = MetaOffsetPositionInFile;
- this.MetaFileWithEncPFS0 = MetaFileWithEncPFS0;
- this.MetaKey = MetaKey;
- this.MetaSectionCTR = MetaSectionCTR;
- this.MetaMediaStartOffset = MetaMediaStartOffset;
- this.MetaMediaEndOffset = MetaMediaEndOffset;
-
- }
-
- @Override
- public void run() {
- long counter = 0; // How many bytes already read
-
- try{
- if (hashTableOffset > 0){
- if (hashTableOffset != pipedInputStream.skip(hashTableOffset))
- return; // TODO: fix?
- counter = hashTableOffset;
- }
- // Loop for collecting all recrods from sha256 hash table
- while ((counter - hashTableOffset) < hashTableSize){
- int hashCounter = 0;
- byte[] sectionHash = new byte[0x20];
- // Loop for collecting bytes for every SINGLE records, where record size == 0x20
- while (hashCounter < 0x20){
- int currentByte = pipedInputStream.read();
- if (currentByte == -1)
- break;
- sectionHash[hashCounter] = (byte)currentByte;
- hashCounter++;
- counter++;
- }
- // Write after collecting
- Pfs0SHA256hashes.add(sectionHash); // From the NCAContentProvider obviously
- }
- // Skip padding and go to PFS0 location
- if (counter < pfs0offset){
- long toSkip = pfs0offset-counter;
- if (toSkip != pipedInputStream.skip(toSkip))
- return; // TODO: fix?
- counter += toSkip;
- }
- //---------------------------------------------------------
- pfs0 = new PFS0EncryptedProvider(pipedInputStream,
- counter,
- MetaOffsetPositionInFile,
- MetaFileWithEncPFS0,
- MetaKey,
- MetaSectionCTR,
- MetaMediaStartOffset,
- MetaMediaEndOffset);
- pipedInputStream.close();
- }
- catch (Exception e){
- log.debug("NCA Content parsing thread exception: ", e);
- }
- //finally { System.out.println("NCA Content thread dies");}
- }
- }
- }
/**
* Export NCA content AS IS.
@@ -362,7 +156,7 @@ public class NCAContent {
"Media start location: " + mediaStartBlocksOffset + "\n" +
"Media end location: " + mediaEndBlocksOffset + "\n" +
"Media size : " + (mediaEndBlocksOffset-mediaStartBlocksOffset) + "\n" +
- "Media act. location: " + (offsetPosition + (mediaStartBlocksOffset * 0x200)) + "\n" +
+ "Media act. location: " + (ncaOffsetPosition + (mediaStartBlocksOffset * 0x200)) + "\n" +
"KEY: " + Converter.byteArrToHexString(decryptedKey) + "\n" +
"CTR: " + Converter.byteArrToHexString(ncaFsHeader.getSectionCTR()) + "\n");
//---------------------------------------------------------------------------------------------------/
@@ -414,7 +208,7 @@ public class NCAContent {
workerThread = new Thread(() -> {
try {
//RandomAccessFile raf = new RandomAccessFile(file, "r");
- long abosluteOffsetPosition = offsetPosition + (mediaStartBlocksOffset * 0x200);
+ long abosluteOffsetPosition = ncaOffsetPosition + (mediaStartBlocksOffset * 0x200);
raf.seek(abosluteOffsetPosition);
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey,
diff --git a/src/main/java/libKonogonka/Tools/NCA/NCAProvider.java b/src/main/java/libKonogonka/Tools/NCA/NCAProvider.java
index fb41e99..585a711 100644
--- a/src/main/java/libKonogonka/Tools/NCA/NCAProvider.java
+++ b/src/main/java/libKonogonka/Tools/NCA/NCAProvider.java
@@ -283,7 +283,8 @@ public class NCAProvider {
key = cipher.doFinal(rightsIDkey);
}
catch (Exception e){
- throw new Exception("No title.keys loaded?", e);
+ throw new Exception("No title.keys loaded for '"+
+ String.format("titlekek_%02x", cryptoTypeReal)+"' or '"+byteArrToHexString(rightsId)+"'? ("+e+")", e);
}
}
getNcaContentByNumber(0, key);
diff --git a/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/NcaFsHeader.java b/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/NcaFsHeader.java
index 6bedfe0..1fdefe3 100644
--- a/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/NcaFsHeader.java
+++ b/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/NcaFsHeader.java
@@ -48,6 +48,7 @@ public class NcaFsHeader {
private final BucketTreeHeader BktrSection2;
private final byte[] generation;
+ private final byte[] secureValue;
private final byte[] sectionCTR;
private final SparseInfo sparseInfo;
private final CompressionInfo compressionInfo;
@@ -79,7 +80,10 @@ public class NcaFsHeader {
BktrSection2 = new BucketTreeHeader(Arrays.copyOfRange(tableBlockBytes, 0x130, 0x140));
generation = Arrays.copyOfRange(tableBlockBytes, 0x140, 0x144);
- sectionCTR = Arrays.copyOfRange(tableBlockBytes, 0x144, 0x148);
+ secureValue = Arrays.copyOfRange(tableBlockBytes, 0x144, 0x148);
+
+ sectionCTR = Arrays.copyOfRange(tableBlockBytes, 0x140, 0x148);
+
sparseInfo = new SparseInfo(Arrays.copyOfRange(tableBlockBytes, 0x148, 0x178));
compressionInfo = new CompressionInfo(Arrays.copyOfRange(tableBlockBytes, 0x178, 0x1a0));
metaDataHashDataInfo = new MetaDataHashDataInfo(Arrays.copyOfRange(tableBlockBytes, 0x1a0, 0x1d0));
@@ -109,6 +113,10 @@ public class NcaFsHeader {
public int getEntryCountSection2() { return BktrSection2.getEntryCount(); }
public byte[] getPatchInfoUnknownSection2() { return BktrSection2.getUnknown(); }
public byte[] getGeneration() {return generation;}
+ public byte[] getSecureValue() {return secureValue;}
+ /**
+ * Used for Aes Ctr decryption in IV context.
+ * */
public byte[] getSectionCTR() { return sectionCTR; }
public SparseInfo getSparseInfo() {return sparseInfo;}
public CompressionInfo getCompressionInfo() {return compressionInfo;}
diff --git a/src/main/java/libKonogonka/Tools/PFS0/IPFS0Provider.java b/src/main/java/libKonogonka/Tools/PFS0/IPFS0Provider.java
index 3482834..0a78532 100644
--- a/src/main/java/libKonogonka/Tools/PFS0/IPFS0Provider.java
+++ b/src/main/java/libKonogonka/Tools/PFS0/IPFS0Provider.java
@@ -20,6 +20,8 @@ package libKonogonka.Tools.PFS0;
import libKonogonka.Tools.ISuperProvider;
+import java.util.LinkedList;
+
public interface IPFS0Provider extends ISuperProvider {
boolean isEncrypted();
String getMagic();
@@ -30,4 +32,5 @@ public interface IPFS0Provider extends ISuperProvider {
PFS0subFile[] getPfs0subFiles();
void printDebug();
+ LinkedList getPfs0SHA256hashes();
}
diff --git a/src/main/java/libKonogonka/Tools/PFS0/PFS0EncryptedProvider.java b/src/main/java/libKonogonka/Tools/PFS0/PFS0EncryptedProvider.java
deleted file mode 100644
index 6527f9e..0000000
--- a/src/main/java/libKonogonka/Tools/PFS0/PFS0EncryptedProvider.java
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- Copyright 2019-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.Tools.PFS0;
-
-import libKonogonka.Converter;
-import libKonogonka.RainbowDump;
-import libKonogonka.Tools.RomFs.Level6Header;
-import libKonogonka.ctraes.AesCtrDecryptSimple;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.io.*;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.util.Arrays;
-
-import static libKonogonka.Converter.*;
-
-public class PFS0EncryptedProvider implements IPFS0Provider{
- private final static Logger log = LogManager.getLogger(PFS0EncryptedProvider.class);
-
- //private long rawFileDataStart; // Always -1 @ PFS0EncryptedProvider
-
- private final String magic;
- private final int filesCount;
- private final int stringTableSize;
- private final byte[] padding;
- private final PFS0subFile[] pfs0subFiles;
-
- //---------------------------------------
-
- private long rawBlockDataStart;
-
- private final long offsetPositionInFile;
- private final File file;
- private final byte[] key;
- private final byte[] sectionCTR;
- private final long mediaStartOffset; // In 512-blocks
- private final long mediaEndOffset; // In 512-blocks
-
- public PFS0EncryptedProvider(PipedInputStream pipedInputStream,
- long pfs0offsetPosition,
- long offsetPositionInFile,
- File fileWithEncPFS0,
- byte[] key,
- byte[] sectionCTR,
- long mediaStartOffset,
- long mediaEndOffset
- ) throws Exception{
- // Populate 'meta' data that is needed for getProviderSubFilePipedInpStream()
- this.offsetPositionInFile = offsetPositionInFile + mediaStartOffset*0x200;
- this.file = fileWithEncPFS0;
- this.key = key;
- this.sectionCTR = sectionCTR;
- this.mediaStartOffset = mediaStartOffset;
- this.mediaEndOffset = mediaEndOffset;
- // pfs0offsetPosition is a position relative to Media block. Let's 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;
-
- byte[] fileStartingBytes = new byte[0x10];
- // Read PFS0Provider, files count, header, padding (4 zero bytes)
-
- for (int i = 0; i < 0x10; i++){
- int currentByte = pipedInputStream.read();
- if (currentByte == -1) {
- throw new Exception("PFS0EncryptedProvider: Reading stream suddenly ended while trying to read starting 0x10 bytes");
- }
- fileStartingBytes[i] = (byte)currentByte;
- }
- // Update position
- rawBlockDataStart += 0x10;
- // Check PFS0Provider
- magic = new String(fileStartingBytes, 0x0, 0x4, StandardCharsets.US_ASCII);
- if (! magic.equals("PFS0")){
- throw new Exception("PFS0EncryptedProvider: Bad magic");
- }
- // Get files count
- filesCount = getLEint(fileStartingBytes, 0x4);
- if (filesCount <= 0 ) {
- throw new Exception("PFS0EncryptedProvider: Files count is too small");
- }
- // Get string table
- stringTableSize = getLEint(fileStartingBytes, 0x8);
- if (stringTableSize <= 0 ){
- throw new Exception("PFS0EncryptedProvider: String table is too small");
- }
- padding = Arrays.copyOfRange(fileStartingBytes, 0xc, 0x10);
- //---------------------------------------------------------------------------------------------------------
- pfs0subFiles = new PFS0subFile[filesCount];
-
- long[] offsetsSubFiles = new long[filesCount];
- long[] sizesSubFiles = new long[filesCount];
- int[] strTableOffsets = new int[filesCount];
- byte[][] zeroBytes = new byte[filesCount][];
-
- byte[] fileEntryTable = new byte[0x18];
- for (int i=0; i < filesCount; i++){
- for (int j = 0; j < 0x18; j++){
- int currentByte = pipedInputStream.read();
- if (currentByte == -1) {
- throw new Exception("PFS0EncryptedProvider: Reading stream suddenly ended while trying to read File Entry Table #"+i);
- }
- fileEntryTable[j] = (byte)currentByte;
- }
- offsetsSubFiles[i] = getLElong(fileEntryTable, 0);
- 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
- String[] subFileNames = new String[filesCount];
- byte[] stringTbl = new byte[stringTableSize];
-
- for (int i = 0; i < stringTableSize; i++){
- int currentByte = pipedInputStream.read();
- if (currentByte == -1) {
- throw new Exception("PFS0EncryptedProvider: Reading stream suddenly ended while trying to read string table");
- }
- stringTbl[i] = (byte)currentByte;
- }
- // Update position
- rawBlockDataStart += stringTableSize;
-
- for (int i=0; i < filesCount; i++){
- int j = 0;
- while (stringTbl[strTableOffsets[i]+j] != (byte)0x00)
- j++;
- subFileNames[i] = new String(stringTbl, strTableOffsets[i], j, StandardCharsets.UTF_8);
- }
- for (int i = 0; i < filesCount; i++){
- pfs0subFiles[i] = new PFS0subFile(
- subFileNames[i],
- offsetsSubFiles[i],
- sizesSubFiles[i],
- zeroBytes[i]
- );
- }
- }
-
- @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 rawBlockDataStart; }
- @Override
- public PFS0subFile[] getPfs0subFiles() { return pfs0subFiles; }
- @Override
- public File getFile(){ return file; }
- @Override
- public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception { // TODO: rewrite
- if (subFileNumber >= pfs0subFiles.length)
- throw new Exception("PFS0Provider -> getPfs0subFilePipedInpStream(): Requested sub file doesn't exists");
-
- Thread workerThread;
- PipedOutputStream streamOut = new PipedOutputStream();
-
- PipedInputStream streamIn = new PipedInputStream(streamOut);
- workerThread = new Thread(() -> {
- log.debug("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Executing thread:\nSub file: " +
- pfs0subFiles[subFileNumber].getName() +
- "\nFor block # "+((rawBlockDataStart + pfs0subFiles[subFileNumber].getOffset()) / 0x200) +
- "\nAnd initial skipped bytes are: "+offsetPositionInFile +
- "\nWhere Raw Block Data Start: "+rawBlockDataStart +
- "\nAnd sub file offset: "+pfs0subFiles[subFileNumber].getOffset()+
- "\nSkip bytes "+((rawBlockDataStart + pfs0subFiles[subFileNumber].getOffset()) - ((rawBlockDataStart + pfs0subFiles[subFileNumber].getOffset()) / 0x200) * 0x200)+
- "\nKEY "+Converter.byteArrToHexString(key)+
- "\nSection CTR "+Converter.byteArrToHexString(sectionCTR)+
- "\n______________________________________________________________");
- try {
- BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(file.toPath()));
- // Check if skip was successful
- if (bis.skip(offsetPositionInFile) != offsetPositionInFile) {
- System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Failed to skip range "+offsetPositionInFile);
- return;
- }
-
- AesCtrDecryptSimple aesCtrDecryptSimple = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
-
- byte[] encryptedBlock;
- byte[] dectyptedBlock;
-
- //----------------------------- Pre-set: skip non-necessary data --------------------------------
-
- long startBlock = (rawBlockDataStart + pfs0subFiles[subFileNumber].getOffset()) / 0x200; // <- pointing to place where actual data starts
- int skipBytes;
-
- if (startBlock > 0) {
- aesCtrDecryptSimple.skipNext(startBlock);
- skipBytes = (int)(startBlock * 0x200);
- if (bis.skip(skipBytes) != skipBytes) {
- System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Failed to skip range "+skipBytes);
- return;
- }
- }
-
- //----------------------------- Step 1: get starting bytes from the end of the junk block --------------------------------
-
- // Since our data could be located in position with some offset from the decrypted block, let's skip bytes left. Considering the case when data is not aligned to block
- skipBytes = (int) ((rawBlockDataStart + pfs0subFiles[subFileNumber].getOffset()) - startBlock * 0x200); // <- How much bytes shall we skip to reach requested data start of sub-file
-
- if (skipBytes > 0) {
- encryptedBlock = new byte[0x200];
- if (bis.read(encryptedBlock) == 0x200) {
- dectyptedBlock = aesCtrDecryptSimple.decryptNext(encryptedBlock);
- // If we have extra-small file that is less then a block and even more
- if ((0x200 - skipBytes) > pfs0subFiles[subFileNumber].getSize()){
- streamOut.write(dectyptedBlock, skipBytes, (int) pfs0subFiles[subFileNumber].getSize()); // safe cast
- bis.close();
- streamOut.close();
- return;
- }
- else
- streamOut.write(dectyptedBlock, skipBytes, 0x200 - skipBytes);
- }
- else {
- System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from 1st bock");
- return;
- }
- startBlock++;
- }
- long endBlock = pfs0subFiles[subFileNumber].getSize() / 0x200 + startBlock; // <- pointing to place where any data related to this media-block ends
-
- //----------------------------- Step 2: Detect if we have junk data on the end of the final block --------------------------------
- int extraData = (int)(rawBlockDataStart+pfs0subFiles[subFileNumber].getOffset()+pfs0subFiles[subFileNumber].getSize() - (endBlock*0x200)); // safe cast
- if (extraData < 0){
- endBlock--;
- }
- //----------------------------- Step 3: Read main part of data --------------------------------
- // Here we're reading main amount of bytes. We can read only less bytes.
- while ( startBlock < endBlock) {
- encryptedBlock = new byte[0x200];
- if (bis.read(encryptedBlock) == 0x200) {
- //dectyptedBlock = aesCtr.decrypt(encryptedBlock);
- dectyptedBlock = aesCtrDecryptSimple.decryptNext(encryptedBlock);
- // Writing decrypted data to pipe
- streamOut.write(dectyptedBlock);
- }
- else {
- System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from bock");
- return;
- }
- startBlock++;
- }
- //----------------------------- Step 4: Read what's left --------------------------------
- // Now we have to find out if data overlaps to one more extra block
- if (extraData > 0){ // In case we didn't get what we want
- encryptedBlock = new byte[0x200];
- if (bis.read(encryptedBlock) == 0x200) {
- dectyptedBlock = aesCtrDecryptSimple.decryptNext(encryptedBlock);
- streamOut.write(dectyptedBlock, 0, extraData);
- }
- else {
- System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from bock");
- return;
- }
- }
- else if (extraData < 0){ // In case we can get more than we need
- encryptedBlock = new byte[0x200];
- if (bis.read(encryptedBlock) == 0x200) {
- dectyptedBlock = aesCtrDecryptSimple.decryptNext(encryptedBlock);
- streamOut.write(dectyptedBlock, 0, 0x200 + extraData); // WTF ??? THIS LOOKS INCORRECT
- }
- else {
- System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from last bock");
- return;
- }
- }
- bis.close();
- streamOut.close();
- }
- catch (Exception e){
- System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): "+e.getMessage());
- e.printStackTrace();
- }
- System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Thread died");
-
-
- });
- workerThread.start();
- return streamIn;
- }
-
- @Override
- public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception{
- for (int i = 0; i < pfs0subFiles.length; i++){
- if (pfs0subFiles[i].getName().equals(subFileName))
- return getProviderSubFilePipedInpStream(i);
- }
- return null;
- }
-
- public void printDebug(){
- log.debug(".:: PFS0EncryptedProvider ::.\n" +
- "File name: " + file.getName() + "\n" +
- "Raw block data start " + RainbowDump.formatDecHexString(rawBlockDataStart) + "\n" +
- "Magic " + magic + "\n" +
- "Files count " + RainbowDump.formatDecHexString(filesCount) + "\n" +
- "String Table Size " + RainbowDump.formatDecHexString(stringTableSize) + "\n" +
- "Padding " + Converter.byteArrToHexString(padding) + "\n\n" +
-
- "Offset position in file " + RainbowDump.formatDecHexString(offsetPositionInFile) + "\n" +
- "Media Start Offset " + RainbowDump.formatDecHexString(mediaStartOffset) + "\n" +
- "Media End Offset " + RainbowDump.formatDecHexString(mediaEndOffset) + "\n"
- );
- for (PFS0subFile subFile : pfs0subFiles){
- log.debug(
- "\nName: " + subFile.getName() + "\n" +
- "Offset " + RainbowDump.formatDecHexString(subFile.getOffset()) + "\n" +
- "Size " + RainbowDump.formatDecHexString(subFile.getSize()) + "\n" +
- "Zeroes " + Converter.byteArrToHexString(subFile.getZeroes()) + "\n" +
- "----------------------------------------------------------------"
- );
- }
- }
-}
diff --git a/src/main/java/libKonogonka/Tools/PFS0/PFS0Provider.java b/src/main/java/libKonogonka/Tools/PFS0/PFS0Provider.java
index 5ff806e..484bef3 100644
--- a/src/main/java/libKonogonka/Tools/PFS0/PFS0Provider.java
+++ b/src/main/java/libKonogonka/Tools/PFS0/PFS0Provider.java
@@ -20,6 +20,7 @@ package libKonogonka.Tools.PFS0;
import libKonogonka.Converter;
import libKonogonka.RainbowDump;
+import libKonogonka.Tools.NCA.NCASectionTableBlock.SuperBlockPFS0;
import libKonogonka.ctraes.AesCtrBufferedInputStream;
import libKonogonka.ctraes.AesCtrDecryptSimple;
import org.apache.logging.log4j.LogManager;
@@ -28,93 +29,124 @@ import org.apache.logging.log4j.Logger;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
+import java.nio.file.Paths;
import java.util.Arrays;
+import java.util.LinkedList;
-import static libKonogonka.Converter.*;
+import static libKonogonka.Converter.getLEint;
+import static libKonogonka.Converter.getLElong;
public class PFS0Provider implements IPFS0Provider{
private final static Logger log = LogManager.getLogger(PFS0Provider.class);
- private long rawFileDataStartOffset;
-
private String magic;
private int filesCount;
private int stringTableSize;
private byte[] padding;
private PFS0subFile[] pfs0subFiles;
+ //---------------------------------------
+ private long rawBlockDataStart;
private final File file;
- private final long offsetPosition; // Where data starts, excluding header, string table etc.
- private long mediaStartOffset;
- private long mediaEndOffset;
+ private long offsetPositionInFile;
+ private long mediaStartOffset; // In 512-blocks
+ private long mediaEndOffset; // In 512-blocks
+
+ private long ncaOffset;
+ private BufferedInputStream stream;
+ private SuperBlockPFS0 superBlockPFS0;
private AesCtrDecryptSimple decryptor;
- private final boolean encrypted;
+ private LinkedList pfs0SHA256hashes;
- public PFS0Provider(File fileWithPfs0,
- long offsetPosition,
+ private boolean encrypted;
+
+ public PFS0Provider(File nspFile) throws Exception{
+ this.file = nspFile;
+ createBufferedInputStream();
+ readPfs0Header();
+ }
+
+ public PFS0Provider(File file,
+ long ncaOffset,
+ SuperBlockPFS0 superBlockPFS0,
long mediaStartOffset,
- long mediaEndOffset,
- AesCtrDecryptSimple decryptor) throws Exception{
- this.file = fileWithPfs0;
- this.offsetPosition = offsetPosition + mediaStartOffset*0x200;
- this.encrypted = true;
-
+ long mediaEndOffset) throws Exception{
+ this.file = file;
+ this.ncaOffset = ncaOffset;
+ this.superBlockPFS0 = superBlockPFS0;
+ this.offsetPositionInFile = ncaOffset + mediaStartOffset * 0x200;
this.mediaStartOffset = mediaStartOffset;
this.mediaEndOffset = mediaEndOffset;
- this.decryptor = decryptor;
- proceedPfs0();
- }
-
- public PFS0Provider(File fileWithPfs0) throws Exception{ this(fileWithPfs0, 0); }
-
- public PFS0Provider(File fileWithPfs0, long offsetPosition) throws Exception{
- this.file = fileWithPfs0;
- this.offsetPosition = offsetPosition;
- this.encrypted = false;
+ this.rawBlockDataStart = superBlockPFS0.getPfs0offset();
//bufferedInputStream = new BufferedInputStream(Files.newInputStream(fileWithPfs0.toPath()));
- proceedPfs0();
+ createBufferedInputStream();
+ long toSkip = offsetPositionInFile + superBlockPFS0.getHashTableOffset();
+ if (toSkip != stream.skip(toSkip))
+ throw new Exception("Can't skip bytes prior Hash Table offset");
+ collectHashes();
+
+ createBufferedInputStream();
+ toSkip = offsetPositionInFile + superBlockPFS0.getPfs0offset();
+ if (toSkip != stream.skip(toSkip))
+ throw new Exception("Can't skip bytes prior PFS0 offset");
+ readPfs0Header();
}
- private void proceedPfs0() throws Exception{
- BufferedInputStream bufferedInputStream;
- if (encrypted) {
- bufferedInputStream = new AesCtrBufferedInputStream(decryptor,
- offsetPosition,
- mediaStartOffset,
- mediaEndOffset,
- Files.newInputStream(file.toPath()));
- }
- else{
- bufferedInputStream = new BufferedInputStream(Files.newInputStream(file.toPath()));
- }
+ public PFS0Provider(File file,
+ long ncaOffset,
+ SuperBlockPFS0 superBlockPFS0,
+ AesCtrDecryptSimple decryptor,
+ long mediaStartOffset,
+ long mediaEndOffset
+ ) throws Exception {
+ this.file = file;
+ this.ncaOffset = ncaOffset;
+ this.superBlockPFS0 = superBlockPFS0;
+ this.decryptor = decryptor;
+ this.offsetPositionInFile = ncaOffset + mediaStartOffset * 0x200;
+ this.mediaStartOffset = mediaStartOffset;
+ this.mediaEndOffset = mediaEndOffset;
+ this.rawBlockDataStart = superBlockPFS0.getPfs0offset();
+ this.encrypted = true;
- if (offsetPosition != bufferedInputStream.skip(offsetPosition))
- throw new Exception("PFS0Provider: Unable to skip initial offset: "+offsetPosition);
+ createAesCtrEncryptedBufferedInputStream();
+ long toSkip = offsetPositionInFile + superBlockPFS0.getHashTableOffset();
+ if (toSkip != stream.skip(toSkip))
+ throw new Exception("Can't skip bytes prior Hash Table offset");
+ collectHashes();
+ createAesCtrEncryptedBufferedInputStream();
+ toSkip = offsetPositionInFile + superBlockPFS0.getPfs0offset();
+ if (toSkip != stream.skip(toSkip))
+ throw new Exception("Can't skip bytes prior PFS0 offset");
+ readPfs0Header();
+ }
+
+ private void readPfs0Header()throws Exception{
byte[] fileStartingBytes = new byte[0x10];
- // Read PFS0Provider, files count, header, padding (4 zero bytes)
- if (bufferedInputStream.read(fileStartingBytes) != 0x10){
- throw new Exception("PFS0Provider: Unable to read starting bytes");
- }
- rawFileDataStartOffset += 0x10;
+ if (0x10 != stream.read(fileStartingBytes))
+ throw new Exception("Reading stream suddenly ended while trying to read starting 0x10 bytes");
+
+ // Update position
+ rawBlockDataStart += 0x10;
// Check PFS0Provider
magic = new String(fileStartingBytes, 0x0, 0x4, StandardCharsets.US_ASCII);
if (! magic.equals("PFS0")){
- throw new Exception("PFS0Provider: Bad magic");
+ throw new Exception("Bad magic");
}
// Get files count
filesCount = getLEint(fileStartingBytes, 0x4);
if (filesCount <= 0 ) {
- throw new Exception("PFS0Provider: Files count is too small");
+ throw new Exception("Files count is too small");
}
// Get string table
stringTableSize = getLEint(fileStartingBytes, 0x8);
if (stringTableSize <= 0 ){
- throw new Exception("PFS0Provider: String table is too small");
+ throw new Exception("String table is too small");
}
padding = Arrays.copyOfRange(fileStartingBytes, 0xc, 0x10);
- //---------------------------------------------------------------------------------------------------------
+ //-------------------------------------------------------------------
pfs0subFiles = new PFS0subFile[filesCount];
long[] offsetsSubFiles = new long[filesCount];
@@ -123,23 +155,27 @@ public class PFS0Provider implements IPFS0Provider{
byte[][] zeroBytes = new byte[filesCount][];
byte[] fileEntryTable = new byte[0x18];
- for (int i=0; i();
+ long hashTableOffset = superBlockPFS0.getHashTableOffset();
+ long hashTableSize = superBlockPFS0.getHashTableSize();
+
+ if (hashTableOffset > 0){
+ if (hashTableOffset != stream.skip(hashTableOffset))
+ throw new Exception("Unable to skip bytes till Hash Table Offset: "+hashTableOffset);
+ }
+ for (int i = 0; i < hashTableSize / 0x20; i++){
+ byte[] sectionHash = new byte[0x20];
+ if (0x20 != stream.read(sectionHash))
+ throw new Exception("Unable to read hash");
+ pfs0SHA256hashes.add(sectionHash);
}
- bufferedInputStream.close();
}
@Override
- public boolean isEncrypted() { return encrypted; }
+ public boolean isEncrypted() { return true; }
@Override
public String getMagic() { return magic; }
@Override
@@ -168,80 +234,90 @@ public class PFS0Provider implements IPFS0Provider{
@Override
public byte[] getPadding() { return padding; }
@Override
- public long getRawFileDataStart() { return rawFileDataStartOffset; }
+ public long getRawFileDataStart() { return rawBlockDataStart;}
@Override
public PFS0subFile[] getPfs0subFiles() { return pfs0subFiles; }
@Override
public File getFile(){ return file; }
+
@Override
- public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception{ // TODO: Throw exceptions?
- if (subFileNumber >= pfs0subFiles.length) {
- throw new Exception("PFS0Provider -> getPfs0subFilePipedInpStream(): Requested sub file doesn't exists");
- }
- PipedOutputStream streamOut = new PipedOutputStream();
- Thread workerThread;
-
- PipedInputStream streamIn = new PipedInputStream(streamOut);
-
- workerThread = new Thread(() -> {
- System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Executing thread");
- try {
- long subFileRealPosition = rawFileDataStartOffset + pfs0subFiles[subFileNumber].getOffset();
- BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
- if (bis.skip(subFileRealPosition) != subFileRealPosition) {
- System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Unable to skip requested offset");
- return;
- }
-
- int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576
-
- long readFrom = 0;
- long realFileSize = pfs0subFiles[subFileNumber].getSize();
-
- byte[] readBuf;
-
- while (readFrom < realFileSize) {
- if (realFileSize - readFrom < readPice)
- readPice = Math.toIntExact(realFileSize - readFrom); // it's safe, I guarantee
- readBuf = new byte[readPice];
- if (bis.read(readBuf) != readPice) {
- System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Unable to read requested size from file.");
- return;
- }
- streamOut.write(readBuf);
- readFrom += readPice;
- }
- bis.close();
- streamOut.close();
- } catch (IOException ioe) {
- System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Unable to provide stream");
- ioe.printStackTrace();
- }
- System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Thread died");
- });
- workerThread.start();
- return streamIn;
- }
- /**
- * Some sugar
- * */
- @Override
- public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception {
+ public boolean exportContent(String saveToLocation, String subFileName){
for (int i = 0; i < pfs0subFiles.length; i++){
if (pfs0subFiles[i].getName().equals(subFileName))
- return getProviderSubFilePipedInpStream(i);
+ return exportContent(saveToLocation, i);
}
- return null;
+ return false;
+ }
+ @Override
+ public boolean exportContent(String saveToLocation, int subFileNumber){
+ PFS0subFile subFile = pfs0subFiles[subFileNumber];
+ File location = new File(saveToLocation);
+ location.mkdirs();
+
+ try (BufferedOutputStream extractedFileBOS = new BufferedOutputStream(
+ Files.newOutputStream(Paths.get(saveToLocation+File.separator+subFile.getName())))){
+ if (encrypted)
+ createAesCtrEncryptedBufferedInputStream();
+ else
+ createBufferedInputStream();
+
+ long subFileSize = subFile.getSize();
+
+ long toSkip = subFile.getOffset() + mediaStartOffset * 0x200 + rawBlockDataStart;
+ if (toSkip != stream.skip(toSkip))
+ throw new Exception("Unable to skip offset: "+toSkip);
+
+ int blockSize = 0x200;
+ if (subFileSize < 0x200)
+ blockSize = (int) subFileSize;
+
+ long i = 0;
+ byte[] block = new byte[blockSize];
+
+ int actuallyRead;
+ while (true) {
+ if ((actuallyRead = stream.read(block)) != blockSize)
+ throw new Exception("Read failure. Block Size: "+blockSize+", actuallyRead: "+actuallyRead);
+ extractedFileBOS.write(block);
+ i += blockSize;
+ if ((i + blockSize) > subFileSize) {
+ blockSize = (int) (subFileSize - i);
+ if (blockSize == 0)
+ break;
+ block = new byte[blockSize];
+ }
+ }
+ }
+ catch (Exception e){
+ log.error("File export failure", e);
+ return false;
+ }
+ return true;
+ }
+
+ //TODO: REMOVE
+ @Override
+ public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception {return null;}
+ @Override
+ public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception {return null;}
+
+
+ public LinkedList getPfs0SHA256hashes() {
+ return pfs0SHA256hashes;
}
public void printDebug(){
log.debug(".:: PFS0Provider ::.\n" +
- "File name: " + file.getName() + "\n\n" +
- "Raw file data start: " + RainbowDump.formatDecHexString(rawFileDataStartOffset) + "\n" +
+ "File name: " + file.getName() + "\n" +
+ "Raw block data start " + RainbowDump.formatDecHexString(rawBlockDataStart) + "\n" +
"Magic " + magic + "\n" +
"Files count " + RainbowDump.formatDecHexString(filesCount) + "\n" +
"String Table Size " + RainbowDump.formatDecHexString(stringTableSize) + "\n" +
- "Padding " + Converter.byteArrToHexString(padding) + "\n"
+ "Padding " + Converter.byteArrToHexString(padding) + "\n\n" +
+
+ "Offset position in file " + RainbowDump.formatDecHexString(offsetPositionInFile) + "\n" +
+ "Media Start Offset " + RainbowDump.formatDecHexString(mediaStartOffset) + "\n" +
+ "Media End Offset " + RainbowDump.formatDecHexString(mediaEndOffset) + "\n"
);
for (PFS0subFile subFile : pfs0subFiles){
log.debug(
@@ -253,4 +329,4 @@ public class PFS0Provider implements IPFS0Provider{
);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/libKonogonka/Tools/XCI/HFS0Provider.java b/src/main/java/libKonogonka/Tools/XCI/HFS0Provider.java
index 26e6101..6d5dd4d 100644
--- a/src/main/java/libKonogonka/Tools/XCI/HFS0Provider.java
+++ b/src/main/java/libKonogonka/Tools/XCI/HFS0Provider.java
@@ -176,6 +176,17 @@ public class HFS0Provider implements ISuperProvider {
workerThread.start();
return streamIn;
}
+
+ @Override
+ public boolean exportContent(String saveToLocation, String subFileName) throws Exception {
+ return false;
+ }
+
+ @Override
+ public boolean exportContent(String saveToLocation, int subFileNumber) throws Exception {
+ return false;
+ }
+
/**
* Sugar
* */
diff --git a/src/main/java/libKonogonka/ctraes/AesCtrBufferedInputStream.java b/src/main/java/libKonogonka/ctraes/AesCtrBufferedInputStream.java
index 426d8ea..b4e8f8f 100644
--- a/src/main/java/libKonogonka/ctraes/AesCtrBufferedInputStream.java
+++ b/src/main/java/libKonogonka/ctraes/AesCtrBufferedInputStream.java
@@ -32,18 +32,18 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
private final long mediaOffsetPositionEnd;
public AesCtrBufferedInputStream(AesCtrDecryptSimple decryptor,
- long offsetPosition,
+ long ncaOffsetPosition,
long mediaStartOffset,
long mediaEndOffset,
InputStream inputStream){
super(inputStream);
this.decryptor = decryptor;
- this.mediaOffsetPositionStart = offsetPosition + (mediaStartOffset * 0x200);
- this.mediaOffsetPositionEnd = offsetPosition + (mediaEndOffset * 0x200);
+ this.mediaOffsetPositionStart = ncaOffsetPosition + (mediaStartOffset * 0x200);
+ this.mediaOffsetPositionEnd = ncaOffsetPosition + (mediaEndOffset * 0x200);
- log.debug("\nOffset Position "+offsetPosition+
- "\nMediaOffsetPositionStart "+RainbowDump.formatDecHexString(mediaOffsetPositionStart)+
- "\nMediaOffsetPositionEnd "+RainbowDump.formatDecHexString(mediaOffsetPositionEnd));
+ log.trace("\n Offset Position "+ncaOffsetPosition+
+ "\n MediaOffsetPositionStart "+RainbowDump.formatDecHexString(mediaOffsetPositionStart)+
+ "\n MediaOffsetPositionEnd "+RainbowDump.formatDecHexString(mediaOffsetPositionEnd));
}
private byte[] decryptedBytes;
@@ -214,7 +214,6 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
fillDecryptedCache();
pseudoPos += n;
pointerInsideDecryptedSection = (int) leftovers;
- log.debug(" "+pseudoPos+" "+pointerInsideDecryptedSection);
return n;
}
log.trace("6. Not encrypted ("+pseudoPos+"-"+(pseudoPos+n)+")");
diff --git a/src/main/java/libKonogonka/ctraes/AesCtrDecryptSimple.java b/src/main/java/libKonogonka/ctraes/AesCtrDecryptSimple.java
index a9de59f..dbd7bee 100644
--- a/src/main/java/libKonogonka/ctraes/AesCtrDecryptSimple.java
+++ b/src/main/java/libKonogonka/ctraes/AesCtrDecryptSimple.java
@@ -70,6 +70,6 @@ public class AesCtrDecryptSimple {
// IV for CTR == 16 bytes
IVarray = new byte[0x10];
// Populate first 4 bytes taken from Header's section Block CTR (aka SecureValue)
- System.arraycopy(Converter.flip(initialSectionCTR), 0x0, IVarray, 0x0, 0x4);
+ System.arraycopy(Converter.flip(initialSectionCTR), 0x0, IVarray, 0x0, 0x8);
}
}
diff --git a/src/main/resources/log4j2.properties b/src/main/resources/log4j2.properties
index b7fcbfc..48ea019 100644
--- a/src/main/resources/log4j2.properties
+++ b/src/main/resources/log4j2.properties
@@ -15,7 +15,7 @@ status = error
name = DebugConfigDevelopmentOnlyVerbose
# Configure root logger level
-rootLogger.level = TRACE
+rootLogger.level = DEBUG
# Root logger referring to console appender
rootLogger.appenderRef.stdout.ref = consoleLogger