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.AppPreferences;
|
||||||
import konogonka.Controllers.ITabController;
|
import konogonka.Controllers.ITabController;
|
||||||
import konogonka.Tools.ISuperProvider;
|
import konogonka.Tools.ISuperProvider;
|
||||||
import konogonka.Tools.NCA.NCAContentPFS0;
|
import konogonka.Tools.NCA.NCAContent;
|
||||||
import konogonka.Tools.NCA.NCAProvider;
|
import konogonka.Tools.NCA.NCAProvider;
|
||||||
import konogonka.Workers.Analyzer;
|
import konogonka.Workers.Analyzer;
|
||||||
|
|
||||||
|
@ -224,15 +224,17 @@ public class NCAController implements ITabController {
|
||||||
NCASectionHeaderThirdController.populateTab(ncaProvider.getSectionBlock2());
|
NCASectionHeaderThirdController.populateTab(ncaProvider.getSectionBlock2());
|
||||||
NCASectionHeaderFourthController.populateTab(ncaProvider.getSectionBlock3());
|
NCASectionHeaderFourthController.populateTab(ncaProvider.getSectionBlock3());
|
||||||
// Section content blocks
|
// Section content blocks
|
||||||
// TODO: FIX: This code executes getNCAContentPFS0() method twice
|
NCASectionContentFirstController.populateFields(
|
||||||
NCAContentPFS0 ncaContentPFS0;
|
ncaProvider.getNCAContentProvider(0),
|
||||||
ncaContentPFS0 = ncaProvider.getNCAContentPFS0(0);
|
0);
|
||||||
NCASectionContentFirstController.populateFields(ncaContentPFS0.getPfs0(), ncaContentPFS0.getSHA256hashes());
|
NCASectionContentSecondController.populateFields(
|
||||||
ncaContentPFS0 = ncaProvider.getNCAContentPFS0(1);
|
ncaProvider.getNCAContentProvider(1),
|
||||||
NCASectionContentSecondController.populateFields(ncaContentPFS0.getPfs0(), ncaContentPFS0.getSHA256hashes());
|
1);
|
||||||
ncaContentPFS0 = ncaProvider.getNCAContentPFS0(2);
|
NCASectionContentThirdController.populateFields(
|
||||||
NCASectionContentThirdController.populateFields(ncaContentPFS0.getPfs0(), ncaContentPFS0.getSHA256hashes());
|
ncaProvider.getNCAContentProvider(2),
|
||||||
ncaContentPFS0 = ncaProvider.getNCAContentPFS0(3);
|
2);
|
||||||
NCASectionContentFourthController.populateFields(ncaContentPFS0.getPfs0(), ncaContentPFS0.getSHA256hashes());
|
NCASectionContentFourthController.populateFields(
|
||||||
|
ncaProvider.getNCAContentProvider(3),
|
||||||
|
3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,31 +19,65 @@
|
||||||
package konogonka.Controllers.NCA;
|
package konogonka.Controllers.NCA;
|
||||||
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
import konogonka.AppPreferences;
|
||||||
import konogonka.Controllers.NSP.NSPController;
|
import konogonka.Controllers.NSP.NSPController;
|
||||||
import konogonka.LoperConverter;
|
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.LinkedList;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
public class NCASectionContentController{
|
public class NCASectionContentController implements Initializable {
|
||||||
|
|
||||||
|
private NCAContent ncaContent;
|
||||||
|
private int sectionNumber;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button extractRawConentBtn;
|
||||||
@FXML
|
@FXML
|
||||||
private NSPController SectionPFS0Controller;
|
private NSPController SectionPFS0Controller;
|
||||||
@FXML
|
@FXML
|
||||||
private VBox sha256pane;
|
private VBox sha256pane;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(URL url, ResourceBundle resourceBundle) {
|
||||||
|
extractRawConentBtn.setDisable(true);
|
||||||
|
extractRawConentBtn.setOnAction(event -> this.extractFiles());
|
||||||
|
}
|
||||||
|
|
||||||
public void resetTab() {
|
public void resetTab() {
|
||||||
SectionPFS0Controller.resetTab();
|
SectionPFS0Controller.resetTab();
|
||||||
sha256pane.getChildren().clear();
|
sha256pane.getChildren().clear();
|
||||||
|
extractRawConentBtn.setDisable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void populateFields(IPFS0Provider pfs0, LinkedList<byte[]> sha256hashList) {
|
public void populateFields(NCAContent ncaContent, int sectionNumber) {
|
||||||
resetTab();
|
resetTab();
|
||||||
SectionPFS0Controller.setData(pfs0, null);
|
|
||||||
if (sha256hashList != null){
|
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++){
|
for (int i = 0; i < sha256hashList.size(); i++){
|
||||||
Label numberLblTmp = new Label(String.format("%10d", i));
|
Label numberLblTmp = new Label(String.format("%10d", i));
|
||||||
numberLblTmp.setPadding(new Insets(5.0, 5.0, 5.0, 5.0));
|
numberLblTmp.setPadding(new Insets(5.0, 5.0, 5.0, 5.0));
|
||||||
|
@ -53,5 +87,29 @@ public class NCASectionContentController{
|
||||||
sha256pane.getChildren().add(new HBox(numberLblTmp, sha256LblTmp));
|
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){
|
public void populateTab(NCASectionBlock ncaSectionBlock){
|
||||||
versionLbl.setText(byteArrToHexString(ncaSectionBlock.getVersion()));
|
versionLbl.setText(byteArrToHexString(ncaSectionBlock.getVersion()));
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append(String.format("%02x ", ncaSectionBlock.getFsType()));
|
sb.append(String.format("0x%02x ", ncaSectionBlock.getFsType()));
|
||||||
if (ncaSectionBlock.getFsType() == 0)
|
if (ncaSectionBlock.getFsType() == 0)
|
||||||
sb.append("(RomFS)");
|
sb.append("(RomFS)");
|
||||||
else if (ncaSectionBlock.getFsType() == 1)
|
else if (ncaSectionBlock.getFsType() == 1)
|
||||||
sb.append("(PFS0)");
|
sb.append("(PFS0)");
|
||||||
fsTypeLbl.setText(sb.toString());
|
fsTypeLbl.setText(sb.toString());
|
||||||
sb = new StringBuilder();
|
sb = new StringBuilder();
|
||||||
sb.append(String.format("%02x ", ncaSectionBlock.getHashType()));
|
sb.append(String.format("0x%02x ", ncaSectionBlock.getHashType()));
|
||||||
if (ncaSectionBlock.getHashType() == 0x3)
|
if (ncaSectionBlock.getHashType() == 0x3)
|
||||||
sb.append("(RomFS)");
|
sb.append("(RomFS)");
|
||||||
else if (ncaSectionBlock.getHashType() == 0x2)
|
else if (ncaSectionBlock.getHashType() == 0x2)
|
||||||
|
@ -212,33 +212,33 @@ public class NCASectionHeaderBlockController {
|
||||||
+(ncaSectionBlock.getSuperBlockIVFC().getMagicNumber() == 0x20000? " (OK)":" (wrong magic number)"));
|
+(ncaSectionBlock.getSuperBlockIVFC().getMagicNumber() == 0x20000? " (OK)":" (wrong magic number)"));
|
||||||
romFsMasterHashSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getMasterHashSize()));
|
romFsMasterHashSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getMasterHashSize()));
|
||||||
romFsTotalNumberOfLevelsLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getTotalNumberOfLevels()));
|
romFsTotalNumberOfLevelsLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getTotalNumberOfLevels()));
|
||||||
romFsLvl1OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl1Offset()));
|
romFsLvl1OffsetLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl1Offset()));
|
||||||
romFsLvl1SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl1Size()));
|
romFsLvl1SizeLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl1Size()));
|
||||||
romFsLvl1SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl1SBlockSize()));
|
romFsLvl1SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl1SBlockSize()));
|
||||||
romFsReserved1Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved1()));
|
romFsReserved1Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved1()));
|
||||||
|
|
||||||
romFsLvl2OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl2Offset()));
|
romFsLvl2OffsetLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl2Offset()));
|
||||||
romFsLvl2SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl2Size()));
|
romFsLvl2SizeLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl2Size()));
|
||||||
romFsLvl2SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl2SBlockSize()));
|
romFsLvl2SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl2SBlockSize()));
|
||||||
romFsReserved2Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved2()));
|
romFsReserved2Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved2()));
|
||||||
|
|
||||||
romFsLvl3OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl3Offset()));
|
romFsLvl3OffsetLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl3Offset()));
|
||||||
romFsLvl3SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl3Size()));
|
romFsLvl3SizeLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl3Size()));
|
||||||
romFsLvl3SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl3SBlockSize()));
|
romFsLvl3SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl3SBlockSize()));
|
||||||
romFsReserved3Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved3()));
|
romFsReserved3Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved3()));
|
||||||
|
|
||||||
romFsLvl4OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl4Offset()));
|
romFsLvl4OffsetLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl4Offset()));
|
||||||
romFsLvl4SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl4Size()));
|
romFsLvl4SizeLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl4Size()));
|
||||||
romFsLvl4SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl4SBlockSize()));
|
romFsLvl4SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl4SBlockSize()));
|
||||||
romFsReserved4Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved4()));
|
romFsReserved4Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved4()));
|
||||||
|
|
||||||
romFsLvl5OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl5Offset()));
|
romFsLvl5OffsetLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl5Offset()));
|
||||||
romFsLvl5SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl5Size()));
|
romFsLvl5SizeLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl5Size()));
|
||||||
romFsLvl5SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl5SBlockSize()));
|
romFsLvl5SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl5SBlockSize()));
|
||||||
romFsReserved5Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved5()));
|
romFsReserved5Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved5()));
|
||||||
|
|
||||||
romFsLvl6OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl6Offset()));
|
romFsLvl6OffsetLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl6Offset()));
|
||||||
romFsLvl6SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl6Size()));
|
romFsLvl6SizeLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl6Size()));
|
||||||
romFsLvl6SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl6SBlockSize()));
|
romFsLvl6SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl6SBlockSize()));
|
||||||
romFsReserved6Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved6()));
|
romFsReserved6Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved6()));
|
||||||
|
|
||||||
|
@ -277,4 +277,8 @@ public class NCASectionHeaderBlockController {
|
||||||
sectionCTRLbl.setText(byteArrToHexString(ncaSectionBlock.getSectionCTR()));
|
sectionCTRLbl.setText(byteArrToHexString(ncaSectionBlock.getSectionCTR()));
|
||||||
unknwnEndPaddingTF.setText(byteArrToHexString(ncaSectionBlock.getUnknownEndPadding()));
|
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;
|
package konogonka.Controllers.NSP;
|
||||||
|
|
||||||
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
|
@ -162,7 +163,10 @@ public class Pfs0TableViewController implements Initializable {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
table.setItems(rowsObsLst);
|
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
|
* 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;
|
package konogonka.Tools.NCA;
|
||||||
|
|
||||||
import konogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock;
|
import konogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock;
|
||||||
|
import konogonka.exceptions.EmptySectionException;
|
||||||
import konogonka.xtsaes.XTSAESCipher;
|
import konogonka.xtsaes.XTSAESCipher;
|
||||||
import org.bouncycastle.crypto.params.KeyParameter;
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
|
@ -81,6 +82,11 @@ public class NCAProvider {
|
||||||
private NCASectionBlock sectionBlock2;
|
private NCASectionBlock sectionBlock2;
|
||||||
private NCASectionBlock sectionBlock3;
|
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{
|
public NCAProvider(File file, HashMap<String, String> keys) throws Exception{
|
||||||
this(file, keys, 0);
|
this(file, keys, 0);
|
||||||
}
|
}
|
||||||
|
@ -126,6 +132,7 @@ public class NCAProvider {
|
||||||
|
|
||||||
raf.close();
|
raf.close();
|
||||||
|
|
||||||
|
getNCAContent();
|
||||||
/*
|
/*
|
||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
FileInputStream fis = new FileInputStream(file);
|
FileInputStream fis = new FileInputStream(file);
|
||||||
|
@ -215,7 +222,22 @@ public class NCAProvider {
|
||||||
decryptedKey2 = cipher.doFinal(encryptedKey2);
|
decryptedKey2 = cipher.doFinal(encryptedKey2);
|
||||||
decryptedKey3 = cipher.doFinal(encryptedKey3);
|
decryptedKey3 = cipher.doFinal(encryptedKey3);
|
||||||
}
|
}
|
||||||
else{
|
else
|
||||||
|
keyAreaKeyNotSupportedOrFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
tableEntry0 = new NCAHeaderTableEntry(tableBytes);
|
||||||
|
tableEntry1 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x10, 0x20));
|
||||||
|
tableEntry2 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x20, 0x30));
|
||||||
|
tableEntry3 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x30, 0x40));
|
||||||
|
|
||||||
|
sectionBlock0 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x400, 0x600));
|
||||||
|
sectionBlock1 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x600, 0x800));
|
||||||
|
sectionBlock2 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x800, 0xa00));
|
||||||
|
sectionBlock3 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0xa00, 0xc00));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void keyAreaKeyNotSupportedOrFound() throws Exception{
|
||||||
StringBuilder exceptionStringBuilder = new StringBuilder("key_area_key_");
|
StringBuilder exceptionStringBuilder = new StringBuilder("key_area_key_");
|
||||||
switch (keyIndex){
|
switch (keyIndex){
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -233,20 +255,65 @@ public class NCAProvider {
|
||||||
}
|
}
|
||||||
exceptionStringBuilder.append(String.format("%02x", cryptoTypeReal));
|
exceptionStringBuilder.append(String.format("%02x", cryptoTypeReal));
|
||||||
exceptionStringBuilder.append(" requested. Not supported or not found.");
|
exceptionStringBuilder.append(" requested. Not supported or not found.");
|
||||||
|
|
||||||
throw new Exception(exceptionStringBuilder.toString());
|
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
|
||||||
|
|
||||||
tableEntry0 = new NCAHeaderTableEntry(tableBytes);
|
SecretKeySpec skSpec = new SecretKeySpec(
|
||||||
tableEntry1 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x10, 0x20));
|
hexStrToByteArray(keys.get(String.format("titlekek_%02x", cryptoTypeReal))
|
||||||
tableEntry2 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x20, 0x30));
|
), "AES");
|
||||||
tableEntry3 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x30, 0x40));
|
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, skSpec);
|
||||||
sectionBlock0 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x400, 0x600));
|
key = cipher.doFinal(rightsIDkey);
|
||||||
sectionBlock1 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x600, 0x800));
|
}
|
||||||
sectionBlock2 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x800, 0xa00));
|
catch (Exception e){
|
||||||
sectionBlock3 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0xa00, 0xc00));
|
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[] getRsa2048one() { return rsa2048one; }
|
||||||
|
@ -298,41 +365,18 @@ public class NCAProvider {
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get content for the selected section
|
* Get content for the selected section
|
||||||
* @param sectionNumber should be 1-4
|
* @param sectionNumber should be 0-3
|
||||||
* */
|
* */
|
||||||
public NCAContentPFS0 getNCAContentPFS0(int sectionNumber){
|
public NCAContent getNCAContentProvider(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch (sectionNumber) {
|
switch (sectionNumber) {
|
||||||
case 0:
|
case 0:
|
||||||
return new NCAContentPFS0(file, offset, sectionBlock0, tableEntry0, key); // TODO: remove decryptedKey2 ?
|
return ncaContent0;
|
||||||
case 1:
|
case 1:
|
||||||
return new NCAContentPFS0(file, offset, sectionBlock1, tableEntry1, key);
|
return ncaContent1;
|
||||||
case 2:
|
case 2:
|
||||||
return new NCAContentPFS0(file, offset, sectionBlock2, tableEntry2, key);
|
return ncaContent2;
|
||||||
case 3:
|
case 3:
|
||||||
return new NCAContentPFS0(file, offset, sectionBlock3, tableEntry3, key);
|
return ncaContent3;
|
||||||
default:
|
default:
|
||||||
return null;
|
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"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?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.TitledPane?>
|
<?import javafx.scene.control.TitledPane?>
|
||||||
|
<?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.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.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>
|
||||||
|
<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">
|
<TitledPane text="PFS0">
|
||||||
<content>
|
<content>
|
||||||
<VBox spacing="5.0">
|
<VBox spacing="5.0">
|
||||||
|
|
|
@ -1670,7 +1670,7 @@
|
||||||
</AnchorPane>
|
</AnchorPane>
|
||||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="1" GridPane.rowIndex="9">
|
<AnchorPane styleClass="customGrid" GridPane.columnIndex="1" GridPane.rowIndex="9">
|
||||||
<children>
|
<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>
|
<padding>
|
||||||
<Insets left="5.0" right="5.0" />
|
<Insets left="5.0" right="5.0" />
|
||||||
</padding>
|
</padding>
|
||||||
|
|
Loading…
Reference in a new issue