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:
Dmitry Isaenko 2020-04-12 15:55:23 +03:00
parent bb3bbc2efc
commit 24d0925dda
11 changed files with 842 additions and 372 deletions

View file

@ -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);
} }
} }

View file

@ -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();
} }
} }

View file

@ -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);
}
} }

View file

@ -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

View 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();
}
}

View file

@ -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");
}
}
}
}
}

View file

@ -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;
} }

View file

@ -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;
}
}

View file

@ -0,0 +1,7 @@
package konogonka.exceptions;
public class EmptySectionException extends NullPointerException {
public EmptySectionException(String message){
super(message);
}
}

View file

@ -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">

View file

@ -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>