From 9a854774d79e118b93b78c693edc5c9dbc2c36f6 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko Date: Mon, 12 Dec 2022 04:26:21 +0300 Subject: [PATCH] Simplify, fix KeyChainHolder, implement HFS0 export functions --- .../java/libKonogonka/KeyChainHolder.java | 3 +- .../Tools/ASuperInFileProvider.java | 42 --- .../libKonogonka/Tools/ISuperProvider.java | 10 +- .../libKonogonka/Tools/NCA/NCAContent.java | 52 ++- .../NCA/NCASectionTableBlock/NcaFsHeader.java | 4 +- .../Tools/NPDM/ACI0/ACI0Provider.java | 28 +- .../Tools/NPDM/ACID/ACIDProvider.java | 42 +-- .../NPDM/ACID/FSAccessControlProvider.java | 8 +- .../libKonogonka/Tools/NPDM/NPDMProvider.java | 146 ++++----- .../NPDM/ServiceAccessControlProvider.java | 2 +- .../Tools/PFS0/IPFS0Provider.java | 43 --- .../libKonogonka/Tools/PFS0/PFS0Header.java | 125 ++++++++ .../libKonogonka/Tools/PFS0/PFS0Provider.java | 303 +++--------------- .../Tools/RomFs/IRomFsProvider.java | 32 -- .../Tools/RomFs/RomFsConstruct.java | 54 +--- .../Tools/RomFs/RomFsContentRetrieve.java | 125 -------- .../Tools/RomFs/RomFsProvider.java | 182 +++++------ .../java/libKonogonka/Tools/XCI/HFS0File.java | 12 +- .../libKonogonka/Tools/XCI/HFS0Provider.java | 119 ++++--- .../Tools/XCI/XCIGamecardHeader.java | 130 +++----- .../libKonogonka/Tools/XCI/XCIProvider.java | 16 +- .../ctraes/InFileStreamProducer.java | 63 ++-- .../RomFsDecrypted/KeyChainHolderTest.java | 3 +- .../libKonogonka/RomFsDecrypted/NSOTest.java | 7 +- .../RomFsDecrypted/NSPpfs0EncryptedTest.java | 9 +- .../libKonogonka/RomFsDecrypted/PFS0Test.java | 72 +++++ .../RomFsDecrypted/Pfs0EncryptedTest.java | 11 +- .../RomFsDecrypted/RomFsDecryptedTest.java | 100 +++++- .../RomFsDecrypted/RomFsEncryptedTest.java | 10 +- .../libKonogonka/RomFsDecrypted/XciTest.java | 2 +- 30 files changed, 719 insertions(+), 1036 deletions(-) delete mode 100644 src/main/java/libKonogonka/Tools/ASuperInFileProvider.java delete mode 100644 src/main/java/libKonogonka/Tools/PFS0/IPFS0Provider.java create mode 100644 src/main/java/libKonogonka/Tools/PFS0/PFS0Header.java delete mode 100644 src/main/java/libKonogonka/Tools/RomFs/IRomFsProvider.java delete mode 100644 src/main/java/libKonogonka/Tools/RomFs/RomFsContentRetrieve.java create mode 100644 src/test/java/libKonogonka/RomFsDecrypted/PFS0Test.java diff --git a/src/main/java/libKonogonka/KeyChainHolder.java b/src/main/java/libKonogonka/KeyChainHolder.java index 5c6c1a0..c3c3bfb 100644 --- a/src/main/java/libKonogonka/KeyChainHolder.java +++ b/src/main/java/libKonogonka/KeyChainHolder.java @@ -63,10 +63,9 @@ public class KeyChainHolder { 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){ + while ((keyParsed = rawKeySet.get(String.format(keyNamePattern, counter))) != null){ tempKeySet.put(String.format(keyNamePattern, counter), keyParsed); counter++; } diff --git a/src/main/java/libKonogonka/Tools/ASuperInFileProvider.java b/src/main/java/libKonogonka/Tools/ASuperInFileProvider.java deleted file mode 100644 index 7499481..0000000 --- a/src/main/java/libKonogonka/Tools/ASuperInFileProvider.java +++ /dev/null @@ -1,42 +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; - -import java.io.IOException; -import java.io.PipedInputStream; - -/** - * Any class of this type must be able to accept data from stream (and file as any other). - * */ - -public abstract class ASuperInFileProvider { - protected byte[] readFromStream(PipedInputStream pis, int size) throws IOException { - byte[] buffer = new byte[size]; - int startingPos = 0; - int readCnt; - while (size > 0){ - readCnt = pis.read(buffer, startingPos, size); - if (readCnt == -1) - return null; - startingPos += readCnt; - size -= readCnt; - } - return buffer; - } -} diff --git a/src/main/java/libKonogonka/Tools/ISuperProvider.java b/src/main/java/libKonogonka/Tools/ISuperProvider.java index 6aa50d7..27b3822 100644 --- a/src/main/java/libKonogonka/Tools/ISuperProvider.java +++ b/src/main/java/libKonogonka/Tools/ISuperProvider.java @@ -18,16 +18,18 @@ */ package libKonogonka.Tools; +import libKonogonka.ctraes.InFileStreamProducer; + import java.io.File; -import java.io.PipedInputStream; + /** * Any class of this type must provide streams * */ public interface ISuperProvider { - PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception; - PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception; + InFileStreamProducer getStreamProducer(String subFileName) throws Exception; + InFileStreamProducer getStreamProducer(int subFileNumber) throws Exception; boolean exportContent(String saveToLocation, String subFileName) throws Exception; boolean exportContent(String saveToLocation, int subFileNumber) throws Exception; File getFile(); long getRawFileDataStart(); -} +} \ No newline at end of file diff --git a/src/main/java/libKonogonka/Tools/NCA/NCAContent.java b/src/main/java/libKonogonka/Tools/NCA/NCAContent.java index 8de079d..fa5e054 100644 --- a/src/main/java/libKonogonka/Tools/NCA/NCAContent.java +++ b/src/main/java/libKonogonka/Tools/NCA/NCAContent.java @@ -19,12 +19,11 @@ package libKonogonka.Tools.NCA; import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader; -import libKonogonka.Tools.PFS0.IPFS0Provider; import libKonogonka.Tools.PFS0.PFS0Provider; -import libKonogonka.Tools.RomFs.IRomFsProvider; import libKonogonka.Tools.RomFs.RomFsProvider; import libKonogonka.ctraes.AesCtrBufferedInputStream; import libKonogonka.ctraes.AesCtrDecryptSimple; +import libKonogonka.ctraes.InFileStreamProducer; import libKonogonka.exceptions.EmptySectionException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -35,15 +34,15 @@ import java.nio.file.Paths; public class NCAContent { private final static Logger log = LogManager.getLogger(NCAContent.class); - + private final File file; private final long ncaOffsetPosition; private final NcaFsHeader ncaFsHeader; private final NCAHeaderTableEntry ncaHeaderTableEntry; private final byte[] decryptedKey; - private IPFS0Provider pfs0; - private IRomFsProvider romfs; + private PFS0Provider pfs0; + private RomFsProvider romfs; // TODO: if decryptedKey is empty, throw exception? public NCAContent(File file, @@ -82,22 +81,16 @@ public class NCAContent { } } private void proceedPFS0NotEncrypted() throws Exception{ - pfs0 = new PFS0Provider(file, - ncaOffsetPosition, + InFileStreamProducer producer = new InFileStreamProducer(file); // no need to bypass ncaOffsetPosition! + pfs0 = new PFS0Provider(producer, + makeOffsetPositionInFile(), ncaFsHeader.getSuperBlockPFS0(), - ncaHeaderTableEntry.getMediaStartOffset(), - ncaHeaderTableEntry.getMediaEndOffset()); + ncaHeaderTableEntry.getMediaStartOffset()); } private void proceedPFS0Encrypted() throws Exception{ - AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey, ncaFsHeader.getSectionCTR(), - ncaHeaderTableEntry.getMediaStartOffset() * 0x200); - pfs0 = new PFS0Provider(file, - ncaOffsetPosition, - ncaFsHeader.getSuperBlockPFS0(), - decryptor, - ncaHeaderTableEntry.getMediaStartOffset(), - ncaHeaderTableEntry.getMediaEndOffset()); + pfs0 = new PFS0Provider(makeEncryptedProducer(), makeOffsetPositionInFile(), ncaFsHeader.getSuperBlockPFS0(), + ncaHeaderTableEntry.getMediaStartOffset()); } private void proceedRomFs() throws Exception{ @@ -109,7 +102,7 @@ public class NCAContent { proceedRomFsEncrypted(); break; default: - throw new Exception("Non-supported 'Crypto type'"); + throw new Exception("Non-supported 'Crypto type' "+ncaFsHeader.getCryptoType()); } } private void proceedRomFsNotEncrypted(){ // TODO: Clarify, implement if needed @@ -119,18 +112,21 @@ public class NCAContent { if (decryptedKey == null) throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided."); - this.romfs = new RomFsProvider( - ncaFsHeader.getSuperBlockIVFC().getLvl6Offset(), - file, - ncaOffsetPosition, - decryptedKey, - ncaFsHeader.getSectionCTR(), - ncaHeaderTableEntry.getMediaStartOffset(), - ncaHeaderTableEntry.getMediaEndOffset()); + this.romfs = new RomFsProvider(makeEncryptedProducer(), ncaFsHeader.getSuperBlockIVFC().getLvl6Offset(), + makeOffsetPositionInFile(), ncaHeaderTableEntry.getMediaStartOffset()); } - public IPFS0Provider getPfs0() { return pfs0; } - public IRomFsProvider getRomfs() { return romfs; } + public PFS0Provider getPfs0() { return pfs0; } + public RomFsProvider getRomfs() { return romfs; } + private InFileStreamProducer makeEncryptedProducer() throws Exception{ + AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey, ncaFsHeader.getSectionCTR(), + ncaHeaderTableEntry.getMediaStartOffset() * 0x200); + return new InFileStreamProducer(file, ncaOffsetPosition, 0, decryptor, + ncaHeaderTableEntry.getMediaStartOffset(), ncaHeaderTableEntry.getMediaEndOffset()); + } + private long makeOffsetPositionInFile(){ + return ncaOffsetPosition + ncaHeaderTableEntry.getMediaStartOffset() * 0x200; + } /** * Export NCA content AS IS. * Not so good for PFS0 since there are SHAs list that discourages but good for 'romfs' and things like that diff --git a/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/NcaFsHeader.java b/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/NcaFsHeader.java index 1fdefe3..977a76c 100644 --- a/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/NcaFsHeader.java +++ b/src/main/java/libKonogonka/Tools/NCA/NCASectionTableBlock/NcaFsHeader.java @@ -184,8 +184,8 @@ public class NcaFsHeader { "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" + + "Super Block IVFC : " + (superBlockIVFC == null ? "-\n": "YES\n") + + "Super Block PFS0 : " + (superBlockPFS0 == null ? "-\n": "YES\n") + "================================================================================================\n" + (((fsType == 0) && (hashType == 0x3))? ("| Hash Data - RomFS\n" + diff --git a/src/main/java/libKonogonka/Tools/NPDM/ACI0/ACI0Provider.java b/src/main/java/libKonogonka/Tools/NPDM/ACI0/ACI0Provider.java index 4540b37..729645c 100644 --- a/src/main/java/libKonogonka/Tools/NPDM/ACI0/ACI0Provider.java +++ b/src/main/java/libKonogonka/Tools/NPDM/ACI0/ACI0Provider.java @@ -27,21 +27,21 @@ import java.util.Arrays; import static libKonogonka.Converter.getLEint; public class ACI0Provider { - private String magicNum; - private byte[] reserved1; - private byte[] titleID; - private byte[] reserved2; - private int fsAccessHeaderOffset; - private int fsAccessHeaderSize; - private int serviceAccessControlOffset; - private int serviceAccessControlSize; - private int kernelAccessControlOffset; - private int kernelAccessControlSize; - private byte[] reserved3; + private final String magicNum; + private final byte[] reserved1; + private final byte[] titleID; + private final byte[] reserved2; + private final int fsAccessHeaderOffset; + private final int fsAccessHeaderSize; + private final int serviceAccessControlOffset; + private final int serviceAccessControlSize; + private final int kernelAccessControlOffset; + private final int kernelAccessControlSize; + private final byte[] reserved3; - private FSAccessHeaderProvider fsAccessHeaderProvider; - private ServiceAccessControlProvider serviceAccessControlProvider; - private KernelAccessControlProvider kernelAccessControlProvider; + private final FSAccessHeaderProvider fsAccessHeaderProvider; + private final ServiceAccessControlProvider serviceAccessControlProvider; + private final KernelAccessControlProvider kernelAccessControlProvider; public ACI0Provider(byte[] aci0bytes) throws Exception { if (aci0bytes.length < 0x40) diff --git a/src/main/java/libKonogonka/Tools/NPDM/ACID/ACIDProvider.java b/src/main/java/libKonogonka/Tools/NPDM/ACID/ACIDProvider.java index 3084b74..5ab84f8 100644 --- a/src/main/java/libKonogonka/Tools/NPDM/ACID/ACIDProvider.java +++ b/src/main/java/libKonogonka/Tools/NPDM/ACID/ACIDProvider.java @@ -28,28 +28,28 @@ import static libKonogonka.Converter.*; public class ACIDProvider { - private byte[] rsa2048signature; - private byte[] rsa2048publicKey; - private String magicNum; - private int dataSize; - private byte[] reserved1; - private byte flag1; - private byte flag2; - private byte flag3; - private byte flag4; - private long titleRangeMin; - private long titleRangeMax; - private int fsAccessControlOffset; - private int fsAccessControlSize; - private int serviceAccessControlOffset; - private int serviceAccessControlSize; - private int kernelAccessControlOffset; - private int kernelAccessControlSize; - private byte[] reserved2; + private final byte[] rsa2048signature; + private final byte[] rsa2048publicKey; + private final String magicNum; + private final int dataSize; + private final byte[] reserved1; + private final byte flag1; + private final byte flag2; + private final byte flag3; + private final byte flag4; + private final long titleRangeMin; + private final long titleRangeMax; + private final int fsAccessControlOffset; + private final int fsAccessControlSize; + private final int serviceAccessControlOffset; + private final int serviceAccessControlSize; + private final int kernelAccessControlOffset; + private final int kernelAccessControlSize; + private final byte[] reserved2; - private FSAccessControlProvider fsAccessControlProvider; - private ServiceAccessControlProvider serviceAccessControlProvider; - private KernelAccessControlProvider kernelAccessControlProvider; + private final FSAccessControlProvider fsAccessControlProvider; + private final ServiceAccessControlProvider serviceAccessControlProvider; + private final KernelAccessControlProvider kernelAccessControlProvider; public ACIDProvider(byte[] acidBytes) throws Exception{ if (acidBytes.length < 0x240) diff --git a/src/main/java/libKonogonka/Tools/NPDM/ACID/FSAccessControlProvider.java b/src/main/java/libKonogonka/Tools/NPDM/ACID/FSAccessControlProvider.java index 5879df1..a06c2c3 100644 --- a/src/main/java/libKonogonka/Tools/NPDM/ACID/FSAccessControlProvider.java +++ b/src/main/java/libKonogonka/Tools/NPDM/ACID/FSAccessControlProvider.java @@ -27,10 +27,10 @@ import java.util.Arrays; * */ public class FSAccessControlProvider { - private byte version; - private byte[] padding; - private long permissionsBitmask; - private byte[] reserved; + private final byte version; + private final byte[] padding; + private final long permissionsBitmask; + private final byte[] reserved; public FSAccessControlProvider(byte[] bytes) { version = bytes[0]; diff --git a/src/main/java/libKonogonka/Tools/NPDM/NPDMProvider.java b/src/main/java/libKonogonka/Tools/NPDM/NPDMProvider.java index 8511370..b930c74 100644 --- a/src/main/java/libKonogonka/Tools/NPDM/NPDMProvider.java +++ b/src/main/java/libKonogonka/Tools/NPDM/NPDMProvider.java @@ -18,19 +18,18 @@ */ package libKonogonka.Tools.NPDM; -import libKonogonka.Tools.ASuperInFileProvider; import libKonogonka.Tools.NPDM.ACI0.ACI0Provider; import libKonogonka.Tools.NPDM.ACID.ACIDProvider; +import libKonogonka.ctraes.InFileStreamProducer; +import java.io.BufferedInputStream; import java.io.File; -import java.io.PipedInputStream; -import java.io.RandomAccessFile; import java.nio.charset.StandardCharsets; import java.util.Arrays; import static libKonogonka.Converter.*; -public class NPDMProvider extends ASuperInFileProvider { +public class NPDMProvider{ private final String magicNum; private final byte[] reserved1; @@ -50,98 +49,63 @@ public class NPDMProvider extends ASuperInFileProvider { private final int acidOffset; // originally 4-bytes (u-int) private final int acidSize; // originally 4-bytes (u-int) - private final ACI0Provider aci0; - private final ACIDProvider acid; + private ACI0Provider aci0; + private ACIDProvider acid; - public NPDMProvider(PipedInputStream pis) throws Exception{ - byte[] mainBuf = new byte[0x80]; - if(pis.read(mainBuf) != 0x80) - throw new Exception("NPDMProvider: Failed to read 'META'"); - aci0offset = getLEint(mainBuf, 0x70); - aci0size = getLEint(mainBuf, 0x74); - acidOffset = getLEint(mainBuf, 0x78); - acidSize = getLEint(mainBuf, 0x7C); - byte[] aci0Buf; - byte[] acidBuf; - if (aci0offset < acidOffset){ - if (pis.skip(aci0offset - 0x80) != (aci0offset - 0x80)) - throw new Exception("NPDMProvider: Failed to skip bytes till 'ACI0'"); - if ((aci0Buf = readFromStream(pis, aci0size)) == null) - throw new Exception("NPDMProvider: Failed to read 'ACI0'"); - if (pis.skip(acidOffset - aci0offset - aci0size) != (acidOffset - aci0offset - aci0size)) - throw new Exception("NPDMProvider: Failed to skip bytes till 'ACID'"); - if ((acidBuf = readFromStream(pis, acidSize)) == null) - throw new Exception("NPDMProvider: Failed to read 'ACID'"); - } - else { - if (pis.skip(acidOffset - 0x80) != (acidOffset - 0x80)) - throw new Exception("NPDMProvider: Failed to skip bytes till 'ACID'"); - if ((acidBuf = readFromStream(pis, acidSize)) == null) - throw new Exception("NPDMProvider: Failed to read 'ACID'"); - if (pis.skip(aci0offset - acidOffset - acidSize) != (aci0offset - acidOffset - acidSize)) - throw new Exception("NPDMProvider: Failed to skip bytes till 'ACI0'"); - if ((aci0Buf = readFromStream(pis, aci0size)) == null) - throw new Exception("NPDMProvider: Failed to read 'ACI0'"); - } - magicNum = new String(mainBuf, 0, 4, StandardCharsets.UTF_8); - reserved1 = Arrays.copyOfRange(mainBuf, 0x4, 0xC); - MMUFlags = mainBuf[0xC]; - reserved2 = mainBuf[0xD]; - mainThreadPrio = mainBuf[0xE]; - mainThreadCoreNum = mainBuf[0xF]; - reserved3 = Arrays.copyOfRange(mainBuf, 0x10, 0x14); - personalMmHeapSize = getLEint(mainBuf, 0x14); - version = getLEint(mainBuf, 0x18); - mainThreadStackSize = getLElongOfInt(mainBuf, 0x1C); - titleName = new String(mainBuf, 0x20, 0x10, StandardCharsets.UTF_8); - productCode = Arrays.copyOfRange(mainBuf, 0x30, 0x40); - reserved4 = Arrays.copyOfRange(mainBuf, 0x40, 0x70); + public NPDMProvider(File file) throws Exception { + this(file, 0); + } + public NPDMProvider(File file, long offset) throws Exception { + this(new InFileStreamProducer(file, offset)); + } + public NPDMProvider(InFileStreamProducer producer) throws Exception{ + try (BufferedInputStream stream = producer.produce()) { + byte[] mainBuf = new byte[0x80]; + if (stream.read(mainBuf) != 0x80) + throw new Exception("NPDMProvider: Failed to read 'META'"); + aci0offset = getLEint(mainBuf, 0x70); + aci0size = getLEint(mainBuf, 0x74); + acidOffset = getLEint(mainBuf, 0x78); + acidSize = getLEint(mainBuf, 0x7C); - aci0 = new ACI0Provider(aci0Buf); - acid = new ACIDProvider(acidBuf); + if (aci0offset < acidOffset) { + calculateACI0(stream, aci0offset - 0x80); + calculateACID(stream, acidOffset - aci0offset - aci0size); + } else { + calculateACID(stream, acidOffset - 0x80); + calculateACI0(stream, aci0offset - acidOffset - acidSize); + } + magicNum = new String(mainBuf, 0, 4, StandardCharsets.UTF_8); + reserved1 = Arrays.copyOfRange(mainBuf, 0x4, 0xC); + MMUFlags = mainBuf[0xC]; + reserved2 = mainBuf[0xD]; + mainThreadPrio = mainBuf[0xE]; + mainThreadCoreNum = mainBuf[0xF]; + reserved3 = Arrays.copyOfRange(mainBuf, 0x10, 0x14); + personalMmHeapSize = getLEint(mainBuf, 0x14); + version = getLEint(mainBuf, 0x18); + mainThreadStackSize = getLElongOfInt(mainBuf, 0x1C); + titleName = new String(mainBuf, 0x20, 0x10, StandardCharsets.UTF_8); + productCode = Arrays.copyOfRange(mainBuf, 0x30, 0x40); + reserved4 = Arrays.copyOfRange(mainBuf, 0x40, 0x70); + } } - public NPDMProvider(File file) throws Exception { this(file, 0); } - - public NPDMProvider(File file, long offset) throws Exception { - if (file.length() - offset < 0x80) // Header's size - throw new Exception("NPDMProvider: File is too small."); - RandomAccessFile raf = new RandomAccessFile(file, "r"); - raf.seek(offset); - // Get META - byte[] metaBuf = new byte[0x80]; - if (raf.read(metaBuf) != 0x80) - throw new Exception("NPDMProvider: Failed to read 'META'"); - magicNum = new String(metaBuf, 0, 4, StandardCharsets.UTF_8); - reserved1 = Arrays.copyOfRange(metaBuf, 0x4, 0xC); - MMUFlags = metaBuf[0xC]; - reserved2 = metaBuf[0xD]; - mainThreadPrio = metaBuf[0xE]; - mainThreadCoreNum = metaBuf[0xF]; - reserved3 = Arrays.copyOfRange(metaBuf, 0x10, 0x14); - personalMmHeapSize = getLEint(metaBuf, 0x14); - version = getLEint(metaBuf, 0x18); - mainThreadStackSize = getLElongOfInt(metaBuf, 0x1C); - titleName = new String(metaBuf, 0x20, 0x10, StandardCharsets.UTF_8); - productCode = Arrays.copyOfRange(metaBuf, 0x30, 0x40); - reserved4 = Arrays.copyOfRange(metaBuf, 0x40, 0x70); - aci0offset = getLEint(metaBuf, 0x70); - aci0size = getLEint(metaBuf, 0x74); - acidOffset = getLEint(metaBuf, 0x78); - acidSize = getLEint(metaBuf, 0x7C); - // Get ACI0 - raf.seek(aci0offset); - metaBuf = new byte[aci0size]; // TODO: NOTE: we read all size but it's memory consuming - if (raf.read(metaBuf) != aci0size) - throw new Exception("NPDMProvider: Failed to read 'ACI0'"); - aci0 = new ACI0Provider(metaBuf); - // Get ACID - raf.seek(acidOffset); - metaBuf = new byte[acidSize]; // TODO: NOTE: we read all size but it's memory consuming - if (raf.read(metaBuf) != acidSize) + private void calculateACID(BufferedInputStream stream, int toSkip) throws Exception{ + byte[] acidBuf = new byte[acidSize]; + if (stream.skip(toSkip) != toSkip) + throw new Exception("NPDMProvider: Failed to skip bytes till 'ACID'"); + if (acidSize != stream.read(acidBuf)) throw new Exception("NPDMProvider: Failed to read 'ACID'"); - acid = new ACIDProvider(metaBuf); - raf.close(); + acid = new ACIDProvider(acidBuf); + } + private void calculateACI0(BufferedInputStream stream, int toSkip) throws Exception{ + byte[] aci0Buf = new byte[aci0size]; + if (stream.skip(toSkip) != toSkip) + throw new Exception("NPDMProvider: Failed to skip bytes till 'ACI0'"); + if (aci0size != stream.read(aci0Buf)) + throw new Exception("NPDMProvider: Failed to read 'ACI0'"); + aci0 = new ACI0Provider(aci0Buf); } public String getMagicNum() { return magicNum; } diff --git a/src/main/java/libKonogonka/Tools/NPDM/ServiceAccessControlProvider.java b/src/main/java/libKonogonka/Tools/NPDM/ServiceAccessControlProvider.java index 6e48bf4..d97d254 100644 --- a/src/main/java/libKonogonka/Tools/NPDM/ServiceAccessControlProvider.java +++ b/src/main/java/libKonogonka/Tools/NPDM/ServiceAccessControlProvider.java @@ -23,7 +23,7 @@ import java.util.LinkedHashMap; public class ServiceAccessControlProvider { - private LinkedHashMap collection; + private final LinkedHashMap collection; public ServiceAccessControlProvider(byte[] bytes){ collection = new LinkedHashMap<>(); diff --git a/src/main/java/libKonogonka/Tools/PFS0/IPFS0Provider.java b/src/main/java/libKonogonka/Tools/PFS0/IPFS0Provider.java deleted file mode 100644 index 834ca60..0000000 --- a/src/main/java/libKonogonka/Tools/PFS0/IPFS0Provider.java +++ /dev/null @@ -1,43 +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.PFS0; - -import libKonogonka.Tools.ISuperProvider; -import libKonogonka.ctraes.InFileStreamProducer; - -import java.io.FileNotFoundException; -import java.util.LinkedList; - -public interface IPFS0Provider extends ISuperProvider { - boolean isEncrypted(); - String getMagic(); - int getFilesCount(); - int getStringTableSize(); - byte[] getPadding(); - - PFS0subFile[] getPfs0subFiles(); - - void printDebug(); - - InFileStreamProducer getStreamProducer(String subFileName) throws FileNotFoundException; - - InFileStreamProducer getStreamProducer(int subFileNumber); - - LinkedList getPfs0SHA256hashes(); -} diff --git a/src/main/java/libKonogonka/Tools/PFS0/PFS0Header.java b/src/main/java/libKonogonka/Tools/PFS0/PFS0Header.java new file mode 100644 index 0000000..7e20d11 --- /dev/null +++ b/src/main/java/libKonogonka/Tools/PFS0/PFS0Header.java @@ -0,0 +1,125 @@ +/* + 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.PFS0; + +import libKonogonka.Converter; +import libKonogonka.RainbowDump; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.BufferedInputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import static libKonogonka.Converter.getLEint; +import static libKonogonka.Converter.getLElong; + +public class PFS0Header { + private final static Logger log = LogManager.getLogger(PFS0Header.class); + + private final String magic; + private final int filesCount; + private final int stringTableSize; + private final byte[] padding; + private final PFS0subFile[] pfs0subFiles; + + public PFS0Header(BufferedInputStream stream) throws Exception{ + byte[] fileStartingBytes = new byte[0x10]; + if (0x10 != stream.read(fileStartingBytes)) + throw new Exception("Reading stream suddenly ended while trying to read starting 0x10 bytes"); + // Check PFS0Provider + magic = new String(fileStartingBytes, 0x0, 0x4, StandardCharsets.US_ASCII); + if (! magic.equals("PFS0")){ + throw new Exception("Bad magic"); + } + // Get files count + filesCount = getLEint(fileStartingBytes, 0x4); + if (filesCount <= 0 ) { + throw new Exception("Files count is too small"); + } + // Get string table + stringTableSize = getLEint(fileStartingBytes, 0x8); + if (stringTableSize <= 0 ){ + throw new Exception("String table is too small"); + } + padding = Arrays.copyOfRange(fileStartingBytes, 0xc, 0x10); + //------------------------------------------------------------------- + pfs0subFiles = new PFS0subFile[filesCount]; + + long[] offsetsSubFiles = new long[filesCount]; + long[] sizesSubFiles = new long[filesCount]; + int[] strTableOffsets = new int[filesCount]; + byte[][] zeroBytes = new byte[filesCount][]; + + byte[] fileEntryTable = new byte[0x18]; + for (int i=0; i < filesCount; i++){ + if (0x18 != stream.read(fileEntryTable)) + throw new Exception("Reading stream suddenly ended while trying to read File Entry Table #"+i); + + offsetsSubFiles[i] = getLElong(fileEntryTable, 0); + sizesSubFiles[i] = getLElong(fileEntryTable, 0x8); + strTableOffsets[i] = getLEint(fileEntryTable, 0x10); + zeroBytes[i] = Arrays.copyOfRange(fileEntryTable, 0x14, 0x18); + } + //******************************************************************* + // Here pointer is in front of String table + String[] subFileNames = new String[filesCount]; + byte[] stringTbl = new byte[stringTableSize]; + if (stream.read(stringTbl) != stringTableSize){ + throw new Exception("Read PFS0Provider String table failure. Can't read requested string table size ("+stringTableSize+")"); + } + + for (int i=0; i < filesCount; i++){ + int j = 0; + while (stringTbl[strTableOffsets[i]+j] != (byte)0x00) + j++; + subFileNames[i] = new String(stringTbl, strTableOffsets[i], j, StandardCharsets.UTF_8); + } + for (int i = 0; i < filesCount; i++){ + pfs0subFiles[i] = new PFS0subFile( + subFileNames[i], + offsetsSubFiles[i], + sizesSubFiles[i], + zeroBytes[i]); + } + } + + public String getMagic() {return magic;} + public int getFilesCount() {return filesCount;} + public int getStringTableSize() {return stringTableSize;} + public byte[] getPadding() {return padding;} + public PFS0subFile[] getPfs0subFiles() {return pfs0subFiles;} + + public void printDebug(){ + log.debug(".:: PFS0Header ::.\n" + + "Magic " + magic + "\n" + + "Files count " + RainbowDump.formatDecHexString(filesCount) + "\n" + + "String Table Size " + RainbowDump.formatDecHexString(stringTableSize) + "\n" + + "Padding " + Converter.byteArrToHexString(padding) + "\n\n" + ); + for (PFS0subFile subFile : pfs0subFiles){ + log.debug("\nName: " + subFile.getName() + "\n" + + "Offset " + RainbowDump.formatDecHexString(subFile.getOffset()) + "\n" + + "Size " + RainbowDump.formatDecHexString(subFile.getSize()) + "\n" + + "Zeroes " + Converter.byteArrToHexString(subFile.getZeroes()) + "\n" + + "----------------------------------------------------------------" + ); + } + } +} diff --git a/src/main/java/libKonogonka/Tools/PFS0/PFS0Provider.java b/src/main/java/libKonogonka/Tools/PFS0/PFS0Provider.java index 806ef93..fbe47c3 100644 --- a/src/main/java/libKonogonka/Tools/PFS0/PFS0Provider.java +++ b/src/main/java/libKonogonka/Tools/PFS0/PFS0Provider.java @@ -18,192 +18,61 @@ */ package libKonogonka.Tools.PFS0; -import libKonogonka.Converter; import libKonogonka.RainbowDump; +import libKonogonka.Tools.ISuperProvider; import libKonogonka.Tools.NCA.NCASectionTableBlock.SuperBlockPFS0; -import libKonogonka.ctraes.AesCtrBufferedInputStream; -import libKonogonka.ctraes.AesCtrDecryptSimple; import libKonogonka.ctraes.InFileStreamProducer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; 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.LinkedList; -import static libKonogonka.Converter.*; - -public class PFS0Provider implements IPFS0Provider{ +public class PFS0Provider implements ISuperProvider { private final static Logger log = LogManager.getLogger(PFS0Provider.class); - private String magic; - private int filesCount; - private int stringTableSize; - private byte[] padding; - private PFS0subFile[] pfs0subFiles; - //--------------------------------------- - private long rawBlockDataStart; + private final long rawBlockDataStart; - private final File file; private long offsetPositionInFile; - private long mediaStartOffset; // In 512-blocks - private long mediaEndOffset; // In 512-blocks - - private long ncaOffset; + private final InFileStreamProducer producer; private BufferedInputStream stream; private SuperBlockPFS0 superBlockPFS0; - private AesCtrDecryptSimple decryptor; + private long mediaStartOffset; + + private final PFS0Header header; private LinkedList pfs0SHA256hashes; - private boolean encrypted; - public PFS0Provider(File nspFile) throws Exception{ - this.file = nspFile; - createBufferedInputStream(); - readPfs0Header(); + this.producer = new InFileStreamProducer(nspFile); + this.stream = producer.produce(); + this.header = new PFS0Header(stream); + this.rawBlockDataStart = 0x10L + (header.getFilesCount() * 0x18L) + header.getStringTableSize(); } - public PFS0Provider(File file, - long ncaOffset, + public PFS0Provider(InFileStreamProducer producer, + long offsetPositionInFile, SuperBlockPFS0 superBlockPFS0, - long mediaStartOffset, - long mediaEndOffset) throws Exception{ - this.file = file; - this.ncaOffset = ncaOffset; + long mediaStartOffset) throws Exception { + this.producer = producer; + this.offsetPositionInFile = offsetPositionInFile; this.superBlockPFS0 = superBlockPFS0; - this.offsetPositionInFile = ncaOffset + mediaStartOffset * 0x200; this.mediaStartOffset = mediaStartOffset; - this.mediaEndOffset = mediaEndOffset; - this.rawBlockDataStart = superBlockPFS0.getPfs0offset(); - //bufferedInputStream = new BufferedInputStream(Files.newInputStream(fileWithPfs0.toPath())); - createBufferedInputStream(); + + this.stream = producer.produce(); long toSkip = offsetPositionInFile + superBlockPFS0.getHashTableOffset(); if (toSkip != stream.skip(toSkip)) throw new Exception("Can't skip bytes prior Hash Table offset"); collectHashes(); - createBufferedInputStream(); + this.stream = producer.produce(); toSkip = offsetPositionInFile + superBlockPFS0.getPfs0offset(); if (toSkip != stream.skip(toSkip)) throw new Exception("Can't skip bytes prior PFS0 offset"); - readPfs0Header(); - } - - public PFS0Provider(File file, - long ncaOffset, - SuperBlockPFS0 superBlockPFS0, - AesCtrDecryptSimple decryptor, - long mediaStartOffset, - long mediaEndOffset - ) throws Exception { - this.file = file; - this.ncaOffset = ncaOffset; - this.superBlockPFS0 = superBlockPFS0; - this.decryptor = decryptor; - this.offsetPositionInFile = ncaOffset + mediaStartOffset * 0x200; - this.mediaStartOffset = mediaStartOffset; - this.mediaEndOffset = mediaEndOffset; - this.rawBlockDataStart = superBlockPFS0.getPfs0offset(); - this.encrypted = true; - - createAesCtrEncryptedBufferedInputStream(); - long toSkip = offsetPositionInFile + superBlockPFS0.getHashTableOffset(); - if (toSkip != stream.skip(toSkip)) - throw new Exception("Can't skip bytes prior Hash Table offset"); - collectHashes(); - - createAesCtrEncryptedBufferedInputStream(); - toSkip = offsetPositionInFile + superBlockPFS0.getPfs0offset(); - if (toSkip != stream.skip(toSkip)) - throw new Exception("Can't skip bytes prior PFS0 offset"); - readPfs0Header(); - } - - private void readPfs0Header()throws Exception{ - byte[] fileStartingBytes = new byte[0x10]; - if (0x10 != stream.read(fileStartingBytes)) - throw new Exception("Reading stream suddenly ended while trying to read starting 0x10 bytes"); - - // Update position - rawBlockDataStart += 0x10; - // Check PFS0Provider - magic = new String(fileStartingBytes, 0x0, 0x4, StandardCharsets.US_ASCII); - if (! magic.equals("PFS0")){ - throw new Exception("Bad magic"); - } - // Get files count - filesCount = getLEint(fileStartingBytes, 0x4); - if (filesCount <= 0 ) { - throw new Exception("Files count is too small"); - } - // Get string table - stringTableSize = getLEint(fileStartingBytes, 0x8); - if (stringTableSize <= 0 ){ - throw new Exception("String table is too small"); - } - padding = Arrays.copyOfRange(fileStartingBytes, 0xc, 0x10); - //------------------------------------------------------------------- - pfs0subFiles = new PFS0subFile[filesCount]; - - long[] offsetsSubFiles = new long[filesCount]; - long[] sizesSubFiles = new long[filesCount]; - int[] strTableOffsets = new int[filesCount]; - byte[][] zeroBytes = new byte[filesCount][]; - - byte[] fileEntryTable = new byte[0x18]; - for (int i=0; i < filesCount; i++){ - if (0x18 != stream.read(fileEntryTable)) - throw new Exception("Reading stream suddenly ended while trying to read File Entry Table #"+i); - - offsetsSubFiles[i] = getLElong(fileEntryTable, 0); - sizesSubFiles[i] = getLElong(fileEntryTable, 0x8); - strTableOffsets[i] = getLEint(fileEntryTable, 0x10); - zeroBytes[i] = Arrays.copyOfRange(fileEntryTable, 0x14, 0x18); - rawBlockDataStart += 0x18; - } - //******************************************************************* - // In here pointer in front of String table - String[] subFileNames = new String[filesCount]; - byte[] stringTbl = new byte[stringTableSize]; - if (stream.read(stringTbl) != stringTableSize){ - throw new Exception("Read PFS0Provider String table failure. Can't read requested string table size ("+stringTableSize+")"); - } - - // Update position - rawBlockDataStart += stringTableSize; - - for (int i=0; i < filesCount; i++){ - int j = 0; - while (stringTbl[strTableOffsets[i]+j] != (byte)0x00) - j++; - subFileNames[i] = new String(stringTbl, strTableOffsets[i], j, StandardCharsets.UTF_8); - } - for (int i = 0; i < filesCount; i++){ - pfs0subFiles[i] = new PFS0subFile( - subFileNames[i], - offsetsSubFiles[i], - sizesSubFiles[i], - zeroBytes[i]); - } - stream.close(); - } - - private void createAesCtrEncryptedBufferedInputStream() throws Exception{ - decryptor.reset(); - this.stream = new AesCtrBufferedInputStream( - decryptor, - ncaOffset, - mediaStartOffset, - mediaEndOffset, - Files.newInputStream(file.toPath())); - } - - private void createBufferedInputStream() throws Exception{ - this.stream = new BufferedInputStream(Files.newInputStream(file.toPath())); + this.header = new PFS0Header(stream); + this.rawBlockDataStart = superBlockPFS0.getPfs0offset() + 0x10L + (header.getFilesCount() * 0x18L) + header.getStringTableSize(); } private void collectHashes() throws Exception{ @@ -223,25 +92,14 @@ public class PFS0Provider implements IPFS0Provider{ } } - @Override - public boolean isEncrypted() { return true; } - @Override - public String getMagic() { return magic; } - @Override - public int getFilesCount() { return filesCount; } - @Override - public int getStringTableSize() { return stringTableSize; } - @Override - public byte[] getPadding() { return padding; } + public boolean isEncrypted() { return producer.isEncrypted(); } + public PFS0Header getHeader() {return header;} + @Override public long getRawFileDataStart() { return rawBlockDataStart;} - @Override - public PFS0subFile[] getPfs0subFiles() { return pfs0subFiles; } - @Override - public File getFile(){ return file; } - @Override public boolean exportContent(String saveToLocation, String subFileName){ + PFS0subFile[] pfs0subFiles = header.getPfs0subFiles(); for (int i = 0; i < pfs0subFiles.length; i++){ if (pfs0subFiles[i].getName().equals(subFileName)) return exportContent(saveToLocation, i); @@ -250,16 +108,14 @@ public class PFS0Provider implements IPFS0Provider{ } @Override public boolean exportContent(String saveToLocation, int subFileNumber){ - PFS0subFile subFile = pfs0subFiles[subFileNumber]; + PFS0subFile subFile = header.getPfs0subFiles()[subFileNumber]; File location = new File(saveToLocation); location.mkdirs(); try (BufferedOutputStream extractedFileBOS = new BufferedOutputStream( Files.newOutputStream(Paths.get(saveToLocation+File.separator+subFile.getName())))){ - if (encrypted) - createAesCtrEncryptedBufferedInputStream(); - else - createBufferedInputStream(); + + this.stream = producer.produce(); long subFileSize = subFile.getSize(); @@ -297,6 +153,7 @@ public class PFS0Provider implements IPFS0Provider{ @Override public InFileStreamProducer getStreamProducer(String subFileName) throws FileNotFoundException { + PFS0subFile[] pfs0subFiles = header.getPfs0subFiles(); for (int i = 0; i < pfs0subFiles.length; i++) { if (pfs0subFiles[i].getName().equals(subFileName)) return getStreamProducer(i); @@ -305,110 +162,28 @@ public class PFS0Provider implements IPFS0Provider{ } @Override public InFileStreamProducer getStreamProducer(int subFileNumber) { - PFS0subFile subFile = pfs0subFiles[subFileNumber]; + PFS0subFile subFile = header.getPfs0subFiles()[subFileNumber]; long subFileOffset = subFile.getOffset() + mediaStartOffset * 0x200 + rawBlockDataStart; - if (encrypted) { - return new InFileStreamProducer(file, - pfs0subFiles[subFileNumber].getSize(), - ncaOffset, - subFileOffset, - decryptor, - mediaStartOffset, - mediaEndOffset); - } - return new InFileStreamProducer(file, offsetPositionInFile, subFileOffset); + return producer.getSuccessor(subFileOffset); } - /** - * @deprecated - * */ - @Override - public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception { - for (int i = 0; i < pfs0subFiles.length; i++) { - if (pfs0subFiles[i].getName().equals(subFileName)) - return getProviderSubFilePipedInpStream(i); - } - throw new Exception("No file with such name exists: "+subFileName); - } - /** - * @deprecated - * */ - @Override - public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception { - PipedOutputStream streamOut = new PipedOutputStream(); - PipedInputStream streamInp = new PipedInputStream(streamOut); - - Thread workerThread = new Thread(() -> { - try { - PFS0subFile subFile = pfs0subFiles[subFileNumber]; - - if (encrypted) - createAesCtrEncryptedBufferedInputStream(); - else - createBufferedInputStream(); - - long subFileSize = subFile.getSize(); - - long toSkip = subFile.getOffset() + mediaStartOffset * 0x200 + rawBlockDataStart; - if (toSkip != stream.skip(toSkip)) - throw new Exception("Unable to skip offset: " + toSkip); - - int blockSize = 0x200; - if (subFileSize < 0x200) - blockSize = (int) subFileSize; - - long i = 0; - byte[] block = new byte[blockSize]; - - int actuallyRead; - while (true) { - if ((actuallyRead = stream.read(block)) != blockSize) - throw new Exception("Read failure. Block Size: " + blockSize + ", actuallyRead: " + actuallyRead); - streamOut.write(block); - i += blockSize; - if ((i + blockSize) > subFileSize) { - blockSize = (int) (subFileSize - i); - if (blockSize == 0) - break; - block = new byte[blockSize]; - } - } - } - catch (Exception e){ - log.error(e); - } - }); - workerThread.start(); - return streamInp; - } - - public LinkedList getPfs0SHA256hashes() { return pfs0SHA256hashes; } + @Override + public File getFile() { + return producer.getFile(); + } + public void printDebug(){ log.debug(".:: PFS0Provider ::.\n" + - "File name: " + file.getName() + "\n" + + "File name: " + getFile().getName() + "\n" + "Raw block data start " + RainbowDump.formatDecHexString(rawBlockDataStart) + "\n" + - "Magic " + magic + "\n" + - "Files count " + RainbowDump.formatDecHexString(filesCount) + "\n" + - "String Table Size " + RainbowDump.formatDecHexString(stringTableSize) + "\n" + - "Padding " + Converter.byteArrToHexString(padding) + "\n\n" + - "Offset position in file " + RainbowDump.formatDecHexString(offsetPositionInFile) + "\n" + - "Media Start Offset " + RainbowDump.formatDecHexString(mediaStartOffset) + "\n" + - "Media End Offset " + RainbowDump.formatDecHexString(mediaEndOffset) + "\n" + "Media Start Offset " + RainbowDump.formatDecHexString(mediaStartOffset) + "\n" ); - for (PFS0subFile subFile : pfs0subFiles){ - log.debug( - "\nName: " + subFile.getName() + "\n" + - "Offset " + RainbowDump.formatDecHexString(subFile.getOffset()) + "\n" + - "Size " + RainbowDump.formatDecHexString(subFile.getSize()) + "\n" + - "Zeroes " + Converter.byteArrToHexString(subFile.getZeroes()) + "\n" + - "----------------------------------------------------------------" - ); - } + header.printDebug(); } } \ No newline at end of file diff --git a/src/main/java/libKonogonka/Tools/RomFs/IRomFsProvider.java b/src/main/java/libKonogonka/Tools/RomFs/IRomFsProvider.java deleted file mode 100644 index 8e5f84a..0000000 --- a/src/main/java/libKonogonka/Tools/RomFs/IRomFsProvider.java +++ /dev/null @@ -1,32 +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 java.io.File; -import java.io.PipedInputStream; - -public interface IRomFsProvider { - File getFile(); - long getLevel6Offset(); - Level6Header getHeader(); - FileSystemEntry getRootEntry(); - PipedInputStream getContent(FileSystemEntry entry) throws Exception; - void printDebug(); -} diff --git a/src/main/java/libKonogonka/Tools/RomFs/RomFsConstruct.java b/src/main/java/libKonogonka/Tools/RomFs/RomFsConstruct.java index 0cc329a..e9047bf 100644 --- a/src/main/java/libKonogonka/Tools/RomFs/RomFsConstruct.java +++ b/src/main/java/libKonogonka/Tools/RomFs/RomFsConstruct.java @@ -19,16 +19,11 @@ package libKonogonka.Tools.RomFs; import libKonogonka.Converter; -import libKonogonka.ctraes.AesCtrBufferedInputStream; -import libKonogonka.ctraes.AesCtrDecryptSimple; +import libKonogonka.ctraes.InFileStreamProducer; import java.io.BufferedInputStream; -import java.io.File; -import java.nio.file.Files; public class RomFsConstruct { - //private final static Logger log = LogManager.getLogger(RomFsConstruct.class); - private Level6Header header; private FileSystemEntry rootEntry; @@ -37,55 +32,29 @@ public class RomFsConstruct { private byte[] directoryMetadataTable; private byte[] fileMetadataTable; - - private final File file; - private long offsetPositionInFile; + private final long offsetPositionInFile; private final long level6Offset; - RomFsConstruct(File file, - long level6Offset) throws Exception{ - if (level6Offset < 0) - throw new Exception("Incorrect Level 6 Offset"); - this.file = file; - this.level6Offset = level6Offset; - this.stream = new BufferedInputStream(Files.newInputStream(file.toPath())); - constructEverything(); + RomFsConstruct(InFileStreamProducer producer, long level6Offset) throws Exception{ + this(producer, level6Offset, 0); } - RomFsConstruct(File file, - long ncaOffset, // NCA offset position + RomFsConstruct(InFileStreamProducer producer, long level6Offset, - AesCtrDecryptSimple decryptor, - long mediaStartOffset, - long mediaEndOffset - ) throws Exception { + long offsetPositionInFile) throws Exception{ if (level6Offset < 0) throw new Exception("Incorrect Level 6 Offset"); - this.file = file; this.level6Offset = level6Offset; - this.offsetPositionInFile = ncaOffset + (mediaStartOffset * 0x200); - this.stream = new AesCtrBufferedInputStream( - decryptor, - ncaOffset, - mediaStartOffset, - mediaEndOffset, - Files.newInputStream(file.toPath())); - constructEverything(); - } + this.stream = producer.produce(); + this.offsetPositionInFile = offsetPositionInFile; - private void constructEverything() throws Exception{ goToStartingPosition(); - constructHeader(); - directoryMetadataTableLengthCheck(); directoryMetadataTableConstruct(); - fileMetadataTableLengthCheck(); fileMetadataTableConstruct(); - constructRootFilesystemEntry(); - stream.close(); } @@ -137,12 +106,7 @@ public class RomFsConstruct { } private void constructRootFilesystemEntry() throws Exception{ - try { - rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable); - } - catch (Exception e){ - throw new Exception("File: " + file.getName(), e); - } + rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable); } private void skipBytes(long size) throws Exception{ diff --git a/src/main/java/libKonogonka/Tools/RomFs/RomFsContentRetrieve.java b/src/main/java/libKonogonka/Tools/RomFs/RomFsContentRetrieve.java deleted file mode 100644 index 9fcd3f0..0000000 --- a/src/main/java/libKonogonka/Tools/RomFs/RomFsContentRetrieve.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - 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.AesCtrBufferedInputStream; -import libKonogonka.ctraes.AesCtrDecryptSimple; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.io.*; -import java.nio.file.Files; - -public class RomFsContentRetrieve implements Runnable{ - private final static Logger log = LogManager.getLogger(RomFsContentRetrieve.class); - - private final PipedOutputStream streamOut; - private final long internalFileSize; - private final long startPosition; - private final BufferedInputStream bis; - - RomFsContentRetrieve(File parentFile, - PipedOutputStream streamOut, - long internalFileRealPosition, - long internalFileSize) throws Exception{ - this.streamOut = streamOut; - this.internalFileSize = internalFileSize; - - this.startPosition = internalFileRealPosition; - this.bis = new BufferedInputStream(Files.newInputStream(parentFile.toPath())); - } - - RomFsContentRetrieve(File parentFile, - PipedOutputStream streamOut, - AesCtrDecryptSimple decryptor, - long entryOffset, - long internalFileSize, - long headersFileDataOffset, //level6Header.getFileDataOffset() - long level6Offset, - long ncaOffsetPosition, - long mediaStartOffset, - long mediaEndOffset - ) throws Exception{ - log.fatal("Current implementation works incorrectly"); - this.streamOut = streamOut; - this.internalFileSize = internalFileSize; - - this.startPosition = entryOffset + mediaStartOffset*0x200 + headersFileDataOffset + level6Offset; - - this.bis = new AesCtrBufferedInputStream( - decryptor, - ncaOffsetPosition, - mediaStartOffset, - mediaEndOffset, - Files.newInputStream(parentFile.toPath()) - ); - } - - @Override - public void run() { - log.trace("Executing thread"); - try { - skipBytesTillBegining(); - - int readPiece = 8388608; - long readFrom = 0; - byte[] readBuffer; - - while (readFrom < internalFileSize) { - if (internalFileSize - readFrom < readPiece) - readPiece = Math.toIntExact(internalFileSize - readFrom); - readBuffer = new byte[readPiece]; - if (bis.read(readBuffer) != readPiece) { - log.error("getContent(): Unable to read requested size from file."); - return; - } - streamOut.write(readBuffer); - readFrom += readPiece; - } - } catch (Exception exception) { - log.error("RomFsProvider -> getContent(): Unable to provide stream", exception); - } - finally { - closeStreams(); - } - log.trace("Thread died"); - } - private void skipBytesTillBegining() throws Exception{ - long mustSkip = startPosition; - long skipped = 0; - while (mustSkip > 0){ - skipped += bis.skip(mustSkip); - mustSkip = startPosition - skipped; - } - } - private void closeStreams(){ - try { - streamOut.close(); - } - catch (IOException e){ - log.error("RomFsProvider -> getContent(): Unable to close 'StreamOut'"); - } - try { - bis.close(); - } - catch (IOException e){ - log.error("RomFsProvider -> getContent(): Unable to close 'StreamOut'"); - } - } -} diff --git a/src/main/java/libKonogonka/Tools/RomFs/RomFsProvider.java b/src/main/java/libKonogonka/Tools/RomFs/RomFsProvider.java index afc18b3..7a01fca 100644 --- a/src/main/java/libKonogonka/Tools/RomFs/RomFsProvider.java +++ b/src/main/java/libKonogonka/Tools/RomFs/RomFsProvider.java @@ -18,134 +18,130 @@ */ package libKonogonka.Tools.RomFs; +import libKonogonka.Tools.PFS0.PFS0subFile; import libKonogonka.Tools.RomFs.view.DirectoryMetaTablePlainView; import libKonogonka.Tools.RomFs.view.FileMetaTablePlainView; -import libKonogonka.ctraes.AesCtrDecryptSimple; +import libKonogonka.ctraes.InFileStreamProducer; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; -import java.io.File; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; +import java.io.*; +import java.nio.file.Files; -public class RomFsProvider implements IRomFsProvider{ - private final File file; +public class RomFsProvider{ + private final static Logger log = LogManager.getLogger(RomFsProvider.class); + + private final InFileStreamProducer producer; private final long level6Offset; private final Level6Header level6Header; private final FileSystemEntry rootEntry; - - private long ncaOffsetPosition; - private byte[] key; // Used @ createDecryptor only - private byte[] sectionCTR; // Used @ createDecryptor only - private long mediaStartOffset; // Used @ createDecryptor only - private long mediaEndOffset; + private long mediaStartOffset; // Used only for debug private final byte[] directoryMetadataTable; private final byte[] fileMetadataTable; - private final boolean encryptedAesCtr; - public RomFsProvider(File decryptedFsImageFile, long level6offset) throws Exception{ - RomFsConstruct construct = new RomFsConstruct(decryptedFsImageFile, level6offset); - this.file = decryptedFsImageFile; + this.producer = new InFileStreamProducer(decryptedFsImageFile); + RomFsConstruct construct = new RomFsConstruct(producer, level6offset); this.level6Offset = level6offset; this.level6Header = construct.getHeader(); this.rootEntry = construct.getRootEntry(); - this.directoryMetadataTable = construct.getDirectoryMetadataTable(); this.fileMetadataTable = construct.getFileMetadataTable(); - - this.encryptedAesCtr = false; } - public RomFsProvider(long level6Offset, - File encryptedFsImageFile, - long ncaOffsetPosition, - byte[] key, - byte[] sectionCTR, - long mediaStartOffset, - long mediaEndOffset + public RomFsProvider(InFileStreamProducer producer, + long level6Offset, + long offsetPositionInFile, + long mediaStartOffset ) throws Exception{ - this.key = key; - this.sectionCTR = sectionCTR; + this.producer = producer; this.mediaStartOffset = mediaStartOffset; - this.mediaEndOffset = mediaEndOffset; - this.ncaOffsetPosition = ncaOffsetPosition; - - RomFsConstruct construct = new RomFsConstruct(encryptedFsImageFile, - ncaOffsetPosition, - level6Offset, - createDecryptor(), - mediaStartOffset, - mediaEndOffset); - this.file = encryptedFsImageFile; + RomFsConstruct construct = new RomFsConstruct(producer, level6Offset, offsetPositionInFile); this.level6Offset = level6Offset; this.level6Header = construct.getHeader(); this.rootEntry = construct.getRootEntry(); - this.directoryMetadataTable = construct.getDirectoryMetadataTable(); this.fileMetadataTable = construct.getFileMetadataTable(); - - this.encryptedAesCtr = true; - } - 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 level6Header;} - @Override public FileSystemEntry getRootEntry() { return rootEntry; } - @Override - public PipedInputStream getContent(FileSystemEntry entry) throws Exception { - if (encryptedAesCtr) - return getContentAesCtrEncrypted(entry); - return getContentNonEncrypted(entry); + + public boolean exportContent(String saveToLocation, FileSystemEntry entry){ + try{ + if (! saveToLocation.endsWith(File.separator)) + saveToLocation += File.separator; + + if (entry.isDirectory()) + exportFolderContent(entry, saveToLocation); + else + exportSingleFile(entry, saveToLocation); + } + catch (Exception e){ + log.error("File export failure", e); + return false; + } + return true; } - public PipedInputStream getContentAesCtrEncrypted(FileSystemEntry entry) throws Exception{ + + private void exportFolderContent(FileSystemEntry entry, String saveToLocation) throws Exception{ + File contentFile = new File(saveToLocation + entry.getName()); + contentFile.mkdirs(); + String currentDirPath = saveToLocation + entry.getName() + File.separator; + for (FileSystemEntry fileEntry : entry.getContent()){ + if (fileEntry.isDirectory()) + exportFolderContent(fileEntry, currentDirPath); + else + exportSingleFile(fileEntry, currentDirPath); + } + } + + private void exportSingleFile(FileSystemEntry entry, String saveToLocation) throws Exception { + File contentFile = new File(saveToLocation + entry.getName()); + try(BufferedOutputStream extractedFileBOS = new BufferedOutputStream(Files.newOutputStream(contentFile.toPath())); + BufferedInputStream stream = producer.produce()) { + long skipBytes = entry.getOffset() + mediaStartOffset * 0x200 + level6Header.getFileDataOffset() + level6Offset; + + if (skipBytes != stream.skip(skipBytes)) + throw new Exception("Can't skip"); + + int blockSize = 0x200; + if (entry.getSize() < 0x200) + blockSize = (int) entry.getSize(); + + long i = 0; + byte[] block = new byte[blockSize]; + + int actuallyRead; + + while (true) { + if ((actuallyRead = stream.read(block)) != blockSize) + throw new Exception("Read failure. Block Size: "+blockSize+", actuallyRead: "+actuallyRead); + extractedFileBOS.write(block); + i += blockSize; + if ((i + blockSize) >= entry.getSize()) { + blockSize = (int) (entry.getSize() - i); + if (blockSize == 0) + break; + block = new byte[blockSize]; + } + } + } + } + + public InFileStreamProducer getStreamProducer(FileSystemEntry entry) throws Exception{ if (entry.isDirectory()) - throw new Exception("Request of the binary stream for the folder entry is not supported (and doesn't make sense)."); - - PipedOutputStream streamOut = new PipedOutputStream(); - PipedInputStream streamIn = new PipedInputStream(streamOut); - long internalFileOffset = entry.getOffset(); - long internalFileSize = entry.getSize(); - - Thread contentRetrievingThread = new Thread(new RomFsContentRetrieve( - file, - streamOut, - createDecryptor(), - internalFileOffset, - internalFileSize, - level6Header.getFileDataOffset(), - level6Offset, - ncaOffsetPosition, - mediaStartOffset, - mediaEndOffset)); - contentRetrievingThread.start(); - return streamIn; - } - public PipedInputStream getContentNonEncrypted(FileSystemEntry entry) throws Exception{ - if (entry.isDirectory()) - throw new Exception("Request of the binary stream for the folder entry is not supported (and doesn't make sense)."); - - PipedOutputStream streamOut = new PipedOutputStream(); - PipedInputStream streamIn = new PipedInputStream(streamOut); - long internalFileRealPosition = level6Offset + level6Header.getFileDataOffset() + entry.getOffset(); - long internalFileSize = entry.getSize(); - - Thread contentRetrievingThread = new Thread(new RomFsContentRetrieve( - file, - streamOut, - internalFileRealPosition, - internalFileSize)); - contentRetrievingThread.start(); - return streamIn; + throw new Exception("Directory entries are not supported"); + return producer.getSuccessor( + entry.getOffset() + mediaStartOffset * 0x200 + level6Header.getFileDataOffset() + level6Offset); + } + + public File getFile(){ + return producer.getFile(); } - @Override public void printDebug(){ level6Header.printDebugInfo(); new DirectoryMetaTablePlainView(level6Header.getDirectoryMetadataTableLength(), directoryMetadataTable); diff --git a/src/main/java/libKonogonka/Tools/XCI/HFS0File.java b/src/main/java/libKonogonka/Tools/XCI/HFS0File.java index af7f055..fe53041 100644 --- a/src/main/java/libKonogonka/Tools/XCI/HFS0File.java +++ b/src/main/java/libKonogonka/Tools/XCI/HFS0File.java @@ -19,12 +19,12 @@ package libKonogonka.Tools.XCI; public class HFS0File { - private String name; - private long offset; - private long size; - private long hashedRegionSize; - private boolean padding; - private byte[] SHA256Hash; + private final String name; + private final long offset; + private final long size; + private final long hashedRegionSize; + private final boolean padding; + private final byte[] SHA256Hash; public HFS0File(String name, long offset, long size, long hashedRegionSize, boolean padding, byte[] SHA256Hash){ this.name = name; diff --git a/src/main/java/libKonogonka/Tools/XCI/HFS0Provider.java b/src/main/java/libKonogonka/Tools/XCI/HFS0Provider.java index 44ff19e..3898bff 100644 --- a/src/main/java/libKonogonka/Tools/XCI/HFS0Provider.java +++ b/src/main/java/libKonogonka/Tools/XCI/HFS0Provider.java @@ -19,12 +19,14 @@ package libKonogonka.Tools.XCI; import libKonogonka.Tools.ISuperProvider; +import libKonogonka.ctraes.InFileStreamProducer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Arrays; import static libKonogonka.Converter.*; @@ -125,74 +127,71 @@ public class HFS0Provider implements ISuperProvider { @Override public long getRawFileDataStart() { return rawFileDataStart; } public HFS0File[] getHfs0Files() { return hfs0Files; } - @Override - public File getFile(){ return file; } - /** - * @deprecated - * */ - @Override - public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception{ - PipedOutputStream streamOut = new PipedOutputStream(); - Thread workerThread; - if (subFileNumber >= hfs0Files.length) { - throw new Exception("HFS0Provider -> getHfs0FilePipedInpStream(): Requested sub file doesn't exists"); - } - PipedInputStream streamIn = new PipedInputStream(streamOut); - workerThread = new Thread(() -> { - log.trace("HFS0Provider -> getHfs0FilePipedInpStream(): Executing thread"); - try{ - long subFileRealPosition = rawFileDataStart + hfs0Files[subFileNumber].getOffset(); - BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(file.toPath())); - if (bis.skip(subFileRealPosition) != subFileRealPosition) - throw new Exception("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to skip requested offset"); - - int readPice = 0x800000; // 8mb NOTE: consider switching to 1mb 1048576 - - long readFrom = 0; - long realFileSize = hfs0Files[subFileNumber].getSize(); - - 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) - throw new Exception("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to read requested size from file."); - - streamOut.write(readBuf, 0, readPice); - readFrom += readPice; - } - bis.close(); - streamOut.close(); - } - catch (Exception ioe){ - log.error("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to provide stream"); - ioe.printStackTrace(); - } - log.trace("HFS0Provider -> getHfs0FilePipedInpStream(): Thread died"); - }); - workerThread.start(); - return streamIn; - } - //TODO @Override public boolean exportContent(String saveToLocation, String subFileName) throws Exception { - throw new Exception("Not implemented yet"); - } - //TODO - @Override - public boolean exportContent(String saveToLocation, int subFileNumber) throws Exception { - throw new Exception("Not implemented yet"); + for (int i = 0; i < hfs0Files.length; i++) { + if (hfs0Files[i].getName().equals(subFileName)) + return exportContent(saveToLocation, i); + } + throw new FileNotFoundException("No file with such name exists: " + subFileName); } @Override - public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception { + public boolean exportContent(String saveToLocation, int subFileNumber) throws Exception { + HFS0File subFile = hfs0Files[subFileNumber]; + File location = new File(saveToLocation); + location.mkdirs(); + + try (BufferedOutputStream extractedFileBOS = new BufferedOutputStream( + Files.newOutputStream(Paths.get(saveToLocation+File.separator+subFile.getName()))); + BufferedInputStream stream = getStreamProducer(subFileNumber).produce()){ + + long subFileSize = subFile.getSize(); + + int blockSize = 0x200; + if (subFileSize < 0x200) + blockSize = (int) subFileSize; + + long i = 0; + byte[] block = new byte[blockSize]; + + int actuallyRead; + while (true) { + if ((actuallyRead = stream.read(block)) != blockSize) + throw new Exception("Read failure. Block Size: "+blockSize+", actuallyRead: "+actuallyRead); + extractedFileBOS.write(block); + i += blockSize; + if ((i + blockSize) > subFileSize) { + blockSize = (int) (subFileSize - i); + if (blockSize == 0) + break; + block = new byte[blockSize]; + } + } + } + catch (Exception e){ + log.error("File export failure", e); + return false; + } + return true; + } + @Override + public InFileStreamProducer getStreamProducer(String subFileName) throws FileNotFoundException{ for (int i = 0; i < hfs0Files.length; i++){ if (hfs0Files[i].getName().equals(subFileName)) - return getProviderSubFilePipedInpStream(i); + return getStreamProducer(i); } - return null; + throw new FileNotFoundException("No file with such name exists: "+subFileName); + } + @Override + public InFileStreamProducer getStreamProducer(int subFileNumber) { + long offset = rawFileDataStart + hfs0Files[subFileNumber].getOffset(); + return new InFileStreamProducer(file, offset); + } + + @Override + public File getFile() { + return file; } } \ No newline at end of file diff --git a/src/main/java/libKonogonka/Tools/XCI/XCIGamecardHeader.java b/src/main/java/libKonogonka/Tools/XCI/XCIGamecardHeader.java index d1f85a9..726c976 100644 --- a/src/main/java/libKonogonka/Tools/XCI/XCIGamecardHeader.java +++ b/src/main/java/libKonogonka/Tools/XCI/XCIGamecardHeader.java @@ -18,9 +18,9 @@ */ package libKonogonka.Tools.XCI; +import libKonogonka.Converter; + import java.util.Arrays; -import java.util.Collections; -import java.util.List; import static libKonogonka.Converter.getLEint; import static libKonogonka.Converter.getLElong; @@ -28,27 +28,27 @@ import static libKonogonka.Converter.getLElong; * Header information * */ public class XCIGamecardHeader{ - private byte[] rsa2048PKCS1sig; - private boolean magicHead; - private byte[] SecureAreaStartAddr; - private boolean bkupAreaStartAddr; - private byte titleKEKIndexBoth; - private byte titleKEKIndex; - private byte KEKIndex; - private byte gcSize; - private byte gcVersion; - private byte gcFlags; - private byte[] pkgID; - private long valDataEndAddr; - private byte[] gcInfoIV; - private long hfs0partOffset; - private long hfs0headerSize; - private byte[] hfs0headerSHA256; - private byte[] hfs0initDataSHA256 ; - private int secureModeFlag; - private int titleKeyFlag; - private int keyFlag; - private byte[] normAreaEndAddr; + private final byte[] rsa2048PKCS1sig; + private final boolean magicHead; + private final byte[] SecureAreaStartAddr; + private final boolean bkupAreaStartAddr; + private final byte titleKEKIndexBoth; + private final byte titleKEKIndex; + private final byte KEKIndex; + private final byte gcSize; + private final byte gcVersion; + private final byte gcFlags; + private final byte[] pkgID; + private final long valDataEndAddr; + private final byte[] gcInfoIV; + private final long hfs0partOffset; + private final long hfs0headerSize; + private final byte[] hfs0headerSHA256; + private final byte[] hfs0initDataSHA256; + private final int secureModeFlag; + private final int titleKeyFlag; + private final int keyFlag; + private final byte[] normAreaEndAddress; XCIGamecardHeader(byte[] headerBytes) throws Exception{ if (headerBytes.length != 400) @@ -65,7 +65,7 @@ public class XCIGamecardHeader{ gcFlags = headerBytes[271]; pkgID = Arrays.copyOfRange(headerBytes, 272, 280); valDataEndAddr = getLElong(headerBytes, 280); //TODO: FIX/simplify // - gcInfoIV = reverseBytes(Arrays.copyOfRange(headerBytes, 288, 304)); + gcInfoIV = Converter.flip(Arrays.copyOfRange(headerBytes, 288, 304)); hfs0partOffset = getLElong(headerBytes, 304); hfs0headerSize = getLElong(headerBytes, 312); hfs0headerSHA256 = Arrays.copyOfRange(headerBytes, 320, 352); @@ -73,7 +73,7 @@ public class XCIGamecardHeader{ secureModeFlag = getLEint(headerBytes, 384); titleKeyFlag = getLEint(headerBytes, 388); keyFlag = getLEint(headerBytes, 392); - normAreaEndAddr = Arrays.copyOfRange(headerBytes, 396, 400); + normAreaEndAddress = Arrays.copyOfRange(headerBytes, 396, 400); } public byte[] getRsa2048PKCS1sig() { return rsa2048PKCS1sig; } @@ -83,67 +83,21 @@ public class XCIGamecardHeader{ public byte getTitleKEKIndexBoth() { return titleKEKIndexBoth; } public byte getTitleKEKIndex() { return titleKEKIndex; } public byte getKEKIndex() { return KEKIndex; } - - public byte getGcSize() { - return gcSize; - } - public byte getGcVersion() { - return gcVersion; - } - public byte getGcFlags() { - return gcFlags; - } - public byte[] getPkgID() { - return pkgID; - } - public long getValDataEndAddr() { - return valDataEndAddr; - } - public byte[] getGcInfoIV() { - return gcInfoIV; - } - public long getHfs0partOffset() { - return hfs0partOffset; - } - public long getHfs0headerSize() { - return hfs0headerSize; - } - public byte[] getHfs0headerSHA256() { - return hfs0headerSHA256; - } - public byte[] getHfs0initDataSHA256() { - return hfs0initDataSHA256; - } - public int getSecureModeFlag() { - return secureModeFlag; - } - public boolean isSecureModeFlagOk(){ - return secureModeFlag == 1; - } - public int getTitleKeyFlag() { - return titleKeyFlag; - } - public boolean istitleKeyFlagOk(){ - return titleKeyFlag == 2; - } - public int getKeyFlag() { - return keyFlag; - } - public boolean iskeyFlagOk(){ - return keyFlag == 0; - } - public byte[] getNormAreaEndAddr() { - return normAreaEndAddr; - } - - private byte[] reverseBytes(byte[] bArr){ - Byte[] objArr = new Byte[bArr.length]; - for (int i=0;i < bArr.length; i++) - objArr[i] = bArr[i]; - List bytesList = Arrays.asList(objArr); - Collections.reverse(bytesList); - for (int i=0;i < bArr.length; i++) - bArr[i] = objArr[i]; - return bArr; - } + public byte getGcSize() { return gcSize; } + public byte getGcVersion() { return gcVersion; } + public byte getGcFlags() { return gcFlags; } + public byte[] getPkgID() { return pkgID; } + public long getValDataEndAddr() { return valDataEndAddr; } + public byte[] getGcInfoIV() { return gcInfoIV; } + public long getHfs0partOffset() { return hfs0partOffset; } + public long getHfs0headerSize() { return hfs0headerSize; } + public byte[] getHfs0headerSHA256() { return hfs0headerSHA256; } + public byte[] getHfs0initDataSHA256() { return hfs0initDataSHA256; } + public int getSecureModeFlag() { return secureModeFlag; } + public boolean isSecureModeFlagOk(){ return secureModeFlag == 1; } + public int getTitleKeyFlag() { return titleKeyFlag; } + public boolean istitleKeyFlagOk(){ return titleKeyFlag == 2; } + public int getKeyFlag() { return keyFlag; } + public boolean iskeyFlagOk(){ return keyFlag == 0; } + public byte[] getNormAreaEndAddr() { return normAreaEndAddress; } } \ No newline at end of file diff --git a/src/main/java/libKonogonka/Tools/XCI/XCIProvider.java b/src/main/java/libKonogonka/Tools/XCI/XCIProvider.java index cf25ad3..662d4bd 100644 --- a/src/main/java/libKonogonka/Tools/XCI/XCIProvider.java +++ b/src/main/java/libKonogonka/Tools/XCI/XCIProvider.java @@ -27,14 +27,14 @@ public class XCIProvider{ // TODO: Since LOGO partition added, we have to handle it properly. Is it works?? //private BufferedInputStream xciBIS; - private XCIGamecardHeader xciGamecardHeader; - private XCIGamecardInfo xciGamecardInfo; - private XCIGamecardCert xciGamecardCert; - private HFS0Provider hfs0ProviderMain, - hfs0ProviderUpdate, - hfs0ProviderNormal, - hfs0ProviderSecure, - hfs0ProviderLogo; + private final XCIGamecardHeader xciGamecardHeader; + private final XCIGamecardInfo xciGamecardInfo; + private final XCIGamecardCert xciGamecardCert; + private final HFS0Provider hfs0ProviderMain; + private HFS0Provider hfs0ProviderUpdate; + private HFS0Provider hfs0ProviderNormal; + private HFS0Provider hfs0ProviderSecure; + private HFS0Provider hfs0ProviderLogo; public XCIProvider(File file, String XCI_HEADER_KEY) throws Exception{ // TODO: ADD FILE SIZE CHECK !!! Check xciHdrKey RandomAccessFile raf; diff --git a/src/main/java/libKonogonka/ctraes/InFileStreamProducer.java b/src/main/java/libKonogonka/ctraes/InFileStreamProducer.java index 8876f1e..b770406 100644 --- a/src/main/java/libKonogonka/ctraes/InFileStreamProducer.java +++ b/src/main/java/libKonogonka/ctraes/InFileStreamProducer.java @@ -22,10 +22,8 @@ import java.io.BufferedInputStream; import java.io.File; import java.nio.file.Files; -// TODO: rework, simplify public class InFileStreamProducer { - private final boolean encrypted; - private final long size; + private boolean encrypted; private final File file; private final long initialOffset; @@ -34,50 +32,35 @@ public class InFileStreamProducer { private long mediaStartOffset; private long mediaEndOffset; + public InFileStreamProducer(File file){ + this.file = file; + this.initialOffset = 0; + this.subOffset = 0; + } + public InFileStreamProducer(File file, long subOffset){ + this.file = file; + this.initialOffset = 0; + this.subOffset = subOffset; + } public InFileStreamProducer( File file, - long size, long initialOffset, long subOffset, AesCtrDecryptSimple decryptor, long mediaStartOffset, - long mediaEndOffset - ){ + long mediaEndOffset){ this.encrypted = (decryptor != null); this.file = file; - this.size = size; this.initialOffset = initialOffset; this.subOffset = subOffset; this.decryptor = decryptor; this.mediaStartOffset = mediaStartOffset; this.mediaEndOffset = mediaEndOffset; } - public InFileStreamProducer(File file, long size, long initialOffset, long subOffset){ - this.encrypted = false; - this.file = file; - this.size = size; - this.initialOffset = 0; - this.subOffset = initialOffset+subOffset; - } - public InFileStreamProducer(File file, long size){ - this.encrypted = false; - this.file = file; - this.size = size; - this.initialOffset = 0; - this.subOffset = 0; - } - public InFileStreamProducer(File file, long size, long subOffset){ - this.encrypted = false; - this.file = file; - this.size = size; - this.initialOffset = 0; - this.subOffset = subOffset; - } public BufferedInputStream produce() throws Exception{ - if (encrypted) { + if (encrypted) return produceAesCtr(); - } return produceNotEncrypted(); } private AesCtrBufferedInputStream produceAesCtr() throws Exception{ @@ -88,30 +71,34 @@ public class InFileStreamProducer { mediaStartOffset, mediaEndOffset, Files.newInputStream(file.toPath())); - if (subOffset != stream.skip(subOffset)) - throw new Exception("Unable to skip offset: " + subOffset); + skipBytesTillBeginning(stream, subOffset); return stream; } private BufferedInputStream produceNotEncrypted() throws Exception{ BufferedInputStream stream = new BufferedInputStream(Files.newInputStream(file.toPath())); - if (subOffset != stream.skip(subOffset)) - throw new Exception("Unable to skip offset: " + subOffset); + skipBytesTillBeginning(stream, subOffset); return stream; } - public InFileStreamProducer getSuccessor(long subOffset, long size){ + public InFileStreamProducer getSuccessor(long subOffset){ this.subOffset = subOffset; - return new InFileStreamProducer(file, size, initialOffset, subOffset, decryptor, mediaStartOffset, mediaEndOffset); + return new InFileStreamProducer(file, initialOffset, subOffset, decryptor, mediaStartOffset, mediaEndOffset); } public boolean isEncrypted() { return encrypted; } - public long getSize() { - return size; + private void skipBytesTillBeginning(BufferedInputStream stream, long size) throws Exception{ + long mustSkip = size; + long skipped = 0; + while (mustSkip > 0){ + skipped += stream.skip(mustSkip); + mustSkip = size - skipped; + } } + public File getFile(){ return file; } @Override public String toString(){ return file.getName(); diff --git a/src/test/java/libKonogonka/RomFsDecrypted/KeyChainHolderTest.java b/src/test/java/libKonogonka/RomFsDecrypted/KeyChainHolderTest.java index 99bb998..7805299 100644 --- a/src/test/java/libKonogonka/RomFsDecrypted/KeyChainHolderTest.java +++ b/src/test/java/libKonogonka/RomFsDecrypted/KeyChainHolderTest.java @@ -19,6 +19,7 @@ package libKonogonka.RomFsDecrypted; import libKonogonka.KeyChainHolder; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -31,7 +32,7 @@ public class KeyChainHolderTest { private static final String xci_header_keyFileLocation = "./FilesForTests/xci_header_key.txt"; private KeyChainHolder keyChainHolder; - //@Disabled + @Disabled @DisplayName("Key Chain Holder Test") @Test void keysChain() throws Exception{ diff --git a/src/test/java/libKonogonka/RomFsDecrypted/NSOTest.java b/src/test/java/libKonogonka/RomFsDecrypted/NSOTest.java index e0c9a24..1c3d573 100644 --- a/src/test/java/libKonogonka/RomFsDecrypted/NSOTest.java +++ b/src/test/java/libKonogonka/RomFsDecrypted/NSOTest.java @@ -22,7 +22,7 @@ import libKonogonka.KeyChainHolder; import libKonogonka.RainbowDump; import libKonogonka.Tools.NCA.NCAProvider; import libKonogonka.Tools.NSO.NSO0Provider; -import libKonogonka.Tools.PFS0.IPFS0Provider; +import libKonogonka.Tools.PFS0.PFS0Provider; import libKonogonka.Tools.PFS0.PFS0subFile; import libKonogonka.ctraes.AesCtrDecryptSimple; import org.junit.jupiter.api.Disabled; @@ -83,7 +83,7 @@ public class NSOTest { void nso0Validation() throws Exception{ File nca = new File(ncaFileLocation); - PFS0subFile[] subfiles = ncaProvider.getNCAContentProvider(0).getPfs0().getPfs0subFiles(); + PFS0subFile[] subfiles = ncaProvider.getNCAContentProvider(0).getPfs0().getHeader().getPfs0subFiles(); offsetPosition = ncaProvider.getTableEntry0().getMediaStartOffset()*0x200 + ncaProvider.getNCAContentProvider(0).getPfs0().getRawFileDataStart(); @@ -111,7 +111,8 @@ public class NSOTest { ncaProvider.getSectionBlock0().getSectionCTR(), ncaProvider.getTableEntry0().getMediaStartOffset() * 0x200); - IPFS0Provider pfs0Provider = ncaProvider.getNCAContentProvider(0).getPfs0(); + PFS0Provider pfs0Provider = ncaProvider.getNCAContentProvider(0).getPfs0(); + pfs0Provider.printDebug(); NSO0Provider nso0Provider = new NSO0Provider(pfs0Provider.getStreamProducer(0)); nso0Provider.printDebug(); diff --git a/src/test/java/libKonogonka/RomFsDecrypted/NSPpfs0EncryptedTest.java b/src/test/java/libKonogonka/RomFsDecrypted/NSPpfs0EncryptedTest.java index 35f4598..c171702 100644 --- a/src/test/java/libKonogonka/RomFsDecrypted/NSPpfs0EncryptedTest.java +++ b/src/test/java/libKonogonka/RomFsDecrypted/NSPpfs0EncryptedTest.java @@ -73,7 +73,7 @@ public class NSPpfs0EncryptedTest { pfs0Provider = new PFS0Provider(nspFile); - for (PFS0subFile subFile : pfs0Provider.getPfs0subFiles()) { + for (PFS0subFile subFile : pfs0Provider.getHeader().getPfs0subFiles()) { if (subFile.getName().endsWith("890.nca")) { System.out.println("File found: "+subFile.getName()); ncaProvider = new NCAProvider(nspFile, finalKeysSet, pfs0Provider.getRawFileDataStart()+subFile.getOffset()); @@ -109,7 +109,7 @@ public class NSPpfs0EncryptedTest { void AesCtrBufferedInputStreamTest() throws Exception { File nca = new File(nspFileLocation); // TODO:NOTICE - PFS0subFile[] subfiles = ncaProvider.getNCAContentProvider(0).getPfs0().getPfs0subFiles(); + PFS0subFile[] subfiles = ncaProvider.getNCAContentProvider(0).getPfs0().getHeader().getPfs0subFiles(); offsetPosition = ncaProvider.getTableEntry0().getMediaStartOffset()*0x200 + ncaProvider.getNCAContentProvider(0).getPfs0().getRawFileDataStart(); @@ -207,7 +207,10 @@ public class NSPpfs0EncryptedTest { File contentFile = new File(saveToLocation + entry.getName()); BufferedOutputStream extractedFileBOS = new BufferedOutputStream(new FileOutputStream(contentFile)); - PipedInputStream pis = ncaProvider.getNCAContentProvider(0).getPfs0().getProviderSubFilePipedInpStream(entry.getName()); + BufferedInputStream pis = ncaProvider.getNCAContentProvider(0) + .getPfs0() + .getStreamProducer(entry.getName()) + .produce(); byte[] readBuf = new byte[0x200]; // 8mb NOTE: consider switching to 1mb 1048576 int readSize; diff --git a/src/test/java/libKonogonka/RomFsDecrypted/PFS0Test.java b/src/test/java/libKonogonka/RomFsDecrypted/PFS0Test.java new file mode 100644 index 0000000..13b554c --- /dev/null +++ b/src/test/java/libKonogonka/RomFsDecrypted/PFS0Test.java @@ -0,0 +1,72 @@ +/* + 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.RainbowDump; +import libKonogonka.TitleKeyChainHolder; +import libKonogonka.Tools.NCA.NCAProvider; +import libKonogonka.Tools.PFS0.PFS0Provider; +import libKonogonka.Tools.PFS0.PFS0subFile; +import libKonogonka.ctraes.AesCtrBufferedInputStream; +import libKonogonka.ctraes.AesCtrDecryptSimple; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.*; +import java.nio.file.Files; +import java.util.HashMap; + +public class PFS0Test { + private static final String keysFileLocation = "./FilesForTests/prod.keys"; + private static final String titleFileLocation = "./FilesForTests/simple_nsp.title_key"; + private static final String xci_header_keyFileLocation = "./FilesForTests/xci_header_key.txt"; + private static final String nspFileLocation = "./FilesForTests/sample.nsp"; + private static KeyChainHolder keyChainHolder; + private static PFS0Provider pfs0Provider; + + @Disabled + @DisplayName("NSP validation") + @Test + void pfs0test() 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); + + TitleKeyChainHolder titleKeyChainHolder = new TitleKeyChainHolder(titleFileLocation); + + HashMap finalKeysSet = keyChainHolder.getRawKeySet(); + finalKeysSet.putAll(titleKeyChainHolder.getKeySet()); + + File nspFile = new File(nspFileLocation); + + pfs0Provider = new PFS0Provider(nspFile); + pfs0Provider.printDebug(); + for (PFS0subFile subFile : pfs0Provider.getHeader().getPfs0subFiles()) { + pfs0Provider.exportContent("/tmp/NSP_PFS0_NON-ENCRYPTED_TEST", subFile.getName()); + } + } +} \ No newline at end of file diff --git a/src/test/java/libKonogonka/RomFsDecrypted/Pfs0EncryptedTest.java b/src/test/java/libKonogonka/RomFsDecrypted/Pfs0EncryptedTest.java index 0858a85..8d8d150 100644 --- a/src/test/java/libKonogonka/RomFsDecrypted/Pfs0EncryptedTest.java +++ b/src/test/java/libKonogonka/RomFsDecrypted/Pfs0EncryptedTest.java @@ -21,7 +21,7 @@ package libKonogonka.RomFsDecrypted; import libKonogonka.KeyChainHolder; import libKonogonka.RainbowDump; import libKonogonka.Tools.NCA.NCAProvider; -import libKonogonka.Tools.PFS0.IPFS0Provider; +import libKonogonka.Tools.PFS0.PFS0Provider; import libKonogonka.Tools.PFS0.PFS0subFile; import libKonogonka.ctraes.AesCtrBufferedInputStream; import libKonogonka.ctraes.AesCtrDecryptSimple; @@ -80,7 +80,7 @@ public class Pfs0EncryptedTest { void AesCtrBufferedInputStreamTest() throws Exception { File nca = new File(ncaFileLocation); - PFS0subFile[] subfiles = ncaProvider.getNCAContentProvider(0).getPfs0().getPfs0subFiles(); + PFS0subFile[] subfiles = ncaProvider.getNCAContentProvider(0).getPfs0().getHeader().getPfs0subFiles(); offsetPosition = ncaProvider.getTableEntry0().getMediaStartOffset()*0x200 + ncaProvider.getNCAContentProvider(0).getPfs0().getRawFileDataStart(); @@ -113,7 +113,7 @@ public class Pfs0EncryptedTest { } */ - IPFS0Provider pfs0Provider = ncaProvider.getNCAContentProvider(0).getPfs0(); + PFS0Provider pfs0Provider = ncaProvider.getNCAContentProvider(0).getPfs0(); //---------------------------------------------------------------------- for (PFS0subFile subFile : subfiles) { System.out.println("Exporting "+subFile.getName()); @@ -181,7 +181,10 @@ public class Pfs0EncryptedTest { File contentFile = new File(saveToLocation + entry.getName()); BufferedOutputStream extractedFileBOS = new BufferedOutputStream(new FileOutputStream(contentFile)); - PipedInputStream pis = ncaProvider.getNCAContentProvider(0).getPfs0().getProviderSubFilePipedInpStream(entry.getName()); + BufferedInputStream pis = ncaProvider.getNCAContentProvider(0) + .getPfs0() + .getStreamProducer(entry.getName()) + .produce(); byte[] readBuf = new byte[0x200]; // 8mb NOTE: consider switching to 1mb 1048576 int readSize; diff --git a/src/test/java/libKonogonka/RomFsDecrypted/RomFsDecryptedTest.java b/src/test/java/libKonogonka/RomFsDecrypted/RomFsDecryptedTest.java index 91d8ff2..0bb9307 100644 --- a/src/test/java/libKonogonka/RomFsDecrypted/RomFsDecryptedTest.java +++ b/src/test/java/libKonogonka/RomFsDecrypted/RomFsDecryptedTest.java @@ -18,9 +18,11 @@ */ package libKonogonka.RomFsDecrypted; -import java.io.File; +import java.io.*; +import java.nio.file.Files; import java.nio.file.Path; +import libKonogonka.Tools.RomFs.FileSystemEntry; import libKonogonka.Tools.RomFs.RomFsProvider; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; @@ -39,13 +41,14 @@ public class RomFsDecryptedTest { RomFsProvider provider; @Disabled - @DisplayName("RomFsDecryptedProvider: tests") + @DisplayName("RomFsDecryptedProvider: Overall and export") @Test void romFsValidation() throws Exception{ makeFile(); parseLv6offsetFromFileName(); makeProvider(); provider.printDebug(); + export(); } void makeFile(){ @@ -58,12 +61,93 @@ public class RomFsDecryptedTest { provider = new RomFsProvider(decryptedFile, lv6offset); } -/* - void checkFilesWorkers(){ - assertTrue(fw1 instanceof WorkerFiles); - assertTrue(fw2 instanceof WorkerFiles); - assertTrue(fw3 instanceof WorkerFiles); + void export() throws Exception { + System.out.println("lv6offset = "+lv6offset); + + FileSystemEntry entry = provider.getRootEntry(); + System.out.print(" entry.getFileOffset(): " + entry.getOffset() + + "\n entry.getFileSize(): " + entry.getSize() + + "\nExport new......."); + exportFolderContent(entry, "/tmp/decrypted_brandnew"); + System.out.println("done"); + /*---------------------------------------------------------------------- + System.out.print("Export legacy...."); + exportFolderContentLegacy(entry, "/tmp/decrypted_legacy"); + System.out.println("done"); */ } - */ + private void exportFolderContent(FileSystemEntry entry, String saveToLocation) throws Exception{ + File contentFile = new File(saveToLocation + entry.getName()); + contentFile.mkdirs(); + String currentDirPath = saveToLocation + entry.getName() + File.separator; + for (FileSystemEntry fse : entry.getContent()){ + if (fse.isDirectory()) + exportFolderContent(fse, currentDirPath); + else + exportSingleFile(fse, currentDirPath); + } + } + private void exportSingleFile(FileSystemEntry entry, String saveToLocation) throws Exception { + File contentFile = new File(saveToLocation + entry.getName()); + try(BufferedOutputStream extractedFileBOS = new BufferedOutputStream(Files.newOutputStream(contentFile.toPath())); + BufferedInputStream stream = new BufferedInputStream(Files.newInputStream(decryptedFile.toPath()))) { + long skipBytes = entry.getOffset()+ + //ncaProvider.getTableEntry1().getMediaStartOffset()*0x200+ + provider.getHeader().getFileDataOffset()+ + lv6offset; + if (skipBytes != stream.skip(skipBytes)) + throw new Exception("Can't skip"); + + int blockSize = 0x200; + if (entry.getSize() < 0x200) + blockSize = (int) entry.getSize(); + + long i = 0; + byte[] block = new byte[blockSize]; + + int actuallyRead; + + while (true) { + if ((actuallyRead = stream.read(block)) != blockSize) + throw new Exception("Read failure. Block Size: "+blockSize+", actuallyRead: "+actuallyRead); + extractedFileBOS.write(block); + i += blockSize; + if ((i + blockSize) >= entry.getSize()) { + blockSize = (int) (entry.getSize() - i); + if (blockSize == 0) + break; + block = new byte[blockSize]; + } + } + } + } +/* + private void exportFolderContentLegacy(FileSystemEntry entry, String saveToLocation) throws Exception{ + File contentFile = new File(saveToLocation + entry.getName()); + contentFile.mkdirs(); + String currentDirPath = saveToLocation + entry.getName() + File.separator; + for (FileSystemEntry fse : entry.getContent()){ + if (fse.isDirectory()) + exportFolderContentLegacy(fse, currentDirPath); + else + exportSingleFileLegacy(fse, currentDirPath); + } + } + + private void exportSingleFileLegacy(FileSystemEntry entry, String saveToLocation) throws Exception { + File contentFile = new File(saveToLocation + entry.getName()); + + BufferedOutputStream extractedFileBOS = new BufferedOutputStream(Files.newOutputStream(contentFile.toPath())); + PipedInputStream pis = provider.getContent(entry); + + byte[] readBuf = new byte[0x200]; // 8mb NOTE: consider switching to 1mb 1048576 + int readSize; + + while ((readSize = pis.read(readBuf)) > -1) { + extractedFileBOS.write(readBuf, 0, readSize); + readBuf = new byte[0x200]; + } + + extractedFileBOS.close(); + }*/ } \ No newline at end of file diff --git a/src/test/java/libKonogonka/RomFsDecrypted/RomFsEncryptedTest.java b/src/test/java/libKonogonka/RomFsDecrypted/RomFsEncryptedTest.java index a3e5771..5d989bf 100644 --- a/src/test/java/libKonogonka/RomFsDecrypted/RomFsEncryptedTest.java +++ b/src/test/java/libKonogonka/RomFsDecrypted/RomFsEncryptedTest.java @@ -117,7 +117,7 @@ public class RomFsEncryptedTest { exportFolderContent(entry, "/tmp/brandnew"); //---------------------------------------------------------------------- - // exportFolderContentLegacy(entry, "/tmp/legacy"); + //exportFolderContentLegacy(entry, "/tmp/legacy"); } private void exportFolderContent(FileSystemEntry entry, String saveToLocation) throws Exception{ @@ -170,7 +170,7 @@ public class RomFsEncryptedTest { throw new Exception("Read failure. Block Size: "+blockSize+", actuallyRead: "+actuallyRead); extractedFileBOS.write(block); i += blockSize; - if ((i + blockSize) > entry.getSize()) { + if ((i + blockSize) >= entry.getSize()) { blockSize = (int) (entry.getSize() - i); if (blockSize == 0) break; @@ -180,7 +180,7 @@ public class RomFsEncryptedTest { //--- extractedFileBOS.close(); } - +/* private void exportFolderContentLegacy(FileSystemEntry entry, String saveToLocation) throws Exception{ File contentFile = new File(saveToLocation + entry.getName()); contentFile.mkdirs(); @@ -192,6 +192,7 @@ public class RomFsEncryptedTest { exportSingleFileLegacy(fse, currentDirPath); } } + private void exportSingleFileLegacy(FileSystemEntry entry, String saveToLocation) throws Exception { File contentFile = new File(saveToLocation + entry.getName()); @@ -207,8 +208,7 @@ public class RomFsEncryptedTest { } extractedFileBOS.close(); - } - + }*/ @Disabled @Order(6) @DisplayName("RomFsEncryptedProvider: PFS test") diff --git a/src/test/java/libKonogonka/RomFsDecrypted/XciTest.java b/src/test/java/libKonogonka/RomFsDecrypted/XciTest.java index ef042c3..1a82d56 100644 --- a/src/test/java/libKonogonka/RomFsDecrypted/XciTest.java +++ b/src/test/java/libKonogonka/RomFsDecrypted/XciTest.java @@ -90,6 +90,6 @@ public class XciTest { NCAProvider ncaProvider = new NCAProvider(xciFile, keyChainHolder.getRawKeySet(), hfs0Provider.getRawFileDataStart() + hfs0File.getOffset()); - //ncaProvider. + ncaProvider.getSectionBlock0().printDebug(); } } \ No newline at end of file