From 8225f0fcba805fbcfc7f79dc276edaf22bdec5af Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko Date: Wed, 29 Apr 2020 04:49:55 +0300 Subject: [PATCH] Support for files view of the RomFs content on encrypted NCA3 --- .../konogonka/Controllers/MainController.java | 6 +- .../NCA/NCASectionContentController.java | 12 + .../Controllers/RFS/RomFsController.java | 20 +- .../java/konogonka/Tools/NCA/NCAContent.java | 134 +--------- .../Tools/PFS0/PFS0EncryptedProvider.java | 11 +- .../Tools/RomFs/FolderMeta4Debug.java | 21 +- .../konogonka/Tools/RomFs/Level6Header.java | 21 +- .../Tools/RomFs/RomFsDecryptedProvider.java | 3 +- .../Tools/RomFs/RomFsEncryptedProvider.java | 239 ++++++++++++++++++ .../resources/FXML/NCA/NCASectionContent.fxml | 36 +-- src/main/resources/FXML/RomFS/RFSTab.fxml | 2 +- 11 files changed, 326 insertions(+), 179 deletions(-) create mode 100644 src/main/java/konogonka/Tools/RomFs/RomFsEncryptedProvider.java diff --git a/src/main/java/konogonka/Controllers/MainController.java b/src/main/java/konogonka/Controllers/MainController.java index 2c92c43..be53c08 100644 --- a/src/main/java/konogonka/Controllers/MainController.java +++ b/src/main/java/konogonka/Controllers/MainController.java @@ -147,7 +147,7 @@ public class MainController implements Initializable { case "nca": tabPane.getSelectionModel().select(2); break; - case "tic": + case "tik": tabPane.getSelectionModel().select(3); break; case "xml": @@ -176,7 +176,7 @@ public class MainController implements Initializable { case "nca": NCATabController.analyze(selectedFile); break; - case "tic": + case "tik": TIKTabController.analyze(selectedFile); break; case "xml": @@ -195,7 +195,7 @@ public class MainController implements Initializable { case "nsz": case "xci": case "nca": - case "tic": + case "tik": case "xml": case "npdm": case "romfs": diff --git a/src/main/java/konogonka/Controllers/NCA/NCASectionContentController.java b/src/main/java/konogonka/Controllers/NCA/NCASectionContentController.java index 165d023..6b15343 100644 --- a/src/main/java/konogonka/Controllers/NCA/NCASectionContentController.java +++ b/src/main/java/konogonka/Controllers/NCA/NCASectionContentController.java @@ -27,6 +27,7 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import konogonka.AppPreferences; import konogonka.Controllers.NSP.NSPController; +import konogonka.Controllers.RFS.RomFsController; import konogonka.LoperConverter; import konogonka.MediatorControl; import konogonka.Tools.NCA.NCAContent; @@ -47,6 +48,8 @@ public class NCASectionContentController implements Initializable { @FXML private NSPController SectionPFS0Controller; @FXML + private RomFsController SectionRomFsController; + @FXML private VBox sha256pane; @Override @@ -57,6 +60,7 @@ public class NCASectionContentController implements Initializable { public void resetTab() { SectionPFS0Controller.resetTab(); + SectionRomFsController.resetTab(); sha256pane.getChildren().clear(); extractRawConentBtn.setDisable(true); } @@ -71,6 +75,10 @@ public class NCASectionContentController implements Initializable { this.sectionNumber = sectionNumber; this.extractRawConentBtn.setDisable(false); + setPFS0Content(); + setRomFsContent(); + } + private void setPFS0Content(){ if (ncaContent.getPfs0() != null) SectionPFS0Controller.setData(ncaContent.getPfs0(), null);; @@ -87,6 +95,10 @@ public class NCASectionContentController implements Initializable { sha256pane.getChildren().add(new HBox(numberLblTmp, sha256LblTmp)); } } + private void setRomFsContent(){ + if (ncaContent.getRomfs() != null) + SectionRomFsController.setData(ncaContent.getRomfs()); + } private void extractFiles(){ if (ncaContent == null) diff --git a/src/main/java/konogonka/Controllers/RFS/RomFsController.java b/src/main/java/konogonka/Controllers/RFS/RomFsController.java index 56002c2..6a61f74 100644 --- a/src/main/java/konogonka/Controllers/RFS/RomFsController.java +++ b/src/main/java/konogonka/Controllers/RFS/RomFsController.java @@ -42,15 +42,15 @@ public class RomFsController implements ITabController { @FXML private Label headerHeaderLengthLbl, - headerDirectoryHashTableOffsetLbl, - headerDirectoryHashTableLengthLbl, - headerDirectoryMetadataTableOffsetLbl, - headerDirectoryMetadataTableLengthLbl, - headerFileHashTableOffsetLbl, - headerFileHashTableLengthLbl, - headerFileMetadataTableOffsetLbl, - headerFileMetadataTableLengthLbl, - headerFileDataOffsetLbl; + headerDirectoryHashTableOffsetLbl, + headerDirectoryHashTableLengthLbl, + headerDirectoryMetadataTableOffsetLbl, + headerDirectoryMetadataTableLengthLbl, + headerFileHashTableOffsetLbl, + headerFileHashTableLengthLbl, + headerFileMetadataTableOffsetLbl, + headerFileMetadataTableLengthLbl, + headerFileDataOffsetLbl; @FXML private Label headerHeaderLengthHexLbl, headerDirectoryHashTableOffsetHexLbl, @@ -157,7 +157,7 @@ public class RomFsController implements ITabController { workThread.start(); } - public void setData(RomFsDecryptedProvider provider) { + public void setData(IRomFsProvider provider) { try { this.provider = provider; Level6Header header = provider.getHeader(); diff --git a/src/main/java/konogonka/Tools/NCA/NCAContent.java b/src/main/java/konogonka/Tools/NCA/NCAContent.java index 4577908..01f8a47 100644 --- a/src/main/java/konogonka/Tools/NCA/NCAContent.java +++ b/src/main/java/konogonka/Tools/NCA/NCAContent.java @@ -23,6 +23,8 @@ import konogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock; import konogonka.Tools.PFS0.IPFS0Provider; import konogonka.Tools.PFS0.PFS0EncryptedProvider; import konogonka.Tools.PFS0.PFS0Provider; +import konogonka.Tools.RomFs.IRomFsProvider; +import konogonka.Tools.RomFs.RomFsEncryptedProvider; import konogonka.ctraes.AesCtrDecryptSimple; import konogonka.exceptions.EmptySectionException; @@ -41,6 +43,7 @@ public class NCAContent { private LinkedList Pfs0SHA256hashes; private IPFS0Provider pfs0; + private IRomFsProvider romfs; // TODO: if decryptedKey is empty, throw exception ?? public NCAContent(File file, @@ -130,16 +133,22 @@ public class NCAContent { System.out.println("proceedRomFs() -> proceedRomFsNotEncrypted() is not implemented :("); } private void proceedRomFsEncrypted() throws Exception{ - new CryptoSection03RomFS(file, + if (decryptedKey == null) + throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided."); + + this.romfs = new RomFsEncryptedProvider( offsetPosition, + ncaSectionBlock.getSuperBlockIVFC().getLvl6Offset(), + file, decryptedKey, - ncaSectionBlock, + ncaSectionBlock.getSectionCTR(), ncaHeaderTableEntry.getMediaStartOffset(), ncaHeaderTableEntry.getMediaEndOffset()); } public LinkedList getPfs0SHA256hashes() { return Pfs0SHA256hashes; } public IPFS0Provider getPfs0() { return pfs0; } + public IRomFsProvider getRomfs() { return romfs; } private class CryptoSection03Pfs0 { @@ -293,7 +302,8 @@ public class NCAContent { counter += toSkip; } //--------------------------------------------------------- - pfs0 = new PFS0EncryptedProvider(pipedInputStream, counter, + pfs0 = new PFS0EncryptedProvider(pipedInputStream, + counter, MetaOffsetPositionInFile, MetaFileWithEncPFS0, MetaKey, @@ -312,124 +322,6 @@ public class NCAContent { } } } - private class CryptoSection03RomFS{ - CryptoSection03RomFS(File file, - long offsetPosition, - byte[] decryptedKey, - NCASectionBlock ncaSectionBlock, - long mediaStartBlocksOffset, - long mediaEndBlocksOffset) throws Exception - { - if (decryptedKey == null) - throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided."); - - RandomAccessFile raf = new RandomAccessFile(file, "r"); - long abosluteOffsetPosition = offsetPosition + (mediaStartBlocksOffset * 0x200); - raf.seek(abosluteOffsetPosition); - - AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey, - ncaSectionBlock.getSectionCTR(), - mediaStartBlocksOffset * 0x200); - - byte[] encryptedBlock; - byte[] dectyptedBlock; - long mediaBlocksSize = mediaEndBlocksOffset - mediaStartBlocksOffset; - // Prepare thread to parse encrypted data - PipedOutputStream streamOut = new PipedOutputStream(); - PipedInputStream streamInp = new PipedInputStream(streamOut); - - Thread pThread = new Thread(new ParseThreadRomFs( - streamInp, - offsetPosition, - file, - decryptedKey, - ncaSectionBlock.getSectionCTR(), - mediaStartBlocksOffset, - mediaEndBlocksOffset - )); - pThread.start(); - // Decrypt data - for (int i = 0; i < mediaBlocksSize; i++){ - encryptedBlock = new byte[0x200]; - if (raf.read(encryptedBlock) != -1){ - dectyptedBlock = decryptor.dectyptNext(encryptedBlock); - // Writing decrypted data to pipe - try { - streamOut.write(dectyptedBlock); - } - catch (IOException e){ - break; - } - } - } - pThread.join();// TODO:UNCOMMENT - streamOut.close(); - raf.close(); - } - /* - * Since we representing decrypted data as stream (it's easier to look on it this way), - * this thread will be parsing it. - * */ - private class ParseThreadRomFs implements Runnable{ - - PipedInputStream pipedInputStream; - - private long MetaOffsetPositionInFile; - private File MetaFileWithEncPFS0; - private byte[] MetaKey; - private byte[] MetaSectionCTR; - private long MetaMediaStartOffset; - private long MetaMediaEndOffset; - - - ParseThreadRomFs(PipedInputStream pipedInputStream, - long MetaOffsetPositionInFile, - File MetaFileWithEncPFS0, - byte[] MetaKey, - byte[] MetaSectionCTR, - long MetaMediaStartOffset, - long MetaMediaEndOffset - ){ - this.pipedInputStream = pipedInputStream; - //this.hashTableRecordsCount = hashTableSize / 0x20; - - this.MetaOffsetPositionInFile = MetaOffsetPositionInFile; - this.MetaFileWithEncPFS0 = MetaFileWithEncPFS0; - this.MetaKey = MetaKey; - this.MetaSectionCTR = MetaSectionCTR; - this.MetaMediaStartOffset = MetaMediaStartOffset; - this.MetaMediaEndOffset = MetaMediaEndOffset; - - } - - @Override - public void run() { - long counter = 0; // How many bytes already read - try { - // TODO - /* - pfs0 = new PFS0EncryptedProvider(pipedInputStream, - counter, - MetaOffsetPositionInFile, - MetaFileWithEncPFS0, - MetaKey, - MetaSectionCTR, - MetaMediaStartOffset, - MetaMediaEndOffset); - - */ - pipedInputStream.close(); - } - catch (Exception e){ - System.out.println("'ParseThreadRomFs' thread exception"); - e.printStackTrace(); - } - finally { - System.out.println("ParseThreadRomFs dies"); - } - } - } - } /** * Export NCA content AS IS. diff --git a/src/main/java/konogonka/Tools/PFS0/PFS0EncryptedProvider.java b/src/main/java/konogonka/Tools/PFS0/PFS0EncryptedProvider.java index f3a4f01..8549b48 100644 --- a/src/main/java/konogonka/Tools/PFS0/PFS0EncryptedProvider.java +++ b/src/main/java/konogonka/Tools/PFS0/PFS0EncryptedProvider.java @@ -46,7 +46,8 @@ public class PFS0EncryptedProvider implements IPFS0Provider{ private long mediaStartOffset; // In 512-blocks private long mediaEndOffset; // In 512-blocks - public PFS0EncryptedProvider(PipedInputStream pipedInputStream, long pfs0offsetPosition, + public PFS0EncryptedProvider(PipedInputStream pipedInputStream, + long pfs0offsetPosition, long offsetPositionInFile, File fileWithEncPFS0, byte[] key, @@ -166,15 +167,13 @@ public class PFS0EncryptedProvider implements IPFS0Provider{ @Override public File getFile(){ return file; } @Override - public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception { - if (subFileNumber >= pfs0subFiles.length) { + public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception { // TODO: rewrite + if (subFileNumber >= pfs0subFiles.length) throw new Exception("PFS0Provider -> getPfs0subFilePipedInpStream(): Requested sub file doesn't exists"); - } Thread workerThread; PipedOutputStream streamOut = new PipedOutputStream(); - PipedInputStream streamIn = new PipedInputStream(streamOut); workerThread = new Thread(() -> { System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Executing thread"); @@ -272,7 +271,7 @@ public class PFS0EncryptedProvider implements IPFS0Provider{ encryptedBlock = new byte[0x200]; if (bis.read(encryptedBlock) == 0x200) { dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock); - streamOut.write(dectyptedBlock, 0, 0x200 + extraData); + streamOut.write(dectyptedBlock, 0, 0x200 + extraData); // WTF ??? THIS LOOKS INCORRECT } else { System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from last bock"); diff --git a/src/main/java/konogonka/Tools/RomFs/FolderMeta4Debug.java b/src/main/java/konogonka/Tools/RomFs/FolderMeta4Debug.java index 48d5b97..17ac72a 100644 --- a/src/main/java/konogonka/Tools/RomFs/FolderMeta4Debug.java +++ b/src/main/java/konogonka/Tools/RomFs/FolderMeta4Debug.java @@ -51,20 +51,19 @@ public class FolderMeta4Debug { folderMeta.dirName = new String(Arrays.copyOfRange(directoryMetadataTable, i, i + folderMeta.dirNameLength), StandardCharsets.UTF_8); i += getRealNameSize(folderMeta.dirNameLength); - allFolders.add(folderMeta); - } - - for (FolderMeta headersDirectory : allFolders) System.out.println( "---------------------------DIRECTORY------------------------\n" + - "Offset of Parent Directory (self if Root) " + formatDecHexString(headersDirectory.parentDirectoryOffset ) +"\n" + - "Offset of next Sibling Directory " + formatDecHexString(headersDirectory.nextSiblingDirectoryOffset) +"\n" + - "Offset of first Child Directory (Subdirectory) " + formatDecHexString(headersDirectory.firstSubdirectoryOffset ) +"\n" + - "Offset of first File (in File Metadata Table) " + formatDecHexString(headersDirectory.firstFileOffset ) +"\n" + - "Offset of next Directory in the same Hash Table bucket " + formatDecHexString(headersDirectory.nextDirectoryOffset ) +"\n" + - "Name Length " + formatDecHexString(headersDirectory.dirNameLength ) +"\n" + - "Name Length (rounded up to multiple of 4) " + headersDirectory.dirName + "\n" + "Offset of Parent Directory (self if Root) " + formatDecHexString(folderMeta.parentDirectoryOffset ) +"\n" + + "Offset of next Sibling Directory " + formatDecHexString(folderMeta.nextSiblingDirectoryOffset) +"\n" + + "Offset of first Child Directory (Subdirectory) " + formatDecHexString(folderMeta.firstSubdirectoryOffset ) +"\n" + + "Offset of first File (in File Metadata Table) " + formatDecHexString(folderMeta.firstFileOffset ) +"\n" + + "Offset of next Directory in the same Hash Table bucket " + formatDecHexString(folderMeta.nextDirectoryOffset ) +"\n" + + "Name Length " + formatDecHexString(folderMeta.dirNameLength ) +"\n" + + "Name Length (rounded up to multiple of 4) " + folderMeta.dirName + "\n" ); + + allFolders.add(folderMeta); + } } private int getRealNameSize(int value){ diff --git a/src/main/java/konogonka/Tools/RomFs/Level6Header.java b/src/main/java/konogonka/Tools/RomFs/Level6Header.java index 6ae0171..4efaf73 100644 --- a/src/main/java/konogonka/Tools/RomFs/Level6Header.java +++ b/src/main/java/konogonka/Tools/RomFs/Level6Header.java @@ -20,6 +20,7 @@ package konogonka.Tools.RomFs; import konogonka.LoperConverter; +import konogonka.RainbowDump; public class Level6Header { private long headerLength; @@ -36,8 +37,10 @@ public class Level6Header { private byte[] headerBytes; private int i; - Level6Header(byte[] headerBytes){ + Level6Header(byte[] headerBytes) throws Exception{ this.headerBytes = headerBytes; + if (headerBytes.length < 0x50) + throw new Exception("Level 6 Header section is too small"); headerLength = getNext(); directoryHashTableOffset = getNext(); directoryHashTableLength = getNext(); @@ -66,4 +69,20 @@ public class Level6Header { public long getFileMetadataTableOffset() { return fileMetadataTableOffset; } public long getFileMetadataTableLength() { return fileMetadataTableLength; } public long getFileDataOffset() { return fileDataOffset; } + + public void printDebugInfo(){ + System.out.println("== Level 6 Header ==\n" + + "Header Length (always 0x50 ?) "+ RainbowDump.formatDecHexString(headerLength)+" (size of this structure within first 0x200 block of LEVEL 6 part)\n" + + "Directory Hash Table Offset "+ RainbowDump.formatDecHexString(directoryHashTableOffset)+" (against THIS block where HEADER contains)\n" + + "Directory Hash Table Length "+ RainbowDump.formatDecHexString(directoryHashTableLength) + "\n" + + "Directory Metadata Table Offset "+ RainbowDump.formatDecHexString(directoryMetadataTableOffset) + "\n" + + "Directory Metadata Table Length "+ RainbowDump.formatDecHexString(directoryMetadataTableLength) + "\n" + + "File Hash Table Offset "+ RainbowDump.formatDecHexString(fileHashTableOffset) + "\n" + + "File Hash Table Length "+ RainbowDump.formatDecHexString(fileHashTableLength) + "\n" + + "File Metadata Table Offset "+ RainbowDump.formatDecHexString(fileMetadataTableOffset) + "\n" + + "File Metadata Table Length "+ RainbowDump.formatDecHexString(fileMetadataTableLength) + "\n" + + "File Data Offset "+ RainbowDump.formatDecHexString(fileDataOffset) + "\n" + + "-------------------------------------------------------------" + ); + } } diff --git a/src/main/java/konogonka/Tools/RomFs/RomFsDecryptedProvider.java b/src/main/java/konogonka/Tools/RomFs/RomFsDecryptedProvider.java index 05ae1d0..6f7a7b0 100644 --- a/src/main/java/konogonka/Tools/RomFs/RomFsDecryptedProvider.java +++ b/src/main/java/konogonka/Tools/RomFs/RomFsDecryptedProvider.java @@ -23,12 +23,13 @@ import java.io.*; public class RomFsDecryptedProvider implements IRomFsProvider{ - private static final long LEVEL_6_DEFAULT_OFFSET = 0x14000; + private static final long LEVEL_6_DEFAULT_OFFSET = 0x14000; // TODO: FIX incorrect private File file; private Level6Header header; private FileSystemEntry rootEntry; + // TODO: FIX. LEVEL 6 OFFSET MUST be provided public RomFsDecryptedProvider(File decryptedFsImageFile) throws Exception{ // TODO: add default setup AND using meta-data headers from NCA RomFs section (?) this.file = decryptedFsImageFile; diff --git a/src/main/java/konogonka/Tools/RomFs/RomFsEncryptedProvider.java b/src/main/java/konogonka/Tools/RomFs/RomFsEncryptedProvider.java new file mode 100644 index 0000000..36e762f --- /dev/null +++ b/src/main/java/konogonka/Tools/RomFs/RomFsEncryptedProvider.java @@ -0,0 +1,239 @@ +/* + * Copyright 2019-2020 Dmitry Isaenko + * + * This file is part of Konogonka. + * + * Konogonka is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Konogonka is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Konogonka. If not, see . + */ + +package konogonka.Tools.RomFs; + +import konogonka.ctraes.AesCtrDecryptSimple; + +import java.io.*; +import java.util.Arrays; + +public class RomFsEncryptedProvider implements IRomFsProvider{ + + private static long level6Offset; + + private File file; + private Level6Header header; + + private FileSystemEntry rootEntry; + + //-------------------------------- + + private long romFSoffsetPosition; + private byte[] key; + private byte[] sectionCTR; + private long mediaStartOffset; + private long mediaEndOffset; + + public RomFsEncryptedProvider(long romFSoffsetPosition, + long level6Offset, + File fileWithEncPFS0, + byte[] key, + byte[] sectionCTR, + long mediaStartOffset, + long mediaEndOffset + ) throws Exception{ + this.file = fileWithEncPFS0; + this.level6Offset = level6Offset; + this.romFSoffsetPosition = romFSoffsetPosition; + this.key = key; + this.sectionCTR = sectionCTR; + this.mediaStartOffset = mediaStartOffset; + this.mediaEndOffset = mediaEndOffset; + + RandomAccessFile raf = new RandomAccessFile(file, "r"); + long abosluteOffsetPosition = romFSoffsetPosition + (mediaStartOffset * 0x200); + raf.seek(abosluteOffsetPosition + level6Offset); + + AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200); + //Go to Level 6 header + decryptor.skipNext(level6Offset / 0x200); + + // Decrypt data + byte[] encryptedBlock = new byte[0x200]; + byte[] dectyptedBlock; + if (raf.read(encryptedBlock) == 0x200) + dectyptedBlock = decryptor.dectyptNext(encryptedBlock); + else + throw new Exception("Failed to read header header (0x200 - block)"); + + this.header = new Level6Header(dectyptedBlock); + + header.printDebugInfo(); + + if (header.getDirectoryMetadataTableLength() < 0) + throw new Exception("Not supported: DirectoryMetadataTableLength < 0"); + + if (header.getFileMetadataTableLength() < 0) + throw new Exception("Not supported: FileMetadataTableLength < 0"); + + /*---------------------------------*/ + + // Read directories metadata + byte[] directoryMetadataTable = readMetaTable(abosluteOffsetPosition, + header.getDirectoryMetadataTableOffset(), + header.getDirectoryMetadataTableLength(), + raf); + + // Read files metadata + byte[] fileMetadataTable = readMetaTable(abosluteOffsetPosition, + header.getFileMetadataTableOffset(), + header.getFileMetadataTableLength(), + raf); + + rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable); + + raf.close(); + } + + private byte[] readMetaTable(long abosluteOffsetPosition, + long metaOffset, + long metaSize, + RandomAccessFile raf) throws Exception{ + byte[] encryptedBlock; + byte[] dectyptedBlock; + byte[] metadataTable = new byte[(int) metaSize]; + //0 + AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200); + + long startBlock = metaOffset / 0x200; + + decryptor.skipNext(level6Offset / 0x200 + startBlock); + + raf.seek(abosluteOffsetPosition + level6Offset + startBlock * 0x200); + + //1 + long ignoreBytes = metaOffset - startBlock * 0x200; + long currentPosition = 0; + + if (ignoreBytes > 0) { + encryptedBlock = new byte[0x200]; + if (raf.read(encryptedBlock) == 0x200) { + dectyptedBlock = decryptor.dectyptNext(encryptedBlock); + // If we have extra-small file that is less then a block and even more + if ((0x200 - ignoreBytes) > metaSize){ + metadataTable = Arrays.copyOfRange(dectyptedBlock, (int)ignoreBytes, 0x200); + return metadataTable; + } + else { + System.arraycopy(dectyptedBlock, (int) ignoreBytes, metadataTable, 0, 0x200 - (int) ignoreBytes); + currentPosition = 0x200 - ignoreBytes; + } + } + else { + throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from 1st bock for Directory Metadata Table"); + } + startBlock++; + } + long endBlock = (metaSize + ignoreBytes) / 0x200 + startBlock; // <- pointing to place where any data related to this media-block ends + + //2 + int extraData = (int) ((endBlock - startBlock)*0x200 - (metaSize + ignoreBytes)); + + if (extraData < 0) + endBlock--; + //3 + while ( startBlock < endBlock ) { + encryptedBlock = new byte[0x200]; + if (raf.read(encryptedBlock) == 0x200) { + dectyptedBlock = decryptor.dectyptNext(encryptedBlock); + System.arraycopy(dectyptedBlock, 0, metadataTable, (int) currentPosition, 0x200); + } + else + throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory Metadata Table"); + + startBlock++; + currentPosition += 0x200; + } + + //4 + if (extraData != 0){ // In case we didn't get what we want + encryptedBlock = new byte[0x200]; + if (raf.read(encryptedBlock) == 0x200) { + dectyptedBlock = decryptor.dectyptNext(encryptedBlock); + System.arraycopy(dectyptedBlock, 0, metadataTable, (int) currentPosition, Math.abs(extraData)); + } + else + throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory Metadata Table"); + } + + return metadataTable; + } + + @Override + public Level6Header getHeader() { return header; } + @Override + public FileSystemEntry getRootEntry() { return rootEntry; } + @Override + public PipedInputStream getContent(FileSystemEntry entry) throws Exception{ + if (entry.isDirectory()) + throw new Exception("Request of the binary stream for the folder entry doesn't make sense."); + + PipedOutputStream streamOut = new PipedOutputStream(); + Thread workerThread; + + PipedInputStream streamIn = new PipedInputStream(streamOut); + + workerThread = new Thread(() -> { + System.out.println("RomFsDecryptedProvider -> getContent(): Executing thread"); + try { + long subFileRealPosition = level6Offset + header.getFileDataOffset() + entry.getFileOffset(); + BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); + bis.skip(subFileRealPosition); + + int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576 + + long readFrom = 0; + long realFileSize = entry.getFileSize(); + + byte[] readBuf; + + while (readFrom < realFileSize) { + if (realFileSize - readFrom < readPice) + readPice = Math.toIntExact(realFileSize - readFrom); // it's safe, I guarantee + readBuf = new byte[readPice]; + if (bis.read(readBuf) != readPice) { + System.out.println("RomFsDecryptedProvider -> getContent(): Unable to read requested size from file."); + return; + } + streamOut.write(readBuf); + readFrom += readPice; + } + bis.close(); + streamOut.close(); + } catch (Exception e) { + System.out.println("RomFsDecryptedProvider -> getContent(): Unable to provide stream"); + e.printStackTrace(); + } + System.out.println("RomFsDecryptedProvider -> getContent(): Thread is dead"); + }); + workerThread.start(); + return streamIn; + } + @Override + public File getFile() { + return file; + } + + private void printDebug(byte[] directoryMetadataTable, byte[] fileMetadataTable){ + new FolderMeta4Debug(header.getDirectoryMetadataTableLength(), directoryMetadataTable); + new FileMeta4Debug(header.getFileMetadataTableLength(), fileMetadataTable); + rootEntry.printTreeForDebug(); + } +} diff --git a/src/main/resources/FXML/NCA/NCASectionContent.fxml b/src/main/resources/FXML/NCA/NCASectionContent.fxml index 037ccb7..09ffcbc 100644 --- a/src/main/resources/FXML/NCA/NCASectionContent.fxml +++ b/src/main/resources/FXML/NCA/NCASectionContent.fxml @@ -4,11 +4,7 @@ - - - - @@ -16,19 +12,19 @@ - -
- -
- - - -
- +
+ + + + + @@ -64,19 +60,9 @@ - + - - - - + diff --git a/src/main/resources/FXML/RomFS/RFSTab.fxml b/src/main/resources/FXML/RomFS/RFSTab.fxml index a11e724..66986af 100644 --- a/src/main/resources/FXML/RomFS/RFSTab.fxml +++ b/src/main/resources/FXML/RomFS/RFSTab.fxml @@ -15,7 +15,7 @@ - +