diff --git a/README.md b/README.md index 98b56b0..75a78d4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # konogonka -Deep WIP multi-tool to work with XCI/NSP/NCA/NRO(?) files +Deep WIP multi-tool to work with NS-specific files / filesystem images. ### License @@ -24,5 +24,6 @@ JRE/JDK 8u60 or higher. ### Checklist * [ ] LogPrinter to singleton implementation +* [ ] NPDM support * [ ] CNMT support -* [ ] support \ No newline at end of file +* [ ] NSO support \ No newline at end of file diff --git a/pom.xml b/pom.xml index 88a71f2..b5be10a 100644 --- a/pom.xml +++ b/pom.xml @@ -8,11 +8,11 @@ konogonka konogonka - 0.1-SNAPSHOT + 0.0-SNAPSHOT https://github.com/developersu/${project.name}}/ - NSP and XCI multitool + NS filesystem multitool 2019 diff --git a/src/main/java/konogonka/Controllers/MainController.java b/src/main/java/konogonka/Controllers/MainController.java index 7e10a2e..d9e79eb 100644 --- a/src/main/java/konogonka/Controllers/MainController.java +++ b/src/main/java/konogonka/Controllers/MainController.java @@ -8,6 +8,7 @@ import javafx.stage.FileChooser; import konogonka.AppPreferences; import konogonka.Child.ChildWindow; import konogonka.Controllers.NCA.NCAController; +import konogonka.Controllers.NPDM.NPDMController; import konogonka.Controllers.NSP.NSPController; import konogonka.Controllers.TIK.TIKController; import konogonka.Controllers.XCI.XCIController; @@ -50,6 +51,8 @@ public class MainController implements Initializable { private TIKController TIKTabController; @FXML private XMLController XMLTabController; + @FXML + private NPDMController NPDMTabController; private File selectedFile; @@ -83,7 +86,7 @@ public class MainController implements Initializable { else fileChooser.setInitialDirectory(new File(System.getProperty("user.home"))); - fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NS files", "*.nsp", "*.xci", "*.nca", "*.tik", "*.xml")); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NS files", "*.nsp", "*.xci", "*.nca", "*.tik", "*.xml", "*.npdm")); this.selectedFile = fileChooser.showOpenDialog(analyzeBtn.getScene().getWindow()); @@ -93,6 +96,7 @@ public class MainController implements Initializable { NCATabController.resetTab(); TIKTabController.resetTab(); XMLTabController.resetTab(); + NPDMTabController.resetTab(); if (this.selectedFile != null && this.selectedFile.exists()) { filenameSelected.setText(this.selectedFile.getAbsolutePath()); @@ -108,6 +112,8 @@ public class MainController implements Initializable { tabPane.getSelectionModel().select(3); else if (this.selectedFile.getName().toLowerCase().endsWith(".xml")) tabPane.getSelectionModel().select(4); + else if (this.selectedFile.getName().toLowerCase().endsWith(".npdm")) + tabPane.getSelectionModel().select(5); } logArea.clear(); @@ -126,6 +132,8 @@ public class MainController implements Initializable { TIKTabController.analyze(selectedFile); else if (selectedFile.getName().toLowerCase().endsWith("xml")) XMLTabController.analyze(selectedFile); + else if (selectedFile.getName().toLowerCase().endsWith("npdm")) + NPDMTabController.analyze(selectedFile); } @FXML private void showHideLogs(){ diff --git a/src/main/java/konogonka/Controllers/NPDM/ACI0Provider.java b/src/main/java/konogonka/Controllers/NPDM/ACI0Provider.java new file mode 100644 index 0000000..2fa6bc9 --- /dev/null +++ b/src/main/java/konogonka/Controllers/NPDM/ACI0Provider.java @@ -0,0 +1,4 @@ +package konogonka.Controllers.NPDM; + +public class ACI0Provider { +} diff --git a/src/main/java/konogonka/Controllers/NPDM/ACIDProvider.java b/src/main/java/konogonka/Controllers/NPDM/ACIDProvider.java new file mode 100644 index 0000000..9fb5584 --- /dev/null +++ b/src/main/java/konogonka/Controllers/NPDM/ACIDProvider.java @@ -0,0 +1,4 @@ +package konogonka.Controllers.NPDM; + +public class ACIDProvider { +} diff --git a/src/main/java/konogonka/Controllers/NPDM/NPDMController.java b/src/main/java/konogonka/Controllers/NPDM/NPDMController.java new file mode 100644 index 0000000..f6149c3 --- /dev/null +++ b/src/main/java/konogonka/Controllers/NPDM/NPDMController.java @@ -0,0 +1,110 @@ +package konogonka.Controllers.NPDM; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import konogonka.Controllers.ITabController; +import konogonka.Tools.NPDM.NPDMProvider; +import konogonka.Workers.AnalyzerNPDM; + +import java.io.File; +import java.net.URL; +import java.util.Locale; +import java.util.ResourceBundle; + +import static konogonka.LoperConverter.byteArrToHexString; + +public class NPDMController implements ITabController { + + @FXML + private Label magicNumLbl, + reserved1Lbl, + MMUFlagsLbl, + reserved2Lbl, + mainThreadPrioLbl, + mainThreadCoreNumLbl, + reserved3Lbl, + personalMmHeapSizeLbl, + versionLbl, + mainThreadStackSizeLbl, + aci0offsetLbl, + aci0sizeLbl, + acidOffsetLbl, + acidSizeLbl, + npdmFileSize; + + @FXML + private TextField titleNameTf, + productCodeTf, + reserved4Tf; + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { } + + @Override + public void analyze(File file) { analyze(file, 0); } + + @Override + public void analyze(File file, long offset) { + AnalyzerNPDM analyzerNPDM = new AnalyzerNPDM(file, offset); + analyzerNPDM.setOnSucceeded(e->{ + NPDMProvider tik = analyzerNPDM.getValue(); + if (offset == 0) + setData(tik, file); + else + setData(tik, null); + }); + Thread workThread = new Thread(analyzerNPDM); + workThread.setDaemon(true); + workThread.start(); + } + + @Override + public void resetTab() { + magicNumLbl.setText("-"); + reserved1Lbl.setText("-"); + MMUFlagsLbl.setText("-"); + reserved2Lbl.setText("-"); + mainThreadPrioLbl.setText("-"); + mainThreadCoreNumLbl.setText("-"); + reserved3Lbl.setText("-"); + personalMmHeapSizeLbl.setText("-"); + versionLbl.setText("-"); + mainThreadStackSizeLbl.setText("-"); + aci0offsetLbl.setText("-"); + aci0sizeLbl.setText("-"); + acidOffsetLbl.setText("-"); + acidSizeLbl.setText("-"); + titleNameTf.setText("-"); + productCodeTf.setText("-"); + reserved4Tf.setText("-"); + npdmFileSize.setText("-"); + } + private void setData(NPDMProvider npdmProvider, File file) { + if (npdmProvider == null) + return; + if (file != null) + npdmFileSize.setText(Long.toString(file.length())); + else + npdmFileSize.setText("skipping calculation for in-file ticket"); + + magicNumLbl.setText(npdmProvider.getMagicNum()); + reserved1Lbl.setText(byteArrToHexString(npdmProvider.getReserved1())); + MMUFlagsLbl.setText(npdmProvider.getMMUFlags()+" (0b"+String.format("%8s", Integer.toBinaryString(npdmProvider.getMMUFlags() & 0xFF)).replace(' ', '0')+")"); + reserved2Lbl.setText(String.format("0x%02x", npdmProvider.getReserved2())); + mainThreadPrioLbl.setText(Byte.toString(npdmProvider.getMainThreadPrio())); + mainThreadCoreNumLbl.setText(Byte.toString(npdmProvider.getMainThreadCoreNum())); + reserved3Lbl.setText(byteArrToHexString(npdmProvider.getReserved3())); + personalMmHeapSizeLbl.setText(Integer.toString(npdmProvider.getPersonalMmHeapSize())); + versionLbl.setText(Integer.toString(npdmProvider.getVersion())); + mainThreadStackSizeLbl.setText(Long.toString(npdmProvider.getMainThreadStackSize())); + titleNameTf.setText(npdmProvider.getTitleName()); + productCodeTf.setText(byteArrToHexString(npdmProvider.getProductCode())); + reserved4Tf.setText(byteArrToHexString(npdmProvider.getReserved4())); + aci0offsetLbl.setText(Long.toString(npdmProvider.getAci0offset())); + aci0sizeLbl.setText(Long.toString(npdmProvider.getAci0size())); + acidOffsetLbl.setText(Long.toString(npdmProvider.getAcidOffset())); + acidSizeLbl.setText(Long.toString(npdmProvider.getAcidSize())); + } + +} diff --git a/src/main/java/konogonka/Controllers/TIK/TIKController.java b/src/main/java/konogonka/Controllers/TIK/TIKController.java index 855d29b..394bfe0 100644 --- a/src/main/java/konogonka/Controllers/TIK/TIKController.java +++ b/src/main/java/konogonka/Controllers/TIK/TIKController.java @@ -48,9 +48,7 @@ public class TIKController implements ITabController { String key = rightsIdTf.getText(); String value = titleKeyBlockStartTf.getText(); int titleKeysCnt = AppPreferences.getInstance().getTitleKeysCount(); - System.out.println(key+" "+value+" "+titleKeysCnt); if (key.length() > 16 && ! (key.length() > 32) && value.length() == 32){ - System.out.println("OK"); for (int i = 0; i < titleKeysCnt; i++){ if (AppPreferences.getInstance().getTitleKeyPair(i)[0].equals(key)) return; diff --git a/src/main/java/konogonka/Controllers/XML/XMLController.java b/src/main/java/konogonka/Controllers/XML/XMLController.java index d1d0f89..97cb398 100644 --- a/src/main/java/konogonka/Controllers/XML/XMLController.java +++ b/src/main/java/konogonka/Controllers/XML/XMLController.java @@ -51,7 +51,6 @@ public class XMLController implements ITabController { * Read from offset to length * */ public void analyze(File file, long offset, long fileSize) { - System.out.println(file.length()+" "+offset+" "+fileSize); try { if (fileSize > 10485760) // 10mB throw new Exception("XMLController -> analyze(): File is too big. It must be something wrong with it. Usually they're smaller"); diff --git a/src/main/java/konogonka/LoperConverter.java b/src/main/java/konogonka/LoperConverter.java index 6ee0448..2076030 100644 --- a/src/main/java/konogonka/LoperConverter.java +++ b/src/main/java/konogonka/LoperConverter.java @@ -11,6 +11,17 @@ public class LoperConverter { public static long getLElong(byte[] bytes, int fromOffset){ return ByteBuffer.wrap(bytes, fromOffset, 0x8).order(ByteOrder.LITTLE_ENDIAN).getLong(); } + /** + * Convert int to long. Workaround to store unsigned int + * @param bytes original array + * @param fromOffset start position of the 4-bytes value + * */ + public static long getLElongOfInt(byte[] bytes, int fromOffset){ + final byte[] holder = new byte[8]; + System.arraycopy(bytes, fromOffset, holder, 0, 4); + return ByteBuffer.wrap(holder).order(ByteOrder.LITTLE_ENDIAN).getLong(); + } + public static String byteArrToHexString(byte[] bArr){ if (bArr == null) return ""; diff --git a/src/main/java/konogonka/MainFx.java b/src/main/java/konogonka/MainFx.java index f21fdbf..664c969 100644 --- a/src/main/java/konogonka/MainFx.java +++ b/src/main/java/konogonka/MainFx.java @@ -12,7 +12,7 @@ import java.util.Locale; import java.util.ResourceBundle; public class MainFx extends Application { - public static final String appVersion = "v0.1-DEV"; + public static final String appVersion = "v0.0-SNAPSHOT"; @Override public void start(Stage primaryStage) throws Exception{ diff --git a/src/main/java/konogonka/Tools/NPDM/NPDMProvider.java b/src/main/java/konogonka/Tools/NPDM/NPDMProvider.java new file mode 100644 index 0000000..95c1d19 --- /dev/null +++ b/src/main/java/konogonka/Tools/NPDM/NPDMProvider.java @@ -0,0 +1,77 @@ +package konogonka.Tools.NPDM; + +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import static konogonka.LoperConverter.*; + +public class NPDMProvider { + + private String magicNum; + private byte[] reserved1; + private byte MMUFlags; + private byte reserved2; + private byte mainThreadPrio; + private byte mainThreadCoreNum; + private byte[] reserved3; + private int personalMmHeapSize; // safe-to-store + private int version; // safe? + private long mainThreadStackSize; // TODO: check if safe + private String titleName; + private byte[] productCode; + private byte[] reserved4; + private long aci0offset; // originally 4-bytes (u-int) + private long aci0size; // originally 4-bytes (u-int) + private long acidOffset; // originally 4-bytes (u-int) + private long acidSize; // originally 4-bytes (u-int) + + public NPDMProvider(File file) throws Exception { this(file, 0); } + + public NPDMProvider(File file, long offset) throws Exception { + if (file.length() - offset < 0x80) // Header's size + throw new Exception("NPDMProvider: File is too small."); + RandomAccessFile raf = new RandomAccessFile(file, "r"); + raf.seek(offset); + // Get META + byte[] metaBuf = new byte[0x80]; + if (raf.read(metaBuf) != 0x80) + throw new Exception("NPDMProvider: Failed to read 'META'"); + magicNum = new String(metaBuf, 0, 4, StandardCharsets.UTF_8); + reserved1 = Arrays.copyOfRange(metaBuf, 0x4, 0xC); + MMUFlags = metaBuf[0xC]; + reserved2 = metaBuf[0xD]; + mainThreadPrio = metaBuf[0xE]; + mainThreadCoreNum = metaBuf[0xF]; + reserved3 = Arrays.copyOfRange(metaBuf, 0x10, 0x14); + personalMmHeapSize = getLEint(metaBuf, 0x14); + version = getLEint(metaBuf, 0x18); + mainThreadStackSize = getLElongOfInt(metaBuf, 0x1C); + titleName = new String(metaBuf, 0x20, 0x10, StandardCharsets.UTF_8); + productCode = Arrays.copyOfRange(metaBuf, 0x30, 0x40); + reserved4 = Arrays.copyOfRange(metaBuf, 0x40, 0x70); + aci0offset = getLElongOfInt(metaBuf, 0x70); + aci0size = getLElongOfInt(metaBuf, 0x74); + acidOffset = getLElongOfInt(metaBuf, 0x78); + acidSize = getLElongOfInt(metaBuf, 0x7C); + } + + public String getMagicNum() { return magicNum; } + public byte[] getReserved1() { return reserved1; } + public byte getMMUFlags() { return MMUFlags; } + public byte getReserved2() { return reserved2; } + public byte getMainThreadPrio() { return mainThreadPrio; } + public byte getMainThreadCoreNum() { return mainThreadCoreNum; } + public byte[] getReserved3() { return reserved3; } + public int getPersonalMmHeapSize() { return personalMmHeapSize; } + public int getVersion() { return version; } + public long getMainThreadStackSize() { return mainThreadStackSize; } + public String getTitleName() { return titleName; } + public byte[] getProductCode() { return productCode; } + public byte[] getReserved4() { return reserved4; } + public long getAci0offset() { return aci0offset; } + public long getAci0size() { return aci0size; } + public long getAcidOffset() { return acidOffset; } + public long getAcidSize() { return acidSize; } +} diff --git a/src/main/java/konogonka/Tools/PFS0/PFS0Provider.java b/src/main/java/konogonka/Tools/PFS0/PFS0Provider.java index e025ec3..e50294b 100644 --- a/src/main/java/konogonka/Tools/PFS0/PFS0Provider.java +++ b/src/main/java/konogonka/Tools/PFS0/PFS0Provider.java @@ -1,8 +1,5 @@ package konogonka.Tools.PFS0; -import konogonka.ModelControllers.EMsgType; -import konogonka.RainbowHexDump; - import java.io.*; import java.nio.charset.StandardCharsets; import java.util.Arrays; diff --git a/src/main/java/konogonka/Workers/AnalyzerNCA.java b/src/main/java/konogonka/Workers/AnalyzerNCA.java index 5b02985..3171f85 100644 --- a/src/main/java/konogonka/Workers/AnalyzerNCA.java +++ b/src/main/java/konogonka/Workers/AnalyzerNCA.java @@ -3,7 +3,6 @@ package konogonka.Workers; import javafx.concurrent.Task; import konogonka.ModelControllers.EMsgType; import konogonka.ModelControllers.LogPrinter; -import konogonka.Tools.ISuperProvider; import konogonka.Tools.NCA.NCAProvider; import java.io.File; diff --git a/src/main/java/konogonka/Workers/AnalyzerNPDM.java b/src/main/java/konogonka/Workers/AnalyzerNPDM.java new file mode 100644 index 0000000..4745e6e --- /dev/null +++ b/src/main/java/konogonka/Workers/AnalyzerNPDM.java @@ -0,0 +1,46 @@ +package konogonka.Workers; + +import javafx.concurrent.Task; +import konogonka.ModelControllers.EMsgType; +import konogonka.ModelControllers.LogPrinter; +import konogonka.Tools.NPDM.NPDMProvider; + +import java.io.File; + +public class AnalyzerNPDM extends Task { + + private File file; + private long offset; + private LogPrinter logPrinter; + + public AnalyzerNPDM(File file){ + this(file, 0); + } + + public AnalyzerNPDM(File file, long offset){ + this.file = file; + this.offset = offset; + this.logPrinter = new LogPrinter(); + } + + @Override + protected NPDMProvider call() { + logPrinter.print("\tStart chain: NPDM", EMsgType.INFO); + try{ + return new NPDMProvider(file, offset); + } + catch (Exception e){ + logPrinter.print("\tException: "+e.getMessage(), EMsgType.FAIL); + return null; + } + finally { + close(); + } + } + + private void close(){ + logPrinter.print("\tEnd chain: NPDM", EMsgType.INFO); + logPrinter.close(); + } +} + diff --git a/src/main/resources/FXML/NPDM/NPDMTab.fxml b/src/main/resources/FXML/NPDM/NPDMTab.fxml new file mode 100644 index 0000000..75a8095 --- /dev/null +++ b/src/main/resources/FXML/NPDM/NPDMTab.fxml @@ -0,0 +1,442 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/FXML/Settings/SettingsLayout.fxml b/src/main/resources/FXML/Settings/SettingsLayout.fxml index e870201..7ade0e6 100644 --- a/src/main/resources/FXML/Settings/SettingsLayout.fxml +++ b/src/main/resources/FXML/Settings/SettingsLayout.fxml @@ -14,7 +14,7 @@ - + @@ -101,7 +101,7 @@ - + diff --git a/src/main/resources/FXML/landingPage.fxml b/src/main/resources/FXML/landingPage.fxml index 906ae12..47e5bac 100644 --- a/src/main/resources/FXML/landingPage.fxml +++ b/src/main/resources/FXML/landingPage.fxml @@ -87,6 +87,14 @@ + + + + + + +