Simplify, fix KeyChainHolder, implement HFS0 export functions
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
53af386738
commit
9a854774d7
30 changed files with 719 additions and 1036 deletions
|
@ -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++;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" +
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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<>();
|
||||
|
|
|
@ -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();
|
||||
}
|
125
src/main/java/libKonogonka/Tools/PFS0/PFS0Header.java
Normal file
125
src/main/java/libKonogonka/Tools/PFS0/PFS0Header.java
Normal 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" +
|
||||
"----------------------------------------------------------------"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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'");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
72
src/test/java/libKonogonka/RomFsDecrypted/PFS0Test.java
Normal file
72
src/test/java/libKonogonka/RomFsDecrypted/PFS0Test.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}*/
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -90,6 +90,6 @@ public class XciTest {
|
|||
NCAProvider ncaProvider = new NCAProvider(xciFile, keyChainHolder.getRawKeySet(),
|
||||
hfs0Provider.getRawFileDataStart() +
|
||||
hfs0File.getOffset());
|
||||
//ncaProvider.
|
||||
ncaProvider.getSectionBlock0().printDebug();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue