From 24d0925ddadc3d813ad2645bf946a9f1990afb53 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko Date: Sun, 12 Apr 2020 15:55:23 +0300 Subject: [PATCH] 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. --- .../Controllers/NCA/NCAController.java | 24 +- .../NCA/NCASectionContentController.java | 82 ++- .../NCA/NCASectionHeaderBlockController.java | 32 +- .../NSP/Pfs0TableViewController.java | 6 +- .../java/konogonka/Tools/NCA/NCAContent.java | 552 ++++++++++++++++++ .../konogonka/Tools/NCA/NCAContentPFS0.java | 283 --------- .../java/konogonka/Tools/NCA/NCAProvider.java | 144 +++-- .../Workers/DumbNCA3ContentExtractor.java | 67 +++ .../exceptions/EmptySectionException.java | 7 + .../resources/FXML/NCA/NCASectionContent.fxml | 15 + .../FXML/NCA/NCASectionHeaderBlock.fxml | 2 +- 11 files changed, 842 insertions(+), 372 deletions(-) create mode 100644 src/main/java/konogonka/Tools/NCA/NCAContent.java delete mode 100644 src/main/java/konogonka/Tools/NCA/NCAContentPFS0.java create mode 100644 src/main/java/konogonka/Workers/DumbNCA3ContentExtractor.java create mode 100644 src/main/java/konogonka/exceptions/EmptySectionException.java diff --git a/src/main/java/konogonka/Controllers/NCA/NCAController.java b/src/main/java/konogonka/Controllers/NCA/NCAController.java index 395abef..82fad24 100644 --- a/src/main/java/konogonka/Controllers/NCA/NCAController.java +++ b/src/main/java/konogonka/Controllers/NCA/NCAController.java @@ -25,7 +25,7 @@ import javafx.scene.control.TextField; import konogonka.AppPreferences; import konogonka.Controllers.ITabController; import konogonka.Tools.ISuperProvider; -import konogonka.Tools.NCA.NCAContentPFS0; +import konogonka.Tools.NCA.NCAContent; import konogonka.Tools.NCA.NCAProvider; import konogonka.Workers.Analyzer; @@ -224,15 +224,17 @@ public class NCAController implements ITabController { NCASectionHeaderThirdController.populateTab(ncaProvider.getSectionBlock2()); NCASectionHeaderFourthController.populateTab(ncaProvider.getSectionBlock3()); // Section content blocks - // TODO: FIX: This code executes getNCAContentPFS0() method twice - NCAContentPFS0 ncaContentPFS0; - ncaContentPFS0 = ncaProvider.getNCAContentPFS0(0); - NCASectionContentFirstController.populateFields(ncaContentPFS0.getPfs0(), ncaContentPFS0.getSHA256hashes()); - ncaContentPFS0 = ncaProvider.getNCAContentPFS0(1); - NCASectionContentSecondController.populateFields(ncaContentPFS0.getPfs0(), ncaContentPFS0.getSHA256hashes()); - ncaContentPFS0 = ncaProvider.getNCAContentPFS0(2); - NCASectionContentThirdController.populateFields(ncaContentPFS0.getPfs0(), ncaContentPFS0.getSHA256hashes()); - ncaContentPFS0 = ncaProvider.getNCAContentPFS0(3); - NCASectionContentFourthController.populateFields(ncaContentPFS0.getPfs0(), ncaContentPFS0.getSHA256hashes()); + NCASectionContentFirstController.populateFields( + ncaProvider.getNCAContentProvider(0), + 0); + NCASectionContentSecondController.populateFields( + ncaProvider.getNCAContentProvider(1), + 1); + NCASectionContentThirdController.populateFields( + ncaProvider.getNCAContentProvider(2), + 2); + NCASectionContentFourthController.populateFields( + ncaProvider.getNCAContentProvider(3), + 3); } } diff --git a/src/main/java/konogonka/Controllers/NCA/NCASectionContentController.java b/src/main/java/konogonka/Controllers/NCA/NCASectionContentController.java index b468310..165d023 100644 --- a/src/main/java/konogonka/Controllers/NCA/NCASectionContentController.java +++ b/src/main/java/konogonka/Controllers/NCA/NCASectionContentController.java @@ -19,39 +19,97 @@ package konogonka.Controllers.NCA; import javafx.fxml.FXML; +import javafx.fxml.Initializable; import javafx.geometry.Insets; +import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; +import konogonka.AppPreferences; import konogonka.Controllers.NSP.NSPController; import konogonka.LoperConverter; -import konogonka.Tools.PFS0.IPFS0Provider; +import konogonka.MediatorControl; +import konogonka.Tools.NCA.NCAContent; +import konogonka.Workers.DumbNCA3ContentExtractor; +import java.io.File; +import java.net.URL; import java.util.LinkedList; +import java.util.ResourceBundle; -public class NCASectionContentController{ +public class NCASectionContentController implements Initializable { + + private NCAContent ncaContent; + private int sectionNumber; + + @FXML + private Button extractRawConentBtn; @FXML private NSPController SectionPFS0Controller; @FXML private VBox sha256pane; + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + extractRawConentBtn.setDisable(true); + extractRawConentBtn.setOnAction(event -> this.extractFiles()); + } + public void resetTab() { SectionPFS0Controller.resetTab(); sha256pane.getChildren().clear(); + extractRawConentBtn.setDisable(true); } - public void populateFields(IPFS0Provider pfs0, LinkedList sha256hashList) { + public void populateFields(NCAContent ncaContent, int sectionNumber) { resetTab(); - SectionPFS0Controller.setData(pfs0, null); - if (sha256hashList != null){ - for (int i = 0; i < sha256hashList.size(); i++){ - Label numberLblTmp = new Label(String.format("%10d", i)); - numberLblTmp.setPadding(new Insets(5.0, 5.0, 5.0, 5.0)); - Label sha256LblTmp = new Label(LoperConverter.byteArrToHexString(sha256hashList.get(i))); - sha256LblTmp.setPadding(new Insets(5.0, 5.0, 5.0, 5.0)); - sha256pane.getChildren().add(new HBox(numberLblTmp, sha256LblTmp)); - } + if (ncaContent == null) + return; + + this.ncaContent = ncaContent; + this.sectionNumber = sectionNumber; + this.extractRawConentBtn.setDisable(false); + + if (ncaContent.getPfs0() != null) + SectionPFS0Controller.setData(ncaContent.getPfs0(), null);; + + LinkedList sha256hashList = ncaContent.getPfs0SHA256hashes(); + + if (sha256hashList == null) + return; + for (int i = 0; i < sha256hashList.size(); i++){ + Label numberLblTmp = new Label(String.format("%10d", i)); + numberLblTmp.setPadding(new Insets(5.0, 5.0, 5.0, 5.0)); + Label sha256LblTmp = new Label(LoperConverter.byteArrToHexString(sha256hashList.get(i))); + sha256LblTmp.setPadding(new Insets(5.0, 5.0, 5.0, 5.0)); + + sha256pane.getChildren().add(new HBox(numberLblTmp, sha256LblTmp)); } } + + private void extractFiles(){ + if (ncaContent == null) + return; + + File dir = new File(AppPreferences.getInstance().getExtractFilesDir()+File.separator+ncaContent.getFileName()+" extracted"); + try { + dir.mkdir(); + } + catch (SecurityException se){ + MediatorControl.getInstance().getContoller().logArea.setText("Can't create dir to store files."); + } + if (!dir.exists()) + return; + + extractRawConentBtn.setDisable(true); + + DumbNCA3ContentExtractor extractor = new DumbNCA3ContentExtractor(ncaContent, sectionNumber, dir.getAbsolutePath()+File.separator); + extractor.setOnSucceeded(e->{ + extractRawConentBtn.setDisable(false); + }); + Thread workThread = new Thread(extractor); + workThread.setDaemon(true); + workThread.start(); + } } diff --git a/src/main/java/konogonka/Controllers/NCA/NCASectionHeaderBlockController.java b/src/main/java/konogonka/Controllers/NCA/NCASectionHeaderBlockController.java index 94d78b8..cc8c098 100644 --- a/src/main/java/konogonka/Controllers/NCA/NCASectionHeaderBlockController.java +++ b/src/main/java/konogonka/Controllers/NCA/NCASectionHeaderBlockController.java @@ -191,14 +191,14 @@ public class NCASectionHeaderBlockController { public void populateTab(NCASectionBlock ncaSectionBlock){ versionLbl.setText(byteArrToHexString(ncaSectionBlock.getVersion())); StringBuilder sb = new StringBuilder(); - sb.append(String.format("%02x ", ncaSectionBlock.getFsType())); + sb.append(String.format("0x%02x ", ncaSectionBlock.getFsType())); if (ncaSectionBlock.getFsType() == 0) sb.append("(RomFS)"); else if (ncaSectionBlock.getFsType() == 1) sb.append("(PFS0)"); fsTypeLbl.setText(sb.toString()); sb = new StringBuilder(); - sb.append(String.format("%02x ", ncaSectionBlock.getHashType())); + sb.append(String.format("0x%02x ", ncaSectionBlock.getHashType())); if (ncaSectionBlock.getHashType() == 0x3) sb.append("(RomFS)"); else if (ncaSectionBlock.getHashType() == 0x2) @@ -212,33 +212,33 @@ public class NCASectionHeaderBlockController { +(ncaSectionBlock.getSuperBlockIVFC().getMagicNumber() == 0x20000? " (OK)":" (wrong magic number)")); romFsMasterHashSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getMasterHashSize())); romFsTotalNumberOfLevelsLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getTotalNumberOfLevels())); - romFsLvl1OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl1Offset())); - romFsLvl1SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl1Size())); + romFsLvl1OffsetLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl1Offset())); + romFsLvl1SizeLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl1Size())); romFsLvl1SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl1SBlockSize())); romFsReserved1Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved1())); - romFsLvl2OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl2Offset())); - romFsLvl2SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl2Size())); + romFsLvl2OffsetLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl2Offset())); + romFsLvl2SizeLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl2Size())); romFsLvl2SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl2SBlockSize())); romFsReserved2Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved2())); - romFsLvl3OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl3Offset())); - romFsLvl3SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl3Size())); + romFsLvl3OffsetLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl3Offset())); + romFsLvl3SizeLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl3Size())); romFsLvl3SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl3SBlockSize())); romFsReserved3Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved3())); - romFsLvl4OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl4Offset())); - romFsLvl4SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl4Size())); + romFsLvl4OffsetLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl4Offset())); + romFsLvl4SizeLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl4Size())); romFsLvl4SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl4SBlockSize())); romFsReserved4Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved4())); - romFsLvl5OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl5Offset())); - romFsLvl5SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl5Size())); + romFsLvl5OffsetLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl5Offset())); + romFsLvl5SizeLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl5Size())); romFsLvl5SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl5SBlockSize())); romFsReserved5Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved5())); - romFsLvl6OffsetLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl6Offset())); - romFsLvl6SizeLbl.setText(Long.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl6Size())); + romFsLvl6OffsetLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl6Offset())); + romFsLvl6SizeLbl.setText(getCuteDecHexRepresentation(ncaSectionBlock.getSuperBlockIVFC().getLvl6Size())); romFsLvl6SBlockSizeLbl.setText(Integer.toString(ncaSectionBlock.getSuperBlockIVFC().getLvl6SBlockSize())); romFsReserved6Lbl.setText(byteArrToHexString(ncaSectionBlock.getSuperBlockIVFC().getReserved6())); @@ -277,4 +277,8 @@ public class NCASectionHeaderBlockController { sectionCTRLbl.setText(byteArrToHexString(ncaSectionBlock.getSectionCTR())); unknwnEndPaddingTF.setText(byteArrToHexString(ncaSectionBlock.getUnknownEndPadding())); } + + private String getCuteDecHexRepresentation(long value){ + return String.format("%d (0x%02x)", value, value); + } } diff --git a/src/main/java/konogonka/Controllers/NSP/Pfs0TableViewController.java b/src/main/java/konogonka/Controllers/NSP/Pfs0TableViewController.java index 0427bf8..31ca473 100644 --- a/src/main/java/konogonka/Controllers/NSP/Pfs0TableViewController.java +++ b/src/main/java/konogonka/Controllers/NSP/Pfs0TableViewController.java @@ -18,6 +18,7 @@ */ package konogonka.Controllers.NSP; +import javafx.beans.Observable; import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.value.ChangeListener; @@ -162,7 +163,10 @@ public class Pfs0TableViewController implements Initializable { } ); table.setItems(rowsObsLst); - table.getColumns().addAll(numberColumn, fileNameColumn, fileOffsetColumn, fileSizeColumn, uploadColumn); + table.getColumns().add(numberColumn); + table.getColumns().add(fileNameColumn); + table.getColumns().add(fileOffsetColumn); + table.getColumns().add(fileSizeColumn); } /** * Add files when user selected them diff --git a/src/main/java/konogonka/Tools/NCA/NCAContent.java b/src/main/java/konogonka/Tools/NCA/NCAContent.java new file mode 100644 index 0000000..5b8aec8 --- /dev/null +++ b/src/main/java/konogonka/Tools/NCA/NCAContent.java @@ -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 . +*/ +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 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 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(); + } +} \ No newline at end of file diff --git a/src/main/java/konogonka/Tools/NCA/NCAContentPFS0.java b/src/main/java/konogonka/Tools/NCA/NCAContentPFS0.java deleted file mode 100644 index e4c2c5c..0000000 --- a/src/main/java/konogonka/Tools/NCA/NCAContentPFS0.java +++ /dev/null @@ -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 . -*/ -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 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 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"); - } - } - } - } -} \ No newline at end of file diff --git a/src/main/java/konogonka/Tools/NCA/NCAProvider.java b/src/main/java/konogonka/Tools/NCA/NCAProvider.java index 52f3526..3132277 100644 --- a/src/main/java/konogonka/Tools/NCA/NCAProvider.java +++ b/src/main/java/konogonka/Tools/NCA/NCAProvider.java @@ -19,6 +19,7 @@ package konogonka.Tools.NCA; import konogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock; +import konogonka.exceptions.EmptySectionException; import konogonka.xtsaes.XTSAESCipher; import org.bouncycastle.crypto.params.KeyParameter; @@ -81,6 +82,11 @@ public class NCAProvider { private NCASectionBlock sectionBlock2; private NCASectionBlock sectionBlock3; + private NCAContent ncaContent0; + private NCAContent ncaContent1; + private NCAContent ncaContent2; + private NCAContent ncaContent3; + public NCAProvider(File file, HashMap keys) throws Exception{ this(file, keys, 0); } @@ -126,6 +132,7 @@ public class NCAProvider { raf.close(); + getNCAContent(); /* //--------------------------------------------------------------------- FileInputStream fis = new FileInputStream(file); @@ -215,27 +222,8 @@ public class NCAProvider { decryptedKey2 = cipher.doFinal(encryptedKey2); decryptedKey3 = cipher.doFinal(encryptedKey3); } - else{ - StringBuilder exceptionStringBuilder = new StringBuilder("key_area_key_"); - switch (keyIndex){ - case 0: - exceptionStringBuilder.append("application_"); - break; - case 1: - exceptionStringBuilder.append("ocean_"); - break; - case 2: - exceptionStringBuilder.append("system_"); - break; - default: - exceptionStringBuilder.append(keyIndex); - exceptionStringBuilder.append("[UNKNOWN]_"); - } - exceptionStringBuilder.append(String.format("%02x", cryptoTypeReal)); - exceptionStringBuilder.append(" requested. Not supported or not found."); - - throw new Exception(exceptionStringBuilder.toString()); - } + else + keyAreaKeyNotSupportedOrFound(); } tableEntry0 = new NCAHeaderTableEntry(tableBytes); @@ -249,6 +237,85 @@ public class NCAProvider { sectionBlock3 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0xa00, 0xc00)); } + private void keyAreaKeyNotSupportedOrFound() throws Exception{ + StringBuilder exceptionStringBuilder = new StringBuilder("key_area_key_"); + switch (keyIndex){ + case 0: + exceptionStringBuilder.append("application_"); + break; + case 1: + exceptionStringBuilder.append("ocean_"); + break; + case 2: + exceptionStringBuilder.append("system_"); + break; + default: + exceptionStringBuilder.append(keyIndex); + exceptionStringBuilder.append("[UNKNOWN]_"); + } + exceptionStringBuilder.append(String.format("%02x", cryptoTypeReal)); + exceptionStringBuilder.append(" requested. Not supported or not found."); + throw new Exception(exceptionStringBuilder.toString()); + } + + private void getNCAContent(){ + byte[] key; + + // If empty Rights ID + if (Arrays.equals(rightsId, new byte[0x10])) { + key = decryptedKey2; // TODO: Just remember this dumb hack + } + else { + try { + byte[] rightsIDkey = hexStrToByteArray(keys.get(byteArrToHexString(rightsId))); // throws NullPointerException + + SecretKeySpec skSpec = new SecretKeySpec( + hexStrToByteArray(keys.get(String.format("titlekek_%02x", cryptoTypeReal)) + ), "AES"); + Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding"); + cipher.init(Cipher.DECRYPT_MODE, skSpec); + key = cipher.doFinal(rightsIDkey); + } + catch (Exception e){ + e.printStackTrace(); + System.out.println("No title.keys loaded?"); + return; + } + } + try { + this.ncaContent0 = new NCAContent(file, offset, sectionBlock0, tableEntry0, key); + } + catch (EmptySectionException ignored){} + catch (Exception e){ + this.ncaContent0 = null; + e.printStackTrace(); + } + try{ + this.ncaContent1 = new NCAContent(file, offset, sectionBlock1, tableEntry1, key); + } + catch (EmptySectionException ignored){} + catch (Exception e){ + this.ncaContent1 = null; + e.printStackTrace(); + } + try{ + this.ncaContent2 = new NCAContent(file, offset, sectionBlock2, tableEntry2, key); + } + catch (EmptySectionException ignored){} + catch (Exception e){ + this.ncaContent2 = null; + e.printStackTrace(); + } + try{ + this.ncaContent3 = new NCAContent(file, offset, sectionBlock3, tableEntry3, key); + } + catch (EmptySectionException ignored){} + catch (Exception e){ + this.ncaContent3 = null; + e.printStackTrace(); + } + } + public byte[] getRsa2048one() { return rsa2048one; } public byte[] getRsa2048two() { return rsa2048two; } public String getMagicnum() { return magicnum; } @@ -298,41 +365,18 @@ public class NCAProvider { } /** * Get content for the selected section - * @param sectionNumber should be 1-4 + * @param sectionNumber should be 0-3 * */ - public NCAContentPFS0 getNCAContentPFS0(int sectionNumber){ - byte[] key; - - // If empty Rights ID - if (Arrays.equals(rightsId, new byte[0x10])) { - key = decryptedKey2; // TODO: Just remember this dumb hack - } - else { - try { - byte[] rightsIDkey = hexStrToByteArray(keys.get(byteArrToHexString(rightsId))); // throws NullPointerException - - SecretKeySpec skSpec = new SecretKeySpec( - hexStrToByteArray(keys.get(String.format("titlekek_%02x", cryptoTypeReal)) - ), "AES"); - Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding"); - cipher.init(Cipher.DECRYPT_MODE, skSpec); - key = cipher.doFinal(rightsIDkey); - } - catch (Exception e){ - e.printStackTrace(); - System.out.println("No title.keys loaded?"); - return null; - } - } + public NCAContent getNCAContentProvider(int sectionNumber){ switch (sectionNumber) { case 0: - return new NCAContentPFS0(file, offset, sectionBlock0, tableEntry0, key); // TODO: remove decryptedKey2 ? + return ncaContent0; case 1: - return new NCAContentPFS0(file, offset, sectionBlock1, tableEntry1, key); + return ncaContent1; case 2: - return new NCAContentPFS0(file, offset, sectionBlock2, tableEntry2, key); + return ncaContent2; case 3: - return new NCAContentPFS0(file, offset, sectionBlock3, tableEntry3, key); + return ncaContent3; default: return null; } diff --git a/src/main/java/konogonka/Workers/DumbNCA3ContentExtractor.java b/src/main/java/konogonka/Workers/DumbNCA3ContentExtractor.java new file mode 100644 index 0000000..6e99cff --- /dev/null +++ b/src/main/java/konogonka/Workers/DumbNCA3ContentExtractor.java @@ -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 { + + 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; + } +} \ No newline at end of file diff --git a/src/main/java/konogonka/exceptions/EmptySectionException.java b/src/main/java/konogonka/exceptions/EmptySectionException.java new file mode 100644 index 0000000..5b57f91 --- /dev/null +++ b/src/main/java/konogonka/exceptions/EmptySectionException.java @@ -0,0 +1,7 @@ +package konogonka.exceptions; + +public class EmptySectionException extends NullPointerException { + public EmptySectionException(String message){ + super(message); + } +} diff --git a/src/main/resources/FXML/NCA/NCASectionContent.fxml b/src/main/resources/FXML/NCA/NCASectionContent.fxml index 5ab387c..bedecd3 100644 --- a/src/main/resources/FXML/NCA/NCASectionContent.fxml +++ b/src/main/resources/FXML/NCA/NCASectionContent.fxml @@ -1,15 +1,30 @@ + + + + +
+ +
+ + + +
diff --git a/src/main/resources/FXML/NCA/NCASectionHeaderBlock.fxml b/src/main/resources/FXML/NCA/NCASectionHeaderBlock.fxml index 685cdbb..962af82 100644 --- a/src/main/resources/FXML/NCA/NCASectionHeaderBlock.fxml +++ b/src/main/resources/FXML/NCA/NCASectionHeaderBlock.fxml @@ -1670,7 +1670,7 @@ -