Refactoring for NCAContent, NCAController.
Add export function to NCA content to get is as it (has to be used to export RomFS blob) + add exporter class to export it in similar to ISuperProvider.class / Extractor.class manner with keeping names agreement. Add custom exception to notify emptiness of the NCA section since using NullPointerException could discourage.
This commit is contained in:
parent
bb3bbc2efc
commit
24d0925dda
11 changed files with 842 additions and 372 deletions
|
@ -25,7 +25,7 @@ import javafx.scene.control.TextField;
|
|||
import konogonka.AppPreferences;
|
||||
import konogonka.Controllers.ITabController;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import konogonka.Tools.NCA.NCAContentPFS0;
|
||||
import konogonka.Tools.NCA.NCAContent;
|
||||
import konogonka.Tools.NCA.NCAProvider;
|
||||
import konogonka.Workers.Analyzer;
|
||||
|
||||
|
@ -224,15 +224,17 @@ public class NCAController implements ITabController {
|
|||
NCASectionHeaderThirdController.populateTab(ncaProvider.getSectionBlock2());
|
||||
NCASectionHeaderFourthController.populateTab(ncaProvider.getSectionBlock3());
|
||||
// Section content blocks
|
||||
// TODO: FIX: This code executes getNCAContentPFS0() method twice
|
||||
NCAContentPFS0 ncaContentPFS0;
|
||||
ncaContentPFS0 = ncaProvider.getNCAContentPFS0(0);
|
||||
NCASectionContentFirstController.populateFields(ncaContentPFS0.getPfs0(), ncaContentPFS0.getSHA256hashes());
|
||||
ncaContentPFS0 = ncaProvider.getNCAContentPFS0(1);
|
||||
NCASectionContentSecondController.populateFields(ncaContentPFS0.getPfs0(), ncaContentPFS0.getSHA256hashes());
|
||||
ncaContentPFS0 = ncaProvider.getNCAContentPFS0(2);
|
||||
NCASectionContentThirdController.populateFields(ncaContentPFS0.getPfs0(), ncaContentPFS0.getSHA256hashes());
|
||||
ncaContentPFS0 = ncaProvider.getNCAContentPFS0(3);
|
||||
NCASectionContentFourthController.populateFields(ncaContentPFS0.getPfs0(), ncaContentPFS0.getSHA256hashes());
|
||||
NCASectionContentFirstController.populateFields(
|
||||
ncaProvider.getNCAContentProvider(0),
|
||||
0);
|
||||
NCASectionContentSecondController.populateFields(
|
||||
ncaProvider.getNCAContentProvider(1),
|
||||
1);
|
||||
NCASectionContentThirdController.populateFields(
|
||||
ncaProvider.getNCAContentProvider(2),
|
||||
2);
|
||||
NCASectionContentFourthController.populateFields(
|
||||
ncaProvider.getNCAContentProvider(3),
|
||||
3);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,39 +19,97 @@
|
|||
package konogonka.Controllers.NCA;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import konogonka.AppPreferences;
|
||||
import konogonka.Controllers.NSP.NSPController;
|
||||
import konogonka.LoperConverter;
|
||||
import konogonka.Tools.PFS0.IPFS0Provider;
|
||||
import konogonka.MediatorControl;
|
||||
import konogonka.Tools.NCA.NCAContent;
|
||||
import konogonka.Workers.DumbNCA3ContentExtractor;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.LinkedList;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public class NCASectionContentController{
|
||||
public class NCASectionContentController implements Initializable {
|
||||
|
||||
private NCAContent ncaContent;
|
||||
private int sectionNumber;
|
||||
|
||||
@FXML
|
||||
private Button extractRawConentBtn;
|
||||
@FXML
|
||||
private NSPController SectionPFS0Controller;
|
||||
@FXML
|
||||
private VBox sha256pane;
|
||||
|
||||
@Override
|
||||
public void initialize(URL url, ResourceBundle resourceBundle) {
|
||||
extractRawConentBtn.setDisable(true);
|
||||
extractRawConentBtn.setOnAction(event -> this.extractFiles());
|
||||
}
|
||||
|
||||
public void resetTab() {
|
||||
SectionPFS0Controller.resetTab();
|
||||
sha256pane.getChildren().clear();
|
||||
extractRawConentBtn.setDisable(true);
|
||||
}
|
||||
|
||||
public void populateFields(IPFS0Provider pfs0, LinkedList<byte[]> sha256hashList) {
|
||||
public void populateFields(NCAContent ncaContent, int sectionNumber) {
|
||||
resetTab();
|
||||
SectionPFS0Controller.setData(pfs0, null);
|
||||
if (sha256hashList != null){
|
||||
for (int i = 0; i < sha256hashList.size(); i++){
|
||||
Label numberLblTmp = new Label(String.format("%10d", i));
|
||||
numberLblTmp.setPadding(new Insets(5.0, 5.0, 5.0, 5.0));
|
||||
Label sha256LblTmp = new Label(LoperConverter.byteArrToHexString(sha256hashList.get(i)));
|
||||
sha256LblTmp.setPadding(new Insets(5.0, 5.0, 5.0, 5.0));
|
||||
|
||||
sha256pane.getChildren().add(new HBox(numberLblTmp, sha256LblTmp));
|
||||
}
|
||||
if (ncaContent == null)
|
||||
return;
|
||||
|
||||
this.ncaContent = ncaContent;
|
||||
this.sectionNumber = sectionNumber;
|
||||
this.extractRawConentBtn.setDisable(false);
|
||||
|
||||
if (ncaContent.getPfs0() != null)
|
||||
SectionPFS0Controller.setData(ncaContent.getPfs0(), null);;
|
||||
|
||||
LinkedList<byte[]> sha256hashList = ncaContent.getPfs0SHA256hashes();
|
||||
|
||||
if (sha256hashList == null)
|
||||
return;
|
||||
for (int i = 0; i < sha256hashList.size(); i++){
|
||||
Label numberLblTmp = new Label(String.format("%10d", i));
|
||||
numberLblTmp.setPadding(new Insets(5.0, 5.0, 5.0, 5.0));
|
||||
Label sha256LblTmp = new Label(LoperConverter.byteArrToHexString(sha256hashList.get(i)));
|
||||
sha256LblTmp.setPadding(new Insets(5.0, 5.0, 5.0, 5.0));
|
||||
|
||||
sha256pane.getChildren().add(new HBox(numberLblTmp, sha256LblTmp));
|
||||
}
|
||||
}
|
||||
|
||||
private void extractFiles(){
|
||||
if (ncaContent == null)
|
||||
return;
|
||||
|
||||
File dir = new File(AppPreferences.getInstance().getExtractFilesDir()+File.separator+ncaContent.getFileName()+" extracted");
|
||||
try {
|
||||
dir.mkdir();
|
||||
}
|
||||
catch (SecurityException se){
|
||||
MediatorControl.getInstance().getContoller().logArea.setText("Can't create dir to store files.");
|
||||
}
|
||||
if (!dir.exists())
|
||||
return;
|
||||
|
||||
extractRawConentBtn.setDisable(true);
|
||||
|
||||
DumbNCA3ContentExtractor extractor = new DumbNCA3ContentExtractor(ncaContent, sectionNumber, dir.getAbsolutePath()+File.separator);
|
||||
extractor.setOnSucceeded(e->{
|
||||
extractRawConentBtn.setDisable(false);
|
||||
});
|
||||
Thread workThread = new Thread(extractor);
|
||||
workThread.setDaemon(true);
|
||||
workThread.start();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -191,14 +191,14 @@ public class NCASectionHeaderBlockController {
|
|||
public void populateTab(NCASectionBlock ncaSectionBlock){
|
||||
versionLbl.setText(byteArrToHexString(ncaSectionBlock.getVersion()));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(String.format("%02x ", ncaSectionBlock.getFsType()));
|
||||
sb.append(String.format("0x%02x ", ncaSectionBlock.getFsType()));
|
||||
if (ncaSectionBlock.getFsType() == 0)
|
||||
sb.append("(RomFS)");
|
||||
else if (ncaSectionBlock.getFsType() == 1)
|
||||
sb.append("(PFS0)");
|
||||
fsTypeLbl.setText(sb.toString());
|
||||
sb = new StringBuilder();
|
||||
sb.append(String.format("%02x ", ncaSectionBlock.getHashType()));
|
||||
sb.append(String.format("0x%02x ", ncaSectionBlock.getHashType()));
|
||||
if (ncaSectionBlock.getHashType() == 0x3)
|
||||
sb.append("(RomFS)");
|
||||
else if (ncaSectionBlock.getHashType() == 0x2)
|
||||
|
@ -212,33 +212,33 @@ public class NCASectionHeaderBlockController {
|
|||
+(ncaSectionBlock.getSuperBlockIVFC().getMagicNumber() == 0x20000? " (OK)":" (wrong magic number)"));
|
||||
romFsMasterHashSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getMasterHashSize()));
|
||||
romFsTotalNumberOfLevelsLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getTotalNumberOfLevels()));
|
||||
romFsLvl1OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl1Offset()));
|
||||
romFsLvl1SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl1Size()));
|
||||
romFsLvl1OffsetLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl1Offset()));
|
||||
romFsLvl1SizeLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl1Size()));
|
||||
romFsLvl1SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl1SBlockSize()));
|
||||
romFsReserved1Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved1()));
|
||||
|
||||
romFsLvl2OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl2Offset()));
|
||||
romFsLvl2SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl2Size()));
|
||||
romFsLvl2OffsetLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl2Offset()));
|
||||
romFsLvl2SizeLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl2Size()));
|
||||
romFsLvl2SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl2SBlockSize()));
|
||||
romFsReserved2Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved2()));
|
||||
|
||||
romFsLvl3OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl3Offset()));
|
||||
romFsLvl3SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl3Size()));
|
||||
romFsLvl3OffsetLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl3Offset()));
|
||||
romFsLvl3SizeLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl3Size()));
|
||||
romFsLvl3SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl3SBlockSize()));
|
||||
romFsReserved3Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved3()));
|
||||
|
||||
romFsLvl4OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl4Offset()));
|
||||
romFsLvl4SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl4Size()));
|
||||
romFsLvl4OffsetLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl4Offset()));
|
||||
romFsLvl4SizeLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl4Size()));
|
||||
romFsLvl4SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl4SBlockSize()));
|
||||
romFsReserved4Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved4()));
|
||||
|
||||
romFsLvl5OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl5Offset()));
|
||||
romFsLvl5SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl5Size()));
|
||||
romFsLvl5OffsetLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl5Offset()));
|
||||
romFsLvl5SizeLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl5Size()));
|
||||
romFsLvl5SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl5SBlockSize()));
|
||||
romFsReserved5Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved5()));
|
||||
|
||||
romFsLvl6OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl6Offset()));
|
||||
romFsLvl6SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl6Size()));
|
||||
romFsLvl6OffsetLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl6Offset()));
|
||||
romFsLvl6SizeLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl6Size()));
|
||||
romFsLvl6SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl6SBlockSize()));
|
||||
romFsReserved6Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved6()));
|
||||
|
||||
|
@ -277,4 +277,8 @@ public class NCASectionHeaderBlockController {
|
|||
sectionCTRLbl.setText(byteArrToHexString(ncaSectionBlock.getSectionCTR()));
|
||||
unknwnEndPaddingTF.setText(byteArrToHexString(ncaSectionBlock.getUnknownEndPadding()));
|
||||
}
|
||||
|
||||
private String getCuteDecHexRepresentation(long value){
|
||||
return String.format("%d (0x%02x)", value, value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
package konogonka.Controllers.NSP;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
|
@ -162,7 +163,10 @@ public class Pfs0TableViewController implements Initializable {
|
|||
}
|
||||
);
|
||||
table.setItems(rowsObsLst);
|
||||
table.getColumns().addAll(numberColumn, fileNameColumn, fileOffsetColumn, fileSizeColumn, uploadColumn);
|
||||
table.getColumns().add(numberColumn);
|
||||
table.getColumns().add(fileNameColumn);
|
||||
table.getColumns().add(fileOffsetColumn);
|
||||
table.getColumns().add(fileSizeColumn);
|
||||
}
|
||||
/**
|
||||
* Add files when user selected them
|
||||
|
|
552
src/main/java/konogonka/Tools/NCA/NCAContent.java
Normal file
552
src/main/java/konogonka/Tools/NCA/NCAContent.java
Normal file
|
@ -0,0 +1,552 @@
|
|||
/*
|
||||
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.NCA;
|
||||
|
||||
import konogonka.LoperConverter;
|
||||
import konogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock;
|
||||
import konogonka.Tools.PFS0.IPFS0Provider;
|
||||
import konogonka.Tools.PFS0.PFS0EncryptedProvider;
|
||||
import konogonka.Tools.PFS0.PFS0Provider;
|
||||
import konogonka.ctraes.AesCtrDecryptSimple;
|
||||
import konogonka.exceptions.EmptySectionException;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.LinkedList;
|
||||
/**
|
||||
* THIS CLASS BECOMES MORE UGLY AFTER EACH ITERATION OF REFACTORING.
|
||||
* TODO: MAKE SOME DECOMPOSITION
|
||||
* */
|
||||
public class NCAContent {
|
||||
private File file;
|
||||
private long offsetPosition;
|
||||
private NCASectionBlock ncaSectionBlock;
|
||||
private NCAHeaderTableEntry ncaHeaderTableEntry;
|
||||
private byte[] decryptedKey;
|
||||
|
||||
private LinkedList<byte[]> Pfs0SHA256hashes;
|
||||
private IPFS0Provider pfs0;
|
||||
|
||||
// TODO: if decryptedKey is empty, throw exception ??
|
||||
public NCAContent(File file,
|
||||
long offsetPosition,
|
||||
NCASectionBlock ncaSectionBlock,
|
||||
NCAHeaderTableEntry ncaHeaderTableEntry,
|
||||
byte[] decryptedKey) throws Exception
|
||||
{
|
||||
this.file = file;
|
||||
this.offsetPosition = offsetPosition;
|
||||
this.ncaSectionBlock = ncaSectionBlock;
|
||||
this.ncaHeaderTableEntry = ncaHeaderTableEntry;
|
||||
this.decryptedKey = decryptedKey;
|
||||
|
||||
Pfs0SHA256hashes = new LinkedList<>();
|
||||
// If nothing to do
|
||||
if (ncaHeaderTableEntry.getMediaEndOffset() == 0)
|
||||
throw new EmptySectionException("Empty section");
|
||||
// If it's PFS0Provider
|
||||
if (ncaSectionBlock.getSuperBlockPFS0() != null)
|
||||
this.proceedPFS0();
|
||||
else if (ncaSectionBlock.getSuperBlockIVFC() != null)
|
||||
this.proceedRomFs();
|
||||
else
|
||||
throw new Exception("NCAContent(): Not supported. PFS0 or RomFS supported only.");
|
||||
}
|
||||
|
||||
private void proceedPFS0() throws Exception {
|
||||
switch (ncaSectionBlock.getCryptoType()){
|
||||
case 0x01:
|
||||
proceedPFS0NotEncrypted(); // IF NO ENCRYPTION
|
||||
break;
|
||||
case 0x03:
|
||||
proceedPFS0Encrypted(); // If encrypted regular [ 0x03 ]
|
||||
break;
|
||||
default:
|
||||
throw new Exception("NCAContent() -> proceedPFS0(): Non-supported 'Crypto type'");
|
||||
}
|
||||
}
|
||||
private void proceedPFS0NotEncrypted() throws Exception{
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
long thisMediaLocation = offsetPosition + (ncaHeaderTableEntry.getMediaStartOffset() * 0x200);
|
||||
long hashTableLocation = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset();
|
||||
long pfs0Location = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getPfs0offset();
|
||||
|
||||
raf.seek(hashTableLocation);
|
||||
|
||||
byte[] rawData;
|
||||
long sha256recordsNumber = ncaSectionBlock.getSuperBlockPFS0().getHashTableSize() / 0x20;
|
||||
// Collect hashes
|
||||
for (int i = 0; i < sha256recordsNumber; i++){
|
||||
rawData = new byte[0x20]; // 32 bytes - size of SHA256 hash
|
||||
if (raf.read(rawData) != -1)
|
||||
Pfs0SHA256hashes.add(rawData);
|
||||
else {
|
||||
raf.close();
|
||||
return; // TODO: fix
|
||||
}
|
||||
}
|
||||
raf.close();
|
||||
// Get pfs0
|
||||
pfs0 = new PFS0Provider(file, pfs0Location);
|
||||
}
|
||||
private void proceedPFS0Encrypted() throws Exception{
|
||||
new CryptoSection03Pfs0(file,
|
||||
offsetPosition,
|
||||
decryptedKey,
|
||||
ncaSectionBlock,
|
||||
ncaHeaderTableEntry.getMediaStartOffset(),
|
||||
ncaHeaderTableEntry.getMediaEndOffset());
|
||||
}
|
||||
|
||||
private void proceedRomFs() throws Exception{
|
||||
switch (ncaSectionBlock.getCryptoType()){
|
||||
case 0x01:
|
||||
proceedRomFsNotEncrypted(); // IF NO ENCRYPTION
|
||||
break;
|
||||
case 0x03:
|
||||
proceedRomFsEncrypted(); // If encrypted regular [ 0x03 ]
|
||||
break;
|
||||
default:
|
||||
throw new Exception("NCAContent() -> proceedRomFs(): Non-supported 'Crypto type'");
|
||||
}
|
||||
}
|
||||
private void proceedRomFsNotEncrypted(){
|
||||
// TODO: Clarify, implement if needed
|
||||
System.out.println("proceedRomFs() -> proceedRomFsNotEncrypted() is not implemented :(");
|
||||
}
|
||||
private void proceedRomFsEncrypted() throws Exception{
|
||||
new CryptoSection03RomFS(file,
|
||||
offsetPosition,
|
||||
decryptedKey,
|
||||
ncaSectionBlock,
|
||||
ncaHeaderTableEntry.getMediaStartOffset(),
|
||||
ncaHeaderTableEntry.getMediaEndOffset());
|
||||
}
|
||||
|
||||
public LinkedList<byte[]> getPfs0SHA256hashes() { return Pfs0SHA256hashes; }
|
||||
public IPFS0Provider getPfs0() { return pfs0; }
|
||||
|
||||
private class CryptoSection03Pfs0 {
|
||||
|
||||
CryptoSection03Pfs0(File file,
|
||||
long offsetPosition,
|
||||
byte[] decryptedKey,
|
||||
NCASectionBlock ncaSectionBlock,
|
||||
long mediaStartBlocksOffset,
|
||||
long mediaEndBlocksOffset) throws Exception
|
||||
{
|
||||
/*//--------------------------------------------------------------------------------------------------
|
||||
System.out.println("Media start location: " + mediaStartBlocksOffset);
|
||||
System.out.println("Media end location: " + mediaEndBlocksOffset);
|
||||
System.out.println("Media size : " + (mediaEndBlocksOffset-mediaStartBlocksOffset));
|
||||
System.out.println("Media act. location: " + (offsetPosition + (mediaStartBlocksOffset * 0x200)));
|
||||
System.out.println("SHA256 hash tbl size: " + ncaSectionBlock.getSuperBlockPFS0().getHashTableSize());
|
||||
System.out.println("SHA256 hash tbl offs: " + ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset());
|
||||
System.out.println("PFS0 Offs: " + ncaSectionBlock.getSuperBlockPFS0().getPfs0offset());
|
||||
System.out.println("SHA256 records: " + (ncaSectionBlock.getSuperBlockPFS0().getHashTableSize() / 0x20));
|
||||
System.out.println("KEY: " + LoperConverter.byteArrToHexString(decryptedKey));
|
||||
System.out.println("CTR: " + LoperConverter.byteArrToHexString(ncaSectionBlock.getSectionCTR()));
|
||||
System.out.println();
|
||||
//--------------------------------------------------------------------------------------------------*/
|
||||
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 ParseThread(
|
||||
streamInp,
|
||||
ncaSectionBlock.getSuperBlockPFS0().getPfs0offset(),
|
||||
ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset(),
|
||||
ncaSectionBlock.getSuperBlockPFS0().getHashTableSize(),
|
||||
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 = aesCtr.decrypt(encryptedBlock);
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
// Writing decrypted data to pipe
|
||||
try {
|
||||
streamOut.write(dectyptedBlock);
|
||||
}
|
||||
catch (IOException e){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
pThread.join();
|
||||
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 ParseThread implements Runnable{
|
||||
|
||||
PipedInputStream pipedInputStream;
|
||||
|
||||
long hashTableOffset;
|
||||
long hashTableSize;
|
||||
long hashTableRecordsCount;
|
||||
long pfs0offset;
|
||||
|
||||
private long MetaOffsetPositionInFile;
|
||||
private File MetaFileWithEncPFS0;
|
||||
private byte[] MetaKey;
|
||||
private byte[] MetaSectionCTR;
|
||||
private long MetaMediaStartOffset;
|
||||
private long MetaMediaEndOffset;
|
||||
|
||||
|
||||
ParseThread(PipedInputStream pipedInputStream,
|
||||
long pfs0offset,
|
||||
long hashTableOffset,
|
||||
long hashTableSize,
|
||||
|
||||
long MetaOffsetPositionInFile,
|
||||
File MetaFileWithEncPFS0,
|
||||
byte[] MetaKey,
|
||||
byte[] MetaSectionCTR,
|
||||
long MetaMediaStartOffset,
|
||||
long MetaMediaEndOffset
|
||||
){
|
||||
this.pipedInputStream = pipedInputStream;
|
||||
this.hashTableOffset = hashTableOffset;
|
||||
this.hashTableSize = hashTableSize;
|
||||
this.hashTableRecordsCount = hashTableSize / 0x20;
|
||||
this.pfs0offset = pfs0offset;
|
||||
|
||||
this.MetaOffsetPositionInFile = MetaOffsetPositionInFile;
|
||||
this.MetaFileWithEncPFS0 = MetaFileWithEncPFS0;
|
||||
this.MetaKey = MetaKey;
|
||||
this.MetaSectionCTR = MetaSectionCTR;
|
||||
this.MetaMediaStartOffset = MetaMediaStartOffset;
|
||||
this.MetaMediaEndOffset = MetaMediaEndOffset;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
long counter = 0; // How many bytes already read
|
||||
|
||||
try{
|
||||
if (hashTableOffset > 0){
|
||||
if (hashTableOffset != pipedInputStream.skip(hashTableOffset))
|
||||
return; // TODO: fix?
|
||||
counter = hashTableOffset;
|
||||
}
|
||||
// Loop for collecting all recrods from sha256 hash table
|
||||
while ((counter - hashTableOffset) < hashTableSize){
|
||||
int hashCounter = 0;
|
||||
byte[] sectionHash = new byte[0x20];
|
||||
// Loop for collecting bytes for every SINGLE records, where record size == 0x20
|
||||
while (hashCounter < 0x20){
|
||||
int currentByte = pipedInputStream.read();
|
||||
if (currentByte == -1)
|
||||
break;
|
||||
sectionHash[hashCounter] = (byte)currentByte;
|
||||
hashCounter++;
|
||||
counter++;
|
||||
}
|
||||
// Write after collecting
|
||||
Pfs0SHA256hashes.add(sectionHash); // From the NCAContentProvider obviously
|
||||
}
|
||||
// Skip padding and go to PFS0 location
|
||||
if (counter < pfs0offset){
|
||||
long toSkip = pfs0offset-counter;
|
||||
if (toSkip != pipedInputStream.skip(toSkip))
|
||||
return; // TODO: fix?
|
||||
counter += toSkip;
|
||||
}
|
||||
//---------------------------------------------------------
|
||||
pfs0 = new PFS0EncryptedProvider(pipedInputStream, counter,
|
||||
MetaOffsetPositionInFile,
|
||||
MetaFileWithEncPFS0,
|
||||
MetaKey,
|
||||
MetaSectionCTR,
|
||||
MetaMediaStartOffset,
|
||||
MetaMediaEndOffset);
|
||||
pipedInputStream.close();
|
||||
}
|
||||
catch (Exception e){
|
||||
System.out.println("'ParseThread' thread exception");
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally {
|
||||
System.out.println("Thread dies");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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.
|
||||
* Not so good for PFS0 since there are SHAs list that discourages but good for 'romfs' and things like that
|
||||
* */
|
||||
public PipedInputStream getRawDataContentPipedInpStream() throws Exception {
|
||||
long mediaStartBlocksOffset = ncaHeaderTableEntry.getMediaStartOffset();
|
||||
long mediaEndBlocksOffset = ncaHeaderTableEntry.getMediaEndOffset();
|
||||
long mediaBlocksSize = mediaEndBlocksOffset - mediaStartBlocksOffset;
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
///--------------------------------------------------------------------------------------------------
|
||||
System.out.println("NCAContent() -> exportEncryptedSectionType03() Debug information");
|
||||
System.out.println("Media start location: " + mediaStartBlocksOffset);
|
||||
System.out.println("Media end location: " + mediaEndBlocksOffset);
|
||||
System.out.println("Media size : " + (mediaEndBlocksOffset-mediaStartBlocksOffset));
|
||||
System.out.println("Media act. location: " + (offsetPosition + (mediaStartBlocksOffset * 0x200)));
|
||||
System.out.println("KEY: " + LoperConverter.byteArrToHexString(decryptedKey));
|
||||
System.out.println("CTR: " + LoperConverter.byteArrToHexString(ncaSectionBlock.getSectionCTR()));
|
||||
System.out.println();
|
||||
//---------------------------------------------------------------------------------------------------/
|
||||
|
||||
if (ncaSectionBlock.getCryptoType() == 0x01){
|
||||
System.out.println("NCAContent -> getRawDataContentPipedInpStream (Zero encryption section type 01): Thread started");
|
||||
|
||||
Thread workerThread;
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
workerThread = new Thread(() -> {
|
||||
try {
|
||||
byte[] rawDataBlock;
|
||||
for (int i = 0; i < mediaBlocksSize; i++){
|
||||
rawDataBlock = new byte[0x200];
|
||||
if (raf.read(rawDataBlock) != -1)
|
||||
streamOut.write(rawDataBlock);
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception e){
|
||||
System.out.println("NCAContent -> exportRawData(): "+e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
raf.close();
|
||||
}catch (Exception ignored) {}
|
||||
try {
|
||||
streamOut.close();
|
||||
}catch (Exception ignored) {}
|
||||
}
|
||||
System.out.println("NCAContent -> exportRawData(): Thread died");
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
else if (ncaSectionBlock.getCryptoType() == 0x03){
|
||||
System.out.println("NCAContent -> getRawDataContentPipedInpStream (Encrypted Section Type 03): Thread started");
|
||||
|
||||
if (decryptedKey == null)
|
||||
throw new Exception("NCAContent -> exportRawData(): unable to proceed. No decrypted key provided.");
|
||||
|
||||
Thread workerThread;
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
workerThread = new Thread(() -> {
|
||||
try {
|
||||
//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;
|
||||
|
||||
// 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
|
||||
streamOut.write(dectyptedBlock);
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception e){
|
||||
System.out.println("NCAContent -> exportRawData(): "+e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
raf.close();
|
||||
}catch (Exception ignored) {}
|
||||
try {
|
||||
streamOut.close();
|
||||
}catch (Exception ignored) {}
|
||||
}
|
||||
System.out.println("NCAContent -> exportRawData(): Thread died");
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
public long getRawDataContentSize(){
|
||||
return (ncaHeaderTableEntry.getMediaEndOffset() - ncaHeaderTableEntry.getMediaStartOffset()) * 0x200;
|
||||
}
|
||||
public String getFileName(){
|
||||
return file.getName();
|
||||
}
|
||||
}
|
|
@ -1,283 +0,0 @@
|
|||
/*
|
||||
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.NCA;
|
||||
|
||||
import konogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock;
|
||||
import konogonka.Tools.PFS0.IPFS0Provider;
|
||||
import konogonka.Tools.PFS0.PFS0EncryptedProvider;
|
||||
import konogonka.Tools.PFS0.PFS0Provider;
|
||||
import konogonka.ctraes.AesCtrDecryptSimple;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class NCAContentPFS0 {
|
||||
private LinkedList<byte[]> SHA256hashes;
|
||||
private IPFS0Provider pfs0;
|
||||
|
||||
// TODO: if decryptedKey is empty, thorow exception ??
|
||||
public NCAContentPFS0(File file, long offsetPosition, NCASectionBlock ncaSectionBlock, NCAHeaderTableEntry ncaHeaderTableEntry, byte[] decryptedKey){
|
||||
SHA256hashes = new LinkedList<>();
|
||||
// If it's PFS0Provider
|
||||
if (ncaSectionBlock.getSuperBlockPFS0() != null){
|
||||
try {
|
||||
// IF NO ENCRYPTION
|
||||
if (ncaSectionBlock.getCryptoType() == 0x1) {
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
long thisMediaLocation = offsetPosition + (ncaHeaderTableEntry.getMediaStartOffset() * 0x200);
|
||||
long hashTableLocation = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset();
|
||||
long pfs0Location = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getPfs0offset();
|
||||
|
||||
raf.seek(hashTableLocation);
|
||||
|
||||
byte[] rawData;
|
||||
long sha256recordsNumber = ncaSectionBlock.getSuperBlockPFS0().getHashTableSize() / 0x20;
|
||||
// Collect hashes
|
||||
for (int i = 0; i < sha256recordsNumber; i++){
|
||||
rawData = new byte[0x20]; // 32 bytes - size of SHA256 hash
|
||||
if (raf.read(rawData) != -1)
|
||||
SHA256hashes.add(rawData);
|
||||
else {
|
||||
raf.close();
|
||||
return; // TODO: fix
|
||||
}
|
||||
}
|
||||
raf.close();
|
||||
// Get pfs0
|
||||
pfs0 = new PFS0Provider(file, pfs0Location);
|
||||
}
|
||||
// If encrypted regular [ 0x03 ]
|
||||
else if (ncaSectionBlock.getCryptoType() == 0x03){
|
||||
new CryptoSection03(file,
|
||||
offsetPosition,
|
||||
decryptedKey,
|
||||
ncaSectionBlock,
|
||||
ncaHeaderTableEntry.getMediaStartOffset(),
|
||||
ncaHeaderTableEntry.getMediaEndOffset());
|
||||
}
|
||||
}
|
||||
catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
else if (ncaSectionBlock.getSuperBlockIVFC() != null){
|
||||
// TODO
|
||||
}
|
||||
else {
|
||||
return; // TODO: FIX THIS STUFF
|
||||
}
|
||||
}
|
||||
|
||||
public LinkedList<byte[]> getSHA256hashes() { return SHA256hashes; }
|
||||
public IPFS0Provider getPfs0() { return pfs0; }
|
||||
|
||||
private class CryptoSection03{
|
||||
|
||||
CryptoSection03(File file, long offsetPosition, byte[] decryptedKey, NCASectionBlock ncaSectionBlock, long mediaStartBlocksOffset, long mediaEndBlocksOffset) throws Exception{
|
||||
/*//--------------------------------------------------------------------------------------------------
|
||||
System.out.println("Media start location: " + mediaStartBlocksOffset);
|
||||
System.out.println("Media end location: " + mediaEndBlocksOffset);
|
||||
System.out.println("Media size : " + (mediaEndBlocksOffset-mediaStartBlocksOffset));
|
||||
System.out.println("Media act. location: " + (offsetPosition + (mediaStartBlocksOffset * 0x200)));
|
||||
System.out.println("SHA256 hash tbl size: " + ncaSectionBlock.getSuperBlockPFS0().getHashTableSize());
|
||||
System.out.println("SHA256 hash tbl offs: " + ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset());
|
||||
System.out.println("PFS0 Offs: " + ncaSectionBlock.getSuperBlockPFS0().getPfs0offset());
|
||||
System.out.println("SHA256 records: " + (ncaSectionBlock.getSuperBlockPFS0().getHashTableSize() / 0x20));
|
||||
System.out.println("KEY: " + LoperConverter.byteArrToHexString(decryptedKey));
|
||||
System.out.println("CTR: " + LoperConverter.byteArrToHexString(ncaSectionBlock.getSectionCTR()));
|
||||
System.out.println();
|
||||
//--------------------------------------------------------------------------------------------------*/
|
||||
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 ParseThread(
|
||||
streamInp,
|
||||
ncaSectionBlock.getSuperBlockPFS0().getPfs0offset(),
|
||||
ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset(),
|
||||
ncaSectionBlock.getSuperBlockPFS0().getHashTableSize(),
|
||||
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 = aesCtr.decrypt(encryptedBlock);
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
// Writing decrypted data to pipe
|
||||
try {
|
||||
streamOut.write(dectyptedBlock);
|
||||
}
|
||||
catch (IOException e){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
pThread.join();
|
||||
streamOut.close();
|
||||
raf.close();
|
||||
//****************************************___DEBUG___*******************************************************
|
||||
/*
|
||||
File contentFile = new File("/tmp/decryptedNCA0block_"+offsetPosition+".pfs0");
|
||||
BufferedOutputStream extractedFileOS = new BufferedOutputStream(new FileOutputStream(contentFile));
|
||||
|
||||
raf = new RandomAccessFile(file, "r");
|
||||
raf.seek(abosluteOffsetPosition);
|
||||
decryptor = new AesCtrDecryptSimple(decryptedKey, ncaSectionBlock.getSectionCTR(), mediaStartBlocksOffset * 0x200);
|
||||
|
||||
for (int i = 0; i < mediaBlocksSize; i++){
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (raf.read(encryptedBlock) != -1){
|
||||
//dectyptedBlock = aesCtr.decrypt(encryptedBlock);
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
// Writing decrypted data to pipe
|
||||
try {
|
||||
extractedFileOS.write(dectyptedBlock);
|
||||
}
|
||||
catch (IOException e){
|
||||
System.out.println("Exception @extract");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
extractedFileOS.close();
|
||||
raf.close();
|
||||
System.out.println("@extract done");
|
||||
//*//******************************************************************************************************/
|
||||
}
|
||||
/*
|
||||
* Since we representing decrypted data as stream (it's easier to look on it this way),
|
||||
* this thread will be parsing it.
|
||||
* */
|
||||
private class ParseThread implements Runnable{
|
||||
|
||||
PipedInputStream pipedInputStream;
|
||||
|
||||
long hashTableOffset;
|
||||
long hashTableSize;
|
||||
long hashTableRecordsCount;
|
||||
long pfs0offset;
|
||||
|
||||
private long MetaOffsetPositionInFile;
|
||||
private File MetaFileWithEncPFS0;
|
||||
private byte[] MetaKey;
|
||||
private byte[] MetaSectionCTR;
|
||||
private long MetaMediaStartOffset;
|
||||
private long MetaMediaEndOffset;
|
||||
|
||||
|
||||
ParseThread(PipedInputStream pipedInputStream,
|
||||
long pfs0offset,
|
||||
long hashTableOffset,
|
||||
long hashTableSize,
|
||||
|
||||
long MetaOffsetPositionInFile,
|
||||
File MetaFileWithEncPFS0,
|
||||
byte[] MetaKey,
|
||||
byte[] MetaSectionCTR,
|
||||
long MetaMediaStartOffset,
|
||||
long MetaMediaEndOffset
|
||||
){
|
||||
this.pipedInputStream = pipedInputStream;
|
||||
this.hashTableOffset = hashTableOffset;
|
||||
this.hashTableSize = hashTableSize;
|
||||
this.hashTableRecordsCount = hashTableSize / 0x20;
|
||||
this.pfs0offset = pfs0offset;
|
||||
|
||||
this.MetaOffsetPositionInFile = MetaOffsetPositionInFile;
|
||||
this.MetaFileWithEncPFS0 = MetaFileWithEncPFS0;
|
||||
this.MetaKey = MetaKey;
|
||||
this.MetaSectionCTR = MetaSectionCTR;
|
||||
this.MetaMediaStartOffset = MetaMediaStartOffset;
|
||||
this.MetaMediaEndOffset = MetaMediaEndOffset;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
long counter = 0; // How many bytes already read
|
||||
|
||||
try{
|
||||
if (hashTableOffset > 0){
|
||||
if (hashTableOffset != pipedInputStream.skip(hashTableOffset))
|
||||
return; // TODO: fix?
|
||||
counter = hashTableOffset;
|
||||
}
|
||||
// Loop for collecting all recrods from sha256 hash table
|
||||
while ((counter - hashTableOffset) < hashTableSize){
|
||||
int hashCounter = 0;
|
||||
byte[] sectionHash = new byte[0x20];
|
||||
// Loop for collecting bytes for every SINGLE records, where record size == 0x20
|
||||
while (hashCounter < 0x20){
|
||||
int currentByte = pipedInputStream.read();
|
||||
if (currentByte == -1)
|
||||
break;
|
||||
sectionHash[hashCounter] = (byte)currentByte;
|
||||
hashCounter++;
|
||||
counter++;
|
||||
}
|
||||
// Write after collecting
|
||||
SHA256hashes.add(sectionHash); // From the NCAContentProvider obviously
|
||||
}
|
||||
// Skip padding and go to PFS0 location
|
||||
if (counter < pfs0offset){
|
||||
long toSkip = pfs0offset-counter;
|
||||
if (toSkip != pipedInputStream.skip(toSkip))
|
||||
return; // TODO: fix?
|
||||
counter += toSkip;
|
||||
}
|
||||
//---------------------------------------------------------
|
||||
pfs0 = new PFS0EncryptedProvider(pipedInputStream, counter,
|
||||
MetaOffsetPositionInFile,
|
||||
MetaFileWithEncPFS0,
|
||||
MetaKey,
|
||||
MetaSectionCTR,
|
||||
MetaMediaStartOffset,
|
||||
MetaMediaEndOffset);
|
||||
pipedInputStream.close();
|
||||
}
|
||||
catch (Exception e){
|
||||
System.out.println("'ParseThread' thread exception");
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally {
|
||||
System.out.println("Thread dies");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@
|
|||
package konogonka.Tools.NCA;
|
||||
|
||||
import konogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock;
|
||||
import konogonka.exceptions.EmptySectionException;
|
||||
import konogonka.xtsaes.XTSAESCipher;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
|
@ -81,6 +82,11 @@ public class NCAProvider {
|
|||
private NCASectionBlock sectionBlock2;
|
||||
private NCASectionBlock sectionBlock3;
|
||||
|
||||
private NCAContent ncaContent0;
|
||||
private NCAContent ncaContent1;
|
||||
private NCAContent ncaContent2;
|
||||
private NCAContent ncaContent3;
|
||||
|
||||
public NCAProvider(File file, HashMap<String, String> keys) throws Exception{
|
||||
this(file, keys, 0);
|
||||
}
|
||||
|
@ -126,6 +132,7 @@ public class NCAProvider {
|
|||
|
||||
raf.close();
|
||||
|
||||
getNCAContent();
|
||||
/*
|
||||
//---------------------------------------------------------------------
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
|
@ -215,27 +222,8 @@ public class NCAProvider {
|
|||
decryptedKey2 = cipher.doFinal(encryptedKey2);
|
||||
decryptedKey3 = cipher.doFinal(encryptedKey3);
|
||||
}
|
||||
else{
|
||||
StringBuilder exceptionStringBuilder = new StringBuilder("key_area_key_");
|
||||
switch (keyIndex){
|
||||
case 0:
|
||||
exceptionStringBuilder.append("application_");
|
||||
break;
|
||||
case 1:
|
||||
exceptionStringBuilder.append("ocean_");
|
||||
break;
|
||||
case 2:
|
||||
exceptionStringBuilder.append("system_");
|
||||
break;
|
||||
default:
|
||||
exceptionStringBuilder.append(keyIndex);
|
||||
exceptionStringBuilder.append("[UNKNOWN]_");
|
||||
}
|
||||
exceptionStringBuilder.append(String.format("%02x", cryptoTypeReal));
|
||||
exceptionStringBuilder.append(" requested. Not supported or not found.");
|
||||
|
||||
throw new Exception(exceptionStringBuilder.toString());
|
||||
}
|
||||
else
|
||||
keyAreaKeyNotSupportedOrFound();
|
||||
}
|
||||
|
||||
tableEntry0 = new NCAHeaderTableEntry(tableBytes);
|
||||
|
@ -249,6 +237,85 @@ public class NCAProvider {
|
|||
sectionBlock3 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0xa00, 0xc00));
|
||||
}
|
||||
|
||||
private void keyAreaKeyNotSupportedOrFound() throws Exception{
|
||||
StringBuilder exceptionStringBuilder = new StringBuilder("key_area_key_");
|
||||
switch (keyIndex){
|
||||
case 0:
|
||||
exceptionStringBuilder.append("application_");
|
||||
break;
|
||||
case 1:
|
||||
exceptionStringBuilder.append("ocean_");
|
||||
break;
|
||||
case 2:
|
||||
exceptionStringBuilder.append("system_");
|
||||
break;
|
||||
default:
|
||||
exceptionStringBuilder.append(keyIndex);
|
||||
exceptionStringBuilder.append("[UNKNOWN]_");
|
||||
}
|
||||
exceptionStringBuilder.append(String.format("%02x", cryptoTypeReal));
|
||||
exceptionStringBuilder.append(" requested. Not supported or not found.");
|
||||
throw new Exception(exceptionStringBuilder.toString());
|
||||
}
|
||||
|
||||
private void getNCAContent(){
|
||||
byte[] key;
|
||||
|
||||
// If empty Rights ID
|
||||
if (Arrays.equals(rightsId, new byte[0x10])) {
|
||||
key = decryptedKey2; // TODO: Just remember this dumb hack
|
||||
}
|
||||
else {
|
||||
try {
|
||||
byte[] rightsIDkey = hexStrToByteArray(keys.get(byteArrToHexString(rightsId))); // throws NullPointerException
|
||||
|
||||
SecretKeySpec skSpec = new SecretKeySpec(
|
||||
hexStrToByteArray(keys.get(String.format("titlekek_%02x", cryptoTypeReal))
|
||||
), "AES");
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, skSpec);
|
||||
key = cipher.doFinal(rightsIDkey);
|
||||
}
|
||||
catch (Exception e){
|
||||
e.printStackTrace();
|
||||
System.out.println("No title.keys loaded?");
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
this.ncaContent0 = new NCAContent(file, offset, sectionBlock0, tableEntry0, key);
|
||||
}
|
||||
catch (EmptySectionException ignored){}
|
||||
catch (Exception e){
|
||||
this.ncaContent0 = null;
|
||||
e.printStackTrace();
|
||||
}
|
||||
try{
|
||||
this.ncaContent1 = new NCAContent(file, offset, sectionBlock1, tableEntry1, key);
|
||||
}
|
||||
catch (EmptySectionException ignored){}
|
||||
catch (Exception e){
|
||||
this.ncaContent1 = null;
|
||||
e.printStackTrace();
|
||||
}
|
||||
try{
|
||||
this.ncaContent2 = new NCAContent(file, offset, sectionBlock2, tableEntry2, key);
|
||||
}
|
||||
catch (EmptySectionException ignored){}
|
||||
catch (Exception e){
|
||||
this.ncaContent2 = null;
|
||||
e.printStackTrace();
|
||||
}
|
||||
try{
|
||||
this.ncaContent3 = new NCAContent(file, offset, sectionBlock3, tableEntry3, key);
|
||||
}
|
||||
catch (EmptySectionException ignored){}
|
||||
catch (Exception e){
|
||||
this.ncaContent3 = null;
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getRsa2048one() { return rsa2048one; }
|
||||
public byte[] getRsa2048two() { return rsa2048two; }
|
||||
public String getMagicnum() { return magicnum; }
|
||||
|
@ -298,41 +365,18 @@ public class NCAProvider {
|
|||
}
|
||||
/**
|
||||
* Get content for the selected section
|
||||
* @param sectionNumber should be 1-4
|
||||
* @param sectionNumber should be 0-3
|
||||
* */
|
||||
public NCAContentPFS0 getNCAContentPFS0(int sectionNumber){
|
||||
byte[] key;
|
||||
|
||||
// If empty Rights ID
|
||||
if (Arrays.equals(rightsId, new byte[0x10])) {
|
||||
key = decryptedKey2; // TODO: Just remember this dumb hack
|
||||
}
|
||||
else {
|
||||
try {
|
||||
byte[] rightsIDkey = hexStrToByteArray(keys.get(byteArrToHexString(rightsId))); // throws NullPointerException
|
||||
|
||||
SecretKeySpec skSpec = new SecretKeySpec(
|
||||
hexStrToByteArray(keys.get(String.format("titlekek_%02x", cryptoTypeReal))
|
||||
), "AES");
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, skSpec);
|
||||
key = cipher.doFinal(rightsIDkey);
|
||||
}
|
||||
catch (Exception e){
|
||||
e.printStackTrace();
|
||||
System.out.println("No title.keys loaded?");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public NCAContent getNCAContentProvider(int sectionNumber){
|
||||
switch (sectionNumber) {
|
||||
case 0:
|
||||
return new NCAContentPFS0(file, offset, sectionBlock0, tableEntry0, key); // TODO: remove decryptedKey2 ?
|
||||
return ncaContent0;
|
||||
case 1:
|
||||
return new NCAContentPFS0(file, offset, sectionBlock1, tableEntry1, key);
|
||||
return ncaContent1;
|
||||
case 2:
|
||||
return new NCAContentPFS0(file, offset, sectionBlock2, tableEntry2, key);
|
||||
return ncaContent2;
|
||||
case 3:
|
||||
return new NCAContentPFS0(file, offset, sectionBlock3, tableEntry3, key);
|
||||
return ncaContent3;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
package konogonka.Workers;
|
||||
|
||||
import javafx.concurrent.Task;
|
||||
import konogonka.ModelControllers.EMsgType;
|
||||
import konogonka.ModelControllers.LogPrinter;
|
||||
import konogonka.Tools.NCA.NCAContent;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.PipedInputStream;
|
||||
|
||||
public class DumbNCA3ContentExtractor extends Task<Void> {
|
||||
|
||||
private NCAContent ncaContent;
|
||||
private int ncaNumberInFile;
|
||||
private LogPrinter logPrinter;
|
||||
private String filesDestPath;
|
||||
|
||||
public DumbNCA3ContentExtractor(NCAContent ncaContent, int ncaNumberInFile, String filesDestPath){
|
||||
this.ncaContent = ncaContent;
|
||||
this.ncaNumberInFile = ncaNumberInFile;
|
||||
this.filesDestPath = filesDestPath;
|
||||
this.logPrinter = new LogPrinter();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void call() {
|
||||
logPrinter.print("\tStart dummy extracting: \n"+filesDestPath+"NCAContent_"+ncaNumberInFile+".bin", EMsgType.INFO);
|
||||
File contentFile = new File(filesDestPath + "NCAContent_"+ncaNumberInFile+".bin");
|
||||
try {
|
||||
BufferedOutputStream extractedFileBOS = new BufferedOutputStream(new FileOutputStream(contentFile));
|
||||
PipedInputStream pis = ncaContent.getRawDataContentPipedInpStream();
|
||||
|
||||
byte[] readBuf = new byte[0x200]; // 8mb NOTE: consider switching to 1mb 1048576
|
||||
int readSize;
|
||||
//*** PROGRESS BAR VARS START
|
||||
long progressHandleFSize = ncaContent.getRawDataContentSize();
|
||||
int progressHandleFRead = 0;
|
||||
//*** PROGRESS BAR VARS END
|
||||
while ((readSize = pis.read(readBuf)) > -1) {
|
||||
extractedFileBOS.write(readBuf, 0, readSize);
|
||||
readBuf = new byte[0x200];
|
||||
//*** PROGRESS BAR DECORCATIONS START
|
||||
progressHandleFRead += readSize;
|
||||
System.out.println(readSize);
|
||||
try {
|
||||
logPrinter.updateProgress((progressHandleFRead)/(progressHandleFSize/100.0) / 100.0);
|
||||
}catch (InterruptedException ignore){}
|
||||
//*** PROGRESS BAR DECORCATIONS END
|
||||
}
|
||||
try {
|
||||
logPrinter.updateProgress(1.0);
|
||||
}
|
||||
catch (InterruptedException ignored){}
|
||||
extractedFileBOS.close();
|
||||
|
||||
} catch (Exception ioe) {
|
||||
logPrinter.print("\tExtracting dummy issue\n\t" + ioe.getMessage(), EMsgType.INFO);
|
||||
return null;
|
||||
} finally {
|
||||
logPrinter.print("\tEnd dummy extracting", EMsgType.INFO);
|
||||
logPrinter.close();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package konogonka.exceptions;
|
||||
|
||||
public class EmptySectionException extends NullPointerException {
|
||||
public EmptySectionException(String message){
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -1,15 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.Separator?>
|
||||
<?import javafx.scene.control.TitledPane?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.SVGPath?>
|
||||
<?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.NCA.NCASectionContentController">
|
||||
<children>
|
||||
<BorderPane>
|
||||
<center>
|
||||
<Button fx:id="extractRawConentBtn" disable="true" mnemonicParsing="false" text="%btnExtract" BorderPane.alignment="CENTER">
|
||||
<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" />
|
||||
</graphic>
|
||||
</Button>
|
||||
</center>
|
||||
<opaqueInsets>
|
||||
<Insets />
|
||||
</opaqueInsets>
|
||||
</BorderPane>
|
||||
<TitledPane text="PFS0">
|
||||
<content>
|
||||
<VBox spacing="5.0">
|
||||
|
|
|
@ -1670,7 +1670,7 @@
|
|||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="1" GridPane.rowIndex="9">
|
||||
<children>
|
||||
<Label text="0xB8" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<Label text="0x88" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
<Insets left="5.0" right="5.0" />
|
||||
</padding>
|
||||
|
|
Loading…
Reference in a new issue