Support for files view of the RomFs content on encrypted NCA3
This commit is contained in:
parent
f3bcb356ea
commit
8225f0fcba
11 changed files with 326 additions and 179 deletions
|
@ -147,7 +147,7 @@ public class MainController implements Initializable {
|
||||||
case "nca":
|
case "nca":
|
||||||
tabPane.getSelectionModel().select(2);
|
tabPane.getSelectionModel().select(2);
|
||||||
break;
|
break;
|
||||||
case "tic":
|
case "tik":
|
||||||
tabPane.getSelectionModel().select(3);
|
tabPane.getSelectionModel().select(3);
|
||||||
break;
|
break;
|
||||||
case "xml":
|
case "xml":
|
||||||
|
@ -176,7 +176,7 @@ public class MainController implements Initializable {
|
||||||
case "nca":
|
case "nca":
|
||||||
NCATabController.analyze(selectedFile);
|
NCATabController.analyze(selectedFile);
|
||||||
break;
|
break;
|
||||||
case "tic":
|
case "tik":
|
||||||
TIKTabController.analyze(selectedFile);
|
TIKTabController.analyze(selectedFile);
|
||||||
break;
|
break;
|
||||||
case "xml":
|
case "xml":
|
||||||
|
@ -195,7 +195,7 @@ public class MainController implements Initializable {
|
||||||
case "nsz":
|
case "nsz":
|
||||||
case "xci":
|
case "xci":
|
||||||
case "nca":
|
case "nca":
|
||||||
case "tic":
|
case "tik":
|
||||||
case "xml":
|
case "xml":
|
||||||
case "npdm":
|
case "npdm":
|
||||||
case "romfs":
|
case "romfs":
|
||||||
|
|
|
@ -27,6 +27,7 @@ import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import konogonka.AppPreferences;
|
import konogonka.AppPreferences;
|
||||||
import konogonka.Controllers.NSP.NSPController;
|
import konogonka.Controllers.NSP.NSPController;
|
||||||
|
import konogonka.Controllers.RFS.RomFsController;
|
||||||
import konogonka.LoperConverter;
|
import konogonka.LoperConverter;
|
||||||
import konogonka.MediatorControl;
|
import konogonka.MediatorControl;
|
||||||
import konogonka.Tools.NCA.NCAContent;
|
import konogonka.Tools.NCA.NCAContent;
|
||||||
|
@ -47,6 +48,8 @@ public class NCASectionContentController implements Initializable {
|
||||||
@FXML
|
@FXML
|
||||||
private NSPController SectionPFS0Controller;
|
private NSPController SectionPFS0Controller;
|
||||||
@FXML
|
@FXML
|
||||||
|
private RomFsController SectionRomFsController;
|
||||||
|
@FXML
|
||||||
private VBox sha256pane;
|
private VBox sha256pane;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -57,6 +60,7 @@ public class NCASectionContentController implements Initializable {
|
||||||
|
|
||||||
public void resetTab() {
|
public void resetTab() {
|
||||||
SectionPFS0Controller.resetTab();
|
SectionPFS0Controller.resetTab();
|
||||||
|
SectionRomFsController.resetTab();
|
||||||
sha256pane.getChildren().clear();
|
sha256pane.getChildren().clear();
|
||||||
extractRawConentBtn.setDisable(true);
|
extractRawConentBtn.setDisable(true);
|
||||||
}
|
}
|
||||||
|
@ -71,6 +75,10 @@ public class NCASectionContentController implements Initializable {
|
||||||
this.sectionNumber = sectionNumber;
|
this.sectionNumber = sectionNumber;
|
||||||
this.extractRawConentBtn.setDisable(false);
|
this.extractRawConentBtn.setDisable(false);
|
||||||
|
|
||||||
|
setPFS0Content();
|
||||||
|
setRomFsContent();
|
||||||
|
}
|
||||||
|
private void setPFS0Content(){
|
||||||
if (ncaContent.getPfs0() != null)
|
if (ncaContent.getPfs0() != null)
|
||||||
SectionPFS0Controller.setData(ncaContent.getPfs0(), null);;
|
SectionPFS0Controller.setData(ncaContent.getPfs0(), null);;
|
||||||
|
|
||||||
|
@ -87,6 +95,10 @@ public class NCASectionContentController implements Initializable {
|
||||||
sha256pane.getChildren().add(new HBox(numberLblTmp, sha256LblTmp));
|
sha256pane.getChildren().add(new HBox(numberLblTmp, sha256LblTmp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private void setRomFsContent(){
|
||||||
|
if (ncaContent.getRomfs() != null)
|
||||||
|
SectionRomFsController.setData(ncaContent.getRomfs());
|
||||||
|
}
|
||||||
|
|
||||||
private void extractFiles(){
|
private void extractFiles(){
|
||||||
if (ncaContent == null)
|
if (ncaContent == null)
|
||||||
|
|
|
@ -42,15 +42,15 @@ public class RomFsController implements ITabController {
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label headerHeaderLengthLbl,
|
private Label headerHeaderLengthLbl,
|
||||||
headerDirectoryHashTableOffsetLbl,
|
headerDirectoryHashTableOffsetLbl,
|
||||||
headerDirectoryHashTableLengthLbl,
|
headerDirectoryHashTableLengthLbl,
|
||||||
headerDirectoryMetadataTableOffsetLbl,
|
headerDirectoryMetadataTableOffsetLbl,
|
||||||
headerDirectoryMetadataTableLengthLbl,
|
headerDirectoryMetadataTableLengthLbl,
|
||||||
headerFileHashTableOffsetLbl,
|
headerFileHashTableOffsetLbl,
|
||||||
headerFileHashTableLengthLbl,
|
headerFileHashTableLengthLbl,
|
||||||
headerFileMetadataTableOffsetLbl,
|
headerFileMetadataTableOffsetLbl,
|
||||||
headerFileMetadataTableLengthLbl,
|
headerFileMetadataTableLengthLbl,
|
||||||
headerFileDataOffsetLbl;
|
headerFileDataOffsetLbl;
|
||||||
@FXML
|
@FXML
|
||||||
private Label headerHeaderLengthHexLbl,
|
private Label headerHeaderLengthHexLbl,
|
||||||
headerDirectoryHashTableOffsetHexLbl,
|
headerDirectoryHashTableOffsetHexLbl,
|
||||||
|
@ -157,7 +157,7 @@ public class RomFsController implements ITabController {
|
||||||
workThread.start();
|
workThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setData(RomFsDecryptedProvider provider) {
|
public void setData(IRomFsProvider provider) {
|
||||||
try {
|
try {
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
Level6Header header = provider.getHeader();
|
Level6Header header = provider.getHeader();
|
||||||
|
|
|
@ -23,6 +23,8 @@ import konogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock;
|
||||||
import konogonka.Tools.PFS0.IPFS0Provider;
|
import konogonka.Tools.PFS0.IPFS0Provider;
|
||||||
import konogonka.Tools.PFS0.PFS0EncryptedProvider;
|
import konogonka.Tools.PFS0.PFS0EncryptedProvider;
|
||||||
import konogonka.Tools.PFS0.PFS0Provider;
|
import konogonka.Tools.PFS0.PFS0Provider;
|
||||||
|
import konogonka.Tools.RomFs.IRomFsProvider;
|
||||||
|
import konogonka.Tools.RomFs.RomFsEncryptedProvider;
|
||||||
import konogonka.ctraes.AesCtrDecryptSimple;
|
import konogonka.ctraes.AesCtrDecryptSimple;
|
||||||
import konogonka.exceptions.EmptySectionException;
|
import konogonka.exceptions.EmptySectionException;
|
||||||
|
|
||||||
|
@ -41,6 +43,7 @@ public class NCAContent {
|
||||||
|
|
||||||
private LinkedList<byte[]> Pfs0SHA256hashes;
|
private LinkedList<byte[]> Pfs0SHA256hashes;
|
||||||
private IPFS0Provider pfs0;
|
private IPFS0Provider pfs0;
|
||||||
|
private IRomFsProvider romfs;
|
||||||
|
|
||||||
// TODO: if decryptedKey is empty, throw exception ??
|
// TODO: if decryptedKey is empty, throw exception ??
|
||||||
public NCAContent(File file,
|
public NCAContent(File file,
|
||||||
|
@ -130,16 +133,22 @@ public class NCAContent {
|
||||||
System.out.println("proceedRomFs() -> proceedRomFsNotEncrypted() is not implemented :(");
|
System.out.println("proceedRomFs() -> proceedRomFsNotEncrypted() is not implemented :(");
|
||||||
}
|
}
|
||||||
private void proceedRomFsEncrypted() throws Exception{
|
private void proceedRomFsEncrypted() throws Exception{
|
||||||
new CryptoSection03RomFS(file,
|
if (decryptedKey == null)
|
||||||
|
throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided.");
|
||||||
|
|
||||||
|
this.romfs = new RomFsEncryptedProvider(
|
||||||
offsetPosition,
|
offsetPosition,
|
||||||
|
ncaSectionBlock.getSuperBlockIVFC().getLvl6Offset(),
|
||||||
|
file,
|
||||||
decryptedKey,
|
decryptedKey,
|
||||||
ncaSectionBlock,
|
ncaSectionBlock.getSectionCTR(),
|
||||||
ncaHeaderTableEntry.getMediaStartOffset(),
|
ncaHeaderTableEntry.getMediaStartOffset(),
|
||||||
ncaHeaderTableEntry.getMediaEndOffset());
|
ncaHeaderTableEntry.getMediaEndOffset());
|
||||||
}
|
}
|
||||||
|
|
||||||
public LinkedList<byte[]> getPfs0SHA256hashes() { return Pfs0SHA256hashes; }
|
public LinkedList<byte[]> getPfs0SHA256hashes() { return Pfs0SHA256hashes; }
|
||||||
public IPFS0Provider getPfs0() { return pfs0; }
|
public IPFS0Provider getPfs0() { return pfs0; }
|
||||||
|
public IRomFsProvider getRomfs() { return romfs; }
|
||||||
|
|
||||||
private class CryptoSection03Pfs0 {
|
private class CryptoSection03Pfs0 {
|
||||||
|
|
||||||
|
@ -293,7 +302,8 @@ public class NCAContent {
|
||||||
counter += toSkip;
|
counter += toSkip;
|
||||||
}
|
}
|
||||||
//---------------------------------------------------------
|
//---------------------------------------------------------
|
||||||
pfs0 = new PFS0EncryptedProvider(pipedInputStream, counter,
|
pfs0 = new PFS0EncryptedProvider(pipedInputStream,
|
||||||
|
counter,
|
||||||
MetaOffsetPositionInFile,
|
MetaOffsetPositionInFile,
|
||||||
MetaFileWithEncPFS0,
|
MetaFileWithEncPFS0,
|
||||||
MetaKey,
|
MetaKey,
|
||||||
|
@ -312,124 +322,6 @@ public class NCAContent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private class CryptoSection03RomFS{
|
|
||||||
CryptoSection03RomFS(File file,
|
|
||||||
long offsetPosition,
|
|
||||||
byte[] decryptedKey,
|
|
||||||
NCASectionBlock ncaSectionBlock,
|
|
||||||
long mediaStartBlocksOffset,
|
|
||||||
long mediaEndBlocksOffset) throws Exception
|
|
||||||
{
|
|
||||||
if (decryptedKey == null)
|
|
||||||
throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided.");
|
|
||||||
|
|
||||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
|
||||||
long abosluteOffsetPosition = offsetPosition + (mediaStartBlocksOffset * 0x200);
|
|
||||||
raf.seek(abosluteOffsetPosition);
|
|
||||||
|
|
||||||
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey,
|
|
||||||
ncaSectionBlock.getSectionCTR(),
|
|
||||||
mediaStartBlocksOffset * 0x200);
|
|
||||||
|
|
||||||
byte[] encryptedBlock;
|
|
||||||
byte[] dectyptedBlock;
|
|
||||||
long mediaBlocksSize = mediaEndBlocksOffset - mediaStartBlocksOffset;
|
|
||||||
// Prepare thread to parse encrypted data
|
|
||||||
PipedOutputStream streamOut = new PipedOutputStream();
|
|
||||||
PipedInputStream streamInp = new PipedInputStream(streamOut);
|
|
||||||
|
|
||||||
Thread pThread = new Thread(new ParseThreadRomFs(
|
|
||||||
streamInp,
|
|
||||||
offsetPosition,
|
|
||||||
file,
|
|
||||||
decryptedKey,
|
|
||||||
ncaSectionBlock.getSectionCTR(),
|
|
||||||
mediaStartBlocksOffset,
|
|
||||||
mediaEndBlocksOffset
|
|
||||||
));
|
|
||||||
pThread.start();
|
|
||||||
// Decrypt data
|
|
||||||
for (int i = 0; i < mediaBlocksSize; i++){
|
|
||||||
encryptedBlock = new byte[0x200];
|
|
||||||
if (raf.read(encryptedBlock) != -1){
|
|
||||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
|
||||||
// Writing decrypted data to pipe
|
|
||||||
try {
|
|
||||||
streamOut.write(dectyptedBlock);
|
|
||||||
}
|
|
||||||
catch (IOException e){
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pThread.join();// TODO:UNCOMMENT
|
|
||||||
streamOut.close();
|
|
||||||
raf.close();
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* Since we representing decrypted data as stream (it's easier to look on it this way),
|
|
||||||
* this thread will be parsing it.
|
|
||||||
* */
|
|
||||||
private class ParseThreadRomFs implements Runnable{
|
|
||||||
|
|
||||||
PipedInputStream pipedInputStream;
|
|
||||||
|
|
||||||
private long MetaOffsetPositionInFile;
|
|
||||||
private File MetaFileWithEncPFS0;
|
|
||||||
private byte[] MetaKey;
|
|
||||||
private byte[] MetaSectionCTR;
|
|
||||||
private long MetaMediaStartOffset;
|
|
||||||
private long MetaMediaEndOffset;
|
|
||||||
|
|
||||||
|
|
||||||
ParseThreadRomFs(PipedInputStream pipedInputStream,
|
|
||||||
long MetaOffsetPositionInFile,
|
|
||||||
File MetaFileWithEncPFS0,
|
|
||||||
byte[] MetaKey,
|
|
||||||
byte[] MetaSectionCTR,
|
|
||||||
long MetaMediaStartOffset,
|
|
||||||
long MetaMediaEndOffset
|
|
||||||
){
|
|
||||||
this.pipedInputStream = pipedInputStream;
|
|
||||||
//this.hashTableRecordsCount = hashTableSize / 0x20;
|
|
||||||
|
|
||||||
this.MetaOffsetPositionInFile = MetaOffsetPositionInFile;
|
|
||||||
this.MetaFileWithEncPFS0 = MetaFileWithEncPFS0;
|
|
||||||
this.MetaKey = MetaKey;
|
|
||||||
this.MetaSectionCTR = MetaSectionCTR;
|
|
||||||
this.MetaMediaStartOffset = MetaMediaStartOffset;
|
|
||||||
this.MetaMediaEndOffset = MetaMediaEndOffset;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
long counter = 0; // How many bytes already read
|
|
||||||
try {
|
|
||||||
// TODO
|
|
||||||
/*
|
|
||||||
pfs0 = new PFS0EncryptedProvider(pipedInputStream,
|
|
||||||
counter,
|
|
||||||
MetaOffsetPositionInFile,
|
|
||||||
MetaFileWithEncPFS0,
|
|
||||||
MetaKey,
|
|
||||||
MetaSectionCTR,
|
|
||||||
MetaMediaStartOffset,
|
|
||||||
MetaMediaEndOffset);
|
|
||||||
|
|
||||||
*/
|
|
||||||
pipedInputStream.close();
|
|
||||||
}
|
|
||||||
catch (Exception e){
|
|
||||||
System.out.println("'ParseThreadRomFs' thread exception");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
System.out.println("ParseThreadRomFs dies");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export NCA content AS IS.
|
* Export NCA content AS IS.
|
||||||
|
|
|
@ -46,7 +46,8 @@ public class PFS0EncryptedProvider implements IPFS0Provider{
|
||||||
private long mediaStartOffset; // In 512-blocks
|
private long mediaStartOffset; // In 512-blocks
|
||||||
private long mediaEndOffset; // In 512-blocks
|
private long mediaEndOffset; // In 512-blocks
|
||||||
|
|
||||||
public PFS0EncryptedProvider(PipedInputStream pipedInputStream, long pfs0offsetPosition,
|
public PFS0EncryptedProvider(PipedInputStream pipedInputStream,
|
||||||
|
long pfs0offsetPosition,
|
||||||
long offsetPositionInFile,
|
long offsetPositionInFile,
|
||||||
File fileWithEncPFS0,
|
File fileWithEncPFS0,
|
||||||
byte[] key,
|
byte[] key,
|
||||||
|
@ -166,15 +167,13 @@ public class PFS0EncryptedProvider implements IPFS0Provider{
|
||||||
@Override
|
@Override
|
||||||
public File getFile(){ return file; }
|
public File getFile(){ return file; }
|
||||||
@Override
|
@Override
|
||||||
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception {
|
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception { // TODO: rewrite
|
||||||
if (subFileNumber >= pfs0subFiles.length) {
|
if (subFileNumber >= pfs0subFiles.length)
|
||||||
throw new Exception("PFS0Provider -> getPfs0subFilePipedInpStream(): Requested sub file doesn't exists");
|
throw new Exception("PFS0Provider -> getPfs0subFilePipedInpStream(): Requested sub file doesn't exists");
|
||||||
}
|
|
||||||
|
|
||||||
Thread workerThread;
|
Thread workerThread;
|
||||||
PipedOutputStream streamOut = new PipedOutputStream();
|
PipedOutputStream streamOut = new PipedOutputStream();
|
||||||
|
|
||||||
|
|
||||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||||
workerThread = new Thread(() -> {
|
workerThread = new Thread(() -> {
|
||||||
System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Executing thread");
|
System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Executing thread");
|
||||||
|
@ -272,7 +271,7 @@ public class PFS0EncryptedProvider implements IPFS0Provider{
|
||||||
encryptedBlock = new byte[0x200];
|
encryptedBlock = new byte[0x200];
|
||||||
if (bis.read(encryptedBlock) == 0x200) {
|
if (bis.read(encryptedBlock) == 0x200) {
|
||||||
dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock);
|
dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock);
|
||||||
streamOut.write(dectyptedBlock, 0, 0x200 + extraData);
|
streamOut.write(dectyptedBlock, 0, 0x200 + extraData); // WTF ??? THIS LOOKS INCORRECT
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from last bock");
|
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from last bock");
|
||||||
|
|
|
@ -51,20 +51,19 @@ public class FolderMeta4Debug {
|
||||||
folderMeta.dirName = new String(Arrays.copyOfRange(directoryMetadataTable, i, i + folderMeta.dirNameLength), StandardCharsets.UTF_8);
|
folderMeta.dirName = new String(Arrays.copyOfRange(directoryMetadataTable, i, i + folderMeta.dirNameLength), StandardCharsets.UTF_8);
|
||||||
i += getRealNameSize(folderMeta.dirNameLength);
|
i += getRealNameSize(folderMeta.dirNameLength);
|
||||||
|
|
||||||
allFolders.add(folderMeta);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (FolderMeta headersDirectory : allFolders)
|
|
||||||
System.out.println(
|
System.out.println(
|
||||||
"---------------------------DIRECTORY------------------------\n" +
|
"---------------------------DIRECTORY------------------------\n" +
|
||||||
"Offset of Parent Directory (self if Root) " + formatDecHexString(headersDirectory.parentDirectoryOffset ) +"\n" +
|
"Offset of Parent Directory (self if Root) " + formatDecHexString(folderMeta.parentDirectoryOffset ) +"\n" +
|
||||||
"Offset of next Sibling Directory " + formatDecHexString(headersDirectory.nextSiblingDirectoryOffset) +"\n" +
|
"Offset of next Sibling Directory " + formatDecHexString(folderMeta.nextSiblingDirectoryOffset) +"\n" +
|
||||||
"Offset of first Child Directory (Subdirectory) " + formatDecHexString(headersDirectory.firstSubdirectoryOffset ) +"\n" +
|
"Offset of first Child Directory (Subdirectory) " + formatDecHexString(folderMeta.firstSubdirectoryOffset ) +"\n" +
|
||||||
"Offset of first File (in File Metadata Table) " + formatDecHexString(headersDirectory.firstFileOffset ) +"\n" +
|
"Offset of first File (in File Metadata Table) " + formatDecHexString(folderMeta.firstFileOffset ) +"\n" +
|
||||||
"Offset of next Directory in the same Hash Table bucket " + formatDecHexString(headersDirectory.nextDirectoryOffset ) +"\n" +
|
"Offset of next Directory in the same Hash Table bucket " + formatDecHexString(folderMeta.nextDirectoryOffset ) +"\n" +
|
||||||
"Name Length " + formatDecHexString(headersDirectory.dirNameLength ) +"\n" +
|
"Name Length " + formatDecHexString(folderMeta.dirNameLength ) +"\n" +
|
||||||
"Name Length (rounded up to multiple of 4) " + headersDirectory.dirName + "\n"
|
"Name Length (rounded up to multiple of 4) " + folderMeta.dirName + "\n"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
allFolders.add(folderMeta);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getRealNameSize(int value){
|
private int getRealNameSize(int value){
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
package konogonka.Tools.RomFs;
|
package konogonka.Tools.RomFs;
|
||||||
|
|
||||||
import konogonka.LoperConverter;
|
import konogonka.LoperConverter;
|
||||||
|
import konogonka.RainbowDump;
|
||||||
|
|
||||||
public class Level6Header {
|
public class Level6Header {
|
||||||
private long headerLength;
|
private long headerLength;
|
||||||
|
@ -36,8 +37,10 @@ public class Level6Header {
|
||||||
private byte[] headerBytes;
|
private byte[] headerBytes;
|
||||||
private int i;
|
private int i;
|
||||||
|
|
||||||
Level6Header(byte[] headerBytes){
|
Level6Header(byte[] headerBytes) throws Exception{
|
||||||
this.headerBytes = headerBytes;
|
this.headerBytes = headerBytes;
|
||||||
|
if (headerBytes.length < 0x50)
|
||||||
|
throw new Exception("Level 6 Header section is too small");
|
||||||
headerLength = getNext();
|
headerLength = getNext();
|
||||||
directoryHashTableOffset = getNext();
|
directoryHashTableOffset = getNext();
|
||||||
directoryHashTableLength = getNext();
|
directoryHashTableLength = getNext();
|
||||||
|
@ -66,4 +69,20 @@ public class Level6Header {
|
||||||
public long getFileMetadataTableOffset() { return fileMetadataTableOffset; }
|
public long getFileMetadataTableOffset() { return fileMetadataTableOffset; }
|
||||||
public long getFileMetadataTableLength() { return fileMetadataTableLength; }
|
public long getFileMetadataTableLength() { return fileMetadataTableLength; }
|
||||||
public long getFileDataOffset() { return fileDataOffset; }
|
public long getFileDataOffset() { return fileDataOffset; }
|
||||||
|
|
||||||
|
public void printDebugInfo(){
|
||||||
|
System.out.println("== Level 6 Header ==\n" +
|
||||||
|
"Header Length (always 0x50 ?) "+ RainbowDump.formatDecHexString(headerLength)+" (size of this structure within first 0x200 block of LEVEL 6 part)\n" +
|
||||||
|
"Directory Hash Table Offset "+ RainbowDump.formatDecHexString(directoryHashTableOffset)+" (against THIS block where HEADER contains)\n" +
|
||||||
|
"Directory Hash Table Length "+ RainbowDump.formatDecHexString(directoryHashTableLength) + "\n" +
|
||||||
|
"Directory Metadata Table Offset "+ RainbowDump.formatDecHexString(directoryMetadataTableOffset) + "\n" +
|
||||||
|
"Directory Metadata Table Length "+ RainbowDump.formatDecHexString(directoryMetadataTableLength) + "\n" +
|
||||||
|
"File Hash Table Offset "+ RainbowDump.formatDecHexString(fileHashTableOffset) + "\n" +
|
||||||
|
"File Hash Table Length "+ RainbowDump.formatDecHexString(fileHashTableLength) + "\n" +
|
||||||
|
"File Metadata Table Offset "+ RainbowDump.formatDecHexString(fileMetadataTableOffset) + "\n" +
|
||||||
|
"File Metadata Table Length "+ RainbowDump.formatDecHexString(fileMetadataTableLength) + "\n" +
|
||||||
|
"File Data Offset "+ RainbowDump.formatDecHexString(fileDataOffset) + "\n" +
|
||||||
|
"-------------------------------------------------------------"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,12 +23,13 @@ import java.io.*;
|
||||||
|
|
||||||
public class RomFsDecryptedProvider implements IRomFsProvider{
|
public class RomFsDecryptedProvider implements IRomFsProvider{
|
||||||
|
|
||||||
private static final long LEVEL_6_DEFAULT_OFFSET = 0x14000;
|
private static final long LEVEL_6_DEFAULT_OFFSET = 0x14000; // TODO: FIX incorrect
|
||||||
|
|
||||||
private File file;
|
private File file;
|
||||||
private Level6Header header;
|
private Level6Header header;
|
||||||
|
|
||||||
private FileSystemEntry rootEntry;
|
private FileSystemEntry rootEntry;
|
||||||
|
// TODO: FIX. LEVEL 6 OFFSET MUST be provided
|
||||||
|
|
||||||
public RomFsDecryptedProvider(File decryptedFsImageFile) throws Exception{ // TODO: add default setup AND using meta-data headers from NCA RomFs section (?)
|
public RomFsDecryptedProvider(File decryptedFsImageFile) throws Exception{ // TODO: add default setup AND using meta-data headers from NCA RomFs section (?)
|
||||||
this.file = decryptedFsImageFile;
|
this.file = decryptedFsImageFile;
|
||||||
|
|
239
src/main/java/konogonka/Tools/RomFs/RomFsEncryptedProvider.java
Normal file
239
src/main/java/konogonka/Tools/RomFs/RomFsEncryptedProvider.java
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019-2020 Dmitry Isaenko
|
||||||
|
*
|
||||||
|
* This file is part of Konogonka.
|
||||||
|
*
|
||||||
|
* Konogonka 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.
|
||||||
|
*
|
||||||
|
* Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package konogonka.Tools.RomFs;
|
||||||
|
|
||||||
|
import konogonka.ctraes.AesCtrDecryptSimple;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class RomFsEncryptedProvider implements IRomFsProvider{
|
||||||
|
|
||||||
|
private static long level6Offset;
|
||||||
|
|
||||||
|
private File file;
|
||||||
|
private Level6Header header;
|
||||||
|
|
||||||
|
private FileSystemEntry rootEntry;
|
||||||
|
|
||||||
|
//--------------------------------
|
||||||
|
|
||||||
|
private long romFSoffsetPosition;
|
||||||
|
private byte[] key;
|
||||||
|
private byte[] sectionCTR;
|
||||||
|
private long mediaStartOffset;
|
||||||
|
private long mediaEndOffset;
|
||||||
|
|
||||||
|
public RomFsEncryptedProvider(long romFSoffsetPosition,
|
||||||
|
long level6Offset,
|
||||||
|
File fileWithEncPFS0,
|
||||||
|
byte[] key,
|
||||||
|
byte[] sectionCTR,
|
||||||
|
long mediaStartOffset,
|
||||||
|
long mediaEndOffset
|
||||||
|
) throws Exception{
|
||||||
|
this.file = fileWithEncPFS0;
|
||||||
|
this.level6Offset = level6Offset;
|
||||||
|
this.romFSoffsetPosition = romFSoffsetPosition;
|
||||||
|
this.key = key;
|
||||||
|
this.sectionCTR = sectionCTR;
|
||||||
|
this.mediaStartOffset = mediaStartOffset;
|
||||||
|
this.mediaEndOffset = mediaEndOffset;
|
||||||
|
|
||||||
|
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||||
|
long abosluteOffsetPosition = romFSoffsetPosition + (mediaStartOffset * 0x200);
|
||||||
|
raf.seek(abosluteOffsetPosition + level6Offset);
|
||||||
|
|
||||||
|
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
|
||||||
|
//Go to Level 6 header
|
||||||
|
decryptor.skipNext(level6Offset / 0x200);
|
||||||
|
|
||||||
|
// Decrypt data
|
||||||
|
byte[] encryptedBlock = new byte[0x200];
|
||||||
|
byte[] dectyptedBlock;
|
||||||
|
if (raf.read(encryptedBlock) == 0x200)
|
||||||
|
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||||
|
else
|
||||||
|
throw new Exception("Failed to read header header (0x200 - block)");
|
||||||
|
|
||||||
|
this.header = new Level6Header(dectyptedBlock);
|
||||||
|
|
||||||
|
header.printDebugInfo();
|
||||||
|
|
||||||
|
if (header.getDirectoryMetadataTableLength() < 0)
|
||||||
|
throw new Exception("Not supported: DirectoryMetadataTableLength < 0");
|
||||||
|
|
||||||
|
if (header.getFileMetadataTableLength() < 0)
|
||||||
|
throw new Exception("Not supported: FileMetadataTableLength < 0");
|
||||||
|
|
||||||
|
/*---------------------------------*/
|
||||||
|
|
||||||
|
// Read directories metadata
|
||||||
|
byte[] directoryMetadataTable = readMetaTable(abosluteOffsetPosition,
|
||||||
|
header.getDirectoryMetadataTableOffset(),
|
||||||
|
header.getDirectoryMetadataTableLength(),
|
||||||
|
raf);
|
||||||
|
|
||||||
|
// Read files metadata
|
||||||
|
byte[] fileMetadataTable = readMetaTable(abosluteOffsetPosition,
|
||||||
|
header.getFileMetadataTableOffset(),
|
||||||
|
header.getFileMetadataTableLength(),
|
||||||
|
raf);
|
||||||
|
|
||||||
|
rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable);
|
||||||
|
|
||||||
|
raf.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] readMetaTable(long abosluteOffsetPosition,
|
||||||
|
long metaOffset,
|
||||||
|
long metaSize,
|
||||||
|
RandomAccessFile raf) throws Exception{
|
||||||
|
byte[] encryptedBlock;
|
||||||
|
byte[] dectyptedBlock;
|
||||||
|
byte[] metadataTable = new byte[(int) metaSize];
|
||||||
|
//0
|
||||||
|
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
|
||||||
|
|
||||||
|
long startBlock = metaOffset / 0x200;
|
||||||
|
|
||||||
|
decryptor.skipNext(level6Offset / 0x200 + startBlock);
|
||||||
|
|
||||||
|
raf.seek(abosluteOffsetPosition + level6Offset + startBlock * 0x200);
|
||||||
|
|
||||||
|
//1
|
||||||
|
long ignoreBytes = metaOffset - startBlock * 0x200;
|
||||||
|
long currentPosition = 0;
|
||||||
|
|
||||||
|
if (ignoreBytes > 0) {
|
||||||
|
encryptedBlock = new byte[0x200];
|
||||||
|
if (raf.read(encryptedBlock) == 0x200) {
|
||||||
|
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||||
|
// If we have extra-small file that is less then a block and even more
|
||||||
|
if ((0x200 - ignoreBytes) > metaSize){
|
||||||
|
metadataTable = Arrays.copyOfRange(dectyptedBlock, (int)ignoreBytes, 0x200);
|
||||||
|
return metadataTable;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
System.arraycopy(dectyptedBlock, (int) ignoreBytes, metadataTable, 0, 0x200 - (int) ignoreBytes);
|
||||||
|
currentPosition = 0x200 - ignoreBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from 1st bock for Directory Metadata Table");
|
||||||
|
}
|
||||||
|
startBlock++;
|
||||||
|
}
|
||||||
|
long endBlock = (metaSize + ignoreBytes) / 0x200 + startBlock; // <- pointing to place where any data related to this media-block ends
|
||||||
|
|
||||||
|
//2
|
||||||
|
int extraData = (int) ((endBlock - startBlock)*0x200 - (metaSize + ignoreBytes));
|
||||||
|
|
||||||
|
if (extraData < 0)
|
||||||
|
endBlock--;
|
||||||
|
//3
|
||||||
|
while ( startBlock < endBlock ) {
|
||||||
|
encryptedBlock = new byte[0x200];
|
||||||
|
if (raf.read(encryptedBlock) == 0x200) {
|
||||||
|
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||||
|
System.arraycopy(dectyptedBlock, 0, metadataTable, (int) currentPosition, 0x200);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory Metadata Table");
|
||||||
|
|
||||||
|
startBlock++;
|
||||||
|
currentPosition += 0x200;
|
||||||
|
}
|
||||||
|
|
||||||
|
//4
|
||||||
|
if (extraData != 0){ // In case we didn't get what we want
|
||||||
|
encryptedBlock = new byte[0x200];
|
||||||
|
if (raf.read(encryptedBlock) == 0x200) {
|
||||||
|
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||||
|
System.arraycopy(dectyptedBlock, 0, metadataTable, (int) currentPosition, Math.abs(extraData));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory Metadata Table");
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadataTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Level6Header getHeader() { return header; }
|
||||||
|
@Override
|
||||||
|
public FileSystemEntry getRootEntry() { return rootEntry; }
|
||||||
|
@Override
|
||||||
|
public PipedInputStream getContent(FileSystemEntry entry) throws Exception{
|
||||||
|
if (entry.isDirectory())
|
||||||
|
throw new Exception("Request of the binary stream for the folder entry doesn't make sense.");
|
||||||
|
|
||||||
|
PipedOutputStream streamOut = new PipedOutputStream();
|
||||||
|
Thread workerThread;
|
||||||
|
|
||||||
|
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||||
|
|
||||||
|
workerThread = new Thread(() -> {
|
||||||
|
System.out.println("RomFsDecryptedProvider -> getContent(): Executing thread");
|
||||||
|
try {
|
||||||
|
long subFileRealPosition = level6Offset + header.getFileDataOffset() + entry.getFileOffset();
|
||||||
|
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||||
|
bis.skip(subFileRealPosition);
|
||||||
|
|
||||||
|
int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576
|
||||||
|
|
||||||
|
long readFrom = 0;
|
||||||
|
long realFileSize = entry.getFileSize();
|
||||||
|
|
||||||
|
byte[] readBuf;
|
||||||
|
|
||||||
|
while (readFrom < realFileSize) {
|
||||||
|
if (realFileSize - readFrom < readPice)
|
||||||
|
readPice = Math.toIntExact(realFileSize - readFrom); // it's safe, I guarantee
|
||||||
|
readBuf = new byte[readPice];
|
||||||
|
if (bis.read(readBuf) != readPice) {
|
||||||
|
System.out.println("RomFsDecryptedProvider -> getContent(): Unable to read requested size from file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
streamOut.write(readBuf);
|
||||||
|
readFrom += readPice;
|
||||||
|
}
|
||||||
|
bis.close();
|
||||||
|
streamOut.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("RomFsDecryptedProvider -> getContent(): Unable to provide stream");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
System.out.println("RomFsDecryptedProvider -> getContent(): Thread is dead");
|
||||||
|
});
|
||||||
|
workerThread.start();
|
||||||
|
return streamIn;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public File getFile() {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printDebug(byte[] directoryMetadataTable, byte[] fileMetadataTable){
|
||||||
|
new FolderMeta4Debug(header.getDirectoryMetadataTableLength(), directoryMetadataTable);
|
||||||
|
new FileMeta4Debug(header.getFileMetadataTableLength(), fileMetadataTable);
|
||||||
|
rootEntry.printTreeForDebug();
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,11 +4,7 @@
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.Separator?>
|
<?import javafx.scene.control.Separator?>
|
||||||
<?import javafx.scene.control.SplitPane?>
|
|
||||||
<?import javafx.scene.control.TableView?>
|
|
||||||
<?import javafx.scene.control.TitledPane?>
|
<?import javafx.scene.control.TitledPane?>
|
||||||
<?import javafx.scene.control.TreeView?>
|
|
||||||
<?import javafx.scene.layout.BorderPane?>
|
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<?import javafx.scene.shape.SVGPath?>
|
<?import javafx.scene.shape.SVGPath?>
|
||||||
|
@ -16,19 +12,19 @@
|
||||||
|
|
||||||
<VBox spacing="5.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="konogonka.Controllers.NCA.NCASectionContentController">
|
<VBox spacing="5.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="konogonka.Controllers.NCA.NCASectionContentController">
|
||||||
<children>
|
<children>
|
||||||
<BorderPane>
|
<HBox alignment="TOP_CENTER" spacing="5.0">
|
||||||
<center>
|
<children>
|
||||||
<Button fx:id="extractRawConentBtn" disable="true" mnemonicParsing="false" text="%btnExtract" BorderPane.alignment="CENTER">
|
<Button fx:id="extractRawConentBtn" disable="true" mnemonicParsing="false" text="%btnExtract">
|
||||||
<graphic>
|
<graphic>
|
||||||
<SVGPath content="M2,10.96C1.5,10.68 1.35,10.07 1.63,9.59L3.13,7C3.24,6.8 3.41,6.66 3.6,6.58L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.66,6.72 20.82,6.88 20.91,7.08L22.36,9.6C22.64,10.08 22.47,10.69 22,10.96L21,11.54V16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V10.96C2.7,11.13 2.32,11.14 2,10.96M12,4.15V4.15L12,10.85V10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V12.69L14,15.59C13.67,15.77 13.3,15.76 13,15.6V19.29L19,15.91M13.85,13.36L20.13,9.73L19.55,8.72L13.27,12.35L13.85,13.36Z" />
|
<SVGPath content="M2,10.96C1.5,10.68 1.35,10.07 1.63,9.59L3.13,7C3.24,6.8 3.41,6.66 3.6,6.58L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.66,6.72 20.82,6.88 20.91,7.08L22.36,9.6C22.64,10.08 22.47,10.69 22,10.96L21,11.54V16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V10.96C2.7,11.13 2.32,11.14 2,10.96M12,4.15V4.15L12,10.85V10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V12.69L14,15.59C13.67,15.77 13.3,15.76 13,15.6V19.29L19,15.91M13.85,13.36L20.13,9.73L19.55,8.72L13.27,12.35L13.85,13.36Z" />
|
||||||
</graphic>
|
</graphic>
|
||||||
</Button>
|
</Button>
|
||||||
</center>
|
</children>
|
||||||
<opaqueInsets>
|
<padding>
|
||||||
<Insets />
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
</opaqueInsets>
|
</padding>
|
||||||
</BorderPane>
|
</HBox>
|
||||||
<TitledPane text="PFS0">
|
<TitledPane expanded="false" text="PFS0">
|
||||||
<content>
|
<content>
|
||||||
<VBox spacing="5.0">
|
<VBox spacing="5.0">
|
||||||
<TitledPane expanded="false" text="SHA256 hashes">
|
<TitledPane expanded="false" text="SHA256 hashes">
|
||||||
|
@ -64,19 +60,9 @@
|
||||||
<fx:include fx:id="SectionPFS0" source="../NSP/NSPTab.fxml" />
|
<fx:include fx:id="SectionPFS0" source="../NSP/NSPTab.fxml" />
|
||||||
</VBox>
|
</VBox>
|
||||||
</content></TitledPane>
|
</content></TitledPane>
|
||||||
<TitledPane prefHeight="200.0" prefWidth="200.0" text="'RomFS'">
|
<TitledPane text="'RomFS'" VBox.vgrow="ALWAYS">
|
||||||
<content>
|
<content>
|
||||||
<VBox spacing="5.0">
|
<fx:include fx:id="SectionRomFs" source="../RomFS/RFSTab.fxml" />
|
||||||
<children>
|
|
||||||
<Label text="WIP" />
|
|
||||||
<SplitPane dividerPositions="0.5" prefHeight="160.0" prefWidth="200.0">
|
|
||||||
<items>
|
|
||||||
<TreeView prefHeight="200.0" prefWidth="200.0" />
|
|
||||||
<TableView prefHeight="200.0" prefWidth="200.0" />
|
|
||||||
</items>
|
|
||||||
</SplitPane>
|
|
||||||
</children>
|
|
||||||
</VBox>
|
|
||||||
</content>
|
</content>
|
||||||
</TitledPane>
|
</TitledPane>
|
||||||
</children>
|
</children>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<?import javafx.scene.shape.SVGPath?>
|
<?import javafx.scene.shape.SVGPath?>
|
||||||
<?import javafx.scene.text.Font?>
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
<VBox spacing="5.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="konogonka.Controllers.RFS.RomFsController">
|
<VBox minHeight="600.0" spacing="5.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="konogonka.Controllers.RFS.RomFsController">
|
||||||
<children>
|
<children>
|
||||||
<TitledPane animated="false" expanded="false" text="Header (Level 6)">
|
<TitledPane animated="false" expanded="false" text="Header (Level 6)">
|
||||||
<content>
|
<content>
|
||||||
|
|
Loading…
Reference in a new issue