Simplify, fix KeyChainHolder, implement HFS0 export functions
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Dmitry Isaenko 2022-12-12 04:26:21 +03:00
parent 53af386738
commit 9a854774d7
30 changed files with 719 additions and 1036 deletions

View file

@ -63,10 +63,9 @@ public class KeyChainHolder {
private HashMap<String, String> collectKeysByType(String keyName){
HashMap<String, String> tempKeySet = new HashMap<>();
String keyNamePattern = keyName+"_%02x";
HashMap<String, String> map = new HashMap<>();
String keyParsed;
int counter = 0;
while ((keyParsed = map.get(String.format(keyNamePattern, counter))) != null){
while ((keyParsed = rawKeySet.get(String.format(keyNamePattern, counter))) != null){
tempKeySet.put(String.format(keyNamePattern, counter), keyParsed);
counter++;
}

View file

@ -1,42 +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;
import java.io.IOException;
import java.io.PipedInputStream;
/**
* Any class of this type must be able to accept data from stream (and file as any other).
* */
public abstract class ASuperInFileProvider {
protected byte[] readFromStream(PipedInputStream pis, int size) throws IOException {
byte[] buffer = new byte[size];
int startingPos = 0;
int readCnt;
while (size > 0){
readCnt = pis.read(buffer, startingPos, size);
if (readCnt == -1)
return null;
startingPos += readCnt;
size -= readCnt;
}
return buffer;
}
}

View file

@ -18,14 +18,16 @@
*/
package libKonogonka.Tools;
import libKonogonka.ctraes.InFileStreamProducer;
import java.io.File;
import java.io.PipedInputStream;
/**
* Any class of this type must provide streams
* */
public interface ISuperProvider {
PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception;
PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception;
InFileStreamProducer getStreamProducer(String subFileName) throws Exception;
InFileStreamProducer getStreamProducer(int subFileNumber) throws Exception;
boolean exportContent(String saveToLocation, String subFileName) throws Exception;
boolean exportContent(String saveToLocation, int subFileNumber) throws Exception;
File getFile();

View file

@ -19,12 +19,11 @@
package libKonogonka.Tools.NCA;
import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader;
import libKonogonka.Tools.PFS0.IPFS0Provider;
import libKonogonka.Tools.PFS0.PFS0Provider;
import libKonogonka.Tools.RomFs.IRomFsProvider;
import libKonogonka.Tools.RomFs.RomFsProvider;
import libKonogonka.ctraes.AesCtrBufferedInputStream;
import libKonogonka.ctraes.AesCtrDecryptSimple;
import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.exceptions.EmptySectionException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -42,8 +41,8 @@ public class NCAContent {
private final NCAHeaderTableEntry ncaHeaderTableEntry;
private final byte[] decryptedKey;
private IPFS0Provider pfs0;
private IRomFsProvider romfs;
private PFS0Provider pfs0;
private RomFsProvider romfs;
// TODO: if decryptedKey is empty, throw exception?
public NCAContent(File file,
@ -82,22 +81,16 @@ public class NCAContent {
}
}
private void proceedPFS0NotEncrypted() throws Exception{
pfs0 = new PFS0Provider(file,
ncaOffsetPosition,
InFileStreamProducer producer = new InFileStreamProducer(file); // no need to bypass ncaOffsetPosition!
pfs0 = new PFS0Provider(producer,
makeOffsetPositionInFile(),
ncaFsHeader.getSuperBlockPFS0(),
ncaHeaderTableEntry.getMediaStartOffset(),
ncaHeaderTableEntry.getMediaEndOffset());
ncaHeaderTableEntry.getMediaStartOffset());
}
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());
pfs0 = new PFS0Provider(makeEncryptedProducer(), makeOffsetPositionInFile(), ncaFsHeader.getSuperBlockPFS0(),
ncaHeaderTableEntry.getMediaStartOffset());
}
private void proceedRomFs() throws Exception{
@ -109,7 +102,7 @@ public class NCAContent {
proceedRomFsEncrypted();
break;
default:
throw new Exception("Non-supported 'Crypto type'");
throw new Exception("Non-supported 'Crypto type' "+ncaFsHeader.getCryptoType());
}
}
private void proceedRomFsNotEncrypted(){ // TODO: Clarify, implement if needed
@ -119,18 +112,21 @@ public class NCAContent {
if (decryptedKey == null)
throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided.");
this.romfs = new RomFsProvider(
ncaFsHeader.getSuperBlockIVFC().getLvl6Offset(),
file,
ncaOffsetPosition,
decryptedKey,
ncaFsHeader.getSectionCTR(),
ncaHeaderTableEntry.getMediaStartOffset(),
ncaHeaderTableEntry.getMediaEndOffset());
this.romfs = new RomFsProvider(makeEncryptedProducer(), ncaFsHeader.getSuperBlockIVFC().getLvl6Offset(),
makeOffsetPositionInFile(), ncaHeaderTableEntry.getMediaStartOffset());
}
public IPFS0Provider getPfs0() { return pfs0; }
public IRomFsProvider getRomfs() { return romfs; }
public PFS0Provider getPfs0() { return pfs0; }
public RomFsProvider getRomfs() { return romfs; }
private InFileStreamProducer makeEncryptedProducer() throws Exception{
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey, ncaFsHeader.getSectionCTR(),
ncaHeaderTableEntry.getMediaStartOffset() * 0x200);
return new InFileStreamProducer(file, ncaOffsetPosition, 0, decryptor,
ncaHeaderTableEntry.getMediaStartOffset(), ncaHeaderTableEntry.getMediaEndOffset());
}
private long makeOffsetPositionInFile(){
return ncaOffsetPosition + ncaHeaderTableEntry.getMediaStartOffset() * 0x200;
}
/**
* Export NCA content AS IS.
* Not so good for PFS0 since there are SHAs list that discourages but good for 'romfs' and things like that

View file

@ -184,8 +184,8 @@ public class NcaFsHeader {
"Crypto Type : " + cryptoType + " (" + cryptoTypeDescription + ")\n" +
"Meta Data Hash Type : " + metaDataHashType + "\n" +
"Padding : " + byteArrToHexString(padding) + "\n" +
"Super Block IVFC : " + superBlockIVFC + "\n" +
"Super Block PFS0 : " + superBlockPFS0 + "\n" +
"Super Block IVFC : " + (superBlockIVFC == null ? "-\n": "YES\n") +
"Super Block PFS0 : " + (superBlockPFS0 == null ? "-\n": "YES\n") +
"================================================================================================\n" +
(((fsType == 0) && (hashType == 0x3))?
("| Hash Data - RomFS\n" +

View file

@ -27,21 +27,21 @@ import java.util.Arrays;
import static libKonogonka.Converter.getLEint;
public class ACI0Provider {
private String magicNum;
private byte[] reserved1;
private byte[] titleID;
private byte[] reserved2;
private int fsAccessHeaderOffset;
private int fsAccessHeaderSize;
private int serviceAccessControlOffset;
private int serviceAccessControlSize;
private int kernelAccessControlOffset;
private int kernelAccessControlSize;
private byte[] reserved3;
private final String magicNum;
private final byte[] reserved1;
private final byte[] titleID;
private final byte[] reserved2;
private final int fsAccessHeaderOffset;
private final int fsAccessHeaderSize;
private final int serviceAccessControlOffset;
private final int serviceAccessControlSize;
private final int kernelAccessControlOffset;
private final int kernelAccessControlSize;
private final byte[] reserved3;
private FSAccessHeaderProvider fsAccessHeaderProvider;
private ServiceAccessControlProvider serviceAccessControlProvider;
private KernelAccessControlProvider kernelAccessControlProvider;
private final FSAccessHeaderProvider fsAccessHeaderProvider;
private final ServiceAccessControlProvider serviceAccessControlProvider;
private final KernelAccessControlProvider kernelAccessControlProvider;
public ACI0Provider(byte[] aci0bytes) throws Exception {
if (aci0bytes.length < 0x40)

View file

@ -28,28 +28,28 @@ import static libKonogonka.Converter.*;
public class ACIDProvider {
private byte[] rsa2048signature;
private byte[] rsa2048publicKey;
private String magicNum;
private int dataSize;
private byte[] reserved1;
private byte flag1;
private byte flag2;
private byte flag3;
private byte flag4;
private long titleRangeMin;
private long titleRangeMax;
private int fsAccessControlOffset;
private int fsAccessControlSize;
private int serviceAccessControlOffset;
private int serviceAccessControlSize;
private int kernelAccessControlOffset;
private int kernelAccessControlSize;
private byte[] reserved2;
private final byte[] rsa2048signature;
private final byte[] rsa2048publicKey;
private final String magicNum;
private final int dataSize;
private final byte[] reserved1;
private final byte flag1;
private final byte flag2;
private final byte flag3;
private final byte flag4;
private final long titleRangeMin;
private final long titleRangeMax;
private final int fsAccessControlOffset;
private final int fsAccessControlSize;
private final int serviceAccessControlOffset;
private final int serviceAccessControlSize;
private final int kernelAccessControlOffset;
private final int kernelAccessControlSize;
private final byte[] reserved2;
private FSAccessControlProvider fsAccessControlProvider;
private ServiceAccessControlProvider serviceAccessControlProvider;
private KernelAccessControlProvider kernelAccessControlProvider;
private final FSAccessControlProvider fsAccessControlProvider;
private final ServiceAccessControlProvider serviceAccessControlProvider;
private final KernelAccessControlProvider kernelAccessControlProvider;
public ACIDProvider(byte[] acidBytes) throws Exception{
if (acidBytes.length < 0x240)

View file

@ -27,10 +27,10 @@ import java.util.Arrays;
* */
public class FSAccessControlProvider {
private byte version;
private byte[] padding;
private long permissionsBitmask;
private byte[] reserved;
private final byte version;
private final byte[] padding;
private final long permissionsBitmask;
private final byte[] reserved;
public FSAccessControlProvider(byte[] bytes) {
version = bytes[0];

View file

@ -18,19 +18,18 @@
*/
package libKonogonka.Tools.NPDM;
import libKonogonka.Tools.ASuperInFileProvider;
import libKonogonka.Tools.NPDM.ACI0.ACI0Provider;
import libKonogonka.Tools.NPDM.ACID.ACIDProvider;
import libKonogonka.ctraes.InFileStreamProducer;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.PipedInputStream;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static libKonogonka.Converter.*;
public class NPDMProvider extends ASuperInFileProvider {
public class NPDMProvider{
private final String magicNum;
private final byte[] reserved1;
@ -50,38 +49,31 @@ public class NPDMProvider extends ASuperInFileProvider {
private final int acidOffset; // originally 4-bytes (u-int)
private final int acidSize; // originally 4-bytes (u-int)
private final ACI0Provider aci0;
private final ACIDProvider acid;
private ACI0Provider aci0;
private ACIDProvider acid;
public NPDMProvider(PipedInputStream pis) throws Exception{
public NPDMProvider(File file) throws Exception {
this(file, 0);
}
public NPDMProvider(File file, long offset) throws Exception {
this(new InFileStreamProducer(file, offset));
}
public NPDMProvider(InFileStreamProducer producer) throws Exception{
try (BufferedInputStream stream = producer.produce()) {
byte[] mainBuf = new byte[0x80];
if(pis.read(mainBuf) != 0x80)
if (stream.read(mainBuf) != 0x80)
throw new Exception("NPDMProvider: Failed to read 'META'");
aci0offset = getLEint(mainBuf, 0x70);
aci0size = getLEint(mainBuf, 0x74);
acidOffset = getLEint(mainBuf, 0x78);
acidSize = getLEint(mainBuf, 0x7C);
byte[] aci0Buf;
byte[] acidBuf;
if (aci0offset < acidOffset) {
if (pis.skip(aci0offset - 0x80) != (aci0offset - 0x80))
throw new Exception("NPDMProvider: Failed to skip bytes till 'ACI0'");
if ((aci0Buf = readFromStream(pis, aci0size)) == null)
throw new Exception("NPDMProvider: Failed to read 'ACI0'");
if (pis.skip(acidOffset - aci0offset - aci0size) != (acidOffset - aci0offset - aci0size))
throw new Exception("NPDMProvider: Failed to skip bytes till 'ACID'");
if ((acidBuf = readFromStream(pis, acidSize)) == null)
throw new Exception("NPDMProvider: Failed to read 'ACID'");
}
else {
if (pis.skip(acidOffset - 0x80) != (acidOffset - 0x80))
throw new Exception("NPDMProvider: Failed to skip bytes till 'ACID'");
if ((acidBuf = readFromStream(pis, acidSize)) == null)
throw new Exception("NPDMProvider: Failed to read 'ACID'");
if (pis.skip(aci0offset - acidOffset - acidSize) != (aci0offset - acidOffset - acidSize))
throw new Exception("NPDMProvider: Failed to skip bytes till 'ACI0'");
if ((aci0Buf = readFromStream(pis, aci0size)) == null)
throw new Exception("NPDMProvider: Failed to read 'ACI0'");
calculateACI0(stream, aci0offset - 0x80);
calculateACID(stream, acidOffset - aci0offset - aci0size);
} else {
calculateACID(stream, acidOffset - 0x80);
calculateACI0(stream, aci0offset - acidOffset - acidSize);
}
magicNum = new String(mainBuf, 0, 4, StandardCharsets.UTF_8);
reserved1 = Arrays.copyOfRange(mainBuf, 0x4, 0xC);
@ -96,52 +88,24 @@ public class NPDMProvider extends ASuperInFileProvider {
titleName = new String(mainBuf, 0x20, 0x10, StandardCharsets.UTF_8);
productCode = Arrays.copyOfRange(mainBuf, 0x30, 0x40);
reserved4 = Arrays.copyOfRange(mainBuf, 0x40, 0x70);
aci0 = new ACI0Provider(aci0Buf);
acid = new ACIDProvider(acidBuf);
}
}
public NPDMProvider(File file) throws Exception { this(file, 0); }
public NPDMProvider(File file, long offset) throws Exception {
if (file.length() - offset < 0x80) // Header's size
throw new Exception("NPDMProvider: File is too small.");
RandomAccessFile raf = new RandomAccessFile(file, "r");
raf.seek(offset);
// Get META
byte[] metaBuf = new byte[0x80];
if (raf.read(metaBuf) != 0x80)
throw new Exception("NPDMProvider: Failed to read 'META'");
magicNum = new String(metaBuf, 0, 4, StandardCharsets.UTF_8);
reserved1 = Arrays.copyOfRange(metaBuf, 0x4, 0xC);
MMUFlags = metaBuf[0xC];
reserved2 = metaBuf[0xD];
mainThreadPrio = metaBuf[0xE];
mainThreadCoreNum = metaBuf[0xF];
reserved3 = Arrays.copyOfRange(metaBuf, 0x10, 0x14);
personalMmHeapSize = getLEint(metaBuf, 0x14);
version = getLEint(metaBuf, 0x18);
mainThreadStackSize = getLElongOfInt(metaBuf, 0x1C);
titleName = new String(metaBuf, 0x20, 0x10, StandardCharsets.UTF_8);
productCode = Arrays.copyOfRange(metaBuf, 0x30, 0x40);
reserved4 = Arrays.copyOfRange(metaBuf, 0x40, 0x70);
aci0offset = getLEint(metaBuf, 0x70);
aci0size = getLEint(metaBuf, 0x74);
acidOffset = getLEint(metaBuf, 0x78);
acidSize = getLEint(metaBuf, 0x7C);
// Get ACI0
raf.seek(aci0offset);
metaBuf = new byte[aci0size]; // TODO: NOTE: we read all size but it's memory consuming
if (raf.read(metaBuf) != aci0size)
throw new Exception("NPDMProvider: Failed to read 'ACI0'");
aci0 = new ACI0Provider(metaBuf);
// Get ACID
raf.seek(acidOffset);
metaBuf = new byte[acidSize]; // TODO: NOTE: we read all size but it's memory consuming
if (raf.read(metaBuf) != acidSize)
private void calculateACID(BufferedInputStream stream, int toSkip) throws Exception{
byte[] acidBuf = new byte[acidSize];
if (stream.skip(toSkip) != toSkip)
throw new Exception("NPDMProvider: Failed to skip bytes till 'ACID'");
if (acidSize != stream.read(acidBuf))
throw new Exception("NPDMProvider: Failed to read 'ACID'");
acid = new ACIDProvider(metaBuf);
raf.close();
acid = new ACIDProvider(acidBuf);
}
private void calculateACI0(BufferedInputStream stream, int toSkip) throws Exception{
byte[] aci0Buf = new byte[aci0size];
if (stream.skip(toSkip) != toSkip)
throw new Exception("NPDMProvider: Failed to skip bytes till 'ACI0'");
if (aci0size != stream.read(aci0Buf))
throw new Exception("NPDMProvider: Failed to read 'ACI0'");
aci0 = new ACI0Provider(aci0Buf);
}
public String getMagicNum() { return magicNum; }

View file

@ -23,7 +23,7 @@ import java.util.LinkedHashMap;
public class ServiceAccessControlProvider {
private LinkedHashMap<String, Byte> collection;
private final LinkedHashMap<String, Byte> collection;
public ServiceAccessControlProvider(byte[] bytes){
collection = new LinkedHashMap<>();

View file

@ -1,43 +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.Tools.ISuperProvider;
import libKonogonka.ctraes.InFileStreamProducer;
import java.io.FileNotFoundException;
import java.util.LinkedList;
public interface IPFS0Provider extends ISuperProvider {
boolean isEncrypted();
String getMagic();
int getFilesCount();
int getStringTableSize();
byte[] getPadding();
PFS0subFile[] getPfs0subFiles();
void printDebug();
InFileStreamProducer getStreamProducer(String subFileName) throws FileNotFoundException;
InFileStreamProducer getStreamProducer(int subFileNumber);
LinkedList<byte[]> getPfs0SHA256hashes();
}

View file

@ -0,0 +1,125 @@
/*
Copyright 2018-2022 Dmitry Isaenko
This file is part of libKonogonka.
libKonogonka is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libKonogonka is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with libKonogonka. If not, see <https://www.gnu.org/licenses/>.
*/
package libKonogonka.Tools.PFS0;
import libKonogonka.Converter;
import libKonogonka.RainbowDump;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.BufferedInputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static libKonogonka.Converter.getLEint;
import static libKonogonka.Converter.getLElong;
public class PFS0Header {
private final static Logger log = LogManager.getLogger(PFS0Header.class);
private final String magic;
private final int filesCount;
private final int stringTableSize;
private final byte[] padding;
private final PFS0subFile[] pfs0subFiles;
public PFS0Header(BufferedInputStream stream) throws Exception{
byte[] fileStartingBytes = new byte[0x10];
if (0x10 != stream.read(fileStartingBytes))
throw new Exception("Reading stream suddenly ended while trying to read starting 0x10 bytes");
// Check PFS0Provider
magic = new String(fileStartingBytes, 0x0, 0x4, StandardCharsets.US_ASCII);
if (! magic.equals("PFS0")){
throw new Exception("Bad magic");
}
// Get files count
filesCount = getLEint(fileStartingBytes, 0x4);
if (filesCount <= 0 ) {
throw new Exception("Files count is too small");
}
// Get string table
stringTableSize = getLEint(fileStartingBytes, 0x8);
if (stringTableSize <= 0 ){
throw new Exception("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++){
if (0x18 != stream.read(fileEntryTable))
throw new Exception("Reading stream suddenly ended while trying to read File Entry Table #"+i);
offsetsSubFiles[i] = getLElong(fileEntryTable, 0);
sizesSubFiles[i] = getLElong(fileEntryTable, 0x8);
strTableOffsets[i] = getLEint(fileEntryTable, 0x10);
zeroBytes[i] = Arrays.copyOfRange(fileEntryTable, 0x14, 0x18);
}
//*******************************************************************
// Here pointer is in front of String table
String[] subFileNames = new String[filesCount];
byte[] stringTbl = new byte[stringTableSize];
if (stream.read(stringTbl) != stringTableSize){
throw new Exception("Read PFS0Provider String table failure. Can't read requested string table size ("+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]);
}
}
public String getMagic() {return magic;}
public int getFilesCount() {return filesCount;}
public int getStringTableSize() {return stringTableSize;}
public byte[] getPadding() {return padding;}
public PFS0subFile[] getPfs0subFiles() {return pfs0subFiles;}
public void printDebug(){
log.debug(".:: PFS0Header ::.\n" +
"Magic " + magic + "\n" +
"Files count " + RainbowDump.formatDecHexString(filesCount) + "\n" +
"String Table Size " + RainbowDump.formatDecHexString(stringTableSize) + "\n" +
"Padding " + Converter.byteArrToHexString(padding) + "\n\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" +
"----------------------------------------------------------------"
);
}
}
}

View file

@ -18,192 +18,61 @@
*/
package libKonogonka.Tools.PFS0;
import libKonogonka.Converter;
import libKonogonka.RainbowDump;
import libKonogonka.Tools.ISuperProvider;
import libKonogonka.Tools.NCA.NCASectionTableBlock.SuperBlockPFS0;
import libKonogonka.ctraes.AesCtrBufferedInputStream;
import libKonogonka.ctraes.AesCtrDecryptSimple;
import libKonogonka.ctraes.InFileStreamProducer;
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.nio.file.Paths;
import java.util.Arrays;
import java.util.LinkedList;
import static libKonogonka.Converter.*;
public class PFS0Provider implements IPFS0Provider{
public class PFS0Provider implements ISuperProvider {
private final static Logger log = LogManager.getLogger(PFS0Provider.class);
private String magic;
private int filesCount;
private int stringTableSize;
private byte[] padding;
private PFS0subFile[] pfs0subFiles;
//---------------------------------------
private long rawBlockDataStart;
private final long rawBlockDataStart;
private final File file;
private long offsetPositionInFile;
private long mediaStartOffset; // In 512-blocks
private long mediaEndOffset; // In 512-blocks
private long ncaOffset;
private final InFileStreamProducer producer;
private BufferedInputStream stream;
private SuperBlockPFS0 superBlockPFS0;
private AesCtrDecryptSimple decryptor;
private long mediaStartOffset;
private final PFS0Header header;
private LinkedList<byte[]> pfs0SHA256hashes;
private boolean encrypted;
public PFS0Provider(File nspFile) throws Exception{
this.file = nspFile;
createBufferedInputStream();
readPfs0Header();
this.producer = new InFileStreamProducer(nspFile);
this.stream = producer.produce();
this.header = new PFS0Header(stream);
this.rawBlockDataStart = 0x10L + (header.getFilesCount() * 0x18L) + header.getStringTableSize();
}
public PFS0Provider(File file,
long ncaOffset,
public PFS0Provider(InFileStreamProducer producer,
long offsetPositionInFile,
SuperBlockPFS0 superBlockPFS0,
long mediaStartOffset,
long mediaEndOffset) throws Exception{
this.file = file;
this.ncaOffset = ncaOffset;
long mediaStartOffset) throws Exception {
this.producer = producer;
this.offsetPositionInFile = offsetPositionInFile;
this.superBlockPFS0 = superBlockPFS0;
this.offsetPositionInFile = ncaOffset + mediaStartOffset * 0x200;
this.mediaStartOffset = mediaStartOffset;
this.mediaEndOffset = mediaEndOffset;
this.rawBlockDataStart = superBlockPFS0.getPfs0offset();
//bufferedInputStream = new BufferedInputStream(Files.newInputStream(fileWithPfs0.toPath()));
createBufferedInputStream();
this.stream = producer.produce();
long toSkip = offsetPositionInFile + superBlockPFS0.getHashTableOffset();
if (toSkip != stream.skip(toSkip))
throw new Exception("Can't skip bytes prior Hash Table offset");
collectHashes();
createBufferedInputStream();
this.stream = producer.produce();
toSkip = offsetPositionInFile + superBlockPFS0.getPfs0offset();
if (toSkip != stream.skip(toSkip))
throw new Exception("Can't skip bytes prior PFS0 offset");
readPfs0Header();
}
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;
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];
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("Bad magic");
}
// Get files count
filesCount = getLEint(fileStartingBytes, 0x4);
if (filesCount <= 0 ) {
throw new Exception("Files count is too small");
}
// Get string table
stringTableSize = getLEint(fileStartingBytes, 0x8);
if (stringTableSize <= 0 ){
throw new Exception("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++){
if (0x18 != stream.read(fileEntryTable))
throw new Exception("Reading stream suddenly ended while trying to read File Entry Table #"+i);
offsetsSubFiles[i] = getLElong(fileEntryTable, 0);
sizesSubFiles[i] = getLElong(fileEntryTable, 0x8);
strTableOffsets[i] = getLEint(fileEntryTable, 0x10);
zeroBytes[i] = Arrays.copyOfRange(fileEntryTable, 0x14, 0x18);
rawBlockDataStart += 0x18;
}
//*******************************************************************
// In here pointer in front of String table
String[] subFileNames = new String[filesCount];
byte[] stringTbl = new byte[stringTableSize];
if (stream.read(stringTbl) != stringTableSize){
throw new Exception("Read PFS0Provider String table failure. Can't read requested string table size ("+stringTableSize+")");
}
// 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]);
}
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()));
this.header = new PFS0Header(stream);
this.rawBlockDataStart = superBlockPFS0.getPfs0offset() + 0x10L + (header.getFilesCount() * 0x18L) + header.getStringTableSize();
}
private void collectHashes() throws Exception{
@ -223,25 +92,14 @@ public class PFS0Provider implements IPFS0Provider{
}
}
@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; }
public boolean isEncrypted() { return producer.isEncrypted(); }
public PFS0Header getHeader() {return header;}
@Override
public long getRawFileDataStart() { return rawBlockDataStart;}
@Override
public PFS0subFile[] getPfs0subFiles() { return pfs0subFiles; }
@Override
public File getFile(){ return file; }
@Override
public boolean exportContent(String saveToLocation, String subFileName){
PFS0subFile[] pfs0subFiles = header.getPfs0subFiles();
for (int i = 0; i < pfs0subFiles.length; i++){
if (pfs0subFiles[i].getName().equals(subFileName))
return exportContent(saveToLocation, i);
@ -250,16 +108,14 @@ public class PFS0Provider implements IPFS0Provider{
}
@Override
public boolean exportContent(String saveToLocation, int subFileNumber){
PFS0subFile subFile = pfs0subFiles[subFileNumber];
PFS0subFile subFile = header.getPfs0subFiles()[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();
this.stream = producer.produce();
long subFileSize = subFile.getSize();
@ -297,6 +153,7 @@ public class PFS0Provider implements IPFS0Provider{
@Override
public InFileStreamProducer getStreamProducer(String subFileName) throws FileNotFoundException {
PFS0subFile[] pfs0subFiles = header.getPfs0subFiles();
for (int i = 0; i < pfs0subFiles.length; i++) {
if (pfs0subFiles[i].getName().equals(subFileName))
return getStreamProducer(i);
@ -305,110 +162,28 @@ public class PFS0Provider implements IPFS0Provider{
}
@Override
public InFileStreamProducer getStreamProducer(int subFileNumber) {
PFS0subFile subFile = pfs0subFiles[subFileNumber];
PFS0subFile subFile = header.getPfs0subFiles()[subFileNumber];
long subFileOffset = subFile.getOffset() + mediaStartOffset * 0x200 + rawBlockDataStart;
if (encrypted) {
return new InFileStreamProducer(file,
pfs0subFiles[subFileNumber].getSize(),
ncaOffset,
subFileOffset,
decryptor,
mediaStartOffset,
mediaEndOffset);
return producer.getSuccessor(subFileOffset);
}
return new InFileStreamProducer(file, offsetPositionInFile, subFileOffset);
}
/**
* @deprecated
* */
@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);
}
throw new Exception("No file with such name exists: "+subFileName);
}
/**
* @deprecated
* */
@Override
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception {
PipedOutputStream streamOut = new PipedOutputStream();
PipedInputStream streamInp = new PipedInputStream(streamOut);
Thread workerThread = new Thread(() -> {
try {
PFS0subFile subFile = pfs0subFiles[subFileNumber];
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);
streamOut.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(e);
}
});
workerThread.start();
return streamInp;
}
public LinkedList<byte[]> getPfs0SHA256hashes() {
return pfs0SHA256hashes;
}
@Override
public File getFile() {
return producer.getFile();
}
public void printDebug(){
log.debug(".:: PFS0Provider ::.\n" +
"File name: " + file.getName() + "\n" +
"File name: " + getFile().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" +
"----------------------------------------------------------------"
"Media Start Offset " + RainbowDump.formatDecHexString(mediaStartOffset) + "\n"
);
}
header.printDebug();
}
}

View file

@ -1,32 +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.RomFs;
import java.io.File;
import java.io.PipedInputStream;
public interface IRomFsProvider {
File getFile();
long getLevel6Offset();
Level6Header getHeader();
FileSystemEntry getRootEntry();
PipedInputStream getContent(FileSystemEntry entry) throws Exception;
void printDebug();
}

View file

@ -19,16 +19,11 @@
package libKonogonka.Tools.RomFs;
import libKonogonka.Converter;
import libKonogonka.ctraes.AesCtrBufferedInputStream;
import libKonogonka.ctraes.AesCtrDecryptSimple;
import libKonogonka.ctraes.InFileStreamProducer;
import java.io.BufferedInputStream;
import java.io.File;
import java.nio.file.Files;
public class RomFsConstruct {
//private final static Logger log = LogManager.getLogger(RomFsConstruct.class);
private Level6Header header;
private FileSystemEntry rootEntry;
@ -37,55 +32,29 @@ public class RomFsConstruct {
private byte[] directoryMetadataTable;
private byte[] fileMetadataTable;
private final File file;
private long offsetPositionInFile;
private final long offsetPositionInFile;
private final long level6Offset;
RomFsConstruct(File file,
long level6Offset) throws Exception{
if (level6Offset < 0)
throw new Exception("Incorrect Level 6 Offset");
this.file = file;
this.level6Offset = level6Offset;
this.stream = new BufferedInputStream(Files.newInputStream(file.toPath()));
constructEverything();
RomFsConstruct(InFileStreamProducer producer, long level6Offset) throws Exception{
this(producer, level6Offset, 0);
}
RomFsConstruct(File file,
long ncaOffset, // NCA offset position
RomFsConstruct(InFileStreamProducer producer,
long level6Offset,
AesCtrDecryptSimple decryptor,
long mediaStartOffset,
long mediaEndOffset
) throws Exception {
long offsetPositionInFile) throws Exception{
if (level6Offset < 0)
throw new Exception("Incorrect Level 6 Offset");
this.file = file;
this.level6Offset = level6Offset;
this.offsetPositionInFile = ncaOffset + (mediaStartOffset * 0x200);
this.stream = new AesCtrBufferedInputStream(
decryptor,
ncaOffset,
mediaStartOffset,
mediaEndOffset,
Files.newInputStream(file.toPath()));
constructEverything();
}
this.stream = producer.produce();
this.offsetPositionInFile = offsetPositionInFile;
private void constructEverything() throws Exception{
goToStartingPosition();
constructHeader();
directoryMetadataTableLengthCheck();
directoryMetadataTableConstruct();
fileMetadataTableLengthCheck();
fileMetadataTableConstruct();
constructRootFilesystemEntry();
stream.close();
}
@ -137,13 +106,8 @@ public class RomFsConstruct {
}
private void constructRootFilesystemEntry() throws Exception{
try {
rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable);
}
catch (Exception e){
throw new Exception("File: " + file.getName(), e);
}
}
private void skipBytes(long size) throws Exception{
long mustSkip = size;

View file

@ -1,125 +0,0 @@
/*
Copyright 2018-2022 Dmitry Isaenko
This file is part of libKonogonka.
libKonogonka is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libKonogonka is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with libKonogonka. If not, see <https://www.gnu.org/licenses/>.
*/
package libKonogonka.Tools.RomFs;
import libKonogonka.ctraes.AesCtrBufferedInputStream;
import libKonogonka.ctraes.AesCtrDecryptSimple;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.*;
import java.nio.file.Files;
public class RomFsContentRetrieve implements Runnable{
private final static Logger log = LogManager.getLogger(RomFsContentRetrieve.class);
private final PipedOutputStream streamOut;
private final long internalFileSize;
private final long startPosition;
private final BufferedInputStream bis;
RomFsContentRetrieve(File parentFile,
PipedOutputStream streamOut,
long internalFileRealPosition,
long internalFileSize) throws Exception{
this.streamOut = streamOut;
this.internalFileSize = internalFileSize;
this.startPosition = internalFileRealPosition;
this.bis = new BufferedInputStream(Files.newInputStream(parentFile.toPath()));
}
RomFsContentRetrieve(File parentFile,
PipedOutputStream streamOut,
AesCtrDecryptSimple decryptor,
long entryOffset,
long internalFileSize,
long headersFileDataOffset, //level6Header.getFileDataOffset()
long level6Offset,
long ncaOffsetPosition,
long mediaStartOffset,
long mediaEndOffset
) throws Exception{
log.fatal("Current implementation works incorrectly");
this.streamOut = streamOut;
this.internalFileSize = internalFileSize;
this.startPosition = entryOffset + mediaStartOffset*0x200 + headersFileDataOffset + level6Offset;
this.bis = new AesCtrBufferedInputStream(
decryptor,
ncaOffsetPosition,
mediaStartOffset,
mediaEndOffset,
Files.newInputStream(parentFile.toPath())
);
}
@Override
public void run() {
log.trace("Executing thread");
try {
skipBytesTillBegining();
int readPiece = 8388608;
long readFrom = 0;
byte[] readBuffer;
while (readFrom < internalFileSize) {
if (internalFileSize - readFrom < readPiece)
readPiece = Math.toIntExact(internalFileSize - readFrom);
readBuffer = new byte[readPiece];
if (bis.read(readBuffer) != readPiece) {
log.error("getContent(): Unable to read requested size from file.");
return;
}
streamOut.write(readBuffer);
readFrom += readPiece;
}
} catch (Exception exception) {
log.error("RomFsProvider -> getContent(): Unable to provide stream", exception);
}
finally {
closeStreams();
}
log.trace("Thread died");
}
private void skipBytesTillBegining() throws Exception{
long mustSkip = startPosition;
long skipped = 0;
while (mustSkip > 0){
skipped += bis.skip(mustSkip);
mustSkip = startPosition - skipped;
}
}
private void closeStreams(){
try {
streamOut.close();
}
catch (IOException e){
log.error("RomFsProvider -> getContent(): Unable to close 'StreamOut'");
}
try {
bis.close();
}
catch (IOException e){
log.error("RomFsProvider -> getContent(): Unable to close 'StreamOut'");
}
}
}

View file

@ -18,134 +18,130 @@
*/
package libKonogonka.Tools.RomFs;
import libKonogonka.Tools.PFS0.PFS0subFile;
import libKonogonka.Tools.RomFs.view.DirectoryMetaTablePlainView;
import libKonogonka.Tools.RomFs.view.FileMetaTablePlainView;
import libKonogonka.ctraes.AesCtrDecryptSimple;
import libKonogonka.ctraes.InFileStreamProducer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.*;
import java.nio.file.Files;
public class RomFsProvider implements IRomFsProvider{
private final File file;
public class RomFsProvider{
private final static Logger log = LogManager.getLogger(RomFsProvider.class);
private final InFileStreamProducer producer;
private final long level6Offset;
private final Level6Header level6Header;
private final FileSystemEntry rootEntry;
private long ncaOffsetPosition;
private byte[] key; // Used @ createDecryptor only
private byte[] sectionCTR; // Used @ createDecryptor only
private long mediaStartOffset; // Used @ createDecryptor only
private long mediaEndOffset;
private long mediaStartOffset;
// Used only for debug
private final byte[] directoryMetadataTable;
private final byte[] fileMetadataTable;
private final boolean encryptedAesCtr;
public RomFsProvider(File decryptedFsImageFile, long level6offset) throws Exception{
RomFsConstruct construct = new RomFsConstruct(decryptedFsImageFile, level6offset);
this.file = decryptedFsImageFile;
this.producer = new InFileStreamProducer(decryptedFsImageFile);
RomFsConstruct construct = new RomFsConstruct(producer, level6offset);
this.level6Offset = level6offset;
this.level6Header = construct.getHeader();
this.rootEntry = construct.getRootEntry();
this.directoryMetadataTable = construct.getDirectoryMetadataTable();
this.fileMetadataTable = construct.getFileMetadataTable();
this.encryptedAesCtr = false;
}
public RomFsProvider(long level6Offset,
File encryptedFsImageFile,
long ncaOffsetPosition,
byte[] key,
byte[] sectionCTR,
long mediaStartOffset,
long mediaEndOffset
public RomFsProvider(InFileStreamProducer producer,
long level6Offset,
long offsetPositionInFile,
long mediaStartOffset
) throws Exception{
this.key = key;
this.sectionCTR = sectionCTR;
this.producer = producer;
this.mediaStartOffset = mediaStartOffset;
this.mediaEndOffset = mediaEndOffset;
this.ncaOffsetPosition = ncaOffsetPosition;
RomFsConstruct construct = new RomFsConstruct(encryptedFsImageFile,
ncaOffsetPosition,
level6Offset,
createDecryptor(),
mediaStartOffset,
mediaEndOffset);
this.file = encryptedFsImageFile;
RomFsConstruct construct = new RomFsConstruct(producer, level6Offset, offsetPositionInFile);
this.level6Offset = level6Offset;
this.level6Header = construct.getHeader();
this.rootEntry = construct.getRootEntry();
this.directoryMetadataTable = construct.getDirectoryMetadataTable();
this.fileMetadataTable = construct.getFileMetadataTable();
this.encryptedAesCtr = true;
}
private AesCtrDecryptSimple createDecryptor() throws Exception{
return new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
}
@Override
public File getFile() { return file; }
@Override
public long getLevel6Offset() { return level6Offset; }
@Override
public Level6Header getHeader() {return level6Header;}
@Override
public FileSystemEntry getRootEntry() { return rootEntry; }
@Override
public PipedInputStream getContent(FileSystemEntry entry) throws Exception {
if (encryptedAesCtr)
return getContentAesCtrEncrypted(entry);
return getContentNonEncrypted(entry);
}
public PipedInputStream getContentAesCtrEncrypted(FileSystemEntry entry) throws Exception{
public boolean exportContent(String saveToLocation, FileSystemEntry entry){
try{
if (! saveToLocation.endsWith(File.separator))
saveToLocation += File.separator;
if (entry.isDirectory())
throw new Exception("Request of the binary stream for the folder entry is not supported (and doesn't make sense).");
PipedOutputStream streamOut = new PipedOutputStream();
PipedInputStream streamIn = new PipedInputStream(streamOut);
long internalFileOffset = entry.getOffset();
long internalFileSize = entry.getSize();
Thread contentRetrievingThread = new Thread(new RomFsContentRetrieve(
file,
streamOut,
createDecryptor(),
internalFileOffset,
internalFileSize,
level6Header.getFileDataOffset(),
level6Offset,
ncaOffsetPosition,
mediaStartOffset,
mediaEndOffset));
contentRetrievingThread.start();
return streamIn;
exportFolderContent(entry, saveToLocation);
else
exportSingleFile(entry, saveToLocation);
}
public PipedInputStream getContentNonEncrypted(FileSystemEntry entry) throws Exception{
catch (Exception e){
log.error("File export failure", e);
return false;
}
return true;
}
private void exportFolderContent(FileSystemEntry entry, String saveToLocation) throws Exception{
File contentFile = new File(saveToLocation + entry.getName());
contentFile.mkdirs();
String currentDirPath = saveToLocation + entry.getName() + File.separator;
for (FileSystemEntry fileEntry : entry.getContent()){
if (fileEntry.isDirectory())
exportFolderContent(fileEntry, currentDirPath);
else
exportSingleFile(fileEntry, currentDirPath);
}
}
private void exportSingleFile(FileSystemEntry entry, String saveToLocation) throws Exception {
File contentFile = new File(saveToLocation + entry.getName());
try(BufferedOutputStream extractedFileBOS = new BufferedOutputStream(Files.newOutputStream(contentFile.toPath()));
BufferedInputStream stream = producer.produce()) {
long skipBytes = entry.getOffset() + mediaStartOffset * 0x200 + level6Header.getFileDataOffset() + level6Offset;
if (skipBytes != stream.skip(skipBytes))
throw new Exception("Can't skip");
int blockSize = 0x200;
if (entry.getSize() < 0x200)
blockSize = (int) entry.getSize();
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) >= entry.getSize()) {
blockSize = (int) (entry.getSize() - i);
if (blockSize == 0)
break;
block = new byte[blockSize];
}
}
}
}
public InFileStreamProducer getStreamProducer(FileSystemEntry entry) throws Exception{
if (entry.isDirectory())
throw new Exception("Request of the binary stream for the folder entry is not supported (and doesn't make sense).");
PipedOutputStream streamOut = new PipedOutputStream();
PipedInputStream streamIn = new PipedInputStream(streamOut);
long internalFileRealPosition = level6Offset + level6Header.getFileDataOffset() + entry.getOffset();
long internalFileSize = entry.getSize();
Thread contentRetrievingThread = new Thread(new RomFsContentRetrieve(
file,
streamOut,
internalFileRealPosition,
internalFileSize));
contentRetrievingThread.start();
return streamIn;
throw new Exception("Directory entries are not supported");
return producer.getSuccessor(
entry.getOffset() + mediaStartOffset * 0x200 + level6Header.getFileDataOffset() + level6Offset);
}
public File getFile(){
return producer.getFile();
}
@Override
public void printDebug(){
level6Header.printDebugInfo();
new DirectoryMetaTablePlainView(level6Header.getDirectoryMetadataTableLength(), directoryMetadataTable);

View file

@ -19,12 +19,12 @@
package libKonogonka.Tools.XCI;
public class HFS0File {
private String name;
private long offset;
private long size;
private long hashedRegionSize;
private boolean padding;
private byte[] SHA256Hash;
private final String name;
private final long offset;
private final long size;
private final long hashedRegionSize;
private final boolean padding;
private final byte[] SHA256Hash;
public HFS0File(String name, long offset, long size, long hashedRegionSize, boolean padding, byte[] SHA256Hash){
this.name = name;

View file

@ -19,12 +19,14 @@
package libKonogonka.Tools.XCI;
import libKonogonka.Tools.ISuperProvider;
import libKonogonka.ctraes.InFileStreamProducer;
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.nio.file.Paths;
import java.util.Arrays;
import static libKonogonka.Converter.*;
@ -125,74 +127,71 @@ public class HFS0Provider implements ISuperProvider {
@Override
public long getRawFileDataStart() { return rawFileDataStart; }
public HFS0File[] getHfs0Files() { return hfs0Files; }
@Override
public File getFile(){ return file; }
/**
* @deprecated
* */
@Override
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception{
PipedOutputStream streamOut = new PipedOutputStream();
Thread workerThread;
if (subFileNumber >= hfs0Files.length) {
throw new Exception("HFS0Provider -> getHfs0FilePipedInpStream(): Requested sub file doesn't exists");
}
PipedInputStream streamIn = new PipedInputStream(streamOut);
workerThread = new Thread(() -> {
log.trace("HFS0Provider -> getHfs0FilePipedInpStream(): Executing thread");
try{
long subFileRealPosition = rawFileDataStart + hfs0Files[subFileNumber].getOffset();
BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(file.toPath()));
if (bis.skip(subFileRealPosition) != subFileRealPosition)
throw new Exception("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to skip requested offset");
int readPice = 0x800000; // 8mb NOTE: consider switching to 1mb 1048576
long readFrom = 0;
long realFileSize = hfs0Files[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)
throw new Exception("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to read requested size from file.");
streamOut.write(readBuf, 0, readPice);
readFrom += readPice;
}
bis.close();
streamOut.close();
}
catch (Exception ioe){
log.error("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to provide stream");
ioe.printStackTrace();
}
log.trace("HFS0Provider -> getHfs0FilePipedInpStream(): Thread died");
});
workerThread.start();
return streamIn;
}
//TODO
@Override
public boolean exportContent(String saveToLocation, String subFileName) throws Exception {
throw new Exception("Not implemented yet");
for (int i = 0; i < hfs0Files.length; i++) {
if (hfs0Files[i].getName().equals(subFileName))
return exportContent(saveToLocation, i);
}
//TODO
@Override
public boolean exportContent(String saveToLocation, int subFileNumber) throws Exception {
throw new Exception("Not implemented yet");
throw new FileNotFoundException("No file with such name exists: " + subFileName);
}
@Override
public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception {
public boolean exportContent(String saveToLocation, int subFileNumber) throws Exception {
HFS0File subFile = hfs0Files[subFileNumber];
File location = new File(saveToLocation);
location.mkdirs();
try (BufferedOutputStream extractedFileBOS = new BufferedOutputStream(
Files.newOutputStream(Paths.get(saveToLocation+File.separator+subFile.getName())));
BufferedInputStream stream = getStreamProducer(subFileNumber).produce()){
long subFileSize = subFile.getSize();
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;
}
@Override
public InFileStreamProducer getStreamProducer(String subFileName) throws FileNotFoundException{
for (int i = 0; i < hfs0Files.length; i++){
if (hfs0Files[i].getName().equals(subFileName))
return getProviderSubFilePipedInpStream(i);
return getStreamProducer(i);
}
return null;
throw new FileNotFoundException("No file with such name exists: "+subFileName);
}
@Override
public InFileStreamProducer getStreamProducer(int subFileNumber) {
long offset = rawFileDataStart + hfs0Files[subFileNumber].getOffset();
return new InFileStreamProducer(file, offset);
}
@Override
public File getFile() {
return file;
}
}

View file

@ -18,9 +18,9 @@
*/
package libKonogonka.Tools.XCI;
import libKonogonka.Converter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static libKonogonka.Converter.getLEint;
import static libKonogonka.Converter.getLElong;
@ -28,27 +28,27 @@ import static libKonogonka.Converter.getLElong;
* Header information
* */
public class XCIGamecardHeader{
private byte[] rsa2048PKCS1sig;
private boolean magicHead;
private byte[] SecureAreaStartAddr;
private boolean bkupAreaStartAddr;
private byte titleKEKIndexBoth;
private byte titleKEKIndex;
private byte KEKIndex;
private byte gcSize;
private byte gcVersion;
private byte gcFlags;
private byte[] pkgID;
private long valDataEndAddr;
private byte[] gcInfoIV;
private long hfs0partOffset;
private long hfs0headerSize;
private byte[] hfs0headerSHA256;
private byte[] hfs0initDataSHA256 ;
private int secureModeFlag;
private int titleKeyFlag;
private int keyFlag;
private byte[] normAreaEndAddr;
private final byte[] rsa2048PKCS1sig;
private final boolean magicHead;
private final byte[] SecureAreaStartAddr;
private final boolean bkupAreaStartAddr;
private final byte titleKEKIndexBoth;
private final byte titleKEKIndex;
private final byte KEKIndex;
private final byte gcSize;
private final byte gcVersion;
private final byte gcFlags;
private final byte[] pkgID;
private final long valDataEndAddr;
private final byte[] gcInfoIV;
private final long hfs0partOffset;
private final long hfs0headerSize;
private final byte[] hfs0headerSHA256;
private final byte[] hfs0initDataSHA256;
private final int secureModeFlag;
private final int titleKeyFlag;
private final int keyFlag;
private final byte[] normAreaEndAddress;
XCIGamecardHeader(byte[] headerBytes) throws Exception{
if (headerBytes.length != 400)
@ -65,7 +65,7 @@ public class XCIGamecardHeader{
gcFlags = headerBytes[271];
pkgID = Arrays.copyOfRange(headerBytes, 272, 280);
valDataEndAddr = getLElong(headerBytes, 280); //TODO: FIX/simplify //
gcInfoIV = reverseBytes(Arrays.copyOfRange(headerBytes, 288, 304));
gcInfoIV = Converter.flip(Arrays.copyOfRange(headerBytes, 288, 304));
hfs0partOffset = getLElong(headerBytes, 304);
hfs0headerSize = getLElong(headerBytes, 312);
hfs0headerSHA256 = Arrays.copyOfRange(headerBytes, 320, 352);
@ -73,7 +73,7 @@ public class XCIGamecardHeader{
secureModeFlag = getLEint(headerBytes, 384);
titleKeyFlag = getLEint(headerBytes, 388);
keyFlag = getLEint(headerBytes, 392);
normAreaEndAddr = Arrays.copyOfRange(headerBytes, 396, 400);
normAreaEndAddress = Arrays.copyOfRange(headerBytes, 396, 400);
}
public byte[] getRsa2048PKCS1sig() { return rsa2048PKCS1sig; }
@ -83,67 +83,21 @@ public class XCIGamecardHeader{
public byte getTitleKEKIndexBoth() { return titleKEKIndexBoth; }
public byte getTitleKEKIndex() { return titleKEKIndex; }
public byte getKEKIndex() { return KEKIndex; }
public byte getGcSize() {
return gcSize;
}
public byte getGcVersion() {
return gcVersion;
}
public byte getGcFlags() {
return gcFlags;
}
public byte[] getPkgID() {
return pkgID;
}
public long getValDataEndAddr() {
return valDataEndAddr;
}
public byte[] getGcInfoIV() {
return gcInfoIV;
}
public long getHfs0partOffset() {
return hfs0partOffset;
}
public long getHfs0headerSize() {
return hfs0headerSize;
}
public byte[] getHfs0headerSHA256() {
return hfs0headerSHA256;
}
public byte[] getHfs0initDataSHA256() {
return hfs0initDataSHA256;
}
public int getSecureModeFlag() {
return secureModeFlag;
}
public boolean isSecureModeFlagOk(){
return secureModeFlag == 1;
}
public int getTitleKeyFlag() {
return titleKeyFlag;
}
public boolean istitleKeyFlagOk(){
return titleKeyFlag == 2;
}
public int getKeyFlag() {
return keyFlag;
}
public boolean iskeyFlagOk(){
return keyFlag == 0;
}
public byte[] getNormAreaEndAddr() {
return normAreaEndAddr;
}
private byte[] reverseBytes(byte[] bArr){
Byte[] objArr = new Byte[bArr.length];
for (int i=0;i < bArr.length; i++)
objArr[i] = bArr[i];
List<Byte> bytesList = Arrays.asList(objArr);
Collections.reverse(bytesList);
for (int i=0;i < bArr.length; i++)
bArr[i] = objArr[i];
return bArr;
}
public byte getGcSize() { return gcSize; }
public byte getGcVersion() { return gcVersion; }
public byte getGcFlags() { return gcFlags; }
public byte[] getPkgID() { return pkgID; }
public long getValDataEndAddr() { return valDataEndAddr; }
public byte[] getGcInfoIV() { return gcInfoIV; }
public long getHfs0partOffset() { return hfs0partOffset; }
public long getHfs0headerSize() { return hfs0headerSize; }
public byte[] getHfs0headerSHA256() { return hfs0headerSHA256; }
public byte[] getHfs0initDataSHA256() { return hfs0initDataSHA256; }
public int getSecureModeFlag() { return secureModeFlag; }
public boolean isSecureModeFlagOk(){ return secureModeFlag == 1; }
public int getTitleKeyFlag() { return titleKeyFlag; }
public boolean istitleKeyFlagOk(){ return titleKeyFlag == 2; }
public int getKeyFlag() { return keyFlag; }
public boolean iskeyFlagOk(){ return keyFlag == 0; }
public byte[] getNormAreaEndAddr() { return normAreaEndAddress; }
}

View file

@ -27,14 +27,14 @@ public class XCIProvider{
// TODO: Since LOGO partition added, we have to handle it properly. Is it works??
//private BufferedInputStream xciBIS;
private XCIGamecardHeader xciGamecardHeader;
private XCIGamecardInfo xciGamecardInfo;
private XCIGamecardCert xciGamecardCert;
private HFS0Provider hfs0ProviderMain,
hfs0ProviderUpdate,
hfs0ProviderNormal,
hfs0ProviderSecure,
hfs0ProviderLogo;
private final XCIGamecardHeader xciGamecardHeader;
private final XCIGamecardInfo xciGamecardInfo;
private final XCIGamecardCert xciGamecardCert;
private final HFS0Provider hfs0ProviderMain;
private HFS0Provider hfs0ProviderUpdate;
private HFS0Provider hfs0ProviderNormal;
private HFS0Provider hfs0ProviderSecure;
private HFS0Provider hfs0ProviderLogo;
public XCIProvider(File file, String XCI_HEADER_KEY) throws Exception{ // TODO: ADD FILE SIZE CHECK !!! Check xciHdrKey
RandomAccessFile raf;

View file

@ -22,10 +22,8 @@ import java.io.BufferedInputStream;
import java.io.File;
import java.nio.file.Files;
// TODO: rework, simplify
public class InFileStreamProducer {
private final boolean encrypted;
private final long size;
private boolean encrypted;
private final File file;
private final long initialOffset;
@ -34,50 +32,35 @@ public class InFileStreamProducer {
private long mediaStartOffset;
private long mediaEndOffset;
public InFileStreamProducer(File file){
this.file = file;
this.initialOffset = 0;
this.subOffset = 0;
}
public InFileStreamProducer(File file, long subOffset){
this.file = file;
this.initialOffset = 0;
this.subOffset = subOffset;
}
public InFileStreamProducer(
File file,
long size,
long initialOffset,
long subOffset,
AesCtrDecryptSimple decryptor,
long mediaStartOffset,
long mediaEndOffset
){
long mediaEndOffset){
this.encrypted = (decryptor != null);
this.file = file;
this.size = size;
this.initialOffset = initialOffset;
this.subOffset = subOffset;
this.decryptor = decryptor;
this.mediaStartOffset = mediaStartOffset;
this.mediaEndOffset = mediaEndOffset;
}
public InFileStreamProducer(File file, long size, long initialOffset, long subOffset){
this.encrypted = false;
this.file = file;
this.size = size;
this.initialOffset = 0;
this.subOffset = initialOffset+subOffset;
}
public InFileStreamProducer(File file, long size){
this.encrypted = false;
this.file = file;
this.size = size;
this.initialOffset = 0;
this.subOffset = 0;
}
public InFileStreamProducer(File file, long size, long subOffset){
this.encrypted = false;
this.file = file;
this.size = size;
this.initialOffset = 0;
this.subOffset = subOffset;
}
public BufferedInputStream produce() throws Exception{
if (encrypted) {
if (encrypted)
return produceAesCtr();
}
return produceNotEncrypted();
}
private AesCtrBufferedInputStream produceAesCtr() throws Exception{
@ -88,30 +71,34 @@ public class InFileStreamProducer {
mediaStartOffset,
mediaEndOffset,
Files.newInputStream(file.toPath()));
if (subOffset != stream.skip(subOffset))
throw new Exception("Unable to skip offset: " + subOffset);
skipBytesTillBeginning(stream, subOffset);
return stream;
}
private BufferedInputStream produceNotEncrypted() throws Exception{
BufferedInputStream stream = new BufferedInputStream(Files.newInputStream(file.toPath()));
if (subOffset != stream.skip(subOffset))
throw new Exception("Unable to skip offset: " + subOffset);
skipBytesTillBeginning(stream, subOffset);
return stream;
}
public InFileStreamProducer getSuccessor(long subOffset, long size){
public InFileStreamProducer getSuccessor(long subOffset){
this.subOffset = subOffset;
return new InFileStreamProducer(file, size, initialOffset, subOffset, decryptor, mediaStartOffset, mediaEndOffset);
return new InFileStreamProducer(file, initialOffset, subOffset, decryptor, mediaStartOffset, mediaEndOffset);
}
public boolean isEncrypted() {
return encrypted;
}
public long getSize() {
return size;
private void skipBytesTillBeginning(BufferedInputStream stream, long size) throws Exception{
long mustSkip = size;
long skipped = 0;
while (mustSkip > 0){
skipped += stream.skip(mustSkip);
mustSkip = size - skipped;
}
}
public File getFile(){ return file; }
@Override
public String toString(){
return file.getName();

View file

@ -19,6 +19,7 @@
package libKonogonka.RomFsDecrypted;
import libKonogonka.KeyChainHolder;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@ -31,7 +32,7 @@ public class KeyChainHolderTest {
private static final String xci_header_keyFileLocation = "./FilesForTests/xci_header_key.txt";
private KeyChainHolder keyChainHolder;
//@Disabled
@Disabled
@DisplayName("Key Chain Holder Test")
@Test
void keysChain() throws Exception{

View file

@ -22,7 +22,7 @@ import libKonogonka.KeyChainHolder;
import libKonogonka.RainbowDump;
import libKonogonka.Tools.NCA.NCAProvider;
import libKonogonka.Tools.NSO.NSO0Provider;
import libKonogonka.Tools.PFS0.IPFS0Provider;
import libKonogonka.Tools.PFS0.PFS0Provider;
import libKonogonka.Tools.PFS0.PFS0subFile;
import libKonogonka.ctraes.AesCtrDecryptSimple;
import org.junit.jupiter.api.Disabled;
@ -83,7 +83,7 @@ public class NSOTest {
void nso0Validation() throws Exception{
File nca = new File(ncaFileLocation);
PFS0subFile[] subfiles = ncaProvider.getNCAContentProvider(0).getPfs0().getPfs0subFiles();
PFS0subFile[] subfiles = ncaProvider.getNCAContentProvider(0).getPfs0().getHeader().getPfs0subFiles();
offsetPosition = ncaProvider.getTableEntry0().getMediaStartOffset()*0x200 +
ncaProvider.getNCAContentProvider(0).getPfs0().getRawFileDataStart();
@ -111,7 +111,8 @@ public class NSOTest {
ncaProvider.getSectionBlock0().getSectionCTR(),
ncaProvider.getTableEntry0().getMediaStartOffset() * 0x200);
IPFS0Provider pfs0Provider = ncaProvider.getNCAContentProvider(0).getPfs0();
PFS0Provider pfs0Provider = ncaProvider.getNCAContentProvider(0).getPfs0();
pfs0Provider.printDebug();
NSO0Provider nso0Provider = new NSO0Provider(pfs0Provider.getStreamProducer(0));
nso0Provider.printDebug();

View file

@ -73,7 +73,7 @@ public class NSPpfs0EncryptedTest {
pfs0Provider = new PFS0Provider(nspFile);
for (PFS0subFile subFile : pfs0Provider.getPfs0subFiles()) {
for (PFS0subFile subFile : pfs0Provider.getHeader().getPfs0subFiles()) {
if (subFile.getName().endsWith("890.nca")) {
System.out.println("File found: "+subFile.getName());
ncaProvider = new NCAProvider(nspFile, finalKeysSet, pfs0Provider.getRawFileDataStart()+subFile.getOffset());
@ -109,7 +109,7 @@ public class NSPpfs0EncryptedTest {
void AesCtrBufferedInputStreamTest() throws Exception {
File nca = new File(nspFileLocation); // TODO:NOTICE
PFS0subFile[] subfiles = ncaProvider.getNCAContentProvider(0).getPfs0().getPfs0subFiles();
PFS0subFile[] subfiles = ncaProvider.getNCAContentProvider(0).getPfs0().getHeader().getPfs0subFiles();
offsetPosition = ncaProvider.getTableEntry0().getMediaStartOffset()*0x200 +
ncaProvider.getNCAContentProvider(0).getPfs0().getRawFileDataStart();
@ -207,7 +207,10 @@ public class NSPpfs0EncryptedTest {
File contentFile = new File(saveToLocation + entry.getName());
BufferedOutputStream extractedFileBOS = new BufferedOutputStream(new FileOutputStream(contentFile));
PipedInputStream pis = ncaProvider.getNCAContentProvider(0).getPfs0().getProviderSubFilePipedInpStream(entry.getName());
BufferedInputStream pis = ncaProvider.getNCAContentProvider(0)
.getPfs0()
.getStreamProducer(entry.getName())
.produce();
byte[] readBuf = new byte[0x200]; // 8mb NOTE: consider switching to 1mb 1048576
int readSize;

View file

@ -0,0 +1,72 @@
/*
Copyright 2018-2022 Dmitry Isaenko
This file is part of libKonogonka.
libKonogonka is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
libKonogonka is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with libKonogonka. If not, see <https://www.gnu.org/licenses/>.
*/
package libKonogonka.RomFsDecrypted;
import libKonogonka.KeyChainHolder;
import libKonogonka.RainbowDump;
import libKonogonka.TitleKeyChainHolder;
import libKonogonka.Tools.NCA.NCAProvider;
import libKonogonka.Tools.PFS0.PFS0Provider;
import libKonogonka.Tools.PFS0.PFS0subFile;
import libKonogonka.ctraes.AesCtrBufferedInputStream;
import libKonogonka.ctraes.AesCtrDecryptSimple;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.io.*;
import java.nio.file.Files;
import java.util.HashMap;
public class PFS0Test {
private static final String keysFileLocation = "./FilesForTests/prod.keys";
private static final String titleFileLocation = "./FilesForTests/simple_nsp.title_key";
private static final String xci_header_keyFileLocation = "./FilesForTests/xci_header_key.txt";
private static final String nspFileLocation = "./FilesForTests/sample.nsp";
private static KeyChainHolder keyChainHolder;
private static PFS0Provider pfs0Provider;
@Disabled
@DisplayName("NSP validation")
@Test
void pfs0test() throws Exception {
BufferedReader br = new BufferedReader(new FileReader(xci_header_keyFileLocation));
String keyValue = br.readLine();
br.close();
if (keyValue == null)
throw new Exception("Unable to retrieve xci_header_key");
keyValue = keyValue.trim();
keyChainHolder = new KeyChainHolder(keysFileLocation, keyValue);
TitleKeyChainHolder titleKeyChainHolder = new TitleKeyChainHolder(titleFileLocation);
HashMap<String, String> finalKeysSet = keyChainHolder.getRawKeySet();
finalKeysSet.putAll(titleKeyChainHolder.getKeySet());
File nspFile = new File(nspFileLocation);
pfs0Provider = new PFS0Provider(nspFile);
pfs0Provider.printDebug();
for (PFS0subFile subFile : pfs0Provider.getHeader().getPfs0subFiles()) {
pfs0Provider.exportContent("/tmp/NSP_PFS0_NON-ENCRYPTED_TEST", subFile.getName());
}
}
}

View file

@ -21,7 +21,7 @@ package libKonogonka.RomFsDecrypted;
import libKonogonka.KeyChainHolder;
import libKonogonka.RainbowDump;
import libKonogonka.Tools.NCA.NCAProvider;
import libKonogonka.Tools.PFS0.IPFS0Provider;
import libKonogonka.Tools.PFS0.PFS0Provider;
import libKonogonka.Tools.PFS0.PFS0subFile;
import libKonogonka.ctraes.AesCtrBufferedInputStream;
import libKonogonka.ctraes.AesCtrDecryptSimple;
@ -80,7 +80,7 @@ public class Pfs0EncryptedTest {
void AesCtrBufferedInputStreamTest() throws Exception {
File nca = new File(ncaFileLocation);
PFS0subFile[] subfiles = ncaProvider.getNCAContentProvider(0).getPfs0().getPfs0subFiles();
PFS0subFile[] subfiles = ncaProvider.getNCAContentProvider(0).getPfs0().getHeader().getPfs0subFiles();
offsetPosition = ncaProvider.getTableEntry0().getMediaStartOffset()*0x200 +
ncaProvider.getNCAContentProvider(0).getPfs0().getRawFileDataStart();
@ -113,7 +113,7 @@ public class Pfs0EncryptedTest {
}
*/
IPFS0Provider pfs0Provider = ncaProvider.getNCAContentProvider(0).getPfs0();
PFS0Provider pfs0Provider = ncaProvider.getNCAContentProvider(0).getPfs0();
//----------------------------------------------------------------------
for (PFS0subFile subFile : subfiles) {
System.out.println("Exporting "+subFile.getName());
@ -181,7 +181,10 @@ public class Pfs0EncryptedTest {
File contentFile = new File(saveToLocation + entry.getName());
BufferedOutputStream extractedFileBOS = new BufferedOutputStream(new FileOutputStream(contentFile));
PipedInputStream pis = ncaProvider.getNCAContentProvider(0).getPfs0().getProviderSubFilePipedInpStream(entry.getName());
BufferedInputStream pis = ncaProvider.getNCAContentProvider(0)
.getPfs0()
.getStreamProducer(entry.getName())
.produce();
byte[] readBuf = new byte[0x200]; // 8mb NOTE: consider switching to 1mb 1048576
int readSize;

View file

@ -18,9 +18,11 @@
*/
package libKonogonka.RomFsDecrypted;
import java.io.File;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import libKonogonka.Tools.RomFs.FileSystemEntry;
import libKonogonka.Tools.RomFs.RomFsProvider;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
@ -39,13 +41,14 @@ public class RomFsDecryptedTest {
RomFsProvider provider;
@Disabled
@DisplayName("RomFsDecryptedProvider: tests")
@DisplayName("RomFsDecryptedProvider: Overall and export")
@Test
void romFsValidation() throws Exception{
makeFile();
parseLv6offsetFromFileName();
makeProvider();
provider.printDebug();
export();
}
void makeFile(){
@ -58,12 +61,93 @@ public class RomFsDecryptedTest {
provider = new RomFsProvider(decryptedFile, lv6offset);
}
/*
void checkFilesWorkers(){
assertTrue(fw1 instanceof WorkerFiles);
assertTrue(fw2 instanceof WorkerFiles);
assertTrue(fw3 instanceof WorkerFiles);
void export() throws Exception {
System.out.println("lv6offset = "+lv6offset);
FileSystemEntry entry = provider.getRootEntry();
System.out.print(" entry.getFileOffset(): " + entry.getOffset() +
"\n entry.getFileSize(): " + entry.getSize() +
"\nExport new.......");
exportFolderContent(entry, "/tmp/decrypted_brandnew");
System.out.println("done");
/*----------------------------------------------------------------------
System.out.print("Export legacy....");
exportFolderContentLegacy(entry, "/tmp/decrypted_legacy");
System.out.println("done"); */
}
*/
private void exportFolderContent(FileSystemEntry entry, String saveToLocation) throws Exception{
File contentFile = new File(saveToLocation + entry.getName());
contentFile.mkdirs();
String currentDirPath = saveToLocation + entry.getName() + File.separator;
for (FileSystemEntry fse : entry.getContent()){
if (fse.isDirectory())
exportFolderContent(fse, currentDirPath);
else
exportSingleFile(fse, currentDirPath);
}
}
private void exportSingleFile(FileSystemEntry entry, String saveToLocation) throws Exception {
File contentFile = new File(saveToLocation + entry.getName());
try(BufferedOutputStream extractedFileBOS = new BufferedOutputStream(Files.newOutputStream(contentFile.toPath()));
BufferedInputStream stream = new BufferedInputStream(Files.newInputStream(decryptedFile.toPath()))) {
long skipBytes = entry.getOffset()+
//ncaProvider.getTableEntry1().getMediaStartOffset()*0x200+
provider.getHeader().getFileDataOffset()+
lv6offset;
if (skipBytes != stream.skip(skipBytes))
throw new Exception("Can't skip");
int blockSize = 0x200;
if (entry.getSize() < 0x200)
blockSize = (int) entry.getSize();
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) >= entry.getSize()) {
blockSize = (int) (entry.getSize() - i);
if (blockSize == 0)
break;
block = new byte[blockSize];
}
}
}
}
/*
private void exportFolderContentLegacy(FileSystemEntry entry, String saveToLocation) throws Exception{
File contentFile = new File(saveToLocation + entry.getName());
contentFile.mkdirs();
String currentDirPath = saveToLocation + entry.getName() + File.separator;
for (FileSystemEntry fse : entry.getContent()){
if (fse.isDirectory())
exportFolderContentLegacy(fse, currentDirPath);
else
exportSingleFileLegacy(fse, currentDirPath);
}
}
private void exportSingleFileLegacy(FileSystemEntry entry, String saveToLocation) throws Exception {
File contentFile = new File(saveToLocation + entry.getName());
BufferedOutputStream extractedFileBOS = new BufferedOutputStream(Files.newOutputStream(contentFile.toPath()));
PipedInputStream pis = provider.getContent(entry);
byte[] readBuf = new byte[0x200]; // 8mb NOTE: consider switching to 1mb 1048576
int readSize;
while ((readSize = pis.read(readBuf)) > -1) {
extractedFileBOS.write(readBuf, 0, readSize);
readBuf = new byte[0x200];
}
extractedFileBOS.close();
}*/
}

View file

@ -170,7 +170,7 @@ public class RomFsEncryptedTest {
throw new Exception("Read failure. Block Size: "+blockSize+", actuallyRead: "+actuallyRead);
extractedFileBOS.write(block);
i += blockSize;
if ((i + blockSize) > entry.getSize()) {
if ((i + blockSize) >= entry.getSize()) {
blockSize = (int) (entry.getSize() - i);
if (blockSize == 0)
break;
@ -180,7 +180,7 @@ public class RomFsEncryptedTest {
//---
extractedFileBOS.close();
}
/*
private void exportFolderContentLegacy(FileSystemEntry entry, String saveToLocation) throws Exception{
File contentFile = new File(saveToLocation + entry.getName());
contentFile.mkdirs();
@ -192,6 +192,7 @@ public class RomFsEncryptedTest {
exportSingleFileLegacy(fse, currentDirPath);
}
}
private void exportSingleFileLegacy(FileSystemEntry entry, String saveToLocation) throws Exception {
File contentFile = new File(saveToLocation + entry.getName());
@ -207,8 +208,7 @@ public class RomFsEncryptedTest {
}
extractedFileBOS.close();
}
}*/
@Disabled
@Order(6)
@DisplayName("RomFsEncryptedProvider: PFS test")

View file

@ -90,6 +90,6 @@ public class XciTest {
NCAProvider ncaProvider = new NCAProvider(xciFile, keyChainHolder.getRawKeySet(),
hfs0Provider.getRawFileDataStart() +
hfs0File.getOffset());
//ncaProvider.
ncaProvider.getSectionBlock0().printDebug();
}
}