From 33ef6c5bf8cdfcc9c1b06f62f5b4107ba6d4a5aa Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko Date: Sun, 19 Apr 2020 04:42:54 +0300 Subject: [PATCH] Drafts for RomFs support --- .../konogonka/Controllers/MainController.java | 78 ++++++++++------- .../Controllers/RFS/RFSFolderEntry.java | 36 ++++++++ .../Controllers/RFS/RomFsController.java | 81 +++++++++++++++++ .../{RainbowHexDump.java => RainbowDump.java} | 6 +- .../java/konogonka/Tools/NCA/NCAContent.java | 1 - .../NPDM/KernelAccessControlProvider.java | 3 - .../java/konogonka/Tools/RomFs/FileMeta.java | 29 +++++++ .../konogonka/Tools/RomFs/FolderMeta.java | 29 +++++++ .../konogonka/Tools/RomFs/Level6Header.java | 84 ++++++++++++++++++ .../Tools/RomFs/RomFsDecryptedProvider.java | 87 +++++++++++++++++++ .../konogonka/Tools/XCI/HFS0Provider.java | 1 - .../resources/FXML/NCA/NCASectionContent.fxml | 18 ++++ src/main/resources/FXML/RomFS/RFSTab.fxml | 15 ++++ src/main/resources/FXML/landingPage.fxml | 8 ++ src/main/resources/res/app_light.css | 7 ++ 15 files changed, 445 insertions(+), 38 deletions(-) create mode 100644 src/main/java/konogonka/Controllers/RFS/RFSFolderEntry.java create mode 100644 src/main/java/konogonka/Controllers/RFS/RomFsController.java rename src/main/java/konogonka/{RainbowHexDump.java => RainbowDump.java} (93%) create mode 100644 src/main/java/konogonka/Tools/RomFs/FileMeta.java create mode 100644 src/main/java/konogonka/Tools/RomFs/FolderMeta.java create mode 100644 src/main/java/konogonka/Tools/RomFs/Level6Header.java create mode 100644 src/main/java/konogonka/Tools/RomFs/RomFsDecryptedProvider.java create mode 100644 src/main/resources/FXML/RomFS/RFSTab.fxml diff --git a/src/main/java/konogonka/Controllers/MainController.java b/src/main/java/konogonka/Controllers/MainController.java index 0450770..bd112c4 100644 --- a/src/main/java/konogonka/Controllers/MainController.java +++ b/src/main/java/konogonka/Controllers/MainController.java @@ -28,6 +28,7 @@ import konogonka.Child.ChildWindow; import konogonka.Controllers.NCA.NCAController; import konogonka.Controllers.NPDM.NPDMController; import konogonka.Controllers.NSP.NSPController; +import konogonka.Controllers.RFS.RomFsController; import konogonka.Controllers.TIK.TIKController; import konogonka.Controllers.XCI.XCIController; import konogonka.Controllers.XML.XMLController; @@ -71,6 +72,8 @@ public class MainController implements Initializable { private XMLController XMLTabController; @FXML private NPDMController NPDMTabController; + @FXML + private RomFsController RFSTabController; private File selectedFile; @@ -104,10 +107,23 @@ public class MainController implements Initializable { else fileChooser.setInitialDirectory(new File(System.getProperty("user.home"))); - fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NS files", "*.nsp", "*.nsz", "*.xci", "*.nca", "*.tik", "*.xml", "*.npdm")); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NS files", + "*.nsp", "*.nsz", "*.xci", "*.nca", "*.tik", "*.xml", "*.npdm", "*.romfs")); this.selectedFile = fileChooser.showOpenDialog(analyzeBtn.getScene().getWindow()); - // todo: fix + + if (this.selectedFile != null && this.selectedFile.exists()) { + resetAllTabsContent(); + filenameSelected.setText(this.selectedFile.getAbsolutePath()); + previouslyOpenedPath = this.selectedFile.getParent(); + analyzeBtn.setDisable(false); + String fileExtension = this.selectedFile.getName().toLowerCase().replaceAll("^.*\\.", ""); + setFocusOnPane(fileExtension); + } + + logArea.clear(); + } + private void resetAllTabsContent(){ analyzeBtn.setDisable(true); NSPTabController.resetTab(); XCITabController.resetTab(); @@ -115,36 +131,32 @@ public class MainController implements Initializable { TIKTabController.resetTab(); XMLTabController.resetTab(); NPDMTabController.resetTab(); - - if (this.selectedFile != null && this.selectedFile.exists()) { - filenameSelected.setText(this.selectedFile.getAbsolutePath()); - previouslyOpenedPath = this.selectedFile.getParent(); - analyzeBtn.setDisable(false); - String fileExtension = this.selectedFile.getName().toLowerCase().replaceAll("^.*\\.", ""); - switch (fileExtension){ - case "nsp": - case "nsz": - tabPane.getSelectionModel().select(0); - break; - case "xci": - tabPane.getSelectionModel().select(1); - break; - case "nca": - tabPane.getSelectionModel().select(2); - break; - case "tic": - tabPane.getSelectionModel().select(3); - break; - case "xml": - tabPane.getSelectionModel().select(4); - break; - case "npdm": - tabPane.getSelectionModel().select(5); - break; - } + RFSTabController.resetTab(); + } + private void setFocusOnPane(String fileExtension){ + switch (fileExtension){ + case "nsp": + case "nsz": + tabPane.getSelectionModel().select(0); + break; + case "xci": + tabPane.getSelectionModel().select(1); + break; + case "nca": + tabPane.getSelectionModel().select(2); + break; + case "tic": + tabPane.getSelectionModel().select(3); + break; + case "xml": + tabPane.getSelectionModel().select(4); + break; + case "npdm": + tabPane.getSelectionModel().select(5); + break; + case "romfs": + tabPane.getSelectionModel().select(6); } - - logArea.clear(); } /** * Start analyze @@ -154,7 +166,7 @@ public class MainController implements Initializable { switch (fileExtension){ case "nsp": case "nsz": - NSPTabController.analyze(selectedFile); // TODO: NSP OR XCI + NSPTabController.analyze(selectedFile); // TODO: NSP OR NSZ ? break; case "xci": XCITabController.analyze(selectedFile); @@ -171,6 +183,8 @@ public class MainController implements Initializable { case "npdm": NPDMTabController.analyze(selectedFile); break; + case "romfs": + RFSTabController.analyze(selectedFile); } } @FXML diff --git a/src/main/java/konogonka/Controllers/RFS/RFSFolderEntry.java b/src/main/java/konogonka/Controllers/RFS/RFSFolderEntry.java new file mode 100644 index 0000000..f4bf4b6 --- /dev/null +++ b/src/main/java/konogonka/Controllers/RFS/RFSFolderEntry.java @@ -0,0 +1,36 @@ +/* + 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.Controllers.RFS; + +public class RFSFolderEntry { + private String name; + + public RFSFolderEntry(String name){ + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public String toString(){ + return name; + } +} diff --git a/src/main/java/konogonka/Controllers/RFS/RomFsController.java b/src/main/java/konogonka/Controllers/RFS/RomFsController.java new file mode 100644 index 0000000..46f662f --- /dev/null +++ b/src/main/java/konogonka/Controllers/RFS/RomFsController.java @@ -0,0 +1,81 @@ +/* + 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.Controllers.RFS; + +import javafx.fxml.FXML; +import javafx.scene.control.TreeItem; +import javafx.scene.control.TreeView; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import konogonka.Controllers.ITabController; +import konogonka.Tools.ISuperProvider; + +import java.io.File; +import java.net.URL; +import java.util.ResourceBundle; + +public class RomFsController implements ITabController { + + @FXML + private TreeView filesTreeView; + @FXML + private VBox folderContentVBox; + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + TreeItem rootTest = getEmptyRoot(); + TreeItem test = new TreeItem<>(new RFSFolderEntry("WIP"), getFolderImage()); + + rootTest.getChildren().add(test); + + filesTreeView.setRoot(rootTest); + } + + @Override + public void analyze(File file) { + this.analyze(file, 0); + } + + @Override + public void analyze(File file, long offset) { + TreeItem rootItem = getEmptyRoot(); + + filesTreeView.setRoot(rootItem); + } + + @Override + public void analyze(ISuperProvider parentProvider, int fileNo) throws Exception { + + } + + @Override + public void resetTab() { + filesTreeView.setRoot(null); + } + + private Region getFolderImage(){ + final Region folderImage = new Region(); + folderImage.getStyleClass().add("regionFolder"); + return folderImage; + } + + private TreeItem getEmptyRoot(){ + return new TreeItem<>(new RFSFolderEntry("/")); + } +} diff --git a/src/main/java/konogonka/RainbowHexDump.java b/src/main/java/konogonka/RainbowDump.java similarity index 93% rename from src/main/java/konogonka/RainbowHexDump.java rename to src/main/java/konogonka/RainbowDump.java index 21593ed..97d6e6f 100644 --- a/src/main/java/konogonka/RainbowHexDump.java +++ b/src/main/java/konogonka/RainbowDump.java @@ -23,7 +23,7 @@ import java.nio.charset.StandardCharsets; /** * Debug tool like hexdump <3 */ -public class RainbowHexDump { +public class RainbowDump { private static final String ANSI_RESET = "\u001B[0m"; private static final String ANSI_BLACK = "\u001B[30m"; private static final String ANSI_RED = "\u001B[31m"; @@ -55,4 +55,8 @@ public class RainbowHexDump { public static void octDumpLong(long value){ System.out.println(String.format("%64s", Long.toBinaryString( value )).replace(' ', '0')+" | "+value); } + + public static String formatDecHexString(long value){ + return String.format("%-20d 0x%x", value, value); + } } diff --git a/src/main/java/konogonka/Tools/NCA/NCAContent.java b/src/main/java/konogonka/Tools/NCA/NCAContent.java index 5b8aec8..4577908 100644 --- a/src/main/java/konogonka/Tools/NCA/NCAContent.java +++ b/src/main/java/konogonka/Tools/NCA/NCAContent.java @@ -313,7 +313,6 @@ public class NCAContent { } } private class CryptoSection03RomFS{ - CryptoSection03RomFS(File file, long offsetPosition, byte[] decryptedKey, diff --git a/src/main/java/konogonka/Tools/NPDM/KernelAccessControlProvider.java b/src/main/java/konogonka/Tools/NPDM/KernelAccessControlProvider.java index ddc8625..75d6d66 100644 --- a/src/main/java/konogonka/Tools/NPDM/KernelAccessControlProvider.java +++ b/src/main/java/konogonka/Tools/NPDM/KernelAccessControlProvider.java @@ -19,12 +19,9 @@ package konogonka.Tools.NPDM; import konogonka.LoperConverter; -import konogonka.RainbowHexDump; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedList; -import java.util.Map; /* NOTE: This implementation is extremely bad for using application as library. Use raw for own purposes. diff --git a/src/main/java/konogonka/Tools/RomFs/FileMeta.java b/src/main/java/konogonka/Tools/RomFs/FileMeta.java new file mode 100644 index 0000000..e4f4d0d --- /dev/null +++ b/src/main/java/konogonka/Tools/RomFs/FileMeta.java @@ -0,0 +1,29 @@ +/* + 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; + +public class FileMeta { + private int containingDirectoryOffset; + private int nextSiblingFileOffset; + private long fileDataOffset; + private long fileDataLength; + private int nextFileOffset; + private int fileNameLength; + private String fileName; +} diff --git a/src/main/java/konogonka/Tools/RomFs/FolderMeta.java b/src/main/java/konogonka/Tools/RomFs/FolderMeta.java new file mode 100644 index 0000000..0c6cd7e --- /dev/null +++ b/src/main/java/konogonka/Tools/RomFs/FolderMeta.java @@ -0,0 +1,29 @@ +/* + 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; + +public class FolderMeta { + private int parentDirectoryOffset; + private int nextSiblingDirectoryOffset; + private int firstSubdirectoryOffset; + private int firstFileOffset; + private int nextDirectoryOffset; + private int dirNameLength; + private String dirName; +} diff --git a/src/main/java/konogonka/Tools/RomFs/Level6Header.java b/src/main/java/konogonka/Tools/RomFs/Level6Header.java new file mode 100644 index 0000000..d11c334 --- /dev/null +++ b/src/main/java/konogonka/Tools/RomFs/Level6Header.java @@ -0,0 +1,84 @@ +/* + * 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.LoperConverter; +import konogonka.RainbowDump; + +public class Level6Header { + private long headerHeaderLength; + private long headerDirectoryHashTableOffset; + private long headerDirectoryHashTableLength; + private long headerDirectoryMetadataTableOffset; + private long headerDirectoryMetadataTableLength; + private long headerFileHashTableOffset; + private long headerFileHashTableLength; + private long headerFileMetadataTableOffset; + private long headerFileMetadataTableLength; + private long headerFileDataOffset; + + Level6Header(byte[] headerBytes){ + int i = 0; + headerHeaderLength = LoperConverter.getLEint(headerBytes, i); + i += 0x8; + headerDirectoryHashTableOffset = LoperConverter.getLEint(headerBytes, i); + i += 0x8; + headerDirectoryHashTableLength = LoperConverter.getLEint(headerBytes, i); + i += 0x8; + headerDirectoryMetadataTableOffset = LoperConverter.getLEint(headerBytes, i); + i += 0x8; + headerDirectoryMetadataTableLength = LoperConverter.getLEint(headerBytes, i); + i += 0x8; + headerFileHashTableOffset = LoperConverter.getLEint(headerBytes, i); + i += 0x8; + headerFileHashTableLength = LoperConverter.getLEint(headerBytes, i); + i += 0x8; + headerFileMetadataTableOffset = LoperConverter.getLEint(headerBytes, i); + i += 0x8; + headerFileMetadataTableLength = LoperConverter.getLEint(headerBytes, i); + i += 0x8; + headerFileDataOffset = LoperConverter.getLEint(headerBytes, i); + + System.out.println("== Level 6 Header ==\n" + + "Header Length (always 0x50 ?) "+ RainbowDump.formatDecHexString(headerHeaderLength)+" (size of this structure within first 0x200 block of LEVEL 6 part)\n" + + "Directory Hash Table Offset "+ RainbowDump.formatDecHexString(headerDirectoryHashTableOffset)+" (against THIS block where HEADER contains)\n" + + "Directory Hash Table Length "+ RainbowDump.formatDecHexString(headerDirectoryHashTableLength) + "\n" + + "Directory Metadata Table Offset "+ RainbowDump.formatDecHexString(headerDirectoryMetadataTableOffset) + "\n" + + "Directory Metadata Table Length "+ RainbowDump.formatDecHexString(headerDirectoryMetadataTableLength) + "\n" + + "File Hash Table Offset "+ RainbowDump.formatDecHexString(headerFileHashTableOffset) + "\n" + + "File Hash Table Length "+ RainbowDump.formatDecHexString(headerFileHashTableLength) + "\n" + + "File Metadata Table Offset "+ RainbowDump.formatDecHexString(headerFileMetadataTableOffset) + "\n" + + "File Metadata Table Length "+ RainbowDump.formatDecHexString(headerFileMetadataTableLength) + "\n" + + "File Data Offset "+ RainbowDump.formatDecHexString(headerFileDataOffset) + "\n" + + "-------------------------------------------------------------" + ); + } + + public long getHeaderHeaderLength() { return headerHeaderLength; } + public long getHeaderDirectoryHashTableOffset() { return headerDirectoryHashTableOffset; } + public long getHeaderDirectoryHashTableLength() { return headerDirectoryHashTableLength; } + public long getHeaderDirectoryMetadataTableOffset() { return headerDirectoryMetadataTableOffset; } + public long getHeaderDirectoryMetadataTableLength() { return headerDirectoryMetadataTableLength; } + public long getHeaderFileHashTableOffset() { return headerFileHashTableOffset; } + public long getHeaderFileHashTableLength() { return headerFileHashTableLength; } + public long getHeaderFileMetadataTableOffset() { return headerFileMetadataTableOffset; } + public long getHeaderFileMetadataTableLength() { return headerFileMetadataTableLength; } + public long getHeaderFileDataOffset() { return headerFileDataOffset; } +} diff --git a/src/main/java/konogonka/Tools/RomFs/RomFsDecryptedProvider.java b/src/main/java/konogonka/Tools/RomFs/RomFsDecryptedProvider.java new file mode 100644 index 0000000..ff57205 --- /dev/null +++ b/src/main/java/konogonka/Tools/RomFs/RomFsDecryptedProvider.java @@ -0,0 +1,87 @@ +/* + * 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.Tools.ISuperProvider; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.PipedInputStream; + +public class RomFsDecryptedProvider implements ISuperProvider { + + private static final long LEVEL_6_DEFAULT_OFFSET = 0x14000; + + private File decryptedFSImage; + private Level6Header header; + + public RomFsDecryptedProvider(File decryptedFSImage) throws Exception{ // TODO: add default setup AND using meta-data headers from NCA RomFs section (?) + this.decryptedFSImage = decryptedFSImage; + + BufferedInputStream bis = new BufferedInputStream(new FileInputStream(decryptedFSImage)); + + skipTo(bis, LEVEL_6_DEFAULT_OFFSET); + + byte[] rawDataChunk = new byte[0x50]; + + if (bis.read(rawDataChunk) != 0x50) + throw new Exception("Failed to read header (0x50)"); + + this.header = new Level6Header(rawDataChunk); + + bis.close(); + } + private void skipTo(BufferedInputStream bis, long size) throws Exception{ + long mustSkip = size; + long skipped = 0; + while (mustSkip > 0){ + skipped += bis.skip(mustSkip); + mustSkip = size - skipped; + } + } + private int getRealNameSize(int value){ + if (value % 4 == 0) + return value; + return value + 4 - value % 4; + } + + public Level6Header getHeader() { return header; } + + @Override + public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception { + return null; + } + + @Override + public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception { + return null; + } + + @Override + public File getFile() { + return decryptedFSImage; + } + + @Override + public long getRawFileDataStart() { + return 0; + } +} diff --git a/src/main/java/konogonka/Tools/XCI/HFS0Provider.java b/src/main/java/konogonka/Tools/XCI/HFS0Provider.java index 4bdc1eb..df4ff9b 100644 --- a/src/main/java/konogonka/Tools/XCI/HFS0Provider.java +++ b/src/main/java/konogonka/Tools/XCI/HFS0Provider.java @@ -18,7 +18,6 @@ */ package konogonka.Tools.XCI; -import konogonka.RainbowHexDump; import konogonka.Tools.ISuperProvider; import java.io.*; diff --git a/src/main/resources/FXML/NCA/NCASectionContent.fxml b/src/main/resources/FXML/NCA/NCASectionContent.fxml index bedecd3..037ccb7 100644 --- a/src/main/resources/FXML/NCA/NCASectionContent.fxml +++ b/src/main/resources/FXML/NCA/NCASectionContent.fxml @@ -4,7 +4,10 @@ + + + @@ -61,5 +64,20 @@ + + + + + + + + diff --git a/src/main/resources/FXML/RomFS/RFSTab.fxml b/src/main/resources/FXML/RomFS/RFSTab.fxml new file mode 100644 index 0000000..bd209f1 --- /dev/null +++ b/src/main/resources/FXML/RomFS/RFSTab.fxml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/main/resources/FXML/landingPage.fxml b/src/main/resources/FXML/landingPage.fxml index 525a213..3e2f2a0 100644 --- a/src/main/resources/FXML/landingPage.fxml +++ b/src/main/resources/FXML/landingPage.fxml @@ -95,6 +95,14 @@