New unified PFS0 Class with new method of data export that will be implemented everywhere
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
9b5eacdef9
commit
0238cb4d2c
12 changed files with 313 additions and 705 deletions
58
src/main/java/libKonogonka/TitleKeyChainHolder.java
Normal file
58
src/main/java/libKonogonka/TitleKeyChainHolder.java
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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<String, String> 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<String, String> getKeySet() {
|
||||||
|
return rawKeySet;
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,8 @@ import java.io.PipedInputStream;
|
||||||
public interface ISuperProvider {
|
public interface ISuperProvider {
|
||||||
PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception;
|
PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception;
|
||||||
PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) 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();
|
File getFile();
|
||||||
long getRawFileDataStart();
|
long getRawFileDataStart();
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,8 @@
|
||||||
package libKonogonka.Tools.NCA;
|
package libKonogonka.Tools.NCA;
|
||||||
|
|
||||||
import libKonogonka.Converter;
|
import libKonogonka.Converter;
|
||||||
import libKonogonka.RainbowDump;
|
|
||||||
import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader;
|
import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader;
|
||||||
import libKonogonka.Tools.PFS0.IPFS0Provider;
|
import libKonogonka.Tools.PFS0.IPFS0Provider;
|
||||||
import libKonogonka.Tools.PFS0.PFS0EncryptedProvider;
|
|
||||||
import libKonogonka.Tools.PFS0.PFS0Provider;
|
import libKonogonka.Tools.PFS0.PFS0Provider;
|
||||||
import libKonogonka.Tools.RomFs.IRomFsProvider;
|
import libKonogonka.Tools.RomFs.IRomFsProvider;
|
||||||
import libKonogonka.Tools.RomFs.RomFsEncryptedProvider;
|
import libKonogonka.Tools.RomFs.RomFsEncryptedProvider;
|
||||||
|
@ -41,28 +39,28 @@ public class NCAContent {
|
||||||
private final static Logger log = LogManager.getLogger(NCAContent.class);
|
private final static Logger log = LogManager.getLogger(NCAContent.class);
|
||||||
|
|
||||||
private final File file;
|
private final File file;
|
||||||
private final long offsetPosition;
|
private final long ncaOffsetPosition;
|
||||||
private final NcaFsHeader ncaFsHeader;
|
private final NcaFsHeader ncaFsHeader;
|
||||||
private final NCAHeaderTableEntry ncaHeaderTableEntry;
|
private final NCAHeaderTableEntry ncaHeaderTableEntry;
|
||||||
private final byte[] decryptedKey;
|
private final byte[] decryptedKey;
|
||||||
|
|
||||||
private final LinkedList<byte[]> Pfs0SHA256hashes;
|
private LinkedList<byte[]> Pfs0SHA256hashes;
|
||||||
private IPFS0Provider pfs0;
|
private IPFS0Provider pfs0;
|
||||||
private IRomFsProvider romfs;
|
private IRomFsProvider romfs;
|
||||||
|
|
||||||
// TODO: if decryptedKey is empty, throw exception ??
|
// TODO: if decryptedKey is empty, throw exception ??
|
||||||
public NCAContent(File file,
|
public NCAContent(File file,
|
||||||
long offsetPosition,
|
long ncaOffsetPosition,
|
||||||
NcaFsHeader ncaFsHeader,
|
NcaFsHeader ncaFsHeader,
|
||||||
NCAHeaderTableEntry ncaHeaderTableEntry,
|
NCAHeaderTableEntry ncaHeaderTableEntry,
|
||||||
byte[] decryptedKey) throws Exception
|
byte[] decryptedKey) throws Exception
|
||||||
{
|
{
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.offsetPosition = offsetPosition;
|
this.ncaOffsetPosition = ncaOffsetPosition;
|
||||||
this.ncaFsHeader = ncaFsHeader;
|
this.ncaFsHeader = ncaFsHeader;
|
||||||
this.ncaHeaderTableEntry = ncaHeaderTableEntry;
|
this.ncaHeaderTableEntry = ncaHeaderTableEntry;
|
||||||
this.decryptedKey = decryptedKey;
|
this.decryptedKey = decryptedKey;
|
||||||
|
System.out.println("NCAContent pfs0offsetPosition: "+ncaOffsetPosition);
|
||||||
Pfs0SHA256hashes = new LinkedList<>();
|
Pfs0SHA256hashes = new LinkedList<>();
|
||||||
// If nothing to do
|
// If nothing to do
|
||||||
if (ncaHeaderTableEntry.getMediaEndOffset() == 0)
|
if (ncaHeaderTableEntry.getMediaEndOffset() == 0)
|
||||||
|
@ -89,60 +87,24 @@ public class NCAContent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void proceedPFS0NotEncrypted() throws Exception{
|
private void proceedPFS0NotEncrypted() throws Exception{
|
||||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
pfs0 = new PFS0Provider(file,
|
||||||
long thisMediaLocation = offsetPosition + (ncaHeaderTableEntry.getMediaStartOffset() * 0x200);// TODO: NOTE already defined inside PFS0
|
ncaOffsetPosition,
|
||||||
long hashTableLocation = thisMediaLocation + ncaFsHeader.getSuperBlockPFS0().getHashTableOffset();
|
ncaFsHeader.getSuperBlockPFS0(),
|
||||||
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,
|
|
||||||
ncaHeaderTableEntry.getMediaStartOffset(),
|
ncaHeaderTableEntry.getMediaStartOffset(),
|
||||||
ncaHeaderTableEntry.getMediaEndOffset());
|
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{
|
private void proceedRomFs() throws Exception{
|
||||||
|
@ -167,7 +129,7 @@ public class NCAContent {
|
||||||
this.romfs = new RomFsEncryptedProvider(
|
this.romfs = new RomFsEncryptedProvider(
|
||||||
ncaFsHeader.getSuperBlockIVFC().getLvl6Offset(),
|
ncaFsHeader.getSuperBlockIVFC().getLvl6Offset(),
|
||||||
file,
|
file,
|
||||||
offsetPosition,
|
ncaOffsetPosition,
|
||||||
decryptedKey,
|
decryptedKey,
|
||||||
ncaFsHeader.getSectionCTR(),
|
ncaFsHeader.getSectionCTR(),
|
||||||
ncaHeaderTableEntry.getMediaStartOffset(),
|
ncaHeaderTableEntry.getMediaStartOffset(),
|
||||||
|
@ -178,174 +140,6 @@ public class NCAContent {
|
||||||
public IPFS0Provider getPfs0() { return pfs0; }
|
public IPFS0Provider getPfs0() { return pfs0; }
|
||||||
public IRomFsProvider getRomfs() { return romfs; }
|
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.
|
* Export NCA content AS IS.
|
||||||
|
@ -362,7 +156,7 @@ public class NCAContent {
|
||||||
"Media start location: " + mediaStartBlocksOffset + "\n" +
|
"Media start location: " + mediaStartBlocksOffset + "\n" +
|
||||||
"Media end location: " + mediaEndBlocksOffset + "\n" +
|
"Media end location: " + mediaEndBlocksOffset + "\n" +
|
||||||
"Media size : " + (mediaEndBlocksOffset-mediaStartBlocksOffset) + "\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" +
|
"KEY: " + Converter.byteArrToHexString(decryptedKey) + "\n" +
|
||||||
"CTR: " + Converter.byteArrToHexString(ncaFsHeader.getSectionCTR()) + "\n");
|
"CTR: " + Converter.byteArrToHexString(ncaFsHeader.getSectionCTR()) + "\n");
|
||||||
//---------------------------------------------------------------------------------------------------/
|
//---------------------------------------------------------------------------------------------------/
|
||||||
|
@ -414,7 +208,7 @@ public class NCAContent {
|
||||||
workerThread = new Thread(() -> {
|
workerThread = new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
//RandomAccessFile raf = new RandomAccessFile(file, "r");
|
//RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||||
long abosluteOffsetPosition = offsetPosition + (mediaStartBlocksOffset * 0x200);
|
long abosluteOffsetPosition = ncaOffsetPosition + (mediaStartBlocksOffset * 0x200);
|
||||||
raf.seek(abosluteOffsetPosition);
|
raf.seek(abosluteOffsetPosition);
|
||||||
|
|
||||||
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey,
|
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey,
|
||||||
|
|
|
@ -283,7 +283,8 @@ public class NCAProvider {
|
||||||
key = cipher.doFinal(rightsIDkey);
|
key = cipher.doFinal(rightsIDkey);
|
||||||
}
|
}
|
||||||
catch (Exception e){
|
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);
|
getNcaContentByNumber(0, key);
|
||||||
|
|
|
@ -48,6 +48,7 @@ public class NcaFsHeader {
|
||||||
private final BucketTreeHeader BktrSection2;
|
private final BucketTreeHeader BktrSection2;
|
||||||
|
|
||||||
private final byte[] generation;
|
private final byte[] generation;
|
||||||
|
private final byte[] secureValue;
|
||||||
private final byte[] sectionCTR;
|
private final byte[] sectionCTR;
|
||||||
private final SparseInfo sparseInfo;
|
private final SparseInfo sparseInfo;
|
||||||
private final CompressionInfo compressionInfo;
|
private final CompressionInfo compressionInfo;
|
||||||
|
@ -79,7 +80,10 @@ public class NcaFsHeader {
|
||||||
BktrSection2 = new BucketTreeHeader(Arrays.copyOfRange(tableBlockBytes, 0x130, 0x140));
|
BktrSection2 = new BucketTreeHeader(Arrays.copyOfRange(tableBlockBytes, 0x130, 0x140));
|
||||||
|
|
||||||
generation = Arrays.copyOfRange(tableBlockBytes, 0x140, 0x144);
|
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));
|
sparseInfo = new SparseInfo(Arrays.copyOfRange(tableBlockBytes, 0x148, 0x178));
|
||||||
compressionInfo = new CompressionInfo(Arrays.copyOfRange(tableBlockBytes, 0x178, 0x1a0));
|
compressionInfo = new CompressionInfo(Arrays.copyOfRange(tableBlockBytes, 0x178, 0x1a0));
|
||||||
metaDataHashDataInfo = new MetaDataHashDataInfo(Arrays.copyOfRange(tableBlockBytes, 0x1a0, 0x1d0));
|
metaDataHashDataInfo = new MetaDataHashDataInfo(Arrays.copyOfRange(tableBlockBytes, 0x1a0, 0x1d0));
|
||||||
|
@ -109,6 +113,10 @@ public class NcaFsHeader {
|
||||||
public int getEntryCountSection2() { return BktrSection2.getEntryCount(); }
|
public int getEntryCountSection2() { return BktrSection2.getEntryCount(); }
|
||||||
public byte[] getPatchInfoUnknownSection2() { return BktrSection2.getUnknown(); }
|
public byte[] getPatchInfoUnknownSection2() { return BktrSection2.getUnknown(); }
|
||||||
public byte[] getGeneration() {return generation;}
|
public byte[] getGeneration() {return generation;}
|
||||||
|
public byte[] getSecureValue() {return secureValue;}
|
||||||
|
/**
|
||||||
|
* Used for Aes Ctr decryption in IV context.
|
||||||
|
* */
|
||||||
public byte[] getSectionCTR() { return sectionCTR; }
|
public byte[] getSectionCTR() { return sectionCTR; }
|
||||||
public SparseInfo getSparseInfo() {return sparseInfo;}
|
public SparseInfo getSparseInfo() {return sparseInfo;}
|
||||||
public CompressionInfo getCompressionInfo() {return compressionInfo;}
|
public CompressionInfo getCompressionInfo() {return compressionInfo;}
|
||||||
|
|
|
@ -20,6 +20,8 @@ package libKonogonka.Tools.PFS0;
|
||||||
|
|
||||||
import libKonogonka.Tools.ISuperProvider;
|
import libKonogonka.Tools.ISuperProvider;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
public interface IPFS0Provider extends ISuperProvider {
|
public interface IPFS0Provider extends ISuperProvider {
|
||||||
boolean isEncrypted();
|
boolean isEncrypted();
|
||||||
String getMagic();
|
String getMagic();
|
||||||
|
@ -30,4 +32,5 @@ public interface IPFS0Provider extends ISuperProvider {
|
||||||
PFS0subFile[] getPfs0subFiles();
|
PFS0subFile[] getPfs0subFiles();
|
||||||
|
|
||||||
void printDebug();
|
void printDebug();
|
||||||
|
LinkedList<byte[]> getPfs0SHA256hashes();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
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" +
|
|
||||||
"----------------------------------------------------------------"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -20,6 +20,7 @@ package libKonogonka.Tools.PFS0;
|
||||||
|
|
||||||
import libKonogonka.Converter;
|
import libKonogonka.Converter;
|
||||||
import libKonogonka.RainbowDump;
|
import libKonogonka.RainbowDump;
|
||||||
|
import libKonogonka.Tools.NCA.NCASectionTableBlock.SuperBlockPFS0;
|
||||||
import libKonogonka.ctraes.AesCtrBufferedInputStream;
|
import libKonogonka.ctraes.AesCtrBufferedInputStream;
|
||||||
import libKonogonka.ctraes.AesCtrDecryptSimple;
|
import libKonogonka.ctraes.AesCtrDecryptSimple;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
@ -28,93 +29,124 @@ import org.apache.logging.log4j.Logger;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.Arrays;
|
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{
|
public class PFS0Provider implements IPFS0Provider{
|
||||||
private final static Logger log = LogManager.getLogger(PFS0Provider.class);
|
private final static Logger log = LogManager.getLogger(PFS0Provider.class);
|
||||||
|
|
||||||
private long rawFileDataStartOffset;
|
|
||||||
|
|
||||||
private String magic;
|
private String magic;
|
||||||
private int filesCount;
|
private int filesCount;
|
||||||
private int stringTableSize;
|
private int stringTableSize;
|
||||||
private byte[] padding;
|
private byte[] padding;
|
||||||
private PFS0subFile[] pfs0subFiles;
|
private PFS0subFile[] pfs0subFiles;
|
||||||
|
//---------------------------------------
|
||||||
|
private long rawBlockDataStart;
|
||||||
|
|
||||||
private final File file;
|
private final File file;
|
||||||
private final long offsetPosition; // Where data starts, excluding header, string table etc.
|
private long offsetPositionInFile;
|
||||||
private long mediaStartOffset;
|
private long mediaStartOffset; // In 512-blocks
|
||||||
private long mediaEndOffset;
|
private long mediaEndOffset; // In 512-blocks
|
||||||
|
|
||||||
|
private long ncaOffset;
|
||||||
|
private BufferedInputStream stream;
|
||||||
|
private SuperBlockPFS0 superBlockPFS0;
|
||||||
private AesCtrDecryptSimple decryptor;
|
private AesCtrDecryptSimple decryptor;
|
||||||
|
|
||||||
private final boolean encrypted;
|
private LinkedList<byte[]> pfs0SHA256hashes;
|
||||||
|
|
||||||
public PFS0Provider(File fileWithPfs0,
|
private boolean encrypted;
|
||||||
long offsetPosition,
|
|
||||||
|
public PFS0Provider(File nspFile) throws Exception{
|
||||||
|
this.file = nspFile;
|
||||||
|
createBufferedInputStream();
|
||||||
|
readPfs0Header();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PFS0Provider(File file,
|
||||||
|
long ncaOffset,
|
||||||
|
SuperBlockPFS0 superBlockPFS0,
|
||||||
long mediaStartOffset,
|
long mediaStartOffset,
|
||||||
long mediaEndOffset,
|
long mediaEndOffset) throws Exception{
|
||||||
AesCtrDecryptSimple decryptor) throws Exception{
|
this.file = file;
|
||||||
this.file = fileWithPfs0;
|
this.ncaOffset = ncaOffset;
|
||||||
this.offsetPosition = offsetPosition + mediaStartOffset*0x200;
|
this.superBlockPFS0 = superBlockPFS0;
|
||||||
this.encrypted = true;
|
this.offsetPositionInFile = ncaOffset + mediaStartOffset * 0x200;
|
||||||
|
|
||||||
this.mediaStartOffset = mediaStartOffset;
|
this.mediaStartOffset = mediaStartOffset;
|
||||||
this.mediaEndOffset = mediaEndOffset;
|
this.mediaEndOffset = mediaEndOffset;
|
||||||
this.decryptor = decryptor;
|
this.rawBlockDataStart = superBlockPFS0.getPfs0offset();
|
||||||
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;
|
|
||||||
//bufferedInputStream = new BufferedInputStream(Files.newInputStream(fileWithPfs0.toPath()));
|
//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) {
|
public PFS0Provider(File file,
|
||||||
bufferedInputStream = new AesCtrBufferedInputStream(decryptor,
|
long ncaOffset,
|
||||||
offsetPosition,
|
SuperBlockPFS0 superBlockPFS0,
|
||||||
mediaStartOffset,
|
AesCtrDecryptSimple decryptor,
|
||||||
mediaEndOffset,
|
long mediaStartOffset,
|
||||||
Files.newInputStream(file.toPath()));
|
long mediaEndOffset
|
||||||
}
|
) throws Exception {
|
||||||
else{
|
this.file = file;
|
||||||
bufferedInputStream = new BufferedInputStream(Files.newInputStream(file.toPath()));
|
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))
|
createAesCtrEncryptedBufferedInputStream();
|
||||||
throw new Exception("PFS0Provider: Unable to skip initial offset: "+offsetPosition);
|
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];
|
byte[] fileStartingBytes = new byte[0x10];
|
||||||
// Read PFS0Provider, files count, header, padding (4 zero bytes)
|
if (0x10 != stream.read(fileStartingBytes))
|
||||||
if (bufferedInputStream.read(fileStartingBytes) != 0x10){
|
throw new Exception("Reading stream suddenly ended while trying to read starting 0x10 bytes");
|
||||||
throw new Exception("PFS0Provider: Unable to read starting bytes");
|
|
||||||
}
|
// Update position
|
||||||
rawFileDataStartOffset += 0x10;
|
rawBlockDataStart += 0x10;
|
||||||
// Check PFS0Provider
|
// Check PFS0Provider
|
||||||
magic = new String(fileStartingBytes, 0x0, 0x4, StandardCharsets.US_ASCII);
|
magic = new String(fileStartingBytes, 0x0, 0x4, StandardCharsets.US_ASCII);
|
||||||
if (! magic.equals("PFS0")){
|
if (! magic.equals("PFS0")){
|
||||||
throw new Exception("PFS0Provider: Bad magic");
|
throw new Exception("Bad magic");
|
||||||
}
|
}
|
||||||
// Get files count
|
// Get files count
|
||||||
filesCount = getLEint(fileStartingBytes, 0x4);
|
filesCount = getLEint(fileStartingBytes, 0x4);
|
||||||
if (filesCount <= 0 ) {
|
if (filesCount <= 0 ) {
|
||||||
throw new Exception("PFS0Provider: Files count is too small");
|
throw new Exception("Files count is too small");
|
||||||
}
|
}
|
||||||
// Get string table
|
// Get string table
|
||||||
stringTableSize = getLEint(fileStartingBytes, 0x8);
|
stringTableSize = getLEint(fileStartingBytes, 0x8);
|
||||||
if (stringTableSize <= 0 ){
|
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);
|
padding = Arrays.copyOfRange(fileStartingBytes, 0xc, 0x10);
|
||||||
//---------------------------------------------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
pfs0subFiles = new PFS0subFile[filesCount];
|
pfs0subFiles = new PFS0subFile[filesCount];
|
||||||
|
|
||||||
long[] offsetsSubFiles = new long[filesCount];
|
long[] offsetsSubFiles = new long[filesCount];
|
||||||
|
@ -123,23 +155,27 @@ public class PFS0Provider implements IPFS0Provider{
|
||||||
byte[][] zeroBytes = new byte[filesCount][];
|
byte[][] zeroBytes = new byte[filesCount][];
|
||||||
|
|
||||||
byte[] fileEntryTable = new byte[0x18];
|
byte[] fileEntryTable = new byte[0x18];
|
||||||
for (int i=0; i<filesCount; i++){
|
for (int i=0; i < filesCount; i++){
|
||||||
if (bufferedInputStream.read(fileEntryTable) != 0x18)
|
if (0x18 != stream.read(fileEntryTable))
|
||||||
throw new Exception("PFS0Provider: String table is too small");
|
throw new Exception("Reading stream suddenly ended while trying to read File Entry Table #"+i);
|
||||||
|
|
||||||
offsetsSubFiles[i] = getLElong(fileEntryTable, 0);
|
offsetsSubFiles[i] = getLElong(fileEntryTable, 0);
|
||||||
sizesSubFiles[i] = getLElong(fileEntryTable, 0x8);
|
sizesSubFiles[i] = getLElong(fileEntryTable, 0x8);
|
||||||
strTableOffsets[i] = getLEint(fileEntryTable, 0x10);
|
strTableOffsets[i] = getLEint(fileEntryTable, 0x10);
|
||||||
zeroBytes[i] = Arrays.copyOfRange(fileEntryTable, 0x14, 0x18);
|
zeroBytes[i] = Arrays.copyOfRange(fileEntryTable, 0x14, 0x18);
|
||||||
rawFileDataStartOffset += 0x18;
|
rawBlockDataStart += 0x18;
|
||||||
}
|
}
|
||||||
//**********************************************************************************************************
|
//*******************************************************************
|
||||||
// In here pointer in front of String table
|
// In here pointer in front of String table
|
||||||
String[] subFileNames = new String[filesCount];
|
String[] subFileNames = new String[filesCount];
|
||||||
byte[] stringTbl = new byte[stringTableSize];
|
byte[] stringTbl = new byte[stringTableSize];
|
||||||
if (bufferedInputStream.read(stringTbl) != stringTableSize){
|
if (stream.read(stringTbl) != stringTableSize){
|
||||||
throw new Exception("Read PFS0Provider String table failure. Can't read requested string table size ("+stringTableSize+")");
|
throw new Exception("Read PFS0Provider String table failure. Can't read requested string table size ("+stringTableSize+")");
|
||||||
}
|
}
|
||||||
rawFileDataStartOffset += stringTableSize;
|
|
||||||
|
// Update position
|
||||||
|
rawBlockDataStart += stringTableSize;
|
||||||
|
|
||||||
for (int i=0; i < filesCount; i++){
|
for (int i=0; i < filesCount; i++){
|
||||||
int j = 0;
|
int j = 0;
|
||||||
while (stringTbl[strTableOffsets[i]+j] != (byte)0x00)
|
while (stringTbl[strTableOffsets[i]+j] != (byte)0x00)
|
||||||
|
@ -151,14 +187,44 @@ public class PFS0Provider implements IPFS0Provider{
|
||||||
subFileNames[i],
|
subFileNames[i],
|
||||||
offsetsSubFiles[i],
|
offsetsSubFiles[i],
|
||||||
sizesSubFiles[i],
|
sizesSubFiles[i],
|
||||||
zeroBytes[i]
|
zeroBytes[i]);
|
||||||
);
|
}
|
||||||
|
stream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createAesCtrEncryptedBufferedInputStream() throws Exception{
|
||||||
|
decryptor.reset();
|
||||||
|
this.stream = new AesCtrBufferedInputStream(
|
||||||
|
decryptor,
|
||||||
|
ncaOffset,
|
||||||
|
mediaStartOffset,
|
||||||
|
mediaEndOffset,
|
||||||
|
Files.newInputStream(file.toPath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createBufferedInputStream() throws Exception{
|
||||||
|
this.stream = new BufferedInputStream(Files.newInputStream(file.toPath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectHashes() throws Exception{
|
||||||
|
pfs0SHA256hashes = new LinkedList<>();
|
||||||
|
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
|
@Override
|
||||||
public boolean isEncrypted() { return encrypted; }
|
public boolean isEncrypted() { return true; }
|
||||||
@Override
|
@Override
|
||||||
public String getMagic() { return magic; }
|
public String getMagic() { return magic; }
|
||||||
@Override
|
@Override
|
||||||
|
@ -168,80 +234,90 @@ public class PFS0Provider implements IPFS0Provider{
|
||||||
@Override
|
@Override
|
||||||
public byte[] getPadding() { return padding; }
|
public byte[] getPadding() { return padding; }
|
||||||
@Override
|
@Override
|
||||||
public long getRawFileDataStart() { return rawFileDataStartOffset; }
|
public long getRawFileDataStart() { return rawBlockDataStart;}
|
||||||
@Override
|
@Override
|
||||||
public PFS0subFile[] getPfs0subFiles() { return pfs0subFiles; }
|
public PFS0subFile[] getPfs0subFiles() { return pfs0subFiles; }
|
||||||
@Override
|
@Override
|
||||||
public File getFile(){ return file; }
|
public File getFile(){ return file; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception{ // TODO: Throw exceptions?
|
public boolean exportContent(String saveToLocation, String subFileName){
|
||||||
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 {
|
|
||||||
for (int i = 0; i < pfs0subFiles.length; i++){
|
for (int i = 0; i < pfs0subFiles.length; i++){
|
||||||
if (pfs0subFiles[i].getName().equals(subFileName))
|
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<byte[]> getPfs0SHA256hashes() {
|
||||||
|
return pfs0SHA256hashes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void printDebug(){
|
public void printDebug(){
|
||||||
log.debug(".:: PFS0Provider ::.\n" +
|
log.debug(".:: PFS0Provider ::.\n" +
|
||||||
"File name: " + file.getName() + "\n\n" +
|
"File name: " + file.getName() + "\n" +
|
||||||
"Raw file data start: " + RainbowDump.formatDecHexString(rawFileDataStartOffset) + "\n" +
|
"Raw block data start " + RainbowDump.formatDecHexString(rawBlockDataStart) + "\n" +
|
||||||
"Magic " + magic + "\n" +
|
"Magic " + magic + "\n" +
|
||||||
"Files count " + RainbowDump.formatDecHexString(filesCount) + "\n" +
|
"Files count " + RainbowDump.formatDecHexString(filesCount) + "\n" +
|
||||||
"String Table Size " + RainbowDump.formatDecHexString(stringTableSize) + "\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){
|
for (PFS0subFile subFile : pfs0subFiles){
|
||||||
log.debug(
|
log.debug(
|
||||||
|
@ -253,4 +329,4 @@ public class PFS0Provider implements IPFS0Provider{
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -176,6 +176,17 @@ public class HFS0Provider implements ISuperProvider {
|
||||||
workerThread.start();
|
workerThread.start();
|
||||||
return streamIn;
|
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
|
* Sugar
|
||||||
* */
|
* */
|
||||||
|
|
|
@ -32,18 +32,18 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
|
||||||
private final long mediaOffsetPositionEnd;
|
private final long mediaOffsetPositionEnd;
|
||||||
|
|
||||||
public AesCtrBufferedInputStream(AesCtrDecryptSimple decryptor,
|
public AesCtrBufferedInputStream(AesCtrDecryptSimple decryptor,
|
||||||
long offsetPosition,
|
long ncaOffsetPosition,
|
||||||
long mediaStartOffset,
|
long mediaStartOffset,
|
||||||
long mediaEndOffset,
|
long mediaEndOffset,
|
||||||
InputStream inputStream){
|
InputStream inputStream){
|
||||||
super(inputStream);
|
super(inputStream);
|
||||||
this.decryptor = decryptor;
|
this.decryptor = decryptor;
|
||||||
this.mediaOffsetPositionStart = offsetPosition + (mediaStartOffset * 0x200);
|
this.mediaOffsetPositionStart = ncaOffsetPosition + (mediaStartOffset * 0x200);
|
||||||
this.mediaOffsetPositionEnd = offsetPosition + (mediaEndOffset * 0x200);
|
this.mediaOffsetPositionEnd = ncaOffsetPosition + (mediaEndOffset * 0x200);
|
||||||
|
|
||||||
log.debug("\nOffset Position "+offsetPosition+
|
log.trace("\n Offset Position "+ncaOffsetPosition+
|
||||||
"\nMediaOffsetPositionStart "+RainbowDump.formatDecHexString(mediaOffsetPositionStart)+
|
"\n MediaOffsetPositionStart "+RainbowDump.formatDecHexString(mediaOffsetPositionStart)+
|
||||||
"\nMediaOffsetPositionEnd "+RainbowDump.formatDecHexString(mediaOffsetPositionEnd));
|
"\n MediaOffsetPositionEnd "+RainbowDump.formatDecHexString(mediaOffsetPositionEnd));
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] decryptedBytes;
|
private byte[] decryptedBytes;
|
||||||
|
@ -214,7 +214,6 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
|
||||||
fillDecryptedCache();
|
fillDecryptedCache();
|
||||||
pseudoPos += n;
|
pseudoPos += n;
|
||||||
pointerInsideDecryptedSection = (int) leftovers;
|
pointerInsideDecryptedSection = (int) leftovers;
|
||||||
log.debug(" "+pseudoPos+" "+pointerInsideDecryptedSection);
|
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
log.trace("6. Not encrypted ("+pseudoPos+"-"+(pseudoPos+n)+")");
|
log.trace("6. Not encrypted ("+pseudoPos+"-"+(pseudoPos+n)+")");
|
||||||
|
|
|
@ -70,6 +70,6 @@ public class AesCtrDecryptSimple {
|
||||||
// IV for CTR == 16 bytes
|
// IV for CTR == 16 bytes
|
||||||
IVarray = new byte[0x10];
|
IVarray = new byte[0x10];
|
||||||
// Populate first 4 bytes taken from Header's section Block CTR (aka SecureValue)
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ status = error
|
||||||
name = DebugConfigDevelopmentOnlyVerbose
|
name = DebugConfigDevelopmentOnlyVerbose
|
||||||
|
|
||||||
# Configure root logger level
|
# Configure root logger level
|
||||||
rootLogger.level = TRACE
|
rootLogger.level = DEBUG
|
||||||
# Root logger referring to console appender
|
# Root logger referring to console appender
|
||||||
rootLogger.appenderRef.stdout.ref = consoleLogger
|
rootLogger.appenderRef.stdout.ref = consoleLogger
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue