diff --git a/README.md b/README.md index f1e03c7..4138033 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# konogonka +# libKonogonka [![Build Status](https://ci.redrise.ru/api/badges/desu/libKonogonka/status.svg)](https://ci.redrise.ru/desu/libKonogonka) diff --git a/pom.xml b/pom.xml index a6e1ecd..521eaeb 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ ru.redrise libKonogonka - 0.1 + 0.1-SNAPSHOT https://git.redrise.ru/desu/${project.name}}/ @@ -62,8 +62,34 @@ 1.0 compile - + + + org.apache.logging.log4j + log4j-core + 2.18.0 + compile + + + + org.junit.jupiter + junit-jupiter-engine + 5.5.2 + test + + + org.junit.jupiter + junit-jupiter-api + 5.5.2 + test + + + org.junit.jupiter + junit-jupiter-params + 5.5.2 + test + + ${project.artifactId}-${project.version}-${maven.build.timestamp} @@ -100,6 +126,7 @@ --> + org.apache.maven.plugins maven-compiler-plugin @@ -109,7 +136,7 @@ 8 - + + false - - make-assembly - package + make-assembly + package single + --> \ No newline at end of file diff --git a/src/main/java/libKonogonka/LoperConverter.java b/src/main/java/libKonogonka/Converter.java similarity index 95% rename from src/main/java/libKonogonka/LoperConverter.java rename to src/main/java/libKonogonka/Converter.java index c71b80c..dcf79d1 100644 --- a/src/main/java/libKonogonka/LoperConverter.java +++ b/src/main/java/libKonogonka/Converter.java @@ -21,7 +21,7 @@ package libKonogonka; import java.nio.ByteBuffer; import java.nio.ByteOrder; -public class LoperConverter { +public class Converter { public static int getLEint(byte[] bytes, int fromOffset){ return ByteBuffer.wrap(bytes, fromOffset, 0x4).order(ByteOrder.LITTLE_ENDIAN).getInt(); } @@ -30,7 +30,7 @@ public class LoperConverter { return ByteBuffer.wrap(bytes, fromOffset, 0x8).order(ByteOrder.LITTLE_ENDIAN).getLong(); } /** - * Convert int to long. Workaround to store unsigned int + * Convert (usually unsigned) int to long. Workaround to store unsigned int * @param bytes original array * @param fromOffset start position of the 4-bytes value * */ diff --git a/src/main/java/libKonogonka/KeyChainHolder.java b/src/main/java/libKonogonka/KeyChainHolder.java new file mode 100644 index 0000000..5c6c1a0 --- /dev/null +++ b/src/main/java/libKonogonka/KeyChainHolder.java @@ -0,0 +1,103 @@ +/* + Copyright 2019-2022 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka 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. + + libKonogonka 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 libKonogonka. If not, see . +*/ +package libKonogonka; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.util.HashMap; + +public class KeyChainHolder { + + private final File keysFile; + private final String xci_header_key; + private HashMap rawKeySet; + private HashMap key_area_key_application, + key_area_key_ocean, + key_area_key_system, + titlekek; + + public KeyChainHolder(String pathToKeysFile, String xci_header_key) throws Exception{ + this(new File(pathToKeysFile), xci_header_key); + } + + public KeyChainHolder(File keysFile, String xci_header_key) throws Exception{ + this.keysFile = keysFile; + this.xci_header_key = xci_header_key; + collectEverything(); + } + + private void collectEverything() throws Exception{ + rawKeySet = new HashMap<>(); + BufferedReader br = new BufferedReader(new FileReader(keysFile)); + + String fileLine; + String[] keyValue; + while ((fileLine = br.readLine()) != null){ + keyValue = fileLine.trim().split("\\s+?=\\s+?", 2); + if (keyValue.length == 2) + rawKeySet.put(keyValue[0], keyValue[1]); + } + + key_area_key_application = collectKeysByType("key_area_key_application"); + key_area_key_ocean = collectKeysByType("key_area_key_ocean"); + key_area_key_system = collectKeysByType("key_area_key_system"); + titlekek = collectKeysByType("titlekek"); + } + private HashMap collectKeysByType(String keyName){ + HashMap tempKeySet = new HashMap<>(); + String keyNamePattern = keyName+"_%02x"; + HashMap map = new HashMap<>(); + String keyParsed; + int counter = 0; + while ((keyParsed = map.get(String.format(keyNamePattern, counter))) != null){ + tempKeySet.put(String.format(keyNamePattern, counter), keyParsed); + counter++; + } + return tempKeySet; + } + + public String getXci_header_key() { + return xci_header_key; + } + + public String getHeader_key() { + return rawKeySet.get("header_key"); + } + + public HashMap getRawKeySet() { + return rawKeySet; + } + + public HashMap getKey_area_key_application() { + return key_area_key_application; + } + + public HashMap getKey_area_key_ocean() { + return key_area_key_ocean; + } + + public HashMap getKey_area_key_system() { + return key_area_key_system; + } + + public HashMap getTitlekek() { + return titlekek; + } +} diff --git a/src/main/java/libKonogonka/Tools/NCA/NCAContent.java b/src/main/java/libKonogonka/Tools/NCA/NCAContent.java index 9b42735..1f1a762 100644 --- a/src/main/java/libKonogonka/Tools/NCA/NCAContent.java +++ b/src/main/java/libKonogonka/Tools/NCA/NCAContent.java @@ -18,8 +18,9 @@ */ package libKonogonka.Tools.NCA; -import libKonogonka.LoperConverter; -import libKonogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock; +import libKonogonka.Converter; +import libKonogonka.RainbowDump; +import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader; import libKonogonka.Tools.PFS0.IPFS0Provider; import libKonogonka.Tools.PFS0.PFS0EncryptedProvider; import libKonogonka.Tools.PFS0.PFS0Provider; @@ -27,6 +28,8 @@ import libKonogonka.Tools.RomFs.IRomFsProvider; import libKonogonka.Tools.RomFs.RomFsEncryptedProvider; import libKonogonka.ctraes.AesCtrDecryptSimple; import libKonogonka.exceptions.EmptySectionException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.io.*; import java.util.LinkedList; @@ -35,26 +38,28 @@ import java.util.LinkedList; * TODO: MAKE SOME DECOMPOSITION * */ public class NCAContent { - private File file; - private long offsetPosition; - private NCASectionBlock ncaSectionBlock; - private NCAHeaderTableEntry ncaHeaderTableEntry; - private byte[] decryptedKey; + private final static Logger log = LogManager.getLogger(NCAContent.class); + + private final File file; + private final long offsetPosition; + private final NcaFsHeader ncaFsHeader; + private final NCAHeaderTableEntry ncaHeaderTableEntry; + private final byte[] decryptedKey; - private LinkedList Pfs0SHA256hashes; + private final LinkedList Pfs0SHA256hashes; private IPFS0Provider pfs0; private IRomFsProvider romfs; // TODO: if decryptedKey is empty, throw exception ?? public NCAContent(File file, long offsetPosition, - NCASectionBlock ncaSectionBlock, + NcaFsHeader ncaFsHeader, NCAHeaderTableEntry ncaHeaderTableEntry, byte[] decryptedKey) throws Exception { this.file = file; this.offsetPosition = offsetPosition; - this.ncaSectionBlock = ncaSectionBlock; + this.ncaFsHeader = ncaFsHeader; this.ncaHeaderTableEntry = ncaHeaderTableEntry; this.decryptedKey = decryptedKey; @@ -63,16 +68,16 @@ public class NCAContent { if (ncaHeaderTableEntry.getMediaEndOffset() == 0) throw new EmptySectionException("Empty section"); // If it's PFS0Provider - if (ncaSectionBlock.getSuperBlockPFS0() != null) + if (ncaFsHeader.getSuperBlockPFS0() != null) this.proceedPFS0(); - else if (ncaSectionBlock.getSuperBlockIVFC() != null) + else if (ncaFsHeader.getSuperBlockIVFC() != null) this.proceedRomFs(); else throw new Exception("NCAContent(): Not supported. PFS0 or RomFS supported only."); } private void proceedPFS0() throws Exception { - switch (ncaSectionBlock.getCryptoType()){ + switch (ncaFsHeader.getCryptoType()){ case 0x01: proceedPFS0NotEncrypted(); // IF NO ENCRYPTION break; @@ -86,13 +91,13 @@ public class NCAContent { 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(); + long hashTableLocation = thisMediaLocation + ncaFsHeader.getSuperBlockPFS0().getHashTableOffset(); + long pfs0Location = thisMediaLocation + ncaFsHeader.getSuperBlockPFS0().getPfs0offset(); raf.seek(hashTableLocation); byte[] rawData; - long sha256recordsNumber = ncaSectionBlock.getSuperBlockPFS0().getHashTableSize() / 0x20; + long sha256recordsNumber = ncaFsHeader.getSuperBlockPFS0().getHashTableSize() / 0x20; // Collect hashes for (int i = 0; i < sha256recordsNumber; i++){ rawData = new byte[0x20]; // 32 bytes - size of SHA256 hash @@ -111,13 +116,13 @@ public class NCAContent { new CryptoSection03Pfs0(file, offsetPosition, decryptedKey, - ncaSectionBlock, + ncaFsHeader, ncaHeaderTableEntry.getMediaStartOffset(), ncaHeaderTableEntry.getMediaEndOffset()); } private void proceedRomFs() throws Exception{ - switch (ncaSectionBlock.getCryptoType()){ + switch (ncaFsHeader.getCryptoType()){ case 0x01: proceedRomFsNotEncrypted(); // IF NO ENCRYPTION break; @@ -125,23 +130,22 @@ public class NCAContent { proceedRomFsEncrypted(); // If encrypted regular [ 0x03 ] break; default: - throw new Exception("NCAContent() -> proceedRomFs(): Non-supported 'Crypto type'"); + throw new Exception("Non-supported 'Crypto type'"); } } - private void proceedRomFsNotEncrypted(){ - // TODO: Clarify, implement if needed - System.out.println("proceedRomFs() -> proceedRomFsNotEncrypted() is not implemented :("); + private void proceedRomFsNotEncrypted(){ // TODO: Clarify, implement if needed + log.error("proceedRomFs() -> proceedRomFsNotEncrypted() is not implemented :("); } private void proceedRomFsEncrypted() throws Exception{ if (decryptedKey == null) throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided."); this.romfs = new RomFsEncryptedProvider( - offsetPosition, - ncaSectionBlock.getSuperBlockIVFC().getLvl6Offset(), + ncaFsHeader.getSuperBlockIVFC().getLvl6Offset(), file, + offsetPosition, decryptedKey, - ncaSectionBlock.getSectionCTR(), + ncaFsHeader.getSectionCTR(), ncaHeaderTableEntry.getMediaStartOffset(), ncaHeaderTableEntry.getMediaEndOffset()); } @@ -151,27 +155,24 @@ public class NCAContent { public IRomFsProvider getRomfs() { return romfs; } private class CryptoSection03Pfs0 { - CryptoSection03Pfs0(File file, long offsetPosition, byte[] decryptedKey, - NCASectionBlock ncaSectionBlock, + NcaFsHeader ncaFsHeader, 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(); - //--------------------------------------------------------------------------------------------------*/ + log.debug( "-== Crypto Section 03 PFS0 ==-\n" + + "Media start location: " + RainbowDump.formatDecHexString(mediaStartBlocksOffset) + "\n" + + "Media end location: " + RainbowDump.formatDecHexString(mediaEndBlocksOffset) + "\n" + + "Media size: " + RainbowDump.formatDecHexString((mediaEndBlocksOffset-mediaStartBlocksOffset)) + "\n" + + "Media actual location: " + RainbowDump.formatDecHexString((offsetPosition + (mediaStartBlocksOffset * 0x200))) + "\n" + + "SHA256 hash table size: " + RainbowDump.formatDecHexString(ncaFsHeader.getSuperBlockPFS0().getHashTableSize()) + "\n" + + "SHA256 hash table offs: " + RainbowDump.formatDecHexString(ncaFsHeader.getSuperBlockPFS0().getHashTableOffset()) + "\n" + + "PFS0 Offset: " + RainbowDump.formatDecHexString(ncaFsHeader.getSuperBlockPFS0().getPfs0offset()) + "\n" + + "SHA256 records: " + RainbowDump.formatDecHexString((ncaFsHeader.getSuperBlockPFS0().getHashTableSize() / 0x20)) + "\n" + + "KEY (decrypted): " + Converter.byteArrToHexString(decryptedKey) + "\n" + + "CTR: " + Converter.byteArrToHexString(ncaFsHeader.getSectionCTR()) + "\n"); if (decryptedKey == null) throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided."); @@ -179,7 +180,7 @@ public class NCAContent { long abosluteOffsetPosition = offsetPosition + (mediaStartBlocksOffset * 0x200); raf.seek(abosluteOffsetPosition); - AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey, ncaSectionBlock.getSectionCTR(), mediaStartBlocksOffset * 0x200); + AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey, ncaFsHeader.getSectionCTR(), mediaStartBlocksOffset * 0x200); byte[] encryptedBlock; byte[] dectyptedBlock; @@ -190,13 +191,13 @@ public class NCAContent { Thread pThread = new Thread(new ParseThread( streamInp, - ncaSectionBlock.getSuperBlockPFS0().getPfs0offset(), - ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset(), - ncaSectionBlock.getSuperBlockPFS0().getHashTableSize(), + ncaFsHeader.getSuperBlockPFS0().getPfs0offset(), + ncaFsHeader.getSuperBlockPFS0().getHashTableOffset(), + ncaFsHeader.getSuperBlockPFS0().getHashTableSize(), offsetPosition, file, decryptedKey, - ncaSectionBlock.getSectionCTR(), + ncaFsHeader.getSectionCTR(), mediaStartBlocksOffset, mediaEndBlocksOffset )); @@ -206,7 +207,7 @@ public class NCAContent { encryptedBlock = new byte[0x200]; if (raf.read(encryptedBlock) != -1){ //dectyptedBlock = aesCtr.decrypt(encryptedBlock); - dectyptedBlock = decryptor.dectyptNext(encryptedBlock); + dectyptedBlock = decryptor.decryptNext(encryptedBlock); // Writing decrypted data to pipe try { streamOut.write(dectyptedBlock); @@ -220,8 +221,8 @@ public class NCAContent { streamOut.close(); raf.close(); } - /* - * Since we representing decrypted data as stream (it's easier to look on it this way), + /** + * Since we're 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{ @@ -233,12 +234,12 @@ public class NCAContent { long hashTableRecordsCount; long pfs0offset; - private long MetaOffsetPositionInFile; - private File MetaFileWithEncPFS0; - private byte[] MetaKey; - private byte[] MetaSectionCTR; - private long MetaMediaStartOffset; - private long MetaMediaEndOffset; + private final long MetaOffsetPositionInFile; + private final File MetaFileWithEncPFS0; + private final byte[] MetaKey; + private final byte[] MetaSectionCTR; + private final long MetaMediaStartOffset; + private final long MetaMediaEndOffset; ParseThread(PipedInputStream pipedInputStream, @@ -313,12 +314,9 @@ public class NCAContent { pipedInputStream.close(); } catch (Exception e){ - System.out.println("'ParseThread' thread exception"); - e.printStackTrace(); - } - finally { - System.out.println("Thread dies"); + log.debug("NCA Content parsing thread exception: ", e); } + //finally { System.out.println("NCA Content thread dies");} } } } @@ -334,18 +332,17 @@ public class NCAContent { 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(); + log.debug("NCAContent() -> exportEncryptedSectionType03() information" + "\n" + + "Media start location: " + mediaStartBlocksOffset + "\n" + + "Media end location: " + mediaEndBlocksOffset + "\n" + + "Media size : " + (mediaEndBlocksOffset-mediaStartBlocksOffset) + "\n" + + "Media act. location: " + (offsetPosition + (mediaStartBlocksOffset * 0x200)) + "\n" + + "KEY: " + Converter.byteArrToHexString(decryptedKey) + "\n" + + "CTR: " + Converter.byteArrToHexString(ncaFsHeader.getSectionCTR()) + "\n"); //---------------------------------------------------------------------------------------------------/ - if (ncaSectionBlock.getCryptoType() == 0x01){ - System.out.println("NCAContent -> getRawDataContentPipedInpStream (Zero encryption section type 01): Thread started"); + if (ncaFsHeader.getCryptoType() == 0x01){ + log.trace("NCAContent -> getRawDataContentPipedInpStream (Zero encryption section type 01): Thread started"); Thread workerThread; PipedOutputStream streamOut = new PipedOutputStream(); @@ -363,8 +360,7 @@ public class NCAContent { } } catch (Exception e){ - System.out.println("NCAContent -> exportRawData(): "+e.getMessage()); - e.printStackTrace(); + log.error("NCAContent -> exportRawData() failure", e); } finally { try { @@ -374,13 +370,13 @@ public class NCAContent { streamOut.close(); }catch (Exception ignored) {} } - System.out.println("NCAContent -> exportRawData(): Thread died"); + log.trace("NCAContent -> exportRawData(): Thread died"); }); workerThread.start(); return streamIn; } - else if (ncaSectionBlock.getCryptoType() == 0x03){ - System.out.println("NCAContent -> getRawDataContentPipedInpStream (Encrypted Section Type 03): Thread started"); + else if (ncaFsHeader.getCryptoType() == 0x03){ + log.trace("NCAContent -> getRawDataContentPipedInpStream (Encrypted Section Type 03): Thread started"); if (decryptedKey == null) throw new Exception("NCAContent -> exportRawData(): unable to proceed. No decrypted key provided."); @@ -396,7 +392,7 @@ public class NCAContent { raf.seek(abosluteOffsetPosition); AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey, - ncaSectionBlock.getSectionCTR(), + ncaFsHeader.getSectionCTR(), mediaStartBlocksOffset * 0x200); byte[] encryptedBlock; @@ -406,7 +402,7 @@ public class NCAContent { for (int i = 0; i < mediaBlocksSize; i++){ encryptedBlock = new byte[0x200]; if (raf.read(encryptedBlock) != -1){ - dectyptedBlock = decryptor.dectyptNext(encryptedBlock); + dectyptedBlock = decryptor.decryptNext(encryptedBlock); // Writing decrypted data to pipe streamOut.write(dectyptedBlock); } @@ -415,8 +411,7 @@ public class NCAContent { } } catch (Exception e){ - System.out.println("NCAContent -> exportRawData(): "+e.getMessage()); - e.printStackTrace(); + log.error("NCAContent -> exportRawData(): ", e); } finally { try { @@ -426,7 +421,7 @@ public class NCAContent { streamOut.close(); }catch (Exception ignored) {} } - System.out.println("NCAContent -> exportRawData(): Thread died"); + log.trace("NCAContent -> exportRawData(): Thread died"); }); workerThread.start(); return streamIn; diff --git a/src/main/java/libKonogonka/Tools/NCA/NCAHeaderTableEntry.java b/src/main/java/libKonogonka/Tools/NCA/NCAHeaderTableEntry.java index 9374d34..7364d25 100644 --- a/src/main/java/libKonogonka/Tools/NCA/NCAHeaderTableEntry.java +++ b/src/main/java/libKonogonka/Tools/NCA/NCAHeaderTableEntry.java @@ -18,34 +18,26 @@ */ package libKonogonka.Tools.NCA; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; +import libKonogonka.Converter; + import java.util.Arrays; public class NCAHeaderTableEntry { - - private long mediaStartOffset; - private long mediaEndOffset; - private byte[] unknwn1; - private byte[] unknwn2; + private final long mediaStartOffset; + private final long mediaEndOffset; + private final byte[] unknwn1; + private final byte[] unknwn2; public NCAHeaderTableEntry(byte[] table) throws Exception{ if (table.length < 0x10) throw new Exception("Section Table size is too small."); - this.mediaStartOffset = convertUnsignedIntBytesToLong(Arrays.copyOfRange(table, 0x0, 0x4)); - this.mediaEndOffset = convertUnsignedIntBytesToLong(Arrays.copyOfRange(table, 0x4, 0x8)); + this.mediaStartOffset = Converter.getLElongOfInt(table, 0); + this.mediaEndOffset = Converter.getLElongOfInt(table, 0x4); this.unknwn1 = Arrays.copyOfRange(table, 0x8, 0xC); this.unknwn2 = Arrays.copyOfRange(table, 0xC, 0x10); } - private long convertUnsignedIntBytesToLong(byte[] intBytes){ - if (intBytes.length == 4) - return ByteBuffer.wrap(Arrays.copyOf(intBytes, 8)).order(ByteOrder.LITTLE_ENDIAN).getLong(); - else - return -1; - } - public long getMediaStartOffset() { return mediaStartOffset; } public long getMediaEndOffset() { return mediaEndOffset; } public byte[] getUnknwn1() { return unknwn1; } diff --git a/src/main/java/libKonogonka/Tools/NCA/NCAProvider.java b/src/main/java/libKonogonka/Tools/NCA/NCAProvider.java index 15924d4..08543e3 100644 --- a/src/main/java/libKonogonka/Tools/NCA/NCAProvider.java +++ b/src/main/java/libKonogonka/Tools/NCA/NCAProvider.java @@ -18,27 +18,32 @@ */ package libKonogonka.Tools.NCA; -import libKonogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock; +import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader; import libKonogonka.exceptions.EmptySectionException; import libKonogonka.xtsaes.XTSAESCipher; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.bouncycastle.crypto.params.KeyParameter; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; -import java.io.File; -import java.io.RandomAccessFile; +import java.io.*; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Arrays; import java.util.HashMap; -import static libKonogonka.LoperConverter.byteArrToHexString; -import static libKonogonka.LoperConverter.getLElong; +import static libKonogonka.Converter.byteArrToHexString; +import static libKonogonka.Converter.getLElong; // TODO: check file size public class NCAProvider { - private File file; // File that contains NCA - private long offset; // Offset where NCA actually located - private HashMap keys; // hashmap with keys using _0x naming (where x number 0-N) + private final static Logger log = LogManager.getLogger(NCAProvider.class); + + private final File file; // File that contains NCA + private final long offset; // Offset where NCA actually located + private final HashMap keys; // hashmap with keys using _0x naming (where x number 0-N) // Header private byte[] rsa2048one; private byte[] rsa2048two; @@ -78,10 +83,10 @@ public class NCAProvider { private NCAHeaderTableEntry tableEntry2; private NCAHeaderTableEntry tableEntry3; - private NCASectionBlock sectionBlock0; - private NCASectionBlock sectionBlock1; - private NCASectionBlock sectionBlock2; - private NCASectionBlock sectionBlock3; + private NcaFsHeader sectionBlock0; + private NcaFsHeader sectionBlock1; + private NcaFsHeader sectionBlock2; + private NcaFsHeader sectionBlock3; private NCAContent ncaContent0; private NCAContent ncaContent1; @@ -93,6 +98,7 @@ public class NCAProvider { } public NCAProvider (File file, HashMap keys, long offsetPosition) throws Exception{ + this.file = file; this.keys = keys; String header_key = keys.get("header_key"); if (header_key == null ) @@ -100,15 +106,10 @@ public class NCAProvider { if (header_key.length() != 64) throw new Exception("header_key is too small or too big. Must be 64 symbols."); - this.file = file; this.offset = offsetPosition; - KeyParameter key1 = new KeyParameter( - hexStrToByteArray(header_key.substring(0, 32)) - ); - KeyParameter key2 = new KeyParameter( - hexStrToByteArray(header_key.substring(32, 64)) - ); + KeyParameter key1 = new KeyParameter(hexStrToByteArray(header_key.substring(0, 32))); + KeyParameter key2 = new KeyParameter(hexStrToByteArray(header_key.substring(32, 64))); XTSAESCipher xtsaesCipher = new XTSAESCipher(false); xtsaesCipher.init(false, key1, key2); @@ -134,18 +135,20 @@ public class NCAProvider { raf.close(); getNCAContent(); - /* - //--------------------------------------------------------------------- + /*//--------------------------------------------------------------------- FileInputStream fis = new FileInputStream(file); - BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("/tmp/decrypted.nca")); - int i = 0; - byte[] block = new byte[0x200]; - while (fis.read(block) != -1){ - decryptedSequence = new byte[0x200]; - xtsaesCipher.processDataUnit(block, 0, 0x200, decryptedSequence, 0, i++); - bos.write(decryptedSequence); + try (BufferedOutputStream bos = new BufferedOutputStream(Files.newOutputStream(Paths.get("/tmp/decrypted.nca")))){ + int i = 0; + byte[] block = new byte[0x200]; + while (fis.read(block) != -1){ + decryptedSequence = new byte[0x200]; + xtsaesCipher.processDataUnit(block, 0, 0x200, decryptedSequence, 0, i++); + bos.write(decryptedSequence); + } + } + catch (Exception e){ + throw new Exception("Failed to export decrypted AES-XTS", e); } - bos.close(); //---------------------------------------------------------------------*/ } @@ -193,10 +196,12 @@ public class NCAProvider { else cryptoTypeReal = cryptoType1; - if (cryptoTypeReal > 0) // TODO: CLARIFY WHY THE FUCK IS IT FAIR???? + if (cryptoTypeReal > 0) // TODO: CLARIFY WHY THE FUCK IS IT FAIR???? cryptoTypeReal -= 1; - //todo: if nca3 proceed + //If nca3 proceed + if (! magicnum.equalsIgnoreCase("NCA3")) + throw new Exception("Not supported data type: "+magicnum+". Only NCA3 supported"); // Decrypt keys if encrypted if (Arrays.equals(rightsId, new byte[0x10])) { String keyAreaKey; @@ -232,10 +237,10 @@ public class NCAProvider { tableEntry2 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x20, 0x30)); tableEntry3 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x30, 0x40)); - sectionBlock0 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x400, 0x600)); - sectionBlock1 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x600, 0x800)); - sectionBlock2 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x800, 0xa00)); - sectionBlock3 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0xa00, 0xc00)); + sectionBlock0 = new NcaFsHeader(Arrays.copyOfRange(decryptedData, 0x400, 0x600)); + sectionBlock1 = new NcaFsHeader(Arrays.copyOfRange(decryptedData, 0x600, 0x800)); + sectionBlock2 = new NcaFsHeader(Arrays.copyOfRange(decryptedData, 0x800, 0xa00)); + sectionBlock3 = new NcaFsHeader(Arrays.copyOfRange(decryptedData, 0xa00, 0xc00)); } private void keyAreaKeyNotSupportedOrFound() throws Exception{ @@ -259,12 +264,12 @@ public class NCAProvider { throw new Exception(exceptionStringBuilder.toString()); } - private void getNCAContent(){ + private void getNCAContent() throws Exception{ byte[] key; // If empty Rights ID if (Arrays.equals(rightsId, new byte[0x10])) { - key = decryptedKey2; // TODO: Just remember this dumb hack + key = decryptedKey2; // NOTE: Just remember this dumb hack } else { try { @@ -278,42 +283,35 @@ public class NCAProvider { key = cipher.doFinal(rightsIDkey); } catch (Exception e){ - e.printStackTrace(); - System.out.println("No title.keys loaded?"); - return; + throw new Exception("No title.keys loaded?", e); } } + getNcaContentByNumber(0, key); + getNcaContentByNumber(1, key); + getNcaContentByNumber(2, key); + getNcaContentByNumber(3, key); + } + private void getNcaContentByNumber(int number, byte[] key){ 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); + switch (number) { + case 0: + this.ncaContent0 = new NCAContent(file, offset, sectionBlock0, tableEntry0, key); + break; + case 1: + this.ncaContent1 = new NCAContent(file, offset, sectionBlock1, tableEntry1, key); + break; + case 2: + this.ncaContent2 = new NCAContent(file, offset, sectionBlock2, tableEntry2, key); + break; + case 3: + this.ncaContent3 = new NCAContent(file, offset, sectionBlock3, tableEntry3, key); + break; + } } catch (EmptySectionException ignored){} catch (Exception e){ this.ncaContent3 = null; - e.printStackTrace(); + log.debug("Unable to get NCA Content "+number, e); } } @@ -347,20 +345,54 @@ public class NCAProvider { public byte[] getDecryptedKey1() { return decryptedKey1; } public byte[] getDecryptedKey2() { return decryptedKey2; } public byte[] getDecryptedKey3() { return decryptedKey3; } - + /** + * Get NCA Hedaer Table Entry for selected id + * @param id should be 0-3 + * */ + public NCAHeaderTableEntry getTableEntry(int id) throws Exception{ + switch (id) { + case 0: + return getTableEntry0(); + case 1: + return getTableEntry1(); + case 2: + return getTableEntry2(); + case 3: + return getTableEntry3(); + default: + throw new Exception("NCA Table Entry must be defined in range 0-3 while '"+id+"' requested"); + } + } public NCAHeaderTableEntry getTableEntry0() { return tableEntry0; } public NCAHeaderTableEntry getTableEntry1() { return tableEntry1; } public NCAHeaderTableEntry getTableEntry2() { return tableEntry2; } public NCAHeaderTableEntry getTableEntry3() { return tableEntry3; } + /** + * Get NCA Section Block for selected section + * @param id should be 0-3 + * */ + public NcaFsHeader getSectionBlock(int id) throws Exception{ + switch (id) { + case 0: + return getSectionBlock0(); + case 1: + return getSectionBlock1(); + case 2: + return getSectionBlock2(); + case 3: + return getSectionBlock3(); + default: + throw new Exception("NCA Section Block must be defined in range 0-3 while '"+id+"' requested"); + } + } + public NcaFsHeader getSectionBlock0() { return sectionBlock0; } + public NcaFsHeader getSectionBlock1() { return sectionBlock1; } + public NcaFsHeader getSectionBlock2() { return sectionBlock2; } + public NcaFsHeader getSectionBlock3() { return sectionBlock3; } - public NCASectionBlock getSectionBlock0() { return sectionBlock0; } - public NCASectionBlock getSectionBlock1() { return sectionBlock1; } - public NCASectionBlock getSectionBlock2() { return sectionBlock2; } - public NCASectionBlock getSectionBlock3() { return sectionBlock3; } - - public boolean isKeyAvailable(){ // TODO: USE + public boolean isKeyAvailable(){ // NOTE: never used if (Arrays.equals(rightsId, new byte[0x10])) - return true; + return false; else return keys.containsKey(byteArrToHexString(rightsId)); } @@ -368,7 +400,7 @@ public class NCAProvider { * Get content for the selected section * @param sectionNumber should be 0-3 * */ - public NCAContent getNCAContentProvider(int sectionNumber){ + public NCAContent getNCAContentProvider(int sectionNumber) throws Exception{ switch (sectionNumber) { case 0: return ncaContent0; @@ -379,7 +411,7 @@ public class NCAProvider { case 3: return ncaContent3; default: - return null; + throw new Exception("NCA Content must be requested in range of 0-3, while 'Section Number "+sectionNumber+"' requested"); } } } \ No newline at end of file diff --git a/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/BucketTreeHeader.java b/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/BucketTreeHeader.java new file mode 100644 index 0000000..70d4504 --- /dev/null +++ b/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/BucketTreeHeader.java @@ -0,0 +1,43 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka 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. + + libKonogonka 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 libKonogonka. If not, see . + */ +package libKonogonka.Tools.NCA.NCASectionTableBlock; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import static libKonogonka.Converter.getLEint; + +public class BucketTreeHeader { + private final String magic; + private final int version; + private final int entryCount; + private final byte[] unknown; + + BucketTreeHeader(byte[] rawBytes){ + magic = new String(Arrays.copyOfRange(rawBytes, 0x0, 0x4), StandardCharsets.US_ASCII); + version = getLEint(rawBytes, 0x4); + entryCount = getLEint(rawBytes, 0x8); + unknown = Arrays.copyOfRange(rawBytes, 0xc, 0x10); + } + + public String getMagic() {return magic;} + public int getVersion() {return version;} + public int getEntryCount() {return entryCount;} + public byte[] getUnknown() {return unknown;} +} diff --git a/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/CompressionInfo.java b/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/CompressionInfo.java new file mode 100644 index 0000000..fcef6d1 --- /dev/null +++ b/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/CompressionInfo.java @@ -0,0 +1,45 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka 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. + + libKonogonka 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 libKonogonka. If not, see . + */ +package libKonogonka.Tools.NCA.NCASectionTableBlock; + +import java.util.Arrays; + +import static libKonogonka.Converter.getLElong; + +public class CompressionInfo { + private final long offset; + private final long size; + private final BucketTreeHeader bktr; + private final byte[] unknown; + + CompressionInfo(byte[] rawTable){ + offset = getLElong(rawTable, 0); + size = getLElong(rawTable, 0x8); + bktr = new BucketTreeHeader(Arrays.copyOfRange(rawTable, 0x10, 0x20)); + unknown = Arrays.copyOfRange(rawTable, 0x20, 0x28); + } + + public long getOffset() {return offset;} + public long getSize() {return size;} + public String getBktrMagic() { return bktr.getMagic(); } + public int getBktrVersion() { return bktr.getVersion(); } + public int getBktrEntryCount() { return bktr.getEntryCount(); } + public byte[] getBktrUnknown() { return bktr.getUnknown(); } + public byte[] getUnknown() {return unknown;} +} diff --git a/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/MetaDataHashDataInfo.java b/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/MetaDataHashDataInfo.java new file mode 100644 index 0000000..c97fc24 --- /dev/null +++ b/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/MetaDataHashDataInfo.java @@ -0,0 +1,38 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka 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. + + libKonogonka 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 libKonogonka. If not, see . + */ +package libKonogonka.Tools.NCA.NCASectionTableBlock; + +import java.util.Arrays; + +import static libKonogonka.Converter.getLElong; + +public class MetaDataHashDataInfo { + private final long offset; + private final long size; + private final byte[] tableHash; + MetaDataHashDataInfo(byte[] rawTable){ + offset = getLElong(rawTable, 0); + size = getLElong(rawTable, 0x8); + tableHash = Arrays.copyOfRange(rawTable, 0x10, 0x20); + } + + public long getOffset() {return offset;} + public long getSize() {return size;} + public byte[] getTableHash() {return tableHash;} +} diff --git a/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/NCASectionBlock.java b/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/NCASectionBlock.java deleted file mode 100644 index 38dc0db..0000000 --- a/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/NCASectionBlock.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - Copyright 2019-2022 Dmitry Isaenko - - This file is part of libKonogonka. - - libKonogonka 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. - - libKonogonka 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 libKonogonka. If not, see . -*/ -package libKonogonka.Tools.NCA.NCASectionTableBlock; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; - -import static libKonogonka.LoperConverter.getLEint; -import static libKonogonka.LoperConverter.getLElong; - -public class NCASectionBlock { - private byte[] version; - private byte fsType; - private byte hashType; - private byte cryptoType; - private byte[] padding; - private SuperBlockIVFC superBlockIVFC; - private SuperBlockPFS0 superBlockPFS0; - private byte[] BKTRfullHeader; - // BKTR extended - private long BKTRoffsetSection1; - private long BKTRsizeSection1; - private String BKTRmagicSection1; - private int BKTRu32Section1; - private int BKTRs32Section1; - private byte[] BKTRunknownSection1; - - private long BKTRoffsetSection2; - private long BKTRsizeSection2; - private String BKTRmagicSection2; - private int BKTRu32Section2; - private int BKTRs32Section2; - private byte[] BKTRunknownSection2; - - private byte[] sectionCTR; - private byte[] unknownEndPadding; - - public NCASectionBlock(byte[] tableBlockBytes) throws Exception{ - if (tableBlockBytes.length != 0x200) - throw new Exception("Table Block Section size is incorrect."); - version = Arrays.copyOfRange(tableBlockBytes, 0, 0x2); - fsType = tableBlockBytes[0x2]; - hashType = tableBlockBytes[0x3]; - cryptoType = tableBlockBytes[0x4]; - padding = Arrays.copyOfRange(tableBlockBytes, 0x5, 0x8); - byte[] superBlockBytes = Arrays.copyOfRange(tableBlockBytes, 0x8, 0xf8); - - if ((fsType == 0) && (hashType == 0x3)) - superBlockIVFC = new SuperBlockIVFC(superBlockBytes); - else if ((fsType == 0x1) && (hashType == 0x2)) - superBlockPFS0 = new SuperBlockPFS0(superBlockBytes); - - BKTRfullHeader = Arrays.copyOfRange(tableBlockBytes, 0x100, 0x140); - - BKTRoffsetSection1 = getLElong(BKTRfullHeader, 0); - BKTRsizeSection1 = getLElong(BKTRfullHeader, 0x8); - BKTRmagicSection1 = new String(Arrays.copyOfRange(BKTRfullHeader, 0x10, 0x14), StandardCharsets.US_ASCII); - BKTRu32Section1 = getLEint(BKTRfullHeader, 0x14); - BKTRs32Section1 = getLEint(BKTRfullHeader, 0x18); - BKTRunknownSection1 = Arrays.copyOfRange(tableBlockBytes, 0x1c, 0x20); - - BKTRoffsetSection2 = getLElong(BKTRfullHeader, 0x20); - BKTRsizeSection2 = getLElong(BKTRfullHeader, 0x28); - BKTRmagicSection2 = new String(Arrays.copyOfRange(BKTRfullHeader, 0x30, 0x34), StandardCharsets.US_ASCII); - BKTRu32Section2 = getLEint(BKTRfullHeader, 0x34); - BKTRs32Section2 = getLEint(BKTRfullHeader, 0x38); - BKTRunknownSection2 = Arrays.copyOfRange(BKTRfullHeader, 0x3c, 0x40); - - sectionCTR = Arrays.copyOfRange(tableBlockBytes, 0x140, 0x148); - unknownEndPadding = Arrays.copyOfRange(tableBlockBytes, 0x148, 0x200); - } - - public byte[] getVersion() { return version; } - public byte getFsType() { return fsType; } - public byte getHashType() { return hashType; } - public byte getCryptoType() { return cryptoType; } - public byte[] getPadding() { return padding; } - public SuperBlockIVFC getSuperBlockIVFC() { return superBlockIVFC; } - public SuperBlockPFS0 getSuperBlockPFS0() { return superBlockPFS0; } - public byte[] getBKTRfullHeader() { return BKTRfullHeader; } - - public long getBKTRoffsetSection1() { return BKTRoffsetSection1; } - public long getBKTRsizeSection1() { return BKTRsizeSection1; } - public String getBKTRmagicSection1() { return BKTRmagicSection1; } - public int getBKTRu32Section1() { return BKTRu32Section1; } - public int getBKTRs32Section1() { return BKTRs32Section1; } - public byte[] getBKTRunknownSection1() { return BKTRunknownSection1; } - public long getBKTRoffsetSection2() { return BKTRoffsetSection2; } - public long getBKTRsizeSection2() { return BKTRsizeSection2; } - public String getBKTRmagicSection2() { return BKTRmagicSection2; } - public int getBKTRu32Section2() { return BKTRu32Section2; } - public int getBKTRs32Section2() { return BKTRs32Section2; } - public byte[] getBKTRunknownSection2() { return BKTRunknownSection2; } - public byte[] getSectionCTR() { return sectionCTR; } - public byte[] getUnknownEndPadding() { return unknownEndPadding; } -} - diff --git a/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/NcaFsHeader.java b/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/NcaFsHeader.java new file mode 100644 index 0000000..6bedfe0 --- /dev/null +++ b/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/NcaFsHeader.java @@ -0,0 +1,287 @@ +/* + Copyright 2019-2022 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka 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. + + libKonogonka 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 libKonogonka. If not, see . +*/ +package libKonogonka.Tools.NCA.NCASectionTableBlock; + +import libKonogonka.RainbowDump; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Arrays; + +import static libKonogonka.Converter.byteArrToHexString; +import static libKonogonka.Converter.getLElong; + +public class NcaFsHeader { + private final static Logger log = LogManager.getLogger(NcaFsHeader.class); + + private final byte[] version; + private final byte fsType; + private final byte hashType; + private final byte cryptoType; + private final byte metaDataHashType; + private final byte[] padding; + private SuperBlockIVFC superBlockIVFC; + private SuperBlockPFS0 superBlockPFS0; + // BKTR extended + private final long PatchInfoOffsetSection1; + private final long PatchInfoSizeSection1; + private final BucketTreeHeader BktrSection1; + + private final long PatchInfoOffsetSection2; + private final long PatchInfoSizeSection2; + private final BucketTreeHeader BktrSection2; + + private final byte[] generation; + private final byte[] sectionCTR; + private final SparseInfo sparseInfo; + private final CompressionInfo compressionInfo; + private final MetaDataHashDataInfo metaDataHashDataInfo; + private final byte[] unknownEndPadding; + + public NcaFsHeader(byte[] tableBlockBytes) throws Exception{ + if (tableBlockBytes.length != 0x200) + throw new Exception("Table Block Section size is incorrect."); + version = Arrays.copyOfRange(tableBlockBytes, 0, 0x2); + fsType = tableBlockBytes[0x2]; + hashType = tableBlockBytes[0x3]; + cryptoType = tableBlockBytes[0x4]; + metaDataHashType = tableBlockBytes[0x6]; + padding = Arrays.copyOfRange(tableBlockBytes, 0x6, 0x8); + byte[] superBlockBytes = Arrays.copyOfRange(tableBlockBytes, 0x8, 0xf8); + + if ((fsType == 0) && (hashType == 0x3)) + superBlockIVFC = new SuperBlockIVFC(superBlockBytes); + else if ((fsType == 0x1) && (hashType == 0x2)) + superBlockPFS0 = new SuperBlockPFS0(superBlockBytes); + + PatchInfoOffsetSection1 = getLElong(tableBlockBytes, 0x100); + PatchInfoSizeSection1 = getLElong(tableBlockBytes, 0x108); + BktrSection1 = new BucketTreeHeader(Arrays.copyOfRange(tableBlockBytes, 0x110, 0x120)); + + PatchInfoOffsetSection2 = getLElong(tableBlockBytes, 0x120); + PatchInfoSizeSection2 = getLElong(tableBlockBytes, 0x128); + BktrSection2 = new BucketTreeHeader(Arrays.copyOfRange(tableBlockBytes, 0x130, 0x140)); + + generation = Arrays.copyOfRange(tableBlockBytes, 0x140, 0x144); + sectionCTR = Arrays.copyOfRange(tableBlockBytes, 0x144, 0x148); + sparseInfo = new SparseInfo(Arrays.copyOfRange(tableBlockBytes, 0x148, 0x178)); + compressionInfo = new CompressionInfo(Arrays.copyOfRange(tableBlockBytes, 0x178, 0x1a0)); + metaDataHashDataInfo = new MetaDataHashDataInfo(Arrays.copyOfRange(tableBlockBytes, 0x1a0, 0x1d0)); + + unknownEndPadding = Arrays.copyOfRange(tableBlockBytes, 0x1d0, 0x200); + } + + public byte[] getVersion() { return version; } + public byte getFsType() { return fsType; } + public byte getHashType() { return hashType; } + public byte getCryptoType() { return cryptoType; } + public byte getMetaDataHashType() { return metaDataHashType; } + public byte[] getPadding() { return padding; } + public SuperBlockIVFC getSuperBlockIVFC() { return superBlockIVFC; } + public SuperBlockPFS0 getSuperBlockPFS0() { return superBlockPFS0; } + + public long getPatchInfoOffsetSection1() { return PatchInfoOffsetSection1; } + public long getPatchInfoSizeSection1() { return PatchInfoSizeSection1; } + public String getPatchInfoMagicSection1() { return BktrSection1.getMagic(); } + public int getPatchInfoVersionSection1() { return BktrSection1.getVersion(); } + public int getEntryCountSection1() { return BktrSection1.getEntryCount(); } + public byte[] getPatchInfoUnknownSection1() { return BktrSection1.getUnknown(); } + public long getPatchInfoOffsetSection2() { return PatchInfoOffsetSection2; } + public long getPatchInfoSizeSection2() { return PatchInfoSizeSection2; } + public String getPatchInfoMagicSection2() { return BktrSection2.getMagic(); } + public int getPatchInfoVersionSection2() { return BktrSection2.getVersion(); } + public int getEntryCountSection2() { return BktrSection2.getEntryCount(); } + public byte[] getPatchInfoUnknownSection2() { return BktrSection2.getUnknown(); } + public byte[] getGeneration() {return generation;} + public byte[] getSectionCTR() { return sectionCTR; } + public SparseInfo getSparseInfo() {return sparseInfo;} + public CompressionInfo getCompressionInfo() {return compressionInfo;} + public MetaDataHashDataInfo getMetaDataHashDataInfo() {return metaDataHashDataInfo;} + public byte[] getUnknownEndPadding() { return unknownEndPadding; } + + public void printDebug(){ + String hashTypeDescription; + switch (hashType){ + case 0 : + hashTypeDescription = "Auto"; + break; + case 1 : + hashTypeDescription = "None"; + break; + case 2 : + hashTypeDescription = "HierarchicalSha256Hash"; + break; + case 3 : + hashTypeDescription = "HierarchicalIntegrityHash"; + break; + case 4 : + hashTypeDescription = "AutoSha3"; + break; + case 5 : + hashTypeDescription = "HierarchicalSha3256Hash"; + break; + case 6 : + hashTypeDescription = "HierarchicalIntegritySha3Hash"; + break; + default: + hashTypeDescription = "???"; + } + String cryptoTypeDescription; + switch (cryptoType){ + case 0 : + cryptoTypeDescription = "Auto"; + break; + case 1 : + cryptoTypeDescription = "None"; + break; + case 2 : + cryptoTypeDescription = "AesXts"; + break; + case 3 : + cryptoTypeDescription = "AesCtr"; + break; + case 4 : + cryptoTypeDescription = "AesCtrEx"; + break; + case 5 : + cryptoTypeDescription = "AesCtrSkipLayerHash"; + break; + case 6 : + cryptoTypeDescription = "AesCtrExSkipLayerHash"; + break; + default: + cryptoTypeDescription = "???"; + } + + log.debug("NCASectionBlock:\n" + + "Version : " + byteArrToHexString(version) + "\n" + + "FS Type : " + fsType +(fsType == 0?" (RomFS)":fsType == 1?" (PartitionFS)":" (Unknown)")+ "\n" + + "Hash Type : " + hashType +" ("+ hashTypeDescription + ")\n" + + "Crypto Type : " + cryptoType + " (" + cryptoTypeDescription + ")\n" + + "Meta Data Hash Type : " + metaDataHashType + "\n" + + "Padding : " + byteArrToHexString(padding) + "\n" + + "Super Block IVFC : " + superBlockIVFC + "\n" + + "Super Block PFS0 : " + superBlockPFS0 + "\n" + + "================================================================================================\n" + + (((fsType == 0) && (hashType == 0x3))? + ("| Hash Data - RomFS\n" + + "| Magic : " + superBlockIVFC.getMagic() + "\n" + + "| Version : " + superBlockIVFC.getVersion() + "\n" + + "| Master Hash Size : " + superBlockIVFC.getMasterHashSize() + "\n" + + "| Total Number of Levels : " + superBlockIVFC.getTotalNumberOfLevels() + "\n\n" + + + "| Level 1 Offset : " + RainbowDump.formatDecHexString(superBlockIVFC.getLvl1Offset()) + "\n" + + "| Level 1 Size : " + RainbowDump.formatDecHexString(superBlockIVFC.getLvl1Size()) + "\n" + + "| Level 1 Block Size (log2) : " + RainbowDump.formatDecHexString(superBlockIVFC.getLvl1SBlockSize()) + "\n" + + "| Level 1 reserved : " + byteArrToHexString(superBlockIVFC.getReserved1()) + "\n\n" + + + "| Level 2 Offset : " + RainbowDump.formatDecHexString(superBlockIVFC.getLvl2Offset()) + "\n" + + "| Level 2 Size : " + RainbowDump.formatDecHexString(superBlockIVFC.getLvl2Size()) + "\n" + + "| Level 2 Block Size (log2) : " + RainbowDump.formatDecHexString(superBlockIVFC.getLvl2SBlockSize()) + "\n" + + "| Level 2 reserved : " + byteArrToHexString(superBlockIVFC.getReserved2()) + "\n\n" + + + "| Level 3 Offset : " + RainbowDump.formatDecHexString(superBlockIVFC.getLvl3Offset()) + "\n" + + "| Level 3 Size : " + RainbowDump.formatDecHexString(superBlockIVFC.getLvl3Size()) + "\n" + + "| Level 3 Block Size (log2) : " + RainbowDump.formatDecHexString(superBlockIVFC.getLvl3SBlockSize()) + "\n" + + "| Level 3 reserved : " + byteArrToHexString(superBlockIVFC.getReserved3()) + "\n\n" + + + "| Level 4 Offset : " + RainbowDump.formatDecHexString(superBlockIVFC.getLvl4Offset()) + "\n" + + "| Level 4 Size : " + RainbowDump.formatDecHexString(superBlockIVFC.getLvl4Size()) + "\n" + + "| Level 4 Block Size (log2) : " + RainbowDump.formatDecHexString(superBlockIVFC.getLvl4SBlockSize()) + "\n" + + "| Level 4 reserved : " + byteArrToHexString(superBlockIVFC.getReserved4()) + "\n\n" + + + "| Level 5 Offset : " + RainbowDump.formatDecHexString(superBlockIVFC.getLvl5Offset()) + "\n" + + "| Level 5 Size : " + RainbowDump.formatDecHexString(superBlockIVFC.getLvl5Size()) + "\n" + + "| Level 5 Block Size (log2) : " + RainbowDump.formatDecHexString(superBlockIVFC.getLvl5SBlockSize()) + "\n" + + "| Level 5 reserved : " + byteArrToHexString(superBlockIVFC.getReserved5()) + "\n\n" + + + "| Level 6 Offset : " + RainbowDump.formatDecHexString(superBlockIVFC.getLvl6Offset()) + "\n" + + "| Level 6 Size : " + RainbowDump.formatDecHexString(superBlockIVFC.getLvl6Size()) + "\n" + + "| Level 6 Block Size (log2) : " + RainbowDump.formatDecHexString(superBlockIVFC.getLvl6SBlockSize()) + "\n" + + "| Level 6 reserved : " + byteArrToHexString(superBlockIVFC.getReserved6()) + "\n\n" + + + "| SignatureSalt : " + byteArrToHexString(superBlockIVFC.getSignatureSalt()) + "\n" + + "| Master Hash : " + byteArrToHexString(superBlockIVFC.getMasterHash()) + "\n" + + "| Reserved (tail) : " + byteArrToHexString(superBlockIVFC.getReservedTail()) + "\n" + ) + :(((fsType == 0x1) && (hashType == 0x2))? + ("| Hash Data - PFS0\n" + + "| SHA256 hash : " + byteArrToHexString(superBlockPFS0.getSHA256hash()) + "\n" + + "| Block Size (bytes) : " + superBlockPFS0.getBlockSize() + "\n" + + "| Layer Count (2) : " + superBlockPFS0.getLayerCount() + "\n" + + "| Hash table offset : " + RainbowDump.formatDecHexString(superBlockPFS0.getHashTableOffset()) + "\n" + + "| Hash table size : " + RainbowDump.formatDecHexString(superBlockPFS0.getHashTableSize()) + "\n" + + "| PFS0 header offset : " + RainbowDump.formatDecHexString(superBlockPFS0.getPfs0offset()) + "\n" + + "| PFS0 header size : " + RainbowDump.formatDecHexString(superBlockPFS0.getPfs0size()) + "\n" + + "| Unknown (reserved) : " + byteArrToHexString(superBlockPFS0.getZeroes()) + "\n" + ) + : + " // Hash Data - EMPTY \\\\ \n" + )) + + "================================================================================================\n" + + " PatchInfo\n" + + "================================================================================================\n" + + "Indirect Offset : " + PatchInfoOffsetSection1 + "\n" + + "Indirect Size : " + PatchInfoSizeSection1 + "\n" + + "Magic ('BKTR') : " + BktrSection1.getMagic() + "\n" + + "Version : " + BktrSection1.getVersion() + "\n" + + "EntryCount : " + BktrSection1.getEntryCount() + "\n" + + "Unknown (reserved) : " + byteArrToHexString(BktrSection1.getUnknown()) + "\n" + + "------------------------------------------------------------------------------------------------\n" + + "AesCtrEx Offset : " + PatchInfoOffsetSection2 + "\n" + + "AesCtrEx Size : " + PatchInfoSizeSection2 + "\n" + + "Magic ('BKTR') : " + BktrSection2.getMagic() + "\n" + + "Version : " + BktrSection2.getVersion() + "\n" + + "EntryCount : " + BktrSection2.getEntryCount() + "\n" + + "Unknown (reserved) : " + byteArrToHexString(BktrSection2.getUnknown()) + "\n" + + "================================================================================================\n" + + "Generation : " + byteArrToHexString(generation) + "\n" + + "Section CTR : " + byteArrToHexString(sectionCTR) + "\n" + + "================================================================================================\n" + + " Sparse Info\n" + + "Table Offset : " + sparseInfo.getOffset() + "\n" + + "Table Size : " + sparseInfo.getSize() + "\n" + + "Magic ('BKTR') : " + sparseInfo.getBktrMagic() + "\n" + + "Version : " + sparseInfo.getBktrVersion() + "\n" + + "EntryCount : " + sparseInfo.getBktrEntryCount() + "\n" + + "Unknown (BKTR) : " + byteArrToHexString(sparseInfo.getBktrUnknown()) + "\n" + + "PhysicalOffset : " + sparseInfo.getPhysicalOffset() + "\n" + + "Generation : " + byteArrToHexString(sparseInfo.getGeneration()) + "\n" + + "Unknown (reserved) : " + byteArrToHexString(sparseInfo.getUnknown()) + "\n" + + "================================================================================================\n" + + " Compression Info\n" + + "Table Offset : " + compressionInfo.getOffset() + "\n" + + "Table Size : " + compressionInfo.getSize() + "\n" + + "Magic ('BKTR') : " + compressionInfo.getBktrMagic() + "\n" + + "Version : " + compressionInfo.getBktrVersion() + "\n" + + "EntryCount : " + compressionInfo.getBktrEntryCount() + "\n" + + "Unknown (reserved) : " + byteArrToHexString(compressionInfo.getBktrUnknown()) + "\n" + + "Reserved : " + byteArrToHexString(compressionInfo.getUnknown()) + "\n" + + "================================================================================================\n" + + " Meta Data Hash Data Info\n" + + "Table Offset : " + metaDataHashDataInfo.getOffset() + "\n" + + "Table Size : " + metaDataHashDataInfo.getSize() + "\n" + + "Unknown (reserved) : " + byteArrToHexString(metaDataHashDataInfo.getTableHash()) + "\n" + + "================================================================================================\n" + + "Unknown End Padding : " + byteArrToHexString(unknownEndPadding) + "\n" + + "################################################################################################\n" + ); + } +} + diff --git a/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/SparseInfo.java b/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/SparseInfo.java new file mode 100644 index 0000000..39ce9c7 --- /dev/null +++ b/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/SparseInfo.java @@ -0,0 +1,51 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka 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. + + libKonogonka 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 libKonogonka. If not, see . + */ +package libKonogonka.Tools.NCA.NCASectionTableBlock; + +import java.util.Arrays; + +import static libKonogonka.Converter.getLElong; + +public class SparseInfo { + private final long offset; + private final long size; + private final BucketTreeHeader bktr; + private final long physicalOffset; + private final byte[] generation; + private final byte[] unknown; + + SparseInfo(byte[] rawTable){ + offset = getLElong(rawTable, 0); + size = getLElong(rawTable, 0x8); + bktr = new BucketTreeHeader(Arrays.copyOfRange(rawTable, 0x10, 0x20)); + physicalOffset = getLElong(rawTable, 0x20); + generation = Arrays.copyOfRange(rawTable, 0x28, 0x2a); + unknown = Arrays.copyOfRange(rawTable, 0x2a, 0x30); + } + + public long getOffset() { return offset; } + public long getSize() { return size; } + public String getBktrMagic() { return bktr.getMagic(); } + public int getBktrVersion() { return bktr.getVersion(); } + public int getBktrEntryCount() { return bktr.getEntryCount(); } + public byte[] getBktrUnknown() { return bktr.getUnknown(); } + public long getPhysicalOffset() {return physicalOffset;} + public byte[] getGeneration() {return generation;} + public byte[] getUnknown() {return unknown;} +} \ No newline at end of file diff --git a/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/SuperBlockIVFC.java b/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/SuperBlockIVFC.java index 205ee56..bec990e 100644 --- a/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/SuperBlockIVFC.java +++ b/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/SuperBlockIVFC.java @@ -21,50 +21,55 @@ package libKonogonka.Tools.NCA.NCASectionTableBlock; import java.nio.charset.StandardCharsets; import java.util.Arrays; -import static libKonogonka.LoperConverter.getLEint; -import static libKonogonka.LoperConverter.getLElong; +import static libKonogonka.Converter.getLEint; +import static libKonogonka.Converter.getLElong; public class SuperBlockIVFC { - private String magic; - private int magicNumber; - private int masterHashSize; - private int totalNumberOfLevels; - private long lvl1Offset; - private long lvl1Size; - private int lvl1SBlockSize; - private byte[] reserved1; + private final String magic; + private final int version; + private final int masterHashSize; + private final int totalNumberOfLevels; + private final long lvl1Offset; + private final long lvl1Size; + private final int lvl1SBlockSize; + private final byte[] reserved1; - private long lvl2Offset; - private long lvl2Size; - private int lvl2SBlockSize; - private byte[] reserved2; + private final long lvl2Offset; + private final long lvl2Size; + private final int lvl2SBlockSize; + private final byte[] reserved2; - private long lvl3Offset; - private long lvl3Size; - private int lvl3SBlockSize; - private byte[] reserved3; + private final long lvl3Offset; + private final long lvl3Size; + private final int lvl3SBlockSize; + private final byte[] reserved3; - private long lvl4Offset; - private long lvl4Size; - private int lvl4SBlockSize; - private byte[] reserved4; + private final long lvl4Offset; + private final long lvl4Size; + private final int lvl4SBlockSize; + private final byte[] reserved4; - private long lvl5Offset; - private long lvl5Size; - private int lvl5SBlockSize; - private byte[] reserved5; + private final long lvl5Offset; + private final long lvl5Size; + private final int lvl5SBlockSize; + private final byte[] reserved5; - private long lvl6Offset; - private long lvl6Size; - private int lvl6SBlockSize; - private byte[] reserved6; + private final long lvl6Offset; + private final long lvl6Size; + private final int lvl6SBlockSize; + private final byte[] reserved6; - private byte[] unknown; - private byte[] hash; + private final byte[] signatureSalt; + private final byte[] masterHash; + private final byte[] reservedTail; + /** + * Also known as IntegrityMetaInfo + * @param sbBytes - Chunk of data related for IVFC Hash Data table + */ SuperBlockIVFC(byte[] sbBytes){ this.magic = new String(Arrays.copyOfRange(sbBytes, 0, 4), StandardCharsets.US_ASCII); - this.magicNumber = getLEint(sbBytes, 0x4); + this.version = getLEint(sbBytes, 0x4); this.masterHashSize = getLEint(sbBytes, 0x8); this.totalNumberOfLevels = getLEint(sbBytes, 0xc); @@ -98,50 +103,13 @@ public class SuperBlockIVFC { this.lvl6SBlockSize = getLEint(sbBytes, 0x98); this.reserved6 = Arrays.copyOfRange(sbBytes, 0x9c, 0xa0); - this.unknown = Arrays.copyOfRange(sbBytes, 0xa0, 0xc0); - this.hash = Arrays.copyOfRange(sbBytes, 0xc0, 0xe0); - /* - System.out.println(magic); - System.out.println(magicNumber); - System.out.println(masterHashSize); - System.out.println(totalNumberOfLevels); - System.out.println(lvl1Offset); - System.out.println(lvl1Size); - System.out.println(lvl1SBlockSize); - RainbowHexDump.hexDumpUTF8(reserved1); - - System.out.println(lvl2Offset); - System.out.println(lvl2Size); - System.out.println(lvl2SBlockSize); - RainbowHexDump.hexDumpUTF8(reserved2); - - System.out.println(lvl3Offset); - System.out.println(lvl3Size); - System.out.println(lvl3SBlockSize); - RainbowHexDump.hexDumpUTF8(reserved3); - - System.out.println(lvl4Offset); - System.out.println(lvl4Size); - System.out.println(lvl4SBlockSize); - RainbowHexDump.hexDumpUTF8(reserved4); - - System.out.println(lvl5Offset); - System.out.println(lvl5Size); - System.out.println(lvl5SBlockSize); - RainbowHexDump.hexDumpUTF8(reserved5); - - System.out.println(lvl6Offset); - System.out.println(lvl6Size); - System.out.println(lvl6SBlockSize); - RainbowHexDump.hexDumpUTF8(reserved6); - - RainbowHexDump.hexDumpUTF8(unknown); - RainbowHexDump.hexDumpUTF8(hash); - // */ + this.signatureSalt = Arrays.copyOfRange(sbBytes, 0xa0, 0xc0); + this.masterHash = Arrays.copyOfRange(sbBytes, 0xc0, 0xe0); + this.reservedTail = Arrays.copyOfRange(sbBytes, 0xe0, 0xf8); } public String getMagic() { return magic; } - public int getMagicNumber() { return magicNumber; } + public int getVersion() { return version; } public int getMasterHashSize() { return masterHashSize; } public int getTotalNumberOfLevels() { return totalNumberOfLevels; } public long getLvl1Offset() { return lvl1Offset; } @@ -168,6 +136,7 @@ public class SuperBlockIVFC { public long getLvl6Size() { return lvl6Size; } public int getLvl6SBlockSize() { return lvl6SBlockSize; } public byte[] getReserved6() { return reserved6; } - public byte[] getUnknown() { return unknown; } - public byte[] getHash() { return hash; } + public byte[] getSignatureSalt() { return signatureSalt; } + public byte[] getMasterHash() { return masterHash; } + public byte[] getReservedTail() { return reservedTail; } } diff --git a/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/SuperBlockPFS0.java b/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/SuperBlockPFS0.java index f93903d..6b008a7 100644 --- a/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/SuperBlockPFS0.java +++ b/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/SuperBlockPFS0.java @@ -20,33 +20,37 @@ package libKonogonka.Tools.NCA.NCASectionTableBlock; import java.util.Arrays; -import static libKonogonka.LoperConverter.getLEint; -import static libKonogonka.LoperConverter.getLElong; +import static libKonogonka.Converter.getLEint; +import static libKonogonka.Converter.getLElong; public class SuperBlockPFS0 { - private byte[] SHA256hash; - private int blockSize; - private int unknownNumberTwo; - private long hashTableOffset; - private long hashTableSize; - private long pfs0offset; - private long pfs0size; - private byte[] zeroes; + private final byte[] SHA256hash; + private final int blockSize; + private final int layerCount; + private final long hashTableOffset; + private final long hashTableSize; + private final long pfs0offset; + private final long pfs0size; + private final byte[] zeroes; + /** + * Also known as HierarchicalSha256Data + * @param sbBytes - Chunk of data related for PFS0 Hash Data table + */ SuperBlockPFS0(byte[] sbBytes){ SHA256hash = Arrays.copyOfRange(sbBytes, 0, 0x20); blockSize = getLEint(sbBytes, 0x20); - unknownNumberTwo = getLEint(sbBytes, 0x24); + layerCount = getLEint(sbBytes, 0x24); hashTableOffset = getLElong(sbBytes, 0x28); hashTableSize = getLElong(sbBytes, 0x30); pfs0offset = getLElong(sbBytes, 0x38); pfs0size = getLElong(sbBytes, 0x40); - zeroes = Arrays.copyOfRange(sbBytes, 0x48, 0xf8); + zeroes = Arrays.copyOfRange(sbBytes, 0x48, 0xf0); } public byte[] getSHA256hash() { return SHA256hash; } public int getBlockSize() { return blockSize; } - public int getUnknownNumberTwo() { return unknownNumberTwo; } + public int getLayerCount() { return layerCount; } public long getHashTableOffset() { return hashTableOffset; } public long getHashTableSize() { return hashTableSize; } public long getPfs0offset() { return pfs0offset; } diff --git a/src/main/java/libKonogonka/Tools/NPDM/ACI0/ACI0Provider.java b/src/main/java/libKonogonka/Tools/NPDM/ACI0/ACI0Provider.java index f93e22d..4540b37 100644 --- a/src/main/java/libKonogonka/Tools/NPDM/ACI0/ACI0Provider.java +++ b/src/main/java/libKonogonka/Tools/NPDM/ACI0/ACI0Provider.java @@ -24,7 +24,7 @@ import libKonogonka.Tools.NPDM.ServiceAccessControlProvider; import java.nio.charset.StandardCharsets; import java.util.Arrays; -import static libKonogonka.LoperConverter.getLEint; +import static libKonogonka.Converter.getLEint; public class ACI0Provider { private String magicNum; diff --git a/src/main/java/libKonogonka/Tools/NPDM/ACI0/FSAccessHeaderProvider.java b/src/main/java/libKonogonka/Tools/NPDM/ACI0/FSAccessHeaderProvider.java index b352ab1..da69d0e 100644 --- a/src/main/java/libKonogonka/Tools/NPDM/ACI0/FSAccessHeaderProvider.java +++ b/src/main/java/libKonogonka/Tools/NPDM/ACI0/FSAccessHeaderProvider.java @@ -18,7 +18,7 @@ */ package libKonogonka.Tools.NPDM.ACI0; -import libKonogonka.LoperConverter; +import libKonogonka.Converter; import java.util.Arrays; @@ -39,11 +39,11 @@ public class FSAccessHeaderProvider { public FSAccessHeaderProvider(byte[] bytes) { version = bytes[0]; padding = Arrays.copyOfRange(bytes, 1, 0x4); - permissionsBitmask = LoperConverter.getLElong(bytes, 0x4); - dataSize = LoperConverter.getLEint(bytes, 0xC); - contentOwnIdSectionSize = LoperConverter.getLEint(bytes, 0x10); - dataNownerSizes = LoperConverter.getLEint(bytes, 0x14); - saveDataOwnSectionSize = LoperConverter.getLEint(bytes, 0x18); + permissionsBitmask = Converter.getLElong(bytes, 0x4); + dataSize = Converter.getLEint(bytes, 0xC); + contentOwnIdSectionSize = Converter.getLEint(bytes, 0x10); + dataNownerSizes = Converter.getLEint(bytes, 0x14); + saveDataOwnSectionSize = Converter.getLEint(bytes, 0x18); unknownData = Arrays.copyOfRange(bytes, 0x1C, bytes.length); } diff --git a/src/main/java/libKonogonka/Tools/NPDM/ACID/ACIDProvider.java b/src/main/java/libKonogonka/Tools/NPDM/ACID/ACIDProvider.java index d03beaa..3084b74 100644 --- a/src/main/java/libKonogonka/Tools/NPDM/ACID/ACIDProvider.java +++ b/src/main/java/libKonogonka/Tools/NPDM/ACID/ACIDProvider.java @@ -24,7 +24,7 @@ import libKonogonka.Tools.NPDM.ServiceAccessControlProvider; import java.nio.charset.StandardCharsets; import java.util.Arrays; -import static libKonogonka.LoperConverter.*; +import static libKonogonka.Converter.*; public class ACIDProvider { diff --git a/src/main/java/libKonogonka/Tools/NPDM/ACID/FSAccessControlProvider.java b/src/main/java/libKonogonka/Tools/NPDM/ACID/FSAccessControlProvider.java index 533838d..5879df1 100644 --- a/src/main/java/libKonogonka/Tools/NPDM/ACID/FSAccessControlProvider.java +++ b/src/main/java/libKonogonka/Tools/NPDM/ACID/FSAccessControlProvider.java @@ -18,7 +18,7 @@ */ package libKonogonka.Tools.NPDM.ACID; -import libKonogonka.LoperConverter; +import libKonogonka.Converter; import java.util.Arrays; @@ -35,7 +35,7 @@ public class FSAccessControlProvider { public FSAccessControlProvider(byte[] bytes) { version = bytes[0]; padding = Arrays.copyOfRange(bytes, 1, 0x4); - permissionsBitmask = LoperConverter.getLElong(bytes, 0x4); + permissionsBitmask = Converter.getLElong(bytes, 0x4); reserved = Arrays.copyOfRange(bytes, 0xC, 0x2C); } diff --git a/src/main/java/libKonogonka/Tools/NPDM/KernelAccessControlProvider.java b/src/main/java/libKonogonka/Tools/NPDM/KernelAccessControlProvider.java index 5e1aa07..cbe7f37 100644 --- a/src/main/java/libKonogonka/Tools/NPDM/KernelAccessControlProvider.java +++ b/src/main/java/libKonogonka/Tools/NPDM/KernelAccessControlProvider.java @@ -18,7 +18,7 @@ */ package libKonogonka.Tools.NPDM; -import libKonogonka.LoperConverter; +import libKonogonka.Converter; import java.util.LinkedHashMap; import java.util.LinkedList; @@ -126,7 +126,7 @@ public class KernelAccessControlProvider { int position = 0; // Collect all blocks for (int i = 0; i < bytes.length / 4; i++) { - int block = LoperConverter.getLEint(bytes, position); + int block = Converter.getLEint(bytes, position); position += 4; rawData.add(block); diff --git a/src/main/java/libKonogonka/Tools/NPDM/NPDMProvider.java b/src/main/java/libKonogonka/Tools/NPDM/NPDMProvider.java index a539453..3005060 100644 --- a/src/main/java/libKonogonka/Tools/NPDM/NPDMProvider.java +++ b/src/main/java/libKonogonka/Tools/NPDM/NPDMProvider.java @@ -28,7 +28,7 @@ import java.io.RandomAccessFile; import java.nio.charset.StandardCharsets; import java.util.Arrays; -import static libKonogonka.LoperConverter.*; +import static libKonogonka.Converter.*; public class NPDMProvider extends ASuperInFileProvider { diff --git a/src/main/java/libKonogonka/Tools/PFS0/PFS0EncryptedProvider.java b/src/main/java/libKonogonka/Tools/PFS0/PFS0EncryptedProvider.java index 9471a67..07098b9 100644 --- a/src/main/java/libKonogonka/Tools/PFS0/PFS0EncryptedProvider.java +++ b/src/main/java/libKonogonka/Tools/PFS0/PFS0EncryptedProvider.java @@ -24,27 +24,27 @@ import java.io.*; import java.nio.charset.StandardCharsets; import java.util.Arrays; -import static libKonogonka.LoperConverter.*; +import static libKonogonka.Converter.*; public class PFS0EncryptedProvider implements IPFS0Provider{ private long rawFileDataStart; // Always -1 @ PFS0EncryptedProvider - private String magic; - private int filesCount; - private int stringTableSize; - private byte[] padding; - private PFS0subFile[] pfs0subFiles; + private final String magic; + private final int filesCount; + private final int stringTableSize; + private final byte[] padding; + private final PFS0subFile[] pfs0subFiles; //--------------------------------------- private long rawBlockDataStart; - private long offsetPositionInFile; - private File file; - private byte[] key; - private byte[] sectionCTR; - private long mediaStartOffset; // In 512-blocks - private long mediaEndOffset; // In 512-blocks + private final long offsetPositionInFile; + private final File file; + private final byte[] key; + private final byte[] sectionCTR; + private final long mediaStartOffset; // In 512-blocks + private final long mediaEndOffset; // In 512-blocks public PFS0EncryptedProvider(PipedInputStream pipedInputStream, long pfs0offsetPosition, @@ -62,7 +62,7 @@ public class PFS0EncryptedProvider implements IPFS0Provider{ this.sectionCTR = sectionCTR; this.mediaStartOffset = mediaStartOffset; this.mediaEndOffset = mediaEndOffset; - // pfs0offsetPosition is a position relative to Media block. Lets add pfs0 'header's' bytes count and get raw data start position in media block + // pfs0offsetPosition is a position relative to Media block. Let's add pfs0 'header's' bytes count and get raw data start position in media block rawFileDataStart = -1; // Set -1 for PFS0EncryptedProvider // Detect raw data start position using next var rawBlockDataStart = pfs0offsetPosition; @@ -214,7 +214,7 @@ public class PFS0EncryptedProvider implements IPFS0Provider{ if (skipBytes > 0) { encryptedBlock = new byte[0x200]; if (bis.read(encryptedBlock) == 0x200) { - dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock); + dectyptedBlock = aesCtrDecryptSimple.decryptNext(encryptedBlock); // If we have extra-small file that is less then a block and even more if ((0x200 - skipBytes) > pfs0subFiles[subFileNumber].getSize()){ streamOut.write(dectyptedBlock, skipBytes, (int) pfs0subFiles[subFileNumber].getSize()); // safe cast @@ -244,7 +244,7 @@ public class PFS0EncryptedProvider implements IPFS0Provider{ encryptedBlock = new byte[0x200]; if (bis.read(encryptedBlock) == 0x200) { //dectyptedBlock = aesCtr.decrypt(encryptedBlock); - dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock); + dectyptedBlock = aesCtrDecryptSimple.decryptNext(encryptedBlock); // Writing decrypted data to pipe streamOut.write(dectyptedBlock); } @@ -259,7 +259,7 @@ public class PFS0EncryptedProvider implements IPFS0Provider{ if (extraData > 0){ // In case we didn't get what we want encryptedBlock = new byte[0x200]; if (bis.read(encryptedBlock) == 0x200) { - dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock); + dectyptedBlock = aesCtrDecryptSimple.decryptNext(encryptedBlock); streamOut.write(dectyptedBlock, 0, extraData); } else { @@ -270,7 +270,7 @@ public class PFS0EncryptedProvider implements IPFS0Provider{ else if (extraData < 0){ // In case we can get more than we need encryptedBlock = new byte[0x200]; if (bis.read(encryptedBlock) == 0x200) { - dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock); + dectyptedBlock = aesCtrDecryptSimple.decryptNext(encryptedBlock); streamOut.write(dectyptedBlock, 0, 0x200 + extraData); // WTF ??? THIS LOOKS INCORRECT } else { diff --git a/src/main/java/libKonogonka/Tools/PFS0/PFS0Provider.java b/src/main/java/libKonogonka/Tools/PFS0/PFS0Provider.java index db3b984..718ef9f 100644 --- a/src/main/java/libKonogonka/Tools/PFS0/PFS0Provider.java +++ b/src/main/java/libKonogonka/Tools/PFS0/PFS0Provider.java @@ -22,18 +22,18 @@ import java.io.*; import java.nio.charset.StandardCharsets; import java.util.Arrays; -import static libKonogonka.LoperConverter.*; +import static libKonogonka.Converter.*; public class PFS0Provider implements IPFS0Provider{ - private long rawFileDataStart; // Where data starts, excluding header, string table etc. + private final long rawFileDataStart; // Where data starts, excluding header, string table etc. - private String magic; - private int filesCount; - private int stringTableSize; - private byte[] padding; - private PFS0subFile[] pfs0subFiles; + private final String magic; + private final int filesCount; + private final int stringTableSize; + private final byte[] padding; + private final PFS0subFile[] pfs0subFiles; - private File file; + private final File file; public PFS0Provider(File fileWithPfs0) throws Exception{ this(fileWithPfs0, 0); } diff --git a/src/main/java/libKonogonka/Tools/RomFs/FileMeta4Debug.java b/src/main/java/libKonogonka/Tools/RomFs/FileMeta4Debug.java deleted file mode 100644 index c9db51b..0000000 --- a/src/main/java/libKonogonka/Tools/RomFs/FileMeta4Debug.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - Copyright 2019-2022 Dmitry Isaenko - - This file is part of libKonogonka. - - libKonogonka 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. - - libKonogonka 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 libKonogonka. If not, see . -*/ -package libKonogonka.Tools.RomFs; - -import libKonogonka.LoperConverter; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static libKonogonka.RainbowDump.formatDecHexString; - -public class FileMeta4Debug { - - List allFiles; - - FileMeta4Debug(long fileMetadataTableLength, byte[] fileMetadataTable) { - allFiles = new ArrayList<>(); - int i = 0; - while (i < fileMetadataTableLength) { - FileMeta fileMeta = new FileMeta(); - fileMeta.containingDirectoryOffset = LoperConverter.getLEint(fileMetadataTable, i); - i += 4; - fileMeta.nextSiblingFileOffset = LoperConverter.getLEint(fileMetadataTable, i); - i += 4; - fileMeta.fileDataOffset = LoperConverter.getLElong(fileMetadataTable, i); - i += 8; - fileMeta.fileDataLength = LoperConverter.getLElong(fileMetadataTable, i); - i += 8; - fileMeta.nextFileOffset = LoperConverter.getLEint(fileMetadataTable, i); - i += 4; - fileMeta.fileNameLength = LoperConverter.getLEint(fileMetadataTable, i); - i += 4; - fileMeta.fileName = new String(Arrays.copyOfRange(fileMetadataTable, i, i + fileMeta.fileNameLength), StandardCharsets.UTF_8); - ; - i += getRealNameSize(fileMeta.fileNameLength); - - allFiles.add(fileMeta); - } - - for (FileMeta fileMeta : allFiles){ - System.out.println( - "-------------------------FILE--------------------------------\n" + - "Offset of Containing Directory " + formatDecHexString(fileMeta.containingDirectoryOffset) + "\n" + - "Offset of next Sibling File " + formatDecHexString(fileMeta.nextSiblingFileOffset) + "\n" + - "Offset of File's Data " + formatDecHexString(fileMeta.fileDataOffset) + "\n" + - "Length of File's Data " + formatDecHexString(fileMeta.fileDataLength) + "\n" + - "Offset of next File in the same Hash Table bucket " + formatDecHexString(fileMeta.nextFileOffset) + "\n" + - "Name Length " + formatDecHexString(fileMeta.fileNameLength) + "\n" + - "Name Length (rounded up to multiple of 4) " + fileMeta.fileName + "\n" - ); - } - } - - private int getRealNameSize(int value){ - if (value % 4 == 0) - return value; - return value + 4 - value % 4; - } - - private static class FileMeta{ - int containingDirectoryOffset; - int nextSiblingFileOffset; - long fileDataOffset; - long fileDataLength; - int nextFileOffset; - int fileNameLength; - String fileName; - } -} diff --git a/src/main/java/libKonogonka/Tools/RomFs/FileSystemEntry.java b/src/main/java/libKonogonka/Tools/RomFs/FileSystemEntry.java index 3a91adb..fd10995 100644 --- a/src/main/java/libKonogonka/Tools/RomFs/FileSystemEntry.java +++ b/src/main/java/libKonogonka/Tools/RomFs/FileSystemEntry.java @@ -19,7 +19,11 @@ package libKonogonka.Tools.RomFs; -import libKonogonka.LoperConverter; +import libKonogonka.Converter; +import libKonogonka.Tools.NCA.NCAContent; +import libKonogonka.Tools.RomFs.view.FileSystemTreeViewMaker; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -28,9 +32,11 @@ import java.util.Comparator; import java.util.List; public class FileSystemEntry { + private final static Logger log = LogManager.getLogger(NCAContent.class); + private boolean directoryFlag; private String name; - private List content; + private final List content; private static byte[] dirsMetadataTable; private static byte[] filesMetadataTable; @@ -93,7 +99,7 @@ public class FileSystemEntry { fileSystemEntry.fileOffset = fileMetaData.fileDataRealOffset; fileSystemEntry.fileSize = fileMetaData.fileDataRealLength; if (fileMetaData.nextSiblingFileOffset != -1) - directoryContainer.content.add( getFile(directoryContainer, fileMetaData.nextSiblingFileOffset) ); + directoryContainer.content.add(getFile(directoryContainer, fileMetaData.nextSiblingFileOffset) ); return fileSystemEntry; } @@ -107,31 +113,44 @@ public class FileSystemEntry { private static class DirectoryMetaData { - private int parentDirectoryOffset; - private int nextSiblingDirectoryOffset; - private int firstSubdirectoryOffset; - private int firstFileOffset; + private final int parentDirectoryOffset; + private final int nextSiblingDirectoryOffset; + private final int firstSubdirectoryOffset; + private final int firstFileOffset; + private final int nextHashTableBucketDirectoryOffset; - private String dirName; + private final String dirName; private DirectoryMetaData(){ this(0); } private DirectoryMetaData(int childDirMetaPosition){ int i = childDirMetaPosition; - parentDirectoryOffset = LoperConverter.getLEint(dirsMetadataTable, i); + parentDirectoryOffset = Converter.getLEint(dirsMetadataTable, i); i += 4; - nextSiblingDirectoryOffset = LoperConverter.getLEint(dirsMetadataTable, i); + nextSiblingDirectoryOffset = Converter.getLEint(dirsMetadataTable, i); i += 4; - firstSubdirectoryOffset = LoperConverter.getLEint(dirsMetadataTable, i); + firstSubdirectoryOffset = Converter.getLEint(dirsMetadataTable, i); i += 4; - firstFileOffset = LoperConverter.getLEint(dirsMetadataTable, i); + firstFileOffset = Converter.getLEint(dirsMetadataTable, i); i += 4; - // int nextHashTableBucketDirectoryOffset = LoperConverter.getLEint(dirsMetadataTable, i); + nextHashTableBucketDirectoryOffset = Converter.getLEint(dirsMetadataTable, i); + //* + if (nextHashTableBucketDirectoryOffset < 0) { + System.out.println("nextHashTableBucketDirectoryOffset: "+ nextHashTableBucketDirectoryOffset); + } + //*/ i += 4; - int dirNameLength = LoperConverter.getLEint(dirsMetadataTable, i); - i += 4; - dirName = new String(Arrays.copyOfRange(dirsMetadataTable, i, i + dirNameLength), StandardCharsets.UTF_8); + int dirNameLength = Converter.getLEint(dirsMetadataTable, i); + + if (dirNameLength > 0) { + i += 4; + dirName = new String(Arrays.copyOfRange(dirsMetadataTable, i, i + dirNameLength), StandardCharsets.UTF_8); + } + else { + dirName = ""; + System.out.println("dirName: "+dirNameLength); + } //i += getRealNameSize(dirNameLength); } @@ -142,10 +161,10 @@ public class FileSystemEntry { } } private static class FileMetaData { - - private int nextSiblingFileOffset; - private long fileDataRealOffset; - private long fileDataRealLength; + private final int nextSiblingFileOffset; + private final long fileDataRealOffset; + private final long fileDataRealLength; + private final int nextHashTableBucketFileOffset; private String fileName; @@ -157,36 +176,43 @@ public class FileSystemEntry { int i = childFileMetaPosition; // int containingDirectoryOffset = LoperConverter.getLEint(filesMetadataTable, i); // never used i += 4; - nextSiblingFileOffset = LoperConverter.getLEint(filesMetadataTable, i); + nextSiblingFileOffset = Converter.getLEint(filesMetadataTable, i); i += 4; - fileDataRealOffset = LoperConverter.getLElong(filesMetadataTable, i); + fileDataRealOffset = Converter.getLElong(filesMetadataTable, i); i += 8; - fileDataRealLength = LoperConverter.getLElong(filesMetadataTable, i); + fileDataRealLength = Converter.getLElong(filesMetadataTable, i); i += 8; - //int nextHashTableBucketFileOffset = LoperConverter.getLEint(filesMetadataTable, i); + nextHashTableBucketFileOffset = Converter.getLEint(filesMetadataTable, i); + //* + if (nextHashTableBucketFileOffset < 0) { + System.out.println("nextHashTableBucketFileOffset: "+ nextHashTableBucketFileOffset); + } + //*/ i += 4; - int fileNameLength = LoperConverter.getLEint(filesMetadataTable, i); - i += 4; - fileName = new String(Arrays.copyOfRange(filesMetadataTable, i, i + fileNameLength), StandardCharsets.UTF_8);; + int fileNameLength = Converter.getLEint(filesMetadataTable, i); + if (fileNameLength > 0) { + i += 4; + fileName = ""; + try { + fileName = new String(Arrays.copyOfRange(filesMetadataTable, i, i + fileNameLength), StandardCharsets.UTF_8); + } + catch (Exception e){ + System.out.println("fileName sizes are: "+filesMetadataTable.length+"\t"+i+"\t"+i + fileNameLength+"\t\t"+nextHashTableBucketFileOffset); + } + } + else { + fileName = ""; + System.out.println("fileName: "+fileNameLength); + } //i += getRealNameSize(fileNameLength); } } + public void printTreeForDebug(int spacerForSizes){ + log.debug(FileSystemTreeViewMaker.make(content, spacerForSizes)); + } public void printTreeForDebug(){ - System.out.println("/"); - for (FileSystemEntry entry: content) - printEntry(2, entry); + log.debug(FileSystemTreeViewMaker.make(content, 100)); } - private void printEntry(int cnt, FileSystemEntry entry) { - for (int i = 0; i < cnt; i++) - System.out.print(" "); - if (entry.isDirectory()){ - System.out.println("|-" + entry.getName()); - for (FileSystemEntry e : entry.content) - printEntry(cnt+2, e); - } - else - System.out.println("|-" + entry.getName() + String.format(" 0x%-10x 0x%-10x", entry.fileOffset, entry.fileSize)); - } } diff --git a/src/main/java/libKonogonka/Tools/RomFs/FolderMeta4Debug.java b/src/main/java/libKonogonka/Tools/RomFs/FolderMeta4Debug.java deleted file mode 100644 index 931f8d4..0000000 --- a/src/main/java/libKonogonka/Tools/RomFs/FolderMeta4Debug.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - Copyright 2019-2022 Dmitry Isaenko - - This file is part of libKonogonka. - - libKonogonka 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. - - libKonogonka 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 libKonogonka. If not, see . -*/ -package libKonogonka.Tools.RomFs; - -import libKonogonka.LoperConverter; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import static libKonogonka.RainbowDump.formatDecHexString; - -public class FolderMeta4Debug { - - List allFolders; - - FolderMeta4Debug(long directoryMetadataTableLength, byte[] directoryMetadataTable){ - allFolders = new ArrayList<>(); - int i = 0; - while (i < directoryMetadataTableLength){ - FolderMeta folderMeta = new FolderMeta(); - folderMeta.parentDirectoryOffset = LoperConverter.getLEint(directoryMetadataTable, i); - i += 4; - folderMeta.nextSiblingDirectoryOffset = LoperConverter.getLEint(directoryMetadataTable, i); - i += 4; - folderMeta.firstSubdirectoryOffset = LoperConverter.getLEint(directoryMetadataTable, i); - i += 4; - folderMeta.firstFileOffset = LoperConverter.getLEint(directoryMetadataTable, i); - i += 4; - folderMeta.nextDirectoryOffset = LoperConverter.getLEint(directoryMetadataTable, i); - i += 4; - folderMeta.dirNameLength = LoperConverter.getLEint(directoryMetadataTable, i); - i += 4; - folderMeta.dirName = new String(Arrays.copyOfRange(directoryMetadataTable, i, i + folderMeta.dirNameLength), StandardCharsets.UTF_8); - i += getRealNameSize(folderMeta.dirNameLength); - - System.out.println( - "---------------------------DIRECTORY------------------------\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){ - if (value % 4 == 0) - return value; - return value + 4 - value % 4; - } - - private static class FolderMeta { - int parentDirectoryOffset; - int nextSiblingDirectoryOffset; - int firstSubdirectoryOffset; - int firstFileOffset; - int nextDirectoryOffset; - int dirNameLength; - String dirName; - } -} diff --git a/src/main/java/libKonogonka/Tools/RomFs/IRomFsProvider.java b/src/main/java/libKonogonka/Tools/RomFs/IRomFsProvider.java index 88fa88f..8e5f84a 100644 --- a/src/main/java/libKonogonka/Tools/RomFs/IRomFsProvider.java +++ b/src/main/java/libKonogonka/Tools/RomFs/IRomFsProvider.java @@ -23,9 +23,10 @@ import java.io.File; import java.io.PipedInputStream; public interface IRomFsProvider { + File getFile(); long getLevel6Offset(); Level6Header getHeader(); FileSystemEntry getRootEntry(); PipedInputStream getContent(FileSystemEntry entry) throws Exception; - File getFile(); + void printDebug(); } diff --git a/src/main/java/libKonogonka/Tools/RomFs/Level6Header.java b/src/main/java/libKonogonka/Tools/RomFs/Level6Header.java index 7bba8e5..9f0b662 100644 --- a/src/main/java/libKonogonka/Tools/RomFs/Level6Header.java +++ b/src/main/java/libKonogonka/Tools/RomFs/Level6Header.java @@ -19,25 +19,41 @@ package libKonogonka.Tools.RomFs; -import libKonogonka.LoperConverter; +import libKonogonka.Converter; import libKonogonka.RainbowDump; - -import java.util.Arrays; - +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +/** + * This class stores information contained in Level 6 Header of the RomFS image + * ------------------------------------ + * | Header Length (usually 0x50) | + * | Directory Hash Table Offset | Not used by this library | '<< 32' to get real offset: see implementation + * | Directory Hash Table Length | Not used by this library + * | Directory Metadata Table Offset | + * | Directory Metadata Table Length | + * | File Hash Table Offset | Not used by this library + * | File Hash Table Length | Not used by this library + * | File Metadata Table Offset | + * | File Metadata Table Length | + * | File Data Offset | + * ------------------------------------ + * */ public class Level6Header { - private long headerLength; + private final static Logger log = LogManager.getLogger(Level6Header.class); + + private final long headerLength; private long directoryHashTableOffset; - private long directoryHashTableLength; - private long directoryMetadataTableOffset; - private long directoryMetadataTableLength; - private long fileHashTableOffset; - private long fileHashTableLength; - private long fileMetadataTableOffset; - private long fileMetadataTableLength; - private long fileDataOffset; + private final long directoryHashTableLength; + private final long directoryMetadataTableOffset; + private final long directoryMetadataTableLength; + private final long fileHashTableOffset; + private final long fileHashTableLength; + private final long fileMetadataTableOffset; + private final long fileMetadataTableLength; + private final long fileDataOffset; - private byte[] headerBytes; - private int i; + private final byte[] headerBytes; + private int _cursor; Level6Header(byte[] headerBytes) throws Exception{ this.headerBytes = headerBytes; @@ -54,12 +70,11 @@ public class Level6Header { fileMetadataTableOffset = getNext(); fileMetadataTableLength = getNext(); fileDataOffset = getNext(); - RainbowDump.hexDumpUTF8(Arrays.copyOfRange(headerBytes, 0, 0x50)); } private long getNext(){ - final long result = LoperConverter.getLEint(headerBytes, i); - i += 0x8; + final long result = Converter.getLEint(headerBytes, _cursor); + _cursor += 0x8; return result; } @@ -75,8 +90,8 @@ public class Level6Header { 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" + + log.debug("== Level 6 Header ==\n" + + "Header Length (usually 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" + diff --git a/src/main/java/libKonogonka/Tools/RomFs/RomFsDecryptedConstruct.java b/src/main/java/libKonogonka/Tools/RomFs/RomFsDecryptedConstruct.java new file mode 100644 index 0000000..50853dd --- /dev/null +++ b/src/main/java/libKonogonka/Tools/RomFs/RomFsDecryptedConstruct.java @@ -0,0 +1,121 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka 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. + + libKonogonka 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 libKonogonka. If not, see . + */ +package libKonogonka.Tools.RomFs; + +import libKonogonka.Converter; + +import java.io.BufferedInputStream; +import java.io.File; +import java.nio.file.Files; +/** + * Construct header for RomFs and obtain root fileSystemEntry (meta information) +* */ +class RomFsDecryptedConstruct { + private Level6Header header; + + private FileSystemEntry rootEntry; + private final BufferedInputStream fileBufferedInputStream; + private int headerSize; + private byte[] directoryMetadataTable; + private byte[] fileMetadataTable; + + RomFsDecryptedConstruct(File decryptedFsImageFile, long level6offset) throws Exception{ + if (level6offset < 0) + throw new Exception("Incorrect Level 6 Offset"); + + fileBufferedInputStream = new BufferedInputStream(Files.newInputStream(decryptedFsImageFile.toPath())); + fastForwardBySkippingBytes(level6offset); + + detectHeaderSize(); + constructHeader(); + + fastForwardBySkippingBytes(header.getDirectoryMetadataTableOffset() - headerSize); + + directoryMetadataTableLengthCheck(); + directoryMetadataTableConstruct(); + + fastForwardBySkippingBytes(header.getFileMetadataTableOffset() - header.getFileHashTableOffset()); + + fileMetadataTableLengthCheck(); + fileMetadataTableConstruct(); + + constructRootFilesystemEntry(); + + fileBufferedInputStream.close(); + } + private void detectHeaderSize() throws Exception{ + fileBufferedInputStream.mark(0x10); + byte[] lv6HeaderSizeRaw = new byte[0x8]; + if (fileBufferedInputStream.read(lv6HeaderSizeRaw) != 0x8) + throw new Exception("Failed to read header size"); + headerSize = Converter.getLEint(lv6HeaderSizeRaw, 0); + fileBufferedInputStream.reset(); + } + + private void constructHeader() throws Exception{ + byte[] rawDataChunk = new byte[headerSize]; + + if (fileBufferedInputStream.read(rawDataChunk) != headerSize) + throw new Exception(String.format("Failed to read header (0x%x)", headerSize)); + + this.header = new Level6Header(rawDataChunk); + } + + private void directoryMetadataTableLengthCheck() throws Exception{ + if (header.getDirectoryMetadataTableLength() < 0) + throw new Exception("Not supported operation."); + } + + private void directoryMetadataTableConstruct() throws Exception{ + directoryMetadataTable = new byte[(int) header.getDirectoryMetadataTableLength()]; + if (fileBufferedInputStream.read(directoryMetadataTable) != (int) header.getDirectoryMetadataTableLength()) + throw new Exception("Failed to read "+header.getDirectoryMetadataTableLength()); + } + + private void fileMetadataTableLengthCheck() throws Exception{ + if (header.getFileMetadataTableLength() < 0) + throw new Exception("Not supported operation."); + } + + private void fileMetadataTableConstruct() throws Exception{ + fileMetadataTable = new byte[(int) header.getFileMetadataTableLength()]; + + if (fileBufferedInputStream.read(fileMetadataTable) != (int) header.getFileMetadataTableLength()) + throw new Exception("Failed to read "+header.getFileMetadataTableLength()); + } + + private void constructRootFilesystemEntry() throws Exception{ + rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable); + //rootEntry.printTreeForDebug(); + } + + private void fastForwardBySkippingBytes(long size) throws Exception{ + long mustSkip = size; + long skipped = 0; + while (mustSkip > 0){ + skipped += fileBufferedInputStream.skip(mustSkip); + mustSkip = size - skipped; + } + } + + Level6Header getHeader() { return header; } + FileSystemEntry getRootEntry(){ return rootEntry; } + byte[] getDirectoryMetadataTable() { return directoryMetadataTable; } + byte[] getFileMetadataTable() { return fileMetadataTable;} +} diff --git a/src/main/java/libKonogonka/Tools/RomFs/RomFsDecryptedContentRetrieve.java b/src/main/java/libKonogonka/Tools/RomFs/RomFsDecryptedContentRetrieve.java new file mode 100644 index 0000000..3d2e7b3 --- /dev/null +++ b/src/main/java/libKonogonka/Tools/RomFs/RomFsDecryptedContentRetrieve.java @@ -0,0 +1,93 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka 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. + + libKonogonka 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 libKonogonka. If not, see . + */ +package libKonogonka.Tools.RomFs; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.IOException; +import java.io.PipedOutputStream; +import java.nio.file.Files; + +public class RomFsDecryptedContentRetrieve implements Runnable { + private final static Logger log = LogManager.getLogger(RomFsDecryptedContentRetrieve.class); + + private final File parentFile; + private final PipedOutputStream streamOut; + private final long internalFileRealPosition; + private final long internalFileSize; + + RomFsDecryptedContentRetrieve(File parentFile, + PipedOutputStream streamOut, + long internalFileRealPosition, + long internalFileSize){ + this.parentFile = parentFile; + this.streamOut = streamOut; + this.internalFileRealPosition = internalFileRealPosition; + this.internalFileSize = internalFileSize; + } + + @Override + public void run() { + log.trace("Executing thread"); + try (BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(parentFile.toPath()))){ + fastForwardBySkippingBytes(bis, internalFileRealPosition); + + int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576 + long readFrom = 0; + byte[] readBuffer; + + while (readFrom < internalFileSize) { + if (internalFileSize - readFrom < readPice) + readPice = Math.toIntExact(internalFileSize - readFrom); // it's safe, I guarantee + readBuffer = new byte[readPice]; + if (bis.read(readBuffer) != readPice) { + log.error("getContent(): Unable to read requested size from file."); + return; + } + streamOut.write(readBuffer); + readFrom += readPice; + } + } catch (Exception exception) { + log.error("RomFsDecryptedProvider -> getContent(): Unable to provide stream", exception); + } + finally { + closeStreamOut(); + } + log.trace("Thread died"); + } + private void fastForwardBySkippingBytes(BufferedInputStream bis, long size) throws Exception{ + long mustSkip = size; + long skipped = 0; + while (mustSkip > 0){ + skipped += bis.skip(mustSkip); + mustSkip = size - skipped; + } + } + private void closeStreamOut(){ + try { + streamOut.close(); + } + catch (IOException e){ + log.error("RomFsDecryptedProvider -> getContent(): Unable to close 'StreamOut'"); + } + } +} diff --git a/src/main/java/libKonogonka/Tools/RomFs/RomFsDecryptedProvider.java b/src/main/java/libKonogonka/Tools/RomFs/RomFsDecryptedProvider.java index 3a2fae3..fde28eb 100644 --- a/src/main/java/libKonogonka/Tools/RomFs/RomFsDecryptedProvider.java +++ b/src/main/java/libKonogonka/Tools/RomFs/RomFsDecryptedProvider.java @@ -16,151 +16,61 @@ * You should have received a copy of the GNU General Public License * along with libKonogonka. If not, see . */ - package libKonogonka.Tools.RomFs; +import libKonogonka.Tools.RomFs.view.DirectoryMetaTablePlainView; +import libKonogonka.Tools.RomFs.view.FileMetaTablePlainView; + import java.io.*; public class RomFsDecryptedProvider implements IRomFsProvider{ + private final File file; + private final long level6Offset; + private final Level6Header level6Header; + private final FileSystemEntry rootEntry; + // Used only for debug + private final byte[] directoryMetadataTable; + private final byte[] fileMetadataTable; - private long level6Offset; - - private File file; - private Level6Header header; - - private FileSystemEntry rootEntry; - - public RomFsDecryptedProvider(File decryptedFsImageFile, long level6Offset) throws Exception{ - if (level6Offset < 0) - throw new Exception("Incorrect Level 6 Offset"); - + public RomFsDecryptedProvider(File decryptedFsImageFile, long level6offset) throws Exception{ + RomFsDecryptedConstruct construct = new RomFsDecryptedConstruct(decryptedFsImageFile, level6offset); this.file = decryptedFsImageFile; + this.level6Offset = level6offset; + this.level6Header = construct.getHeader(); + this.rootEntry = construct.getRootEntry(); - BufferedInputStream bis = new BufferedInputStream(new FileInputStream(decryptedFsImageFile)); - - this.level6Offset = level6Offset; - - skipBytes(bis, level6Offset); - - byte[] rawDataChunk = new byte[0x50]; - - if (bis.read(rawDataChunk) != 0x50) - throw new Exception("Failed to read header (0x50)"); - - this.header = new Level6Header(rawDataChunk); - /* - // Print Dir Hash table as is: - long seekTo = header.getDirectoryHashTableOffset() - 0x50; - rawDataChunk = new byte[(int) header.getDirectoryHashTableLength()]; - skipTo(bis, seekTo); - if (bis.read(rawDataChunk) != (int) header.getDirectoryHashTableLength()) - throw new Exception("Failed to read Dir hash table"); - RainbowDump.hexDumpUTF8(rawDataChunk); - // Print Files Hash table as is: - seekTo = header.getFileHashTableOffset() - header.getDirectoryMetadataTableOffset(); - rawDataChunk = new byte[(int) header.getFileHashTableLength()]; - skipTo(bis, seekTo); - if (bis.read(rawDataChunk) != (int) header.getFileHashTableLength()) - throw new Exception("Failed to read Files hash table"); - RainbowDump.hexDumpUTF8(rawDataChunk); - */ - // Read directories metadata - long locationInFile = header.getDirectoryMetadataTableOffset() - 0x50; - - skipBytes(bis, locationInFile); - - if (header.getDirectoryMetadataTableLength() < 0) - throw new Exception("Not supported operation."); - - byte[] directoryMetadataTable = new byte[(int) header.getDirectoryMetadataTableLength()]; - - if (bis.read(directoryMetadataTable) != (int) header.getDirectoryMetadataTableLength()) - throw new Exception("Failed to read "+header.getDirectoryMetadataTableLength()); - // Read files metadata - locationInFile = header.getFileMetadataTableOffset() - header.getFileHashTableOffset(); // TODO: replace to 'CurrentPosition'? - - skipBytes(bis, locationInFile); - - if (header.getFileMetadataTableLength() < 0) - throw new Exception("Not supported operation."); - - byte[] fileMetadataTable = new byte[(int) header.getFileMetadataTableLength()]; - - if (bis.read(fileMetadataTable) != (int) header.getFileMetadataTableLength()) - throw new Exception("Failed to read "+header.getFileMetadataTableLength()); - - rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable); - //printDebug(directoryMetadataTable, fileMetadataTable); - bis.close(); - } - private void skipBytes(BufferedInputStream bis, long size) throws Exception{ - long mustSkip = size; - long skipped = 0; - while (mustSkip > 0){ - skipped += bis.skip(mustSkip); - mustSkip = size - skipped; - } + this.directoryMetadataTable = construct.getDirectoryMetadataTable(); + this.fileMetadataTable = construct.getFileMetadataTable(); } + + @Override + public File getFile() { return file; } @Override public long getLevel6Offset() { return level6Offset; } @Override - public Level6Header getHeader() { return header; } + public Level6Header getHeader() { return level6Header; } @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."); + throw new Exception("Request of the binary stream for the folder entry is not supported (and doesn't make sense)."); PipedOutputStream streamOut = new PipedOutputStream(); - Thread workerThread; - PipedInputStream streamIn = new PipedInputStream(streamOut); + long internalFileRealPosition = level6Offset + level6Header.getFileDataOffset() + entry.getFileOffset(); + long internalFileSize = entry.getFileSize(); - 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)); - skipBytes(bis, 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(); + Thread contentRetrievingThread = new Thread( + new RomFsDecryptedContentRetrieve(file, streamOut, internalFileRealPosition, internalFileSize)); + contentRetrievingThread.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); + public void printDebug(){ + level6Header.printDebugInfo(); + new DirectoryMetaTablePlainView(level6Header.getDirectoryMetadataTableLength(), directoryMetadataTable); + new FileMetaTablePlainView(level6Header.getFileMetadataTableLength(), fileMetadataTable); rootEntry.printTreeForDebug(); } } diff --git a/src/main/java/libKonogonka/Tools/RomFs/RomFsEncryptedConstruct.java b/src/main/java/libKonogonka/Tools/RomFs/RomFsEncryptedConstruct.java new file mode 100644 index 0000000..d90dea4 --- /dev/null +++ b/src/main/java/libKonogonka/Tools/RomFs/RomFsEncryptedConstruct.java @@ -0,0 +1,180 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka 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. + + libKonogonka 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 libKonogonka. If not, see . + */ +package libKonogonka.Tools.RomFs; + +import libKonogonka.ctraes.AesCtrDecryptSimple; + +import java.io.File; +import java.io.RandomAccessFile; +import java.util.Arrays; + +public class RomFsEncryptedConstruct { + + private final long absoluteOffsetPosition; + private final long level6Offset; + + private final RandomAccessFile raf; + private final AesCtrDecryptSimple decryptor; + private Level6Header header; + private byte[] directoryMetadataTable; + private byte[] fileMetadataTable; + private FileSystemEntry rootEntry; + + RomFsEncryptedConstruct(File encryptedFsImageFile, + long romFsOffset, + long level6Offset, + AesCtrDecryptSimple decryptor, + long mediaStartOffset + ) throws Exception { + + if (level6Offset < 0) + throw new Exception("Incorrect Level 6 Offset"); + + this.raf = new RandomAccessFile(encryptedFsImageFile, "r"); + this.level6Offset = level6Offset; + this.absoluteOffsetPosition = romFsOffset + (mediaStartOffset * 0x200); + this.decryptor = decryptor; + raf.seek(absoluteOffsetPosition + level6Offset); + + //Go to Level 6 header position + decryptor.skipNext(level6Offset / 0x200); + + constructHeader(); + + directoryMetadataTableLengthCheck(); + directoryMetadataTableConstruct(); + + fileMetadataTableLengthCheck(); + fileMetadataTableConstruct(); + + constructRootFilesystemEntry(); + + raf.close(); + } + + private void constructHeader() throws Exception{ + // Decrypt data + byte[] encryptedBlock = new byte[0x200]; + byte[] decryptedBlock; + if (raf.read(encryptedBlock) == 0x200) + decryptedBlock = decryptor.decryptNext(encryptedBlock); + else + throw new Exception("Failed to read header header (0x200 - block)"); + this.header = new Level6Header(decryptedBlock); + } + + private void directoryMetadataTableLengthCheck() throws Exception{ + if (header.getDirectoryMetadataTableLength() < 0) + throw new Exception("Not supported: DirectoryMetadataTableLength < 0"); + } + private void directoryMetadataTableConstruct() throws Exception{ + directoryMetadataTable = readMetaTable(header.getDirectoryMetadataTableOffset(), + header.getDirectoryMetadataTableLength()); + } + + private void fileMetadataTableLengthCheck() throws Exception{ + if (header.getFileMetadataTableLength() < 0) + throw new Exception("Not supported: FileMetadataTableLength < 0"); + } + private void fileMetadataTableConstruct() throws Exception{ + fileMetadataTable = readMetaTable(header.getFileMetadataTableOffset(), + header.getFileMetadataTableLength()); + } + + private void constructRootFilesystemEntry() throws Exception{ + rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable); + } + + private byte[] readMetaTable(long metaOffset, + long metaSize) throws Exception{ + byte[] encryptedBlock; + byte[] decryptedBlock; + byte[] metadataTable = new byte[(int) metaSize]; + //0 + decryptor.reset(); + + long startBlock = metaOffset / 0x200; + + decryptor.skipNext(level6Offset / 0x200 + startBlock); + + raf.seek(absoluteOffsetPosition + level6Offset + startBlock * 0x200); + + //1 + long ignoreBytes = metaOffset - startBlock * 0x200; + long currentPosition = 0; + + if (ignoreBytes > 0) { + encryptedBlock = new byte[0x200]; + if (raf.read(encryptedBlock) == 0x200) { + decryptedBlock = decryptor.decryptNext(encryptedBlock); + // If we have extra-small file that is less than a block and even more + if ((0x200 - ignoreBytes) > metaSize){ + metadataTable = Arrays.copyOfRange(decryptedBlock, (int)ignoreBytes, 0x200); + return metadataTable; + } + else { + System.arraycopy(decryptedBlock, (int) ignoreBytes, metadataTable, 0, 0x200 - (int) ignoreBytes); + currentPosition = 0x200 - ignoreBytes; + } + } + else { + throw new Exception("Unable to get 512 bytes from 1st bock for Directory/File 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) { + decryptedBlock = decryptor.decryptNext(encryptedBlock); + System.arraycopy(decryptedBlock, 0, metadataTable, (int) currentPosition, 0x200); + } + else + throw new Exception("Unable to get 512 bytes from block for Directory/File 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) { + decryptedBlock = decryptor.decryptNext(encryptedBlock); + System.arraycopy(decryptedBlock, 0, metadataTable, (int) currentPosition, Math.abs(extraData)); + } + else + throw new Exception("Unable to get 512 bytes from block for Directory/File Metadata Table"); + } + + return metadataTable; + } + + Level6Header getHeader() { return header; } + FileSystemEntry getRootEntry(){ return rootEntry; } + byte[] getDirectoryMetadataTable() { return directoryMetadataTable; } + byte[] getFileMetadataTable() { return fileMetadataTable;} +} diff --git a/src/main/java/libKonogonka/Tools/RomFs/RomFsEncryptedContentRetrieve.java b/src/main/java/libKonogonka/Tools/RomFs/RomFsEncryptedContentRetrieve.java new file mode 100644 index 0000000..10b8bd1 --- /dev/null +++ b/src/main/java/libKonogonka/Tools/RomFs/RomFsEncryptedContentRetrieve.java @@ -0,0 +1,138 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka 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. + + libKonogonka 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 libKonogonka. If not, see . + */ +package libKonogonka.Tools.RomFs; + +import libKonogonka.ctraes.AesCtrDecryptSimple; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.PipedOutputStream; +import java.io.RandomAccessFile; + +public class RomFsEncryptedContentRetrieve implements Runnable{ + private final static Logger log = LogManager.getLogger(RomFsEncryptedContentRetrieve.class); + + private final File parentFile; + private final PipedOutputStream streamOut; + private final long absoluteOffsetPosition; + private final AesCtrDecryptSimple decryptor; + private final long internalFileOffset; + private final long internalFileSize; + private final long level6Offset; + private final long headersFileDataOffset; + + RomFsEncryptedContentRetrieve(File parentFile, + PipedOutputStream streamOut, + long absoluteOffsetPosition, + AesCtrDecryptSimple decryptor, + long internalFileOffset, + long internalFileSize, + long level6Offset, + long headersFileDataOffset + ){ + this.parentFile = parentFile; + this.absoluteOffsetPosition = absoluteOffsetPosition; + this.streamOut = streamOut; + this.decryptor = decryptor; + this.internalFileOffset = internalFileOffset; + this.internalFileSize = internalFileSize; + this.level6Offset = level6Offset; + this.headersFileDataOffset = headersFileDataOffset; + } + + @Override + public void run() { + log.trace("Executing thread"); + try { + byte[] encryptedBlock; + byte[] decryptedBlock; + + RandomAccessFile raf = new RandomAccessFile(parentFile, "r"); + + //0 + long startBlock = (internalFileOffset + headersFileDataOffset) / 0x200; + + decryptor.skipNext(level6Offset / 0x200 + startBlock); + + // long absoluteOffsetPosition = romFsOffsetPosition + (mediaStartOffset * 0x200); // calculated in constructor + + raf.seek(absoluteOffsetPosition + level6Offset + startBlock * 0x200); + + //1 + long ignoreBytes = (internalFileOffset + headersFileDataOffset) - startBlock * 0x200; + + if (ignoreBytes > 0) { + encryptedBlock = new byte[0x200]; + if (raf.read(encryptedBlock) == 0x200) { + decryptedBlock = decryptor.decryptNext(encryptedBlock); + // If we have extra-small file that is less than a block and even more + if ((0x200 - ignoreBytes) > internalFileSize){ + streamOut.write(decryptedBlock, (int)ignoreBytes, (int) internalFileSize); // safe cast + raf.close(); + streamOut.close(); + return; + } + else { + streamOut.write(decryptedBlock, (int) ignoreBytes, 0x200 - (int) ignoreBytes); + } + } + else { + throw new Exception("Unable to get 512 bytes from 1st bock"); + } + startBlock++; + } + long endBlock = (internalFileSize + ignoreBytes) / 0x200 + startBlock; // <- pointing to place where any data related to this media-block ends + + //2 + int extraData = (int) ((endBlock - startBlock)*0x200 - (internalFileSize + ignoreBytes)); + + if (extraData < 0) + endBlock--; + //3 + while ( startBlock < endBlock ) { + encryptedBlock = new byte[0x200]; + if (raf.read(encryptedBlock) == 0x200) { + decryptedBlock = decryptor.decryptNext(encryptedBlock); + streamOut.write(decryptedBlock); + } + else + throw new Exception("Unable to get 512 bytes from block"); + + startBlock++; + } + + //4 + if (extraData != 0){ // In case we didn't get what we want + encryptedBlock = new byte[0x200]; + if (raf.read(encryptedBlock) == 0x200) { + decryptedBlock = decryptor.decryptNext(encryptedBlock); + streamOut.write(decryptedBlock, 0, Math.abs(extraData)); + } + else + throw new Exception("Unable to get 512 bytes from block"); + } + raf.close(); + streamOut.close(); + } catch (Exception exception) { + log.error("Unable to provide stream", exception); + } + log.trace("Thread died"); + } +} diff --git a/src/main/java/libKonogonka/Tools/RomFs/RomFsEncryptedProvider.java b/src/main/java/libKonogonka/Tools/RomFs/RomFsEncryptedProvider.java index 6c94ffe..8c18d35 100644 --- a/src/main/java/libKonogonka/Tools/RomFs/RomFsEncryptedProvider.java +++ b/src/main/java/libKonogonka/Tools/RomFs/RomFsEncryptedProvider.java @@ -19,274 +19,107 @@ package libKonogonka.Tools.RomFs; +import libKonogonka.Tools.RomFs.view.DirectoryMetaTablePlainView; +import libKonogonka.Tools.RomFs.view.FileMetaTablePlainView; import libKonogonka.ctraes.AesCtrDecryptSimple; import java.io.File; import java.io.PipedInputStream; import java.io.PipedOutputStream; -import java.io.RandomAccessFile; -import java.util.Arrays; public class RomFsEncryptedProvider implements IRomFsProvider{ + private final File file; + private final long level6Offset; + private final Level6Header level6Header; + private final FileSystemEntry rootEntry; - private long level6Offset; + private final byte[] key; // Used @ createDecryptor only + private final byte[] sectionCTR; // Used @ createDecryptor only + private final long mediaStartOffset; // Used @ createDecryptor only + private final long absoluteOffsetPosition; - private File file; - private Level6Header header; + //private long mediaEndOffset; // We know this, but actually never use - private FileSystemEntry rootEntry; + // Used only for debug + private final byte[] directoryMetadataTable; + private final byte[] fileMetadataTable; - //-------------------------------- + public RomFsEncryptedProvider(long level6Offset, + File encryptedFsImageFile, + long romFsOffsetPosition, + byte[] key, + byte[] sectionCTR, + long mediaStartOffset + ) throws Exception{ + this(level6Offset, encryptedFsImageFile, romFsOffsetPosition, key, sectionCTR, mediaStartOffset, -1); + } - private long romFSoffsetPosition; - private byte[] key; - private byte[] sectionCTR; - private long mediaStartOffset; - private long mediaEndOffset; - - public RomFsEncryptedProvider(long romFSoffsetPosition, - long level6Offset, - File fileWithEncPFS0, + public RomFsEncryptedProvider(long level6Offset, + File encryptedFsImageFile, + long romFsOffsetPosition, 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); + RomFsEncryptedConstruct construct = new RomFsEncryptedConstruct(encryptedFsImageFile, + romFsOffsetPosition, + level6Offset, + createDecryptor(), + mediaStartOffset); + this.file = encryptedFsImageFile; + this.level6Offset = level6Offset; + this.level6Header = construct.getHeader(); + this.rootEntry = construct.getRootEntry(); - AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200); - //Go to Level 6 header - decryptor.skipNext(level6Offset / 0x200); + this.absoluteOffsetPosition = romFsOffsetPosition + (mediaStartOffset * 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(); + this.directoryMetadataTable = construct.getDirectoryMetadataTable(); + this.fileMetadataTable = construct.getFileMetadataTable(); } - - 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; + private AesCtrDecryptSimple createDecryptor() throws Exception{ + return new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200); } - + @Override + public File getFile() { return file; } @Override public long getLevel6Offset() { return level6Offset; } @Override - public Level6Header getHeader() { return header; } + public Level6Header getHeader() {return level6Header;} @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."); + throw new Exception("Request of the binary stream for the folder entry is not supported (and 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 internalFileOffset = entry.getFileOffset(); + long internalFileSize = entry.getFileSize(); - byte[] encryptedBlock; - byte[] dectyptedBlock; - - RandomAccessFile raf = new RandomAccessFile(file, "r"); - - //0 - AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200); - - long startBlock = (entry.getFileOffset() + header.getFileDataOffset()) / 0x200; - - decryptor.skipNext(level6Offset / 0x200 + startBlock); - - long abosluteOffsetPosition = romFSoffsetPosition + (mediaStartOffset * 0x200); - - raf.seek(abosluteOffsetPosition + level6Offset + startBlock * 0x200); - - //1 - long ignoreBytes = (entry.getFileOffset() + header.getFileDataOffset()) - startBlock * 0x200; - - 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) > entry.getFileSize()){ - streamOut.write(dectyptedBlock, (int)ignoreBytes, (int) entry.getFileSize()); // safe cast - raf.close(); - streamOut.close(); - return; - } - else { - streamOut.write(dectyptedBlock, (int) ignoreBytes, 0x200 - (int) ignoreBytes); - } - } - else { - throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from 1st bock for Directory Metadata Table"); - } - startBlock++; - } - long endBlock = (entry.getFileSize() + ignoreBytes) / 0x200 + startBlock; // <- pointing to place where any data related to this media-block ends - - //2 - int extraData = (int) ((endBlock - startBlock)*0x200 - (entry.getFileSize() + ignoreBytes)); - - if (extraData < 0) - endBlock--; - //3 - while ( startBlock < endBlock ) { - encryptedBlock = new byte[0x200]; - if (raf.read(encryptedBlock) == 0x200) { - dectyptedBlock = decryptor.dectyptNext(encryptedBlock); - streamOut.write(dectyptedBlock); - } - else - throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory Metadata Table"); - - startBlock++; - } - - //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); - streamOut.write(dectyptedBlock, 0, Math.abs(extraData)); - } - else - throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory Metadata Table"); - } - raf.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(); + Thread contentRetrievingThread = new Thread(new RomFsEncryptedContentRetrieve( + file, + streamOut, + absoluteOffsetPosition, + createDecryptor(), + internalFileOffset, + internalFileSize, + level6Offset, + level6Header.getFileDataOffset() + )); + contentRetrievingThread.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); + public void printDebug(){ + level6Header.printDebugInfo(); + new DirectoryMetaTablePlainView(level6Header.getDirectoryMetadataTableLength(), directoryMetadataTable); + new FileMetaTablePlainView(level6Header.getFileMetadataTableLength(), fileMetadataTable); rootEntry.printTreeForDebug(); } } diff --git a/src/main/java/libKonogonka/Tools/RomFs/view/DirectoryMetaTablePlainView.java b/src/main/java/libKonogonka/Tools/RomFs/view/DirectoryMetaTablePlainView.java new file mode 100644 index 0000000..b396457 --- /dev/null +++ b/src/main/java/libKonogonka/Tools/RomFs/view/DirectoryMetaTablePlainView.java @@ -0,0 +1,82 @@ +/* + Copyright 2019-2022 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka 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. + + libKonogonka 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 libKonogonka. If not, see . +*/ +package libKonogonka.Tools.RomFs.view; + +import libKonogonka.Converter; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import static libKonogonka.RainbowDump.formatDecHexString; + +public class DirectoryMetaTablePlainView { + + private final static Logger log = LogManager.getLogger(DirectoryMetaTablePlainView.class); + + // directoryMetadataTableLength must be declared since directoryMetadataTable could be bigger than declared size for encrypted blocks + public DirectoryMetaTablePlainView(long directoryMetadataTableLength, byte[] directoryMetadataTable){ + int i = 0; + while (i < directoryMetadataTableLength){ + FolderMeta folderMeta = new FolderMeta(); + folderMeta.parentDirectoryOffset = Converter.getLEint(directoryMetadataTable, i); + i += 4; + folderMeta.nextSiblingDirectoryOffset = Converter.getLEint(directoryMetadataTable, i); + i += 4; + folderMeta.firstSubdirectoryOffset = Converter.getLEint(directoryMetadataTable, i); + i += 4; + folderMeta.firstFileOffset = Converter.getLEint(directoryMetadataTable, i); + i += 4; + folderMeta.nextDirectoryOffset = Converter.getLEint(directoryMetadataTable, i); + i += 4; + folderMeta.dirNameLength = Converter.getLEint(directoryMetadataTable, i); + i += 4; + folderMeta.dirName = new String(Arrays.copyOfRange(directoryMetadataTable, i, i + folderMeta.dirNameLength), StandardCharsets.UTF_8); + i += getRealNameSize(folderMeta.dirNameLength); + + log.debug( + "- DIRECTORY -\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" + ); + } + } + + private int getRealNameSize(int value){ + if (value % 4 == 0) + return value; + return value + 4 - value % 4; + } + + private static class FolderMeta { + int parentDirectoryOffset; + int nextSiblingDirectoryOffset; + int firstSubdirectoryOffset; + int firstFileOffset; + int nextDirectoryOffset; + int dirNameLength; + String dirName; + } +} diff --git a/src/main/java/libKonogonka/Tools/RomFs/view/FileMetaTablePlainView.java b/src/main/java/libKonogonka/Tools/RomFs/view/FileMetaTablePlainView.java new file mode 100644 index 0000000..919c260 --- /dev/null +++ b/src/main/java/libKonogonka/Tools/RomFs/view/FileMetaTablePlainView.java @@ -0,0 +1,81 @@ +/* + Copyright 2019-2022 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka 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. + + libKonogonka 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 libKonogonka. If not, see . +*/ +package libKonogonka.Tools.RomFs.view; + +import libKonogonka.Converter; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import static libKonogonka.RainbowDump.formatDecHexString; + +public class FileMetaTablePlainView { + private final static Logger log = LogManager.getLogger(FileMetaTablePlainView.class); + + public FileMetaTablePlainView(long fileMetadataTableLength, byte[] fileMetadataTable) { + int i = 0; + while (i < fileMetadataTableLength) { + FileMeta fileMeta = new FileMeta(); + fileMeta.containingDirectoryOffset = Converter.getLEint(fileMetadataTable, i); + i += 4; + fileMeta.nextSiblingFileOffset = Converter.getLEint(fileMetadataTable, i); + i += 4; + fileMeta.fileDataOffset = Converter.getLElong(fileMetadataTable, i); + i += 8; + fileMeta.fileDataLength = Converter.getLElong(fileMetadataTable, i); + i += 8; + fileMeta.nextFileOffset = Converter.getLEint(fileMetadataTable, i); + i += 4; + fileMeta.fileNameLength = Converter.getLEint(fileMetadataTable, i); + i += 4; + fileMeta.fileName = new String(Arrays.copyOfRange(fileMetadataTable, i, i + fileMeta.fileNameLength), StandardCharsets.UTF_8); + + i += getRealNameSize(fileMeta.fileNameLength); + + log.debug( + "- FILE -\n" + + "Offset of Containing Directory " + formatDecHexString(fileMeta.containingDirectoryOffset) + "\n" + + "Offset of next Sibling File " + formatDecHexString(fileMeta.nextSiblingFileOffset) + "\n" + + "Offset of File's Data " + formatDecHexString(fileMeta.fileDataOffset) + "\n" + + "Length of File's Data " + formatDecHexString(fileMeta.fileDataLength) + "\n" + + "Offset of next File in the same Hash Table bucket " + formatDecHexString(fileMeta.nextFileOffset) + "\n" + + "Name Length " + formatDecHexString(fileMeta.fileNameLength) + "\n" + + "Name Length (rounded up to multiple of 4) " + fileMeta.fileName + "\n" + ); + } + } + + private int getRealNameSize(int value){ + if (value % 4 == 0) + return value; + return value + 4 - value % 4; + } + + private static class FileMeta{ + int containingDirectoryOffset; + int nextSiblingFileOffset; + long fileDataOffset; + long fileDataLength; + int nextFileOffset; + int fileNameLength; + String fileName; + } +} diff --git a/src/main/java/libKonogonka/Tools/RomFs/view/FileSystemTreeViewMaker.java b/src/main/java/libKonogonka/Tools/RomFs/view/FileSystemTreeViewMaker.java new file mode 100644 index 0000000..104837e --- /dev/null +++ b/src/main/java/libKonogonka/Tools/RomFs/view/FileSystemTreeViewMaker.java @@ -0,0 +1,65 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka 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. + + libKonogonka 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 libKonogonka. If not, see . + */ +package libKonogonka.Tools.RomFs.view; + +import libKonogonka.Tools.RomFs.FileSystemEntry; +import java.util.List; + +/** + * Used in pair with FileSystemEntry + * */ +public class FileSystemTreeViewMaker { + private StringBuilder tree; + private int spacerForSizes; + + private FileSystemTreeViewMaker(){} + + private void init(List content){ + tree = new StringBuilder("/\n"); + for (FileSystemEntry entry: content) + printEntry(2, entry); + } + + private void printEntry(int count, FileSystemEntry entry) { + int i; + for (i = 0; i < count; i++) + tree.append(" "); + + if (entry.isDirectory()) { + tree.append("|-"); + tree.append(entry.getName()); + tree.append("\n"); + for (FileSystemEntry e : entry.getContent()) + printEntry(count + 2, e); + return; + } + + tree.append("|-"); + tree.append(entry.getName()); + tree.append(String.format("%"+(spacerForSizes-entry.getName().length()-i)+"s0x%-10x 0x%-10x", "", entry.getFileOffset(), entry.getFileSize())); + tree.append("\n"); + } + + public static String make(List content, int spacerForSizes){ + FileSystemTreeViewMaker maker = new FileSystemTreeViewMaker(); + maker.spacerForSizes = spacerForSizes; + maker.init(content); + return maker.tree.toString(); + } +} diff --git a/src/main/java/libKonogonka/Tools/TIK/TIKProvider.java b/src/main/java/libKonogonka/Tools/TIK/TIKProvider.java index 48e844f..0824563 100644 --- a/src/main/java/libKonogonka/Tools/TIK/TIKProvider.java +++ b/src/main/java/libKonogonka/Tools/TIK/TIKProvider.java @@ -20,11 +20,11 @@ package libKonogonka.Tools.TIK; import java.io.BufferedInputStream; import java.io.File; -import java.io.FileInputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.Arrays; -import static libKonogonka.LoperConverter.*; +import static libKonogonka.Converter.*; /* DON'T TRUST WIKI. Ticket size always (?) equal 0x02c0 (704 bytes) @@ -67,41 +67,41 @@ Next: * */ public class TIKProvider { // Signature-related - private byte[] sigType; - private byte[] signature; + private final byte[] sigType; + private final byte[] signature; // Ticket - private String Issuer; - private byte[] TitleKeyBlockStartingBytes; // Actually 32 bytes. - private byte[] TitleKeyBlockEndingBytes; // Anything else - private byte Unknown1; - private byte TitleKeyType; - private byte[] Unknown2; - private byte MasterKeyRevision; - private byte[] Unknown3; - private byte[] TicketId; - private byte[] DeviceId; - private byte[] RightsId; - private byte[] RightsIdEndingBytes; - private byte[] AccountId; - private byte[] Unknown4; + private final String Issuer; + private final byte[] TitleKeyBlockStartingBytes; // Actually 32 bytes. + private final byte[] TitleKeyBlockEndingBytes; // Everything left + private final byte Unknown1; + private final byte TitleKeyType; + private final byte[] Unknown2; + private final byte MasterKeyRevision; + private final byte[] Unknown3; + private final byte[] TicketId; + private final byte[] DeviceId; + private final byte[] RightsId; + //private byte[] RightsIdEndingBytes; + private final byte[] AccountId; + private final byte[] Unknown4; public TIKProvider(File file) throws Exception{ this(file, 0); } public TIKProvider(File file, long offset) throws Exception { if (file.length() - offset < 0x02c0) - throw new Exception("TIKProvider: File is too small."); + throw new Exception("File is too small."); - BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); + BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(file.toPath())); if (bis.skip(offset) != offset) { bis.close(); - throw new Exception("TIKProvider: Unable to skip requested range - " + offset); + throw new Exception("Unable to skip requested range - " + offset); } sigType = new byte[0x4]; if (bis.read(sigType) != 4) { bis.close(); - throw new Exception("TIKProvider: Unable to read requested range - " + offset); + throw new Exception("Unable to read requested range - " + offset); } byte[] readChunk; @@ -112,7 +112,7 @@ public class TIKProvider { readChunk = new byte[0x23c]; if (bis.read(readChunk) != 0x23c) { bis.close(); - throw new Exception("TIKProvider: Unable to read requested range - 0x23c"); + throw new Exception("Unable to read requested range - 0x23c"); } signature = Arrays.copyOfRange(readChunk, 0, 0x200); break; @@ -121,7 +121,7 @@ public class TIKProvider { readChunk = new byte[0x13c]; if (bis.read(readChunk) != 0x13c) { bis.close(); - throw new Exception("TIKProvider: Unable to read requested range - 0x13c"); + throw new Exception("Unable to read requested range - 0x13c"); } signature = Arrays.copyOfRange(readChunk, 0, 0x100); break; @@ -130,20 +130,20 @@ public class TIKProvider { readChunk = new byte[0x7c]; if (bis.read(readChunk) != 0x7c) { bis.close(); - throw new Exception("TIKProvider: Unable to read requested range - 0x7c"); + throw new Exception("Unable to read requested range - 0x7c"); } signature = Arrays.copyOfRange(readChunk, 0, 0x3c); break; default: bis.close(); - throw new Exception("TIKProvider: Unknown ticket (Signature) type. Aborting."); + throw new Exception("Unknown ticket (Signature) type. Aborting."); } // Let's read ticket body itself readChunk = new byte[0x180]; if (bis.read(readChunk) != 0x180) { bis.close(); - throw new Exception("TIKProvider: Unable to read requested range - Ticket data"); + throw new Exception("Unable to read requested range - Ticket data"); } bis.close(); diff --git a/src/main/java/libKonogonka/Tools/XCI/HFS0Provider.java b/src/main/java/libKonogonka/Tools/XCI/HFS0Provider.java index 863a24a..26e6101 100644 --- a/src/main/java/libKonogonka/Tools/XCI/HFS0Provider.java +++ b/src/main/java/libKonogonka/Tools/XCI/HFS0Provider.java @@ -24,7 +24,7 @@ import java.io.*; import java.nio.charset.StandardCharsets; import java.util.Arrays; -import static libKonogonka.LoperConverter.*; +import static libKonogonka.Converter.*; /** * HFS0 diff --git a/src/main/java/libKonogonka/Tools/XCI/XCIGamecardHeader.java b/src/main/java/libKonogonka/Tools/XCI/XCIGamecardHeader.java index 2bce602..d1f85a9 100644 --- a/src/main/java/libKonogonka/Tools/XCI/XCIGamecardHeader.java +++ b/src/main/java/libKonogonka/Tools/XCI/XCIGamecardHeader.java @@ -22,8 +22,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import static libKonogonka.LoperConverter.getLEint; -import static libKonogonka.LoperConverter.getLElong; +import static libKonogonka.Converter.getLEint; +import static libKonogonka.Converter.getLElong; /** * Header information * */ diff --git a/src/main/java/libKonogonka/Tools/XCI/XCIGamecardInfo.java b/src/main/java/libKonogonka/Tools/XCI/XCIGamecardInfo.java index 349ad26..569971d 100644 --- a/src/main/java/libKonogonka/Tools/XCI/XCIGamecardInfo.java +++ b/src/main/java/libKonogonka/Tools/XCI/XCIGamecardInfo.java @@ -23,8 +23,8 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.util.Arrays; -import static libKonogonka.LoperConverter.getLEint; -import static libKonogonka.LoperConverter.getLElong; +import static libKonogonka.Converter.getLEint; +import static libKonogonka.Converter.getLElong; /** * Gamecard Info diff --git a/src/main/java/libKonogonka/ctraes/AesCtrDecryptSimple.java b/src/main/java/libKonogonka/ctraes/AesCtrDecryptSimple.java index 4fef1bd..a9de59f 100644 --- a/src/main/java/libKonogonka/ctraes/AesCtrDecryptSimple.java +++ b/src/main/java/libKonogonka/ctraes/AesCtrDecryptSimple.java @@ -18,7 +18,9 @@ */ package libKonogonka.ctraes; -import libKonogonka.LoperConverter; +import libKonogonka.Converter; +import libKonogonka.RainbowDump; + /** * Simplify decryption of the CTR */ @@ -28,13 +30,15 @@ public class AesCtrDecryptSimple { private byte[] IVarray; private AesCtr aesCtr; + private final byte[] initialKey; + private final byte[] initialSectionCTR; + private final long initialRealMediaOffset; + public AesCtrDecryptSimple(byte[] key, byte[] sectionCTR, long realMediaOffset) throws Exception{ - this.realMediaOffset = realMediaOffset; - aesCtr = new AesCtr(key); - // IV for CTR == 16 bytes - IVarray = new byte[0x10]; - // Populate first 8 bytes taken from Header's section Block CTR - System.arraycopy(LoperConverter.flip(sectionCTR), 0x0, IVarray, 0x0, 0x8); + this.initialKey = key; + this.initialSectionCTR = sectionCTR; + this.initialRealMediaOffset = realMediaOffset; + reset(); } public void skipNext(){ @@ -42,22 +46,30 @@ public class AesCtrDecryptSimple { } public void skipNext(long blocksNum){ - if (blocksNum > 0) - realMediaOffset += blocksNum * 0x200; + realMediaOffset += blocksNum * 0x200; } - public byte[] dectyptNext(byte[] enctyptedBlock) throws Exception{ - updateIV(realMediaOffset); - byte[] decryptedBlock = aesCtr.decrypt(enctyptedBlock, IVarray); + public byte[] decryptNext(byte[] encryptedBlock) throws Exception{ + updateIV(); + byte[] decryptedBlock = aesCtr.decrypt(encryptedBlock, IVarray); realMediaOffset += 0x200; return decryptedBlock; } // Populate last 8 bytes calculated. Thanks hactool project! - private void updateIV(long offset){ - offset >>= 4; + private void updateIV(){ + long offset = realMediaOffset >> 4; for (int i = 0; i < 0x8; i++){ IVarray[0x10-i-1] = (byte)(offset & 0xff); // Note: issues could be here offset >>= 8; } } + + public void reset() throws Exception{ + realMediaOffset = initialRealMediaOffset; + aesCtr = new AesCtr(initialKey); + // IV for CTR == 16 bytes + IVarray = new byte[0x10]; + // Populate first 4 bytes taken from Header's section Block CTR (aka SecureValue) + System.arraycopy(Converter.flip(initialSectionCTR), 0x0, IVarray, 0x0, 0x4); + } } diff --git a/src/main/resources/log4j2.properties b/src/main/resources/log4j2.properties new file mode 100644 index 0000000..b7fcbfc --- /dev/null +++ b/src/main/resources/log4j2.properties @@ -0,0 +1,84 @@ +## LEVELS ARE: +# * ALL +# * TRACE +# * DEBUG +# * INFO +# * WARN +# * ERROR +# * FATAL +# * OFF +############# +# Extra logging related to initialization of Log4j +# Set to debug or trace if log4j initialization is failing +status = error +# Name of the configuration +name = DebugConfigDevelopmentOnlyVerbose + +# Configure root logger level +rootLogger.level = TRACE +# Root logger referring to console appender +rootLogger.appenderRef.stdout.ref = consoleLogger + +# Console appender configuration +appender.console.type = Console +appender.console.name = consoleLogger +appender.console.layout.type = PatternLayout +appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n +################################################## +# # Enable log to files +# rootLogger.appenderRef.rolling.ref = fileLogger +# # Log files location +# property.basePath = /tmp +# property.filename = libKonogonka +# # RollingFileAppender name, pattern, path and rollover policy +# appender.rolling.type = RollingFile +# appender.rolling.name = fileLogger +# appender.rolling.fileName= ${basePath}/${filename}.log +# appender.rolling.filePattern= ${basePath}/${filename}_%d{yyyyMMdd}.log.gz +# appender.rolling.layout.type = PatternLayout +# appender.rolling.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} %level [%t] [%l] - %msg%n +# appender.rolling.policies.type = Policies +# +# # RollingFileAppender rotation policy +# appender.rolling.policies.size.type = SizeBasedTriggeringPolicy +# appender.rolling.policies.size.size = 10MB +# # Setting for time-based policies +# #appender.rolling.policies.time.type = TimeBasedTriggeringPolicy +# #appender.rolling.policies.time.interval = 1 +# #appender.rolling.policies.time.modulate = true +# appender.rolling.strategy.type = DefaultRolloverStrategy +# appender.rolling.strategy.delete.type = Delete +# appender.rolling.strategy.delete.basePath = ${basePath} +# appender.rolling.strategy.delete.maxDepth = 10 +# appender.rolling.strategy.delete.ifLastModified.type = IfLastModified +# +# # Delete all files older than 30 days +# appender.rolling.strategy.delete.ifLastModified.age = 30d +# +################################################## +# +# # Redirect log messages to a log file, support file rolling. +# appender.file.type = RollingFile +# appender.file.name = File +# appender.file.fileName = /opt/IBM/configurator_logs/${filename} +# appender.file.filePattern = /opt/IBM/configurator_logs/${filename}.%i +# appender.file.layout.type = PatternLayout +# appender.file.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1} - %m%n +# appender.file.policies.type = Policies +# appender.file.policies.size.type = SizeBasedTriggeringPolicy +# appender.file.policies.size.size=5MB +# appender.file.strategy.type = DefaultRolloverStrategy +# appender.file.strategy.max = 10 +# +# rootLogger.appenderRefs = file, console +# rootLogger.appenderRef.console.ref = STDOUT +# rootLogger.appenderRef.file.ref = File +# +# loggers = file +# +# logger.file.name = com.comergent.apps.configurator.initializer.FunctionLoader +# logger.file.level = debug +# logger.file.additivity = false +# logger.file.appenderRef.file.ref = File +# +################################################## \ No newline at end of file diff --git a/src/test/java/libKonogonka/RomFsDecrypted/KeyChainHolderTest.java b/src/test/java/libKonogonka/RomFsDecrypted/KeyChainHolderTest.java new file mode 100644 index 0000000..7805299 --- /dev/null +++ b/src/test/java/libKonogonka/RomFsDecrypted/KeyChainHolderTest.java @@ -0,0 +1,85 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka 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. + + libKonogonka 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 libKonogonka. If not, see . + */ +package libKonogonka.RomFsDecrypted; + +import libKonogonka.KeyChainHolder; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.util.Map; + +public class KeyChainHolderTest { + private static final String keysFileLocation = "./FilesForTests/prod.keys"; + private static final String xci_header_keyFileLocation = "./FilesForTests/xci_header_key.txt"; + private KeyChainHolder keyChainHolder; + + @Disabled + @DisplayName("Key Chain Holder Test") + @Test + void keysChain() throws Exception{ + BufferedReader br = new BufferedReader(new FileReader(xci_header_keyFileLocation)); + String keyValue = br.readLine(); + br.close(); + + if (keyValue == null) + throw new Exception("Unable to retrieve xci_header_key"); + + keyValue = keyValue.trim(); + keyChainHolder = new KeyChainHolder(keysFileLocation, keyValue); + } + + void printXciHeaderKey(){ + System.out.println("-=== xci_header ===-"); + System.out.println(keyChainHolder.getXci_header_key()); + } + + void printKAKApplication(){ + System.out.println("-=== key_area_key_application test ===-"); + for (Map.Entry entry : keyChainHolder.getKey_area_key_application().entrySet()){ + System.out.println(entry.getKey() + " - " + entry.getValue()); + } + } + void printKAKOcean(){ + System.out.println("-=== key_area_key_ocean test ===-"); + for (Map.Entry entry : keyChainHolder.getKey_area_key_ocean().entrySet()){ + System.out.println(entry.getKey() + " - " + entry.getValue()); + } + } + void printKAKSystem(){ + System.out.println("-=== key_area_key_system test ===-"); + for (Map.Entry entry : keyChainHolder.getKey_area_key_system().entrySet()){ + System.out.println(entry.getKey() + " - " + entry.getValue()); + } + } + void printKAKTitleKek(){ + System.out.println("-=== titlekek test ===-"); + for (Map.Entry entry : keyChainHolder.getTitlekek().entrySet()){ + System.out.println(entry.getKey() + " - " + entry.getValue()); + } + } + void printRawKeySet(){ + System.out.println("-=== Raw Key Set (everything) test ===-"); + for (Map.Entry entry : keyChainHolder.getRawKeySet().entrySet()){ + System.out.println(entry.getKey() + " - " + entry.getValue()); + } + } +} diff --git a/src/test/java/libKonogonka/RomFsDecrypted/RomFsDecryptedTest.java b/src/test/java/libKonogonka/RomFsDecrypted/RomFsDecryptedTest.java new file mode 100644 index 0000000..2b517fe --- /dev/null +++ b/src/test/java/libKonogonka/RomFsDecrypted/RomFsDecryptedTest.java @@ -0,0 +1,69 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka 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. + + libKonogonka 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 libKonogonka. If not, see . + */ +package libKonogonka.RomFsDecrypted; + +import java.io.File; +import java.nio.file.Path; + +import libKonogonka.Tools.RomFs.RomFsDecryptedProvider; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +// log.fatal("Configuration File Defined To Be :: "+System.getProperty("log4j.configurationFile")); + +public class RomFsDecryptedTest { + @TempDir + Path mainLogsDir; + + private static final String decryptedFileAbsolutePath = "./FilesForTests/NCAContent_0 [lv6 147456].bin"; + private File decryptedFile; + long lv6offset; + RomFsDecryptedProvider provider; + + @Disabled + @DisplayName("RomFsDecryptedProvider: tests") + @Test + void romFsValidation() throws Exception{ + makeFile(); + parseLv6offsetFromFileName(); + makeProvider(); + provider.printDebug(); + } + + void makeFile(){ + decryptedFile = new File(decryptedFileAbsolutePath); + } + void parseLv6offsetFromFileName(){ + lv6offset = Long.parseLong(decryptedFile.getName().replaceAll("(^.*lv6\\s)|(]\\.bin)", "")); + } + void makeProvider() throws Exception{ + provider = new RomFsDecryptedProvider(decryptedFile, lv6offset); + } + +/* + void checkFilesWorkers(){ + assertTrue(fw1 instanceof WorkerFiles); + assertTrue(fw2 instanceof WorkerFiles); + assertTrue(fw3 instanceof WorkerFiles); + } + + */ +} \ No newline at end of file diff --git a/src/test/java/libKonogonka/RomFsDecrypted/RomFsEncryptedTest.java b/src/test/java/libKonogonka/RomFsDecrypted/RomFsEncryptedTest.java new file mode 100644 index 0000000..b5a3968 --- /dev/null +++ b/src/test/java/libKonogonka/RomFsDecrypted/RomFsEncryptedTest.java @@ -0,0 +1,96 @@ +/* + Copyright 2018-2022 Dmitry Isaenko + + This file is part of libKonogonka. + + libKonogonka 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. + + libKonogonka 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 libKonogonka. If not, see . + */ +package libKonogonka.RomFsDecrypted; + +import libKonogonka.KeyChainHolder; +import libKonogonka.Tools.NCA.NCAHeaderTableEntry; +import libKonogonka.Tools.NCA.NCAProvider; +import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader; +import org.junit.jupiter.api.*; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class RomFsEncryptedTest { + private static final String keysFileLocation = "./FilesForTests/prod.keys"; + private static final String xci_header_keyFileLocation = "./FilesForTests/xci_header_key.txt"; + private static final String ncaFileLocation = "./FilesForTests/PFS_RomFS.nca"; + private static KeyChainHolder keyChainHolder; + private static NCAProvider ncaProvider; + + @Disabled + @Order(1) + @DisplayName("KeyChain test") + @Test + void keysChain() throws Exception{ + BufferedReader br = new BufferedReader(new FileReader(xci_header_keyFileLocation)); + String keyValue = br.readLine(); + br.close(); + + if (keyValue == null) + throw new Exception("Unable to retrieve xci_header_key"); + + keyValue = keyValue.trim(); + keyChainHolder = new KeyChainHolder(keysFileLocation, keyValue); + } + + @Disabled + @Order(2) + @DisplayName("RomFsEncryptedProvider: NCA provider quick test") + @Test + void ncaProvider() throws Exception{ + ncaProvider = new NCAProvider(new File(ncaFileLocation), keyChainHolder.getRawKeySet()); + } + + @Disabled + @Order(3) + @DisplayName("RomFsEncryptedProvider: RomFs test") + @Test + void romFsValidation() throws Exception{ + for (byte i = 0; i < 4; i++){ + if (ncaProvider.getSectionBlock(i).getFsType() == 0 && ncaProvider.getSectionBlock(i).getCryptoType() != 0){ + ncaProvider.getNCAContentProvider(i).getRomfs().printDebug(); + ncaProvider.getSectionBlock(i).printDebug(); + return; + } + } + } + + @Disabled + @Order(4) + @DisplayName("RomFsEncryptedProvider: NCA Header Table Entries test") + @Test + void NcaHeaderTableEntryValidation() throws Exception{ + for (byte i = 0; i < 4; i++){ + NcaFsHeader header = ncaProvider.getSectionBlock(i); + if (header != null) + header.printDebug(); + } + } + + @Disabled + @Order(5) + @DisplayName("RomFsEncryptedProvider: PFS test") + @Test + void pfsValidation(){ + //1 PFS and cryptotype != 0 + } +}