Misc fixes, start rewriting RomFS parts to make it in line with AesCtrBufferedInputStream
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
c878992cb6
commit
f058905e2f
20 changed files with 619 additions and 758 deletions
|
@ -1,12 +1,12 @@
|
||||||
# libKonogonka
|
# libKonogonka
|
||||||
|
|
||||||
[![Build Status](https://ci.redrise.ru/api/badges/desu/libKonogonka/status.svg)](https://ci.redrise.ru/desu/libKonogonka)
|
![License](https://img.shields.io/badge/License-GPLv3-blue.svg) [![Build Status](https://ci.redrise.ru/api/badges/desu/libKonogonka/status.svg)](https://ci.redrise.ru/desu/libKonogonka)
|
||||||
|
|
||||||
Library to work with NS-specific files / filesystem images. Dedicated back end part of [konogonka](https://github.com/developersu/konogonka) ([independent src location](https://git.redrise.ru/desu/konogonka))
|
Library to work with NS-specific files / filesystem images. Ex-backend of [konogonka](https://github.com/developersu/konogonka) ([independent src location](https://git.redrise.ru/desu/konogonka))
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
[GNU General Public License v3+](https://git.redrise.ru/desu/libKonogonka/LICENSE)
|
[GNU General Public License v3 or higher](https://git.redrise.ru/desu/libKonogonka/LICENSE)
|
||||||
|
|
||||||
### Used libraries & resources
|
### Used libraries & resources
|
||||||
* [Bouncy Castle](https://www.bouncycastle.org/) for Java.
|
* [Bouncy Castle](https://www.bouncycastle.org/) for Java.
|
||||||
|
|
|
@ -36,14 +36,62 @@ public class RainbowDump {
|
||||||
|
|
||||||
|
|
||||||
public static void hexDumpUTF8(byte[] byteArray){
|
public static void hexDumpUTF8(byte[] byteArray){
|
||||||
|
if (byteArray == null || byteArray.length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int k = 0;
|
||||||
|
System.out.printf("%s%08x %s", ANSI_BLUE, 0, ANSI_RESET);
|
||||||
|
for (int i = 0; i < byteArray.length; i++) {
|
||||||
|
if (k == 8)
|
||||||
|
System.out.print(" ");
|
||||||
|
if (k == 16){
|
||||||
|
System.out.print(ANSI_GREEN+"| "+ANSI_RESET);
|
||||||
|
printChars(byteArray, i);
|
||||||
|
System.out.println();
|
||||||
|
System.out.printf("%s%08x %s", ANSI_BLUE, i, ANSI_RESET);
|
||||||
|
k = 0;
|
||||||
|
}
|
||||||
|
System.out.printf("%02x ", byteArray[i]);
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
int paddingSize = 16 - (byteArray.length % 16);
|
||||||
|
if (paddingSize != 16) {
|
||||||
|
for (int i = 0; i < paddingSize; i++) {
|
||||||
|
System.out.print(" ");
|
||||||
|
}
|
||||||
|
if (paddingSize > 7) {
|
||||||
|
System.out.print(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.print(ANSI_GREEN+"| "+ANSI_RESET);
|
||||||
|
printChars(byteArray, byteArray.length);
|
||||||
|
System.out.println();
|
||||||
|
System.out.print(ANSI_RESET+new String(byteArray, StandardCharsets.UTF_8)+"\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void printChars(byte[] byteArray, int pointer){
|
||||||
|
for (int j = pointer-16; j < pointer; j++){
|
||||||
|
if ((byteArray[j] > 21) && (byteArray[j] < 126)) // man ascii
|
||||||
|
System.out.print((char) byteArray[j]);
|
||||||
|
else if (byteArray[j] == 0x0a)
|
||||||
|
System.out.print("↲"); //""
|
||||||
|
else if (byteArray[j] == 0x0d)
|
||||||
|
System.out.print("←"); // "␍"
|
||||||
|
else
|
||||||
|
System.out.print(".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void hexDumpUTF8Legacy(byte[] byteArray){
|
||||||
if (byteArray == null || byteArray.length == 0)
|
if (byteArray == null || byteArray.length == 0)
|
||||||
return;
|
return;
|
||||||
System.out.print(ANSI_BLUE);
|
System.out.print(ANSI_BLUE);
|
||||||
for (int i=0; i < byteArray.length; i++)
|
for (int i=0; i < byteArray.length; i++)
|
||||||
System.out.print(String.format("%02d-", i%100));
|
System.out.printf("%02d-", i%100);
|
||||||
System.out.println(">"+ANSI_RED+byteArray.length+ANSI_RESET);
|
System.out.println(">"+ANSI_RED+byteArray.length+ANSI_RESET);
|
||||||
for (byte b: byteArray)
|
for (byte b: byteArray)
|
||||||
System.out.print(String.format("%02x ", b));
|
System.out.printf("%02x ", b);
|
||||||
System.out.println();
|
System.out.println();
|
||||||
System.out.print(new String(byteArray, StandardCharsets.UTF_8)+"\n");
|
System.out.print(new String(byteArray, StandardCharsets.UTF_8)+"\n");
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader;
|
||||||
import libKonogonka.Tools.PFS0.IPFS0Provider;
|
import libKonogonka.Tools.PFS0.IPFS0Provider;
|
||||||
import libKonogonka.Tools.PFS0.PFS0Provider;
|
import libKonogonka.Tools.PFS0.PFS0Provider;
|
||||||
import libKonogonka.Tools.RomFs.IRomFsProvider;
|
import libKonogonka.Tools.RomFs.IRomFsProvider;
|
||||||
import libKonogonka.Tools.RomFs.RomFsEncryptedProvider;
|
import libKonogonka.Tools.RomFs.RomFsProvider;
|
||||||
import libKonogonka.ctraes.AesCtrBufferedInputStream;
|
import libKonogonka.ctraes.AesCtrBufferedInputStream;
|
||||||
import libKonogonka.ctraes.AesCtrDecryptSimple;
|
import libKonogonka.ctraes.AesCtrDecryptSimple;
|
||||||
import libKonogonka.exceptions.EmptySectionException;
|
import libKonogonka.exceptions.EmptySectionException;
|
||||||
|
@ -32,7 +32,6 @@ import org.apache.logging.log4j.Logger;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.LinkedList;
|
|
||||||
|
|
||||||
public class NCAContent {
|
public class NCAContent {
|
||||||
private final static Logger log = LogManager.getLogger(NCAContent.class);
|
private final static Logger log = LogManager.getLogger(NCAContent.class);
|
||||||
|
@ -43,7 +42,6 @@ public class NCAContent {
|
||||||
private final NCAHeaderTableEntry ncaHeaderTableEntry;
|
private final NCAHeaderTableEntry ncaHeaderTableEntry;
|
||||||
private final byte[] decryptedKey;
|
private final byte[] decryptedKey;
|
||||||
|
|
||||||
private LinkedList<byte[]> Pfs0SHA256hashes;
|
|
||||||
private IPFS0Provider pfs0;
|
private IPFS0Provider pfs0;
|
||||||
private IRomFsProvider romfs;
|
private IRomFsProvider romfs;
|
||||||
|
|
||||||
|
@ -59,8 +57,6 @@ public class NCAContent {
|
||||||
this.ncaFsHeader = ncaFsHeader;
|
this.ncaFsHeader = ncaFsHeader;
|
||||||
this.ncaHeaderTableEntry = ncaHeaderTableEntry;
|
this.ncaHeaderTableEntry = ncaHeaderTableEntry;
|
||||||
this.decryptedKey = decryptedKey;
|
this.decryptedKey = decryptedKey;
|
||||||
System.out.println("NCAContent pfs0offsetPosition: "+ncaOffsetPosition);
|
|
||||||
Pfs0SHA256hashes = new LinkedList<>();
|
|
||||||
// If nothing to do
|
// If nothing to do
|
||||||
if (ncaHeaderTableEntry.getMediaEndOffset() == 0)
|
if (ncaHeaderTableEntry.getMediaEndOffset() == 0)
|
||||||
throw new EmptySectionException("Empty section");
|
throw new EmptySectionException("Empty section");
|
||||||
|
@ -91,7 +87,6 @@ public class NCAContent {
|
||||||
ncaFsHeader.getSuperBlockPFS0(),
|
ncaFsHeader.getSuperBlockPFS0(),
|
||||||
ncaHeaderTableEntry.getMediaStartOffset(),
|
ncaHeaderTableEntry.getMediaStartOffset(),
|
||||||
ncaHeaderTableEntry.getMediaEndOffset());
|
ncaHeaderTableEntry.getMediaEndOffset());
|
||||||
Pfs0SHA256hashes = pfs0.getPfs0SHA256hashes();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void proceedPFS0Encrypted() throws Exception{
|
private void proceedPFS0Encrypted() throws Exception{
|
||||||
|
@ -103,16 +98,15 @@ public class NCAContent {
|
||||||
decryptor,
|
decryptor,
|
||||||
ncaHeaderTableEntry.getMediaStartOffset(),
|
ncaHeaderTableEntry.getMediaStartOffset(),
|
||||||
ncaHeaderTableEntry.getMediaEndOffset());
|
ncaHeaderTableEntry.getMediaEndOffset());
|
||||||
Pfs0SHA256hashes = pfs0.getPfs0SHA256hashes();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void proceedRomFs() throws Exception{
|
private void proceedRomFs() throws Exception{
|
||||||
switch (ncaFsHeader.getCryptoType()){
|
switch (ncaFsHeader.getCryptoType()){
|
||||||
case 0x01:
|
case 0x01:
|
||||||
proceedRomFsNotEncrypted(); // IF NO ENCRYPTION
|
proceedRomFsNotEncrypted();
|
||||||
break;
|
break;
|
||||||
case 0x03:
|
case 0x03:
|
||||||
proceedRomFsEncrypted(); // If encrypted regular [ 0x03 ]
|
proceedRomFsEncrypted();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Exception("Non-supported 'Crypto type'");
|
throw new Exception("Non-supported 'Crypto type'");
|
||||||
|
@ -125,7 +119,7 @@ public class NCAContent {
|
||||||
if (decryptedKey == null)
|
if (decryptedKey == null)
|
||||||
throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided.");
|
throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided.");
|
||||||
|
|
||||||
this.romfs = new RomFsEncryptedProvider(
|
this.romfs = new RomFsProvider(
|
||||||
ncaFsHeader.getSuperBlockIVFC().getLvl6Offset(),
|
ncaFsHeader.getSuperBlockIVFC().getLvl6Offset(),
|
||||||
file,
|
file,
|
||||||
ncaOffsetPosition,
|
ncaOffsetPosition,
|
||||||
|
|
|
@ -29,8 +29,6 @@ import javax.crypto.Cipher;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
|
|
@ -33,8 +33,7 @@ import java.nio.file.Paths;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
import static libKonogonka.Converter.getLEint;
|
import static libKonogonka.Converter.*;
|
||||||
import static libKonogonka.Converter.getLElong;
|
|
||||||
|
|
||||||
public class PFS0Provider implements IPFS0Provider{
|
public class PFS0Provider implements IPFS0Provider{
|
||||||
private final static Logger log = LogManager.getLogger(PFS0Provider.class);
|
private final static Logger log = LogManager.getLogger(PFS0Provider.class);
|
||||||
|
@ -295,11 +294,68 @@ public class PFS0Provider implements IPFS0Provider{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: REMOVE
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* */
|
||||||
@Override
|
@Override
|
||||||
public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception {return null;}
|
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
|
@Override
|
||||||
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception {return null;}
|
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<byte[]> getPfs0SHA256hashes() {
|
public LinkedList<byte[]> getPfs0SHA256hashes() {
|
||||||
|
|
|
@ -27,7 +27,7 @@ import org.apache.logging.log4j.Logger;
|
||||||
* This class stores information contained in Level 6 Header of the RomFS image
|
* This class stores information contained in Level 6 Header of the RomFS image
|
||||||
* ------------------------------------
|
* ------------------------------------
|
||||||
* | Header Length (usually 0x50) |
|
* | Header Length (usually 0x50) |
|
||||||
* | Directory Hash Table Offset | Not used by this library | '<< 32' to get real offset: see implementation
|
* | Directory Hash Table Offset | Not used by this library
|
||||||
* | Directory Hash Table Length | Not used by this library
|
* | Directory Hash Table Length | Not used by this library
|
||||||
* | Directory Metadata Table Offset |
|
* | Directory Metadata Table Offset |
|
||||||
* | Directory Metadata Table Length |
|
* | Directory Metadata Table Length |
|
||||||
|
@ -42,7 +42,7 @@ public class Level6Header {
|
||||||
private final static Logger log = LogManager.getLogger(Level6Header.class);
|
private final static Logger log = LogManager.getLogger(Level6Header.class);
|
||||||
|
|
||||||
private final long headerLength;
|
private final long headerLength;
|
||||||
private long directoryHashTableOffset;
|
private final long directoryHashTableOffset;
|
||||||
private final long directoryHashTableLength;
|
private final long directoryHashTableLength;
|
||||||
private final long directoryMetadataTableOffset;
|
private final long directoryMetadataTableOffset;
|
||||||
private final long directoryMetadataTableLength;
|
private final long directoryMetadataTableLength;
|
||||||
|
@ -61,7 +61,7 @@ public class Level6Header {
|
||||||
throw new Exception("Level 6 Header section is too small");
|
throw new Exception("Level 6 Header section is too small");
|
||||||
headerLength = getNext();
|
headerLength = getNext();
|
||||||
directoryHashTableOffset = getNext();
|
directoryHashTableOffset = getNext();
|
||||||
directoryHashTableOffset <<= 32;
|
//directoryHashTableOffset <<= 32;
|
||||||
directoryHashTableLength = getNext();
|
directoryHashTableLength = getNext();
|
||||||
directoryMetadataTableOffset = getNext();
|
directoryMetadataTableOffset = getNext();
|
||||||
directoryMetadataTableLength = getNext();
|
directoryMetadataTableLength = getNext();
|
||||||
|
@ -73,7 +73,7 @@ public class Level6Header {
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getNext(){
|
private long getNext(){
|
||||||
final long result = Converter.getLEint(headerBytes, _cursor);
|
final long result = Converter.getLElongOfInt(headerBytes, _cursor);
|
||||||
_cursor += 0x8;
|
_cursor += 0x8;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
160
src/main/java/libKonogonka/Tools/RomFs/RomFsConstruct.java
Normal file
160
src/main/java/libKonogonka/Tools/RomFs/RomFsConstruct.java
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
/*
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package libKonogonka.Tools.RomFs;
|
||||||
|
|
||||||
|
import libKonogonka.Converter;
|
||||||
|
import libKonogonka.ctraes.AesCtrBufferedInputStream;
|
||||||
|
import libKonogonka.ctraes.AesCtrDecryptSimple;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
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;
|
||||||
|
private final BufferedInputStream stream;
|
||||||
|
private int headerSize;
|
||||||
|
private byte[] directoryMetadataTable;
|
||||||
|
private byte[] fileMetadataTable;
|
||||||
|
|
||||||
|
|
||||||
|
private final File file;
|
||||||
|
private 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(File file,
|
||||||
|
long ncaOffset, // NCA offset position
|
||||||
|
long level6Offset,
|
||||||
|
AesCtrDecryptSimple decryptor,
|
||||||
|
long mediaStartOffset,
|
||||||
|
long mediaEndOffset
|
||||||
|
) throws Exception {
|
||||||
|
if (level6Offset < 0)
|
||||||
|
throw new Exception("Incorrect Level 6 Offset");
|
||||||
|
this.file = file;
|
||||||
|
this.level6Offset = level6Offset;
|
||||||
|
this.offsetPositionInFile = ncaOffset + (mediaStartOffset * 0x200);
|
||||||
|
// In 512-blocks
|
||||||
|
// In 512-blocks
|
||||||
|
this.stream = new AesCtrBufferedInputStream(
|
||||||
|
decryptor,
|
||||||
|
ncaOffset,
|
||||||
|
mediaStartOffset,
|
||||||
|
mediaEndOffset,
|
||||||
|
Files.newInputStream(file.toPath()));
|
||||||
|
constructEverything();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void constructEverything() throws Exception{
|
||||||
|
goToStartingPosition();
|
||||||
|
|
||||||
|
constructHeader();
|
||||||
|
|
||||||
|
directoryMetadataTableLengthCheck();
|
||||||
|
directoryMetadataTableConstruct();
|
||||||
|
|
||||||
|
fileMetadataTableLengthCheck();
|
||||||
|
fileMetadataTableConstruct();
|
||||||
|
|
||||||
|
constructRootFilesystemEntry();
|
||||||
|
|
||||||
|
stream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void goToStartingPosition() throws Exception{
|
||||||
|
skipBytes(offsetPositionInFile + level6Offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void constructHeader() throws Exception{
|
||||||
|
byte[] headerSizeBytes = detectHeaderSize();
|
||||||
|
byte[] rawDataChunk = new byte[headerSize-0x8];
|
||||||
|
|
||||||
|
if (stream.read(rawDataChunk) != headerSize-0x8)
|
||||||
|
throw new Exception(String.format("Failed to read header (0x%x)", (headerSize-0x8)));
|
||||||
|
byte[] lv6headerBytes = new byte[headerSize];
|
||||||
|
System.arraycopy(headerSizeBytes, 0, lv6headerBytes, 0, 0x8);
|
||||||
|
System.arraycopy(rawDataChunk, 0, lv6headerBytes, 0x8, headerSize-0x8);
|
||||||
|
this.header = new Level6Header(lv6headerBytes);
|
||||||
|
}
|
||||||
|
private byte[] detectHeaderSize() throws Exception{
|
||||||
|
byte[] lv6HeaderSizeRaw = new byte[0x8];
|
||||||
|
if (stream.read(lv6HeaderSizeRaw) != 0x8)
|
||||||
|
throw new Exception("Failed to read header size");
|
||||||
|
headerSize = Converter.getLEint(lv6HeaderSizeRaw, 0);
|
||||||
|
return lv6HeaderSizeRaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void directoryMetadataTableLengthCheck() throws Exception{
|
||||||
|
if (header.getDirectoryMetadataTableLength() < 0)
|
||||||
|
throw new Exception("Not supported: DirectoryMetadataTableLength < 0");
|
||||||
|
}
|
||||||
|
private void directoryMetadataTableConstruct() throws Exception{
|
||||||
|
skipBytes(header.getDirectoryMetadataTableOffset() - headerSize);
|
||||||
|
|
||||||
|
directoryMetadataTable = new byte[(int) header.getDirectoryMetadataTableLength()];
|
||||||
|
if (stream.read(directoryMetadataTable) != (int) header.getDirectoryMetadataTableLength())
|
||||||
|
throw new Exception("Failed to read "+header.getDirectoryMetadataTableLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fileMetadataTableLengthCheck() throws Exception{
|
||||||
|
if (header.getFileMetadataTableLength() < 0)
|
||||||
|
throw new Exception("Not supported: FileMetadataTableLength < 0");
|
||||||
|
}
|
||||||
|
private void fileMetadataTableConstruct() throws Exception{
|
||||||
|
skipBytes(header.getFileMetadataTableOffset() - header.getFileHashTableOffset());
|
||||||
|
|
||||||
|
fileMetadataTable = new byte[(int) header.getFileMetadataTableLength()];
|
||||||
|
if (stream.read(fileMetadataTable) != (int) header.getFileMetadataTableLength())
|
||||||
|
throw new Exception("Failed to read "+header.getFileMetadataTableLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void constructRootFilesystemEntry() throws Exception{
|
||||||
|
rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void skipBytes(long size) throws Exception{
|
||||||
|
long mustSkip = size;
|
||||||
|
long skipped = 0;
|
||||||
|
while (mustSkip > 0){
|
||||||
|
skipped += stream.skip(mustSkip);
|
||||||
|
mustSkip = size - skipped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Level6Header getHeader() { return header; }
|
||||||
|
FileSystemEntry getRootEntry(){ return rootEntry; }
|
||||||
|
byte[] getDirectoryMetadataTable() { return directoryMetadataTable; }
|
||||||
|
byte[] getFileMetadataTable() { return fileMetadataTable;}
|
||||||
|
}
|
125
src/main/java/libKonogonka/Tools/RomFs/RomFsContentRetrieve.java
Normal file
125
src/main/java/libKonogonka/Tools/RomFs/RomFsContentRetrieve.java
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
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'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,121 +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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package libKonogonka.Tools.RomFs;
|
|
||||||
|
|
||||||
import libKonogonka.Converter;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
/**
|
|
||||||
* Construct header for RomFs and obtain root fileSystemEntry (meta information)
|
|
||||||
* */
|
|
||||||
class RomFsDecryptedConstruct {
|
|
||||||
private Level6Header header;
|
|
||||||
|
|
||||||
private FileSystemEntry rootEntry;
|
|
||||||
private final BufferedInputStream fileBufferedInputStream;
|
|
||||||
private int headerSize;
|
|
||||||
private byte[] directoryMetadataTable;
|
|
||||||
private byte[] fileMetadataTable;
|
|
||||||
|
|
||||||
RomFsDecryptedConstruct(File decryptedFsImageFile, long level6offset) throws Exception{
|
|
||||||
if (level6offset < 0)
|
|
||||||
throw new Exception("Incorrect Level 6 Offset");
|
|
||||||
|
|
||||||
fileBufferedInputStream = new BufferedInputStream(Files.newInputStream(decryptedFsImageFile.toPath()));
|
|
||||||
fastForwardBySkippingBytes(level6offset);
|
|
||||||
|
|
||||||
detectHeaderSize();
|
|
||||||
constructHeader();
|
|
||||||
|
|
||||||
fastForwardBySkippingBytes(header.getDirectoryMetadataTableOffset() - headerSize);
|
|
||||||
|
|
||||||
directoryMetadataTableLengthCheck();
|
|
||||||
directoryMetadataTableConstruct();
|
|
||||||
|
|
||||||
fastForwardBySkippingBytes(header.getFileMetadataTableOffset() - header.getFileHashTableOffset());
|
|
||||||
|
|
||||||
fileMetadataTableLengthCheck();
|
|
||||||
fileMetadataTableConstruct();
|
|
||||||
|
|
||||||
constructRootFilesystemEntry();
|
|
||||||
|
|
||||||
fileBufferedInputStream.close();
|
|
||||||
}
|
|
||||||
private void detectHeaderSize() throws Exception{
|
|
||||||
fileBufferedInputStream.mark(0x10);
|
|
||||||
byte[] lv6HeaderSizeRaw = new byte[0x8];
|
|
||||||
if (fileBufferedInputStream.read(lv6HeaderSizeRaw) != 0x8)
|
|
||||||
throw new Exception("Failed to read header size");
|
|
||||||
headerSize = Converter.getLEint(lv6HeaderSizeRaw, 0);
|
|
||||||
fileBufferedInputStream.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void constructHeader() throws Exception{
|
|
||||||
byte[] rawDataChunk = new byte[headerSize];
|
|
||||||
|
|
||||||
if (fileBufferedInputStream.read(rawDataChunk) != headerSize)
|
|
||||||
throw new Exception(String.format("Failed to read header (0x%x)", headerSize));
|
|
||||||
|
|
||||||
this.header = new Level6Header(rawDataChunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void directoryMetadataTableLengthCheck() throws Exception{
|
|
||||||
if (header.getDirectoryMetadataTableLength() < 0)
|
|
||||||
throw new Exception("Not supported operation.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void directoryMetadataTableConstruct() throws Exception{
|
|
||||||
directoryMetadataTable = new byte[(int) header.getDirectoryMetadataTableLength()];
|
|
||||||
if (fileBufferedInputStream.read(directoryMetadataTable) != (int) header.getDirectoryMetadataTableLength())
|
|
||||||
throw new Exception("Failed to read "+header.getDirectoryMetadataTableLength());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fileMetadataTableLengthCheck() throws Exception{
|
|
||||||
if (header.getFileMetadataTableLength() < 0)
|
|
||||||
throw new Exception("Not supported operation.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fileMetadataTableConstruct() throws Exception{
|
|
||||||
fileMetadataTable = new byte[(int) header.getFileMetadataTableLength()];
|
|
||||||
|
|
||||||
if (fileBufferedInputStream.read(fileMetadataTable) != (int) header.getFileMetadataTableLength())
|
|
||||||
throw new Exception("Failed to read "+header.getFileMetadataTableLength());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void constructRootFilesystemEntry() throws Exception{
|
|
||||||
rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable);
|
|
||||||
//rootEntry.printTreeForDebug();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fastForwardBySkippingBytes(long size) throws Exception{
|
|
||||||
long mustSkip = size;
|
|
||||||
long skipped = 0;
|
|
||||||
while (mustSkip > 0){
|
|
||||||
skipped += fileBufferedInputStream.skip(mustSkip);
|
|
||||||
mustSkip = size - skipped;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Level6Header getHeader() { return header; }
|
|
||||||
FileSystemEntry getRootEntry(){ return rootEntry; }
|
|
||||||
byte[] getDirectoryMetadataTable() { return directoryMetadataTable; }
|
|
||||||
byte[] getFileMetadataTable() { return fileMetadataTable;}
|
|
||||||
}
|
|
|
@ -1,93 +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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package libKonogonka.Tools.RomFs;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PipedOutputStream;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
|
|
||||||
public class RomFsDecryptedContentRetrieve implements Runnable {
|
|
||||||
private final static Logger log = LogManager.getLogger(RomFsDecryptedContentRetrieve.class);
|
|
||||||
|
|
||||||
private final File parentFile;
|
|
||||||
private final PipedOutputStream streamOut;
|
|
||||||
private final long internalFileRealPosition;
|
|
||||||
private final long internalFileSize;
|
|
||||||
|
|
||||||
RomFsDecryptedContentRetrieve(File parentFile,
|
|
||||||
PipedOutputStream streamOut,
|
|
||||||
long internalFileRealPosition,
|
|
||||||
long internalFileSize){
|
|
||||||
this.parentFile = parentFile;
|
|
||||||
this.streamOut = streamOut;
|
|
||||||
this.internalFileRealPosition = internalFileRealPosition;
|
|
||||||
this.internalFileSize = internalFileSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
log.trace("Executing thread");
|
|
||||||
try (BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(parentFile.toPath()))){
|
|
||||||
fastForwardBySkippingBytes(bis, internalFileRealPosition);
|
|
||||||
|
|
||||||
int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576
|
|
||||||
long readFrom = 0;
|
|
||||||
byte[] readBuffer;
|
|
||||||
|
|
||||||
while (readFrom < internalFileSize) {
|
|
||||||
if (internalFileSize - readFrom < readPice)
|
|
||||||
readPice = Math.toIntExact(internalFileSize - readFrom); // it's safe, I guarantee
|
|
||||||
readBuffer = new byte[readPice];
|
|
||||||
if (bis.read(readBuffer) != readPice) {
|
|
||||||
log.error("getContent(): Unable to read requested size from file.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
streamOut.write(readBuffer);
|
|
||||||
readFrom += readPice;
|
|
||||||
}
|
|
||||||
} catch (Exception exception) {
|
|
||||||
log.error("RomFsDecryptedProvider -> getContent(): Unable to provide stream", exception);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
closeStreamOut();
|
|
||||||
}
|
|
||||||
log.trace("Thread died");
|
|
||||||
}
|
|
||||||
private void fastForwardBySkippingBytes(BufferedInputStream bis, long size) throws Exception{
|
|
||||||
long mustSkip = size;
|
|
||||||
long skipped = 0;
|
|
||||||
while (mustSkip > 0){
|
|
||||||
skipped += bis.skip(mustSkip);
|
|
||||||
mustSkip = size - skipped;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void closeStreamOut(){
|
|
||||||
try {
|
|
||||||
streamOut.close();
|
|
||||||
}
|
|
||||||
catch (IOException e){
|
|
||||||
log.error("RomFsDecryptedProvider -> getContent(): Unable to close 'StreamOut'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,76 +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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package libKonogonka.Tools.RomFs;
|
|
||||||
|
|
||||||
import libKonogonka.Tools.RomFs.view.DirectoryMetaTablePlainView;
|
|
||||||
import libKonogonka.Tools.RomFs.view.FileMetaTablePlainView;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
|
|
||||||
public class RomFsDecryptedProvider implements IRomFsProvider{
|
|
||||||
private final File file;
|
|
||||||
private final long level6Offset;
|
|
||||||
private final Level6Header level6Header;
|
|
||||||
private final FileSystemEntry rootEntry;
|
|
||||||
// Used only for debug
|
|
||||||
private final byte[] directoryMetadataTable;
|
|
||||||
private final byte[] fileMetadataTable;
|
|
||||||
|
|
||||||
public RomFsDecryptedProvider(File decryptedFsImageFile, long level6offset) throws Exception{
|
|
||||||
RomFsDecryptedConstruct construct = new RomFsDecryptedConstruct(decryptedFsImageFile, level6offset);
|
|
||||||
this.file = decryptedFsImageFile;
|
|
||||||
this.level6Offset = level6offset;
|
|
||||||
this.level6Header = construct.getHeader();
|
|
||||||
this.rootEntry = construct.getRootEntry();
|
|
||||||
|
|
||||||
this.directoryMetadataTable = construct.getDirectoryMetadataTable();
|
|
||||||
this.fileMetadataTable = construct.getFileMetadataTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public File getFile() { return file; }
|
|
||||||
@Override
|
|
||||||
public long getLevel6Offset() { return level6Offset; }
|
|
||||||
@Override
|
|
||||||
public Level6Header getHeader() { return level6Header; }
|
|
||||||
@Override
|
|
||||||
public FileSystemEntry getRootEntry() { return rootEntry; }
|
|
||||||
@Override
|
|
||||||
public PipedInputStream getContent(FileSystemEntry entry) throws Exception{
|
|
||||||
if (entry.isDirectory())
|
|
||||||
throw new Exception("Request of the binary stream for the folder entry 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 RomFsDecryptedContentRetrieve(file, streamOut, internalFileRealPosition, internalFileSize));
|
|
||||||
contentRetrievingThread.start();
|
|
||||||
return streamIn;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void printDebug(){
|
|
||||||
level6Header.printDebugInfo();
|
|
||||||
new DirectoryMetaTablePlainView(level6Header.getDirectoryMetadataTableLength(), directoryMetadataTable);
|
|
||||||
new FileMetaTablePlainView(level6Header.getFileMetadataTableLength(), fileMetadataTable);
|
|
||||||
rootEntry.printTreeForDebug();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,180 +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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package libKonogonka.Tools.RomFs;
|
|
||||||
|
|
||||||
import libKonogonka.ctraes.AesCtrDecryptSimple;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.RandomAccessFile;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public class RomFsEncryptedConstruct {
|
|
||||||
|
|
||||||
private final long absoluteOffsetPosition;
|
|
||||||
private final long level6Offset;
|
|
||||||
|
|
||||||
private final RandomAccessFile raf;
|
|
||||||
private final AesCtrDecryptSimple decryptor;
|
|
||||||
private Level6Header header;
|
|
||||||
private byte[] directoryMetadataTable;
|
|
||||||
private byte[] fileMetadataTable;
|
|
||||||
private FileSystemEntry rootEntry;
|
|
||||||
|
|
||||||
RomFsEncryptedConstruct(File encryptedFsImageFile,
|
|
||||||
long romFsOffset,
|
|
||||||
long level6Offset,
|
|
||||||
AesCtrDecryptSimple decryptor,
|
|
||||||
long mediaStartOffset
|
|
||||||
) throws Exception {
|
|
||||||
|
|
||||||
if (level6Offset < 0)
|
|
||||||
throw new Exception("Incorrect Level 6 Offset");
|
|
||||||
|
|
||||||
this.raf = new RandomAccessFile(encryptedFsImageFile, "r");
|
|
||||||
this.level6Offset = level6Offset;
|
|
||||||
this.absoluteOffsetPosition = romFsOffset + (mediaStartOffset * 0x200);
|
|
||||||
this.decryptor = decryptor;
|
|
||||||
raf.seek(absoluteOffsetPosition + level6Offset);
|
|
||||||
|
|
||||||
//Go to Level 6 header position
|
|
||||||
decryptor.skipNext(level6Offset / 0x200);
|
|
||||||
|
|
||||||
constructHeader();
|
|
||||||
|
|
||||||
directoryMetadataTableLengthCheck();
|
|
||||||
directoryMetadataTableConstruct();
|
|
||||||
|
|
||||||
fileMetadataTableLengthCheck();
|
|
||||||
fileMetadataTableConstruct();
|
|
||||||
|
|
||||||
constructRootFilesystemEntry();
|
|
||||||
|
|
||||||
raf.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void constructHeader() throws Exception{
|
|
||||||
// Decrypt data
|
|
||||||
byte[] encryptedBlock = new byte[0x200];
|
|
||||||
byte[] decryptedBlock;
|
|
||||||
if (raf.read(encryptedBlock) == 0x200)
|
|
||||||
decryptedBlock = decryptor.decryptNext(encryptedBlock);
|
|
||||||
else
|
|
||||||
throw new Exception("Failed to read header header (0x200 - block)");
|
|
||||||
this.header = new Level6Header(decryptedBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void directoryMetadataTableLengthCheck() throws Exception{
|
|
||||||
if (header.getDirectoryMetadataTableLength() < 0)
|
|
||||||
throw new Exception("Not supported: DirectoryMetadataTableLength < 0");
|
|
||||||
}
|
|
||||||
private void directoryMetadataTableConstruct() throws Exception{
|
|
||||||
directoryMetadataTable = readMetaTable(header.getDirectoryMetadataTableOffset(),
|
|
||||||
header.getDirectoryMetadataTableLength());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fileMetadataTableLengthCheck() throws Exception{
|
|
||||||
if (header.getFileMetadataTableLength() < 0)
|
|
||||||
throw new Exception("Not supported: FileMetadataTableLength < 0");
|
|
||||||
}
|
|
||||||
private void fileMetadataTableConstruct() throws Exception{
|
|
||||||
fileMetadataTable = readMetaTable(header.getFileMetadataTableOffset(),
|
|
||||||
header.getFileMetadataTableLength());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void constructRootFilesystemEntry() throws Exception{
|
|
||||||
rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] readMetaTable(long metaOffset,
|
|
||||||
long metaSize) throws Exception{
|
|
||||||
byte[] encryptedBlock;
|
|
||||||
byte[] decryptedBlock;
|
|
||||||
byte[] metadataTable = new byte[(int) metaSize];
|
|
||||||
//0
|
|
||||||
decryptor.reset();
|
|
||||||
|
|
||||||
long startBlock = metaOffset / 0x200;
|
|
||||||
|
|
||||||
decryptor.skipNext(level6Offset / 0x200 + startBlock);
|
|
||||||
|
|
||||||
raf.seek(absoluteOffsetPosition + level6Offset + startBlock * 0x200);
|
|
||||||
|
|
||||||
//1
|
|
||||||
long ignoreBytes = metaOffset - startBlock * 0x200;
|
|
||||||
long currentPosition = 0;
|
|
||||||
|
|
||||||
if (ignoreBytes > 0) {
|
|
||||||
encryptedBlock = new byte[0x200];
|
|
||||||
if (raf.read(encryptedBlock) == 0x200) {
|
|
||||||
decryptedBlock = decryptor.decryptNext(encryptedBlock);
|
|
||||||
// If we have extra-small file that is less than a block and even more
|
|
||||||
if ((0x200 - ignoreBytes) > metaSize){
|
|
||||||
metadataTable = Arrays.copyOfRange(decryptedBlock, (int)ignoreBytes, 0x200);
|
|
||||||
return metadataTable;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
System.arraycopy(decryptedBlock, (int) ignoreBytes, metadataTable, 0, 0x200 - (int) ignoreBytes);
|
|
||||||
currentPosition = 0x200 - ignoreBytes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new Exception("Unable to get 512 bytes from 1st bock for Directory/File Metadata Table");
|
|
||||||
}
|
|
||||||
startBlock++;
|
|
||||||
}
|
|
||||||
long endBlock = (metaSize + ignoreBytes) / 0x200 + startBlock; // <- pointing to place where any data related to this media-block ends
|
|
||||||
|
|
||||||
//2
|
|
||||||
int extraData = (int) ((endBlock - startBlock)*0x200 - (metaSize + ignoreBytes));
|
|
||||||
|
|
||||||
if (extraData < 0)
|
|
||||||
endBlock--;
|
|
||||||
//3
|
|
||||||
while ( startBlock < endBlock ) {
|
|
||||||
encryptedBlock = new byte[0x200];
|
|
||||||
if (raf.read(encryptedBlock) == 0x200) {
|
|
||||||
decryptedBlock = decryptor.decryptNext(encryptedBlock);
|
|
||||||
System.arraycopy(decryptedBlock, 0, metadataTable, (int) currentPosition, 0x200);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw new Exception("Unable to get 512 bytes from block for Directory/File Metadata Table");
|
|
||||||
|
|
||||||
startBlock++;
|
|
||||||
currentPosition += 0x200;
|
|
||||||
}
|
|
||||||
|
|
||||||
//4
|
|
||||||
if (extraData != 0){ // In case we didn't get what we want
|
|
||||||
encryptedBlock = new byte[0x200];
|
|
||||||
if (raf.read(encryptedBlock) == 0x200) {
|
|
||||||
decryptedBlock = decryptor.decryptNext(encryptedBlock);
|
|
||||||
System.arraycopy(decryptedBlock, 0, metadataTable, (int) currentPosition, Math.abs(extraData));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw new Exception("Unable to get 512 bytes from block for Directory/File Metadata Table");
|
|
||||||
}
|
|
||||||
|
|
||||||
return metadataTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
Level6Header getHeader() { return header; }
|
|
||||||
FileSystemEntry getRootEntry(){ return rootEntry; }
|
|
||||||
byte[] getDirectoryMetadataTable() { return directoryMetadataTable; }
|
|
||||||
byte[] getFileMetadataTable() { return fileMetadataTable;}
|
|
||||||
}
|
|
|
@ -1,139 +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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package libKonogonka.Tools.RomFs;
|
|
||||||
|
|
||||||
import libKonogonka.ctraes.AesCtrDecryptSimple;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.PipedOutputStream;
|
|
||||||
import java.io.RandomAccessFile;
|
|
||||||
|
|
||||||
public class RomFsEncryptedContentRetrieve implements Runnable{
|
|
||||||
private final static Logger log = LogManager.getLogger(RomFsEncryptedContentRetrieve.class);
|
|
||||||
|
|
||||||
private final File parentFile;
|
|
||||||
private final PipedOutputStream streamOut;
|
|
||||||
private final long absoluteOffsetPosition;
|
|
||||||
private final AesCtrDecryptSimple decryptor;
|
|
||||||
private final long internalFileOffset;
|
|
||||||
private final long internalFileSize;
|
|
||||||
private final long level6Offset;
|
|
||||||
private final long headersFileDataOffset;
|
|
||||||
|
|
||||||
RomFsEncryptedContentRetrieve(File parentFile,
|
|
||||||
PipedOutputStream streamOut,
|
|
||||||
long absoluteOffsetPosition,
|
|
||||||
AesCtrDecryptSimple decryptor,
|
|
||||||
long internalFileOffset,
|
|
||||||
long internalFileSize,
|
|
||||||
long level6Offset,
|
|
||||||
long headersFileDataOffset
|
|
||||||
){
|
|
||||||
log.fatal("Current implementation works incorrectly");
|
|
||||||
this.parentFile = parentFile;
|
|
||||||
this.absoluteOffsetPosition = absoluteOffsetPosition;
|
|
||||||
this.streamOut = streamOut;
|
|
||||||
this.decryptor = decryptor;
|
|
||||||
this.internalFileOffset = internalFileOffset;
|
|
||||||
this.internalFileSize = internalFileSize;
|
|
||||||
this.level6Offset = level6Offset;
|
|
||||||
this.headersFileDataOffset = headersFileDataOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
log.trace("Executing thread");
|
|
||||||
try {
|
|
||||||
byte[] encryptedBlock;
|
|
||||||
byte[] decryptedBlock;
|
|
||||||
|
|
||||||
RandomAccessFile raf = new RandomAccessFile(parentFile, "r");
|
|
||||||
|
|
||||||
//0
|
|
||||||
long startBlock = (internalFileOffset + headersFileDataOffset) / 0x200;
|
|
||||||
|
|
||||||
decryptor.skipNext(level6Offset / 0x200 + startBlock);
|
|
||||||
|
|
||||||
// long absoluteOffsetPosition = romFsOffsetPosition + (mediaStartOffset * 0x200); // calculated in constructor
|
|
||||||
|
|
||||||
raf.seek(absoluteOffsetPosition + level6Offset + startBlock * 0x200);
|
|
||||||
|
|
||||||
//1
|
|
||||||
long ignoreBytes = (internalFileOffset + headersFileDataOffset) - startBlock * 0x200;
|
|
||||||
|
|
||||||
if (ignoreBytes > 0) {
|
|
||||||
encryptedBlock = new byte[0x200];
|
|
||||||
if (raf.read(encryptedBlock) == 0x200) {
|
|
||||||
decryptedBlock = decryptor.decryptNext(encryptedBlock);
|
|
||||||
// If we have extra-small file that is less than a block and even more
|
|
||||||
if ((0x200 - ignoreBytes) > internalFileSize){
|
|
||||||
streamOut.write(decryptedBlock, (int)ignoreBytes, (int) internalFileSize); // safe cast
|
|
||||||
raf.close();
|
|
||||||
streamOut.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
streamOut.write(decryptedBlock, (int) ignoreBytes, 0x200 - (int) ignoreBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new Exception("Unable to get 512 bytes from 1st bock");
|
|
||||||
}
|
|
||||||
startBlock++;
|
|
||||||
}
|
|
||||||
long endBlock = (internalFileSize + ignoreBytes) / 0x200 + startBlock; // <- pointing to place where any data related to this media-block ends
|
|
||||||
|
|
||||||
//2
|
|
||||||
int extraData = (int) ((endBlock - startBlock)*0x200 - (internalFileSize + ignoreBytes));
|
|
||||||
|
|
||||||
if (extraData < 0)
|
|
||||||
endBlock--;
|
|
||||||
//3
|
|
||||||
while ( startBlock < endBlock ) {
|
|
||||||
encryptedBlock = new byte[0x200];
|
|
||||||
if (raf.read(encryptedBlock) == 0x200) {
|
|
||||||
decryptedBlock = decryptor.decryptNext(encryptedBlock);
|
|
||||||
streamOut.write(decryptedBlock);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw new Exception("Unable to get 512 bytes from block");
|
|
||||||
|
|
||||||
startBlock++;
|
|
||||||
}
|
|
||||||
|
|
||||||
//4
|
|
||||||
if (extraData != 0){ // In case we didn't get what we want
|
|
||||||
encryptedBlock = new byte[0x200];
|
|
||||||
if (raf.read(encryptedBlock) == 0x200) {
|
|
||||||
decryptedBlock = decryptor.decryptNext(encryptedBlock);
|
|
||||||
streamOut.write(decryptedBlock, 0, Math.abs(extraData));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw new Exception("Unable to get 512 bytes from block");
|
|
||||||
}
|
|
||||||
raf.close();
|
|
||||||
streamOut.close();
|
|
||||||
} catch (Exception exception) {
|
|
||||||
log.error("Unable to provide stream", exception);
|
|
||||||
}
|
|
||||||
log.trace("Thread died");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -16,7 +16,6 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with libKonogonka. If not, see <https://www.gnu.org/licenses/>.
|
* along with libKonogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package libKonogonka.Tools.RomFs;
|
package libKonogonka.Tools.RomFs;
|
||||||
|
|
||||||
import libKonogonka.Tools.RomFs.view.DirectoryMetaTablePlainView;
|
import libKonogonka.Tools.RomFs.view.DirectoryMetaTablePlainView;
|
||||||
|
@ -27,36 +26,39 @@ import java.io.File;
|
||||||
import java.io.PipedInputStream;
|
import java.io.PipedInputStream;
|
||||||
import java.io.PipedOutputStream;
|
import java.io.PipedOutputStream;
|
||||||
|
|
||||||
public class RomFsEncryptedProvider implements IRomFsProvider{
|
public class RomFsProvider implements IRomFsProvider{
|
||||||
private final File file;
|
private final File file;
|
||||||
private final long level6Offset;
|
private final long level6Offset;
|
||||||
private final Level6Header level6Header;
|
private final Level6Header level6Header;
|
||||||
private final FileSystemEntry rootEntry;
|
private final FileSystemEntry rootEntry;
|
||||||
|
|
||||||
private final byte[] key; // Used @ createDecryptor only
|
private long ncaOffsetPosition;
|
||||||
private final byte[] sectionCTR; // Used @ createDecryptor only
|
private byte[] key; // Used @ createDecryptor only
|
||||||
private final long mediaStartOffset; // Used @ createDecryptor only
|
private byte[] sectionCTR; // Used @ createDecryptor only
|
||||||
private final long absoluteOffsetPosition;
|
private long mediaStartOffset; // Used @ createDecryptor only
|
||||||
|
private long mediaEndOffset;
|
||||||
//private long mediaEndOffset; // We know this, but actually never use
|
|
||||||
|
|
||||||
// Used only for debug
|
// Used only for debug
|
||||||
private final byte[] directoryMetadataTable;
|
private final byte[] directoryMetadataTable;
|
||||||
private final byte[] fileMetadataTable;
|
private final byte[] fileMetadataTable;
|
||||||
|
|
||||||
public RomFsEncryptedProvider(long level6Offset,
|
private final boolean encryptedAesCtr;
|
||||||
File encryptedFsImageFile,
|
|
||||||
long romFsOffsetPosition,
|
public RomFsProvider(File decryptedFsImageFile, long level6offset) throws Exception{
|
||||||
byte[] key,
|
RomFsConstruct construct = new RomFsConstruct(decryptedFsImageFile, level6offset);
|
||||||
byte[] sectionCTR,
|
this.file = decryptedFsImageFile;
|
||||||
long mediaStartOffset
|
this.level6Offset = level6offset;
|
||||||
) throws Exception{
|
this.level6Header = construct.getHeader();
|
||||||
this(level6Offset, encryptedFsImageFile, romFsOffsetPosition, key, sectionCTR, mediaStartOffset, -1);
|
this.rootEntry = construct.getRootEntry();
|
||||||
|
|
||||||
|
this.directoryMetadataTable = construct.getDirectoryMetadataTable();
|
||||||
|
this.fileMetadataTable = construct.getFileMetadataTable();
|
||||||
|
|
||||||
|
this.encryptedAesCtr = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RomFsEncryptedProvider(long level6Offset,
|
public RomFsProvider(long level6Offset,
|
||||||
File encryptedFsImageFile,
|
File encryptedFsImageFile,
|
||||||
long romFsOffsetPosition,
|
long ncaOffsetPosition,
|
||||||
byte[] key,
|
byte[] key,
|
||||||
byte[] sectionCTR,
|
byte[] sectionCTR,
|
||||||
long mediaStartOffset,
|
long mediaStartOffset,
|
||||||
|
@ -65,24 +67,29 @@ public class RomFsEncryptedProvider implements IRomFsProvider{
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.sectionCTR = sectionCTR;
|
this.sectionCTR = sectionCTR;
|
||||||
this.mediaStartOffset = mediaStartOffset;
|
this.mediaStartOffset = mediaStartOffset;
|
||||||
|
this.mediaEndOffset = mediaEndOffset;
|
||||||
|
this.ncaOffsetPosition = ncaOffsetPosition;
|
||||||
|
|
||||||
RomFsEncryptedConstruct construct = new RomFsEncryptedConstruct(encryptedFsImageFile,
|
RomFsConstruct construct = new RomFsConstruct(encryptedFsImageFile,
|
||||||
romFsOffsetPosition,
|
ncaOffsetPosition,
|
||||||
level6Offset,
|
level6Offset,
|
||||||
createDecryptor(),
|
createDecryptor(),
|
||||||
mediaStartOffset);
|
mediaStartOffset,
|
||||||
|
mediaEndOffset);
|
||||||
this.file = encryptedFsImageFile;
|
this.file = encryptedFsImageFile;
|
||||||
this.level6Offset = level6Offset;
|
this.level6Offset = level6Offset;
|
||||||
this.level6Header = construct.getHeader();
|
this.level6Header = construct.getHeader();
|
||||||
this.rootEntry = construct.getRootEntry();
|
this.rootEntry = construct.getRootEntry();
|
||||||
this.absoluteOffsetPosition = romFsOffsetPosition + (mediaStartOffset * 0x200);
|
|
||||||
|
|
||||||
this.directoryMetadataTable = construct.getDirectoryMetadataTable();
|
this.directoryMetadataTable = construct.getDirectoryMetadataTable();
|
||||||
this.fileMetadataTable = construct.getFileMetadataTable();
|
this.fileMetadataTable = construct.getFileMetadataTable();
|
||||||
|
|
||||||
|
this.encryptedAesCtr = true;
|
||||||
}
|
}
|
||||||
private AesCtrDecryptSimple createDecryptor() throws Exception{
|
private AesCtrDecryptSimple createDecryptor() throws Exception{
|
||||||
return new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
|
return new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File getFile() { return file; }
|
public File getFile() { return file; }
|
||||||
@Override
|
@Override
|
||||||
|
@ -92,7 +99,12 @@ public class RomFsEncryptedProvider implements IRomFsProvider{
|
||||||
@Override
|
@Override
|
||||||
public FileSystemEntry getRootEntry() { return rootEntry; }
|
public FileSystemEntry getRootEntry() { return rootEntry; }
|
||||||
@Override
|
@Override
|
||||||
public PipedInputStream getContent(FileSystemEntry entry) throws Exception{
|
public PipedInputStream getContent(FileSystemEntry entry) throws Exception {
|
||||||
|
if (encryptedAesCtr)
|
||||||
|
return getContentAesCtrEncrypted(entry);
|
||||||
|
return getContentNonEncrypted(entry);
|
||||||
|
}
|
||||||
|
public PipedInputStream getContentAesCtrEncrypted(FileSystemEntry entry) throws Exception{
|
||||||
if (entry.isDirectory())
|
if (entry.isDirectory())
|
||||||
throw new Exception("Request of the binary stream for the folder entry is not supported (and doesn't make sense).");
|
throw new Exception("Request of the binary stream for the folder entry is not supported (and doesn't make sense).");
|
||||||
|
|
||||||
|
@ -101,19 +113,38 @@ public class RomFsEncryptedProvider implements IRomFsProvider{
|
||||||
long internalFileOffset = entry.getOffset();
|
long internalFileOffset = entry.getOffset();
|
||||||
long internalFileSize = entry.getSize();
|
long internalFileSize = entry.getSize();
|
||||||
|
|
||||||
Thread contentRetrievingThread = new Thread(new RomFsEncryptedContentRetrieve(
|
Thread contentRetrievingThread = new Thread(new RomFsContentRetrieve(
|
||||||
file,
|
file,
|
||||||
streamOut,
|
streamOut,
|
||||||
absoluteOffsetPosition,
|
|
||||||
createDecryptor(),
|
createDecryptor(),
|
||||||
internalFileOffset,
|
internalFileOffset,
|
||||||
internalFileSize,
|
internalFileSize,
|
||||||
|
level6Header.getFileDataOffset(),
|
||||||
level6Offset,
|
level6Offset,
|
||||||
level6Header.getFileDataOffset()
|
ncaOffsetPosition,
|
||||||
));
|
mediaStartOffset,
|
||||||
|
mediaEndOffset));
|
||||||
contentRetrievingThread.start();
|
contentRetrievingThread.start();
|
||||||
return streamIn;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void printDebug(){
|
public void printDebug(){
|
||||||
level6Header.printDebugInfo();
|
level6Header.printDebugInfo();
|
|
@ -22,6 +22,7 @@ import libKonogonka.Tools.ISuperProvider;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static libKonogonka.Converter.*;
|
import static libKonogonka.Converter.*;
|
||||||
|
@ -30,59 +31,54 @@ import static libKonogonka.Converter.*;
|
||||||
* HFS0
|
* HFS0
|
||||||
* */
|
* */
|
||||||
public class HFS0Provider implements ISuperProvider {
|
public class HFS0Provider implements ISuperProvider {
|
||||||
|
private final String magic;
|
||||||
|
private final int filesCount;
|
||||||
|
private final byte[] padding;
|
||||||
|
private final int stringTableSize;
|
||||||
|
private final long rawFileDataStart;
|
||||||
|
|
||||||
private boolean magicHFS0;
|
private final HFS0File[] hfs0Files;
|
||||||
private int filesCnt;
|
|
||||||
private boolean paddingHfs0;
|
|
||||||
private int stringTableSize;
|
|
||||||
private long rawFileDataStart;
|
|
||||||
|
|
||||||
private HFS0File[] hfs0Files;
|
private final File file;
|
||||||
|
|
||||||
private File file;
|
|
||||||
|
|
||||||
HFS0Provider(long hfsOffsetPosition, RandomAccessFile raf, File file) throws Exception{
|
HFS0Provider(long hfsOffsetPosition, RandomAccessFile raf, File file) throws Exception{
|
||||||
this.file = file; // Will be used @ getHfs0FilePipedInpStream. It's a bad implementation.
|
this.file = file; // Will be used @ getHfs0FilePipedInpStream. It's a bad implementation.
|
||||||
byte[] hfs0bytes = new byte[16];
|
byte[] hfs0bytes = new byte[16];
|
||||||
try{
|
|
||||||
raf.seek(hfsOffsetPosition);
|
raf.seek(hfsOffsetPosition);
|
||||||
if (raf.read(hfs0bytes) != 16){
|
if (raf.read(hfs0bytes) != 16){
|
||||||
throw new Exception("Read HFS0 structure failure. Can't read first 16 bytes on requested offset.");
|
throw new Exception("Read HFS0 structure failure. Can't read first 16 bytes on requested offset.");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (IOException ioe){
|
|
||||||
throw new Exception("Read HFS0 structure failure. Can't read first 16 bytes on requested offset: "+ioe.getMessage());
|
|
||||||
}
|
|
||||||
magicHFS0 = Arrays.equals(Arrays.copyOfRange(hfs0bytes, 0, 4),new byte[]{0x48, 0x46, 0x53, 0x30});
|
|
||||||
filesCnt = getLEint(hfs0bytes, 0x4);
|
|
||||||
stringTableSize = getLEint(hfs0bytes, 8);
|
|
||||||
paddingHfs0 = Arrays.equals(Arrays.copyOfRange(hfs0bytes, 12, 16),new byte[4]);
|
|
||||||
|
|
||||||
hfs0Files = new HFS0File[filesCnt];
|
this.magic = new String(hfs0bytes, 0x0, 0x4, StandardCharsets.US_ASCII);
|
||||||
|
this.filesCount = getLEint(hfs0bytes, 0x4);
|
||||||
|
this.stringTableSize = getLEint(hfs0bytes, 8);
|
||||||
|
this.padding = Arrays.copyOfRange(hfs0bytes, 12, 16);
|
||||||
|
|
||||||
// TODO: IF NOT EMPTY TABLE:
|
hfs0Files = new HFS0File[filesCount];
|
||||||
|
|
||||||
long[] offsetHfs0files = new long[filesCnt];
|
// TODO: IF NOT EMPTY TABLE: add validation
|
||||||
long[] sizeHfs0files = new long[filesCnt];
|
|
||||||
int[] hashedRegionSizeHfs0Files = new int[filesCnt];
|
long[] offsetSubFile = new long[filesCount];
|
||||||
boolean[] paddingHfs0Files = new boolean[filesCnt];
|
long[] sizeSubFile = new long[filesCount];
|
||||||
byte[][] SHA256HashHfs0Files = new byte[filesCnt][];
|
int[] hashedRegionSubFile = new int[filesCount];
|
||||||
int[] strTableOffsets = new int[filesCnt];
|
boolean[] paddingSubFile = new boolean[filesCount];
|
||||||
|
byte[][] SHA256HashSubFile = new byte[filesCount][];
|
||||||
|
int[] stringTableOffsetSubFile = new int[filesCount];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Populate meta information regarding each file inside (?) HFS0
|
// Populate meta information regarding each file inside (?) HFS0
|
||||||
byte[] metaInfoBytes = new byte[64];
|
byte[] metaInfoBytes = new byte[64];
|
||||||
for (int i=0; i < filesCnt; i++){
|
for (int i = 0; i < filesCount; i++){
|
||||||
if (raf.read(metaInfoBytes) != 64) {
|
if (raf.read(metaInfoBytes) != 64)
|
||||||
throw new Exception("Read HFS0 File Entry Table failure for file # "+i);
|
throw new Exception("Read HFS0 File Entry Table failure for file # "+i);
|
||||||
}
|
offsetSubFile[i] = getLElong(metaInfoBytes, 0);
|
||||||
offsetHfs0files[i] = getLElong(metaInfoBytes, 0);
|
sizeSubFile[i] = getLElong(metaInfoBytes, 8);
|
||||||
sizeHfs0files[i] = getLElong(metaInfoBytes, 8);
|
hashedRegionSubFile[i] = getLEint(metaInfoBytes, 20);
|
||||||
hashedRegionSizeHfs0Files[i] = getLEint(metaInfoBytes, 20);
|
paddingSubFile[i] = Arrays.equals(Arrays.copyOfRange(metaInfoBytes, 24, 32), new byte[8]);
|
||||||
paddingHfs0Files[i] = Arrays.equals(Arrays.copyOfRange(metaInfoBytes, 24, 32), new byte[8]);
|
SHA256HashSubFile[i] = Arrays.copyOfRange(metaInfoBytes, 32, 64);
|
||||||
SHA256HashHfs0Files[i] = Arrays.copyOfRange(metaInfoBytes, 32, 64);
|
|
||||||
|
|
||||||
strTableOffsets[i] = getLEint(metaInfoBytes, 16);
|
stringTableOffsetSubFile[i] = getLEint(metaInfoBytes, 16);
|
||||||
}
|
}
|
||||||
// Define location of actual data for this HFS0
|
// Define location of actual data for this HFS0
|
||||||
rawFileDataStart = raf.getFilePointer()+stringTableSize;
|
rawFileDataStart = raf.getFilePointer()+stringTableSize;
|
||||||
|
@ -92,24 +88,24 @@ public class HFS0Provider implements ISuperProvider {
|
||||||
if (raf.read(stringTbl) != stringTableSize){
|
if (raf.read(stringTbl) != stringTableSize){
|
||||||
throw new Exception("Read HFS0 String table failure. Can't read requested string table size ("+stringTableSize+")");
|
throw new Exception("Read HFS0 String table failure. Can't read requested string table size ("+stringTableSize+")");
|
||||||
}
|
}
|
||||||
String[] namesHfs0files = new String[filesCnt];
|
String[] namesSubFile = new String[filesCount];
|
||||||
// Parse string table
|
// Parse string table
|
||||||
for (int i=0; i < filesCnt; i++){
|
for (int i = 0; i < filesCount; i++){
|
||||||
int j = 0;
|
int j = 0;
|
||||||
while (stringTbl[strTableOffsets[i]+j] != (byte)0x00)
|
while (stringTbl[stringTableOffsetSubFile[i]+j] != (byte)0x00)
|
||||||
j++;
|
j++;
|
||||||
namesHfs0files[i] = new String(stringTbl, strTableOffsets[i], j, StandardCharsets.UTF_8);
|
namesSubFile[i] = new String(stringTbl, stringTableOffsetSubFile[i], j, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
//----------------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------------
|
||||||
// Set files
|
// Set files
|
||||||
for (int i=0; i < filesCnt; i++){
|
for (int i = 0; i < filesCount; i++){
|
||||||
hfs0Files[i] = new HFS0File(
|
hfs0Files[i] = new HFS0File(
|
||||||
namesHfs0files[i],
|
namesSubFile[i],
|
||||||
offsetHfs0files[i],
|
offsetSubFile[i],
|
||||||
sizeHfs0files[i],
|
sizeSubFile[i],
|
||||||
hashedRegionSizeHfs0Files[i],
|
hashedRegionSubFile[i],
|
||||||
paddingHfs0Files[i],
|
paddingSubFile[i],
|
||||||
SHA256HashHfs0Files[i]
|
SHA256HashSubFile[i]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,15 +114,18 @@ public class HFS0Provider implements ISuperProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMagicHFS0() { return magicHFS0; }
|
public String getMagic() { return magic; }
|
||||||
public int getFilesCnt() { return filesCnt; }
|
public int getFilesCount() { return filesCount; }
|
||||||
public boolean isPaddingHfs0() { return paddingHfs0; }
|
public byte[] getPadding() { return padding; }
|
||||||
public int getStringTableSize() { return stringTableSize; }
|
public int getStringTableSize() { return stringTableSize; }
|
||||||
@Override
|
@Override
|
||||||
public long getRawFileDataStart() { return rawFileDataStart; }
|
public long getRawFileDataStart() { return rawFileDataStart; }
|
||||||
public HFS0File[] getHfs0Files() { return hfs0Files; }
|
public HFS0File[] getHfs0Files() { return hfs0Files; }
|
||||||
@Override
|
@Override
|
||||||
public File getFile(){ return file; }
|
public File getFile(){ return file; }
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* */
|
||||||
@Override
|
@Override
|
||||||
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception{
|
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception{
|
||||||
PipedOutputStream streamOut = new PipedOutputStream();
|
PipedOutputStream streamOut = new PipedOutputStream();
|
||||||
|
@ -140,13 +139,11 @@ public class HFS0Provider implements ISuperProvider {
|
||||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Executing thread");
|
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Executing thread");
|
||||||
try{
|
try{
|
||||||
long subFileRealPosition = rawFileDataStart + hfs0Files[subFileNumber].getOffset();
|
long subFileRealPosition = rawFileDataStart + hfs0Files[subFileNumber].getOffset();
|
||||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(file.toPath()));
|
||||||
if (bis.skip(subFileRealPosition) != subFileRealPosition) {
|
if (bis.skip(subFileRealPosition) != subFileRealPosition)
|
||||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to skip requested offset");
|
throw new Exception("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to skip requested offset");
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576
|
int readPice = 0x800000; // 8mb NOTE: consider switching to 1mb 1048576
|
||||||
|
|
||||||
long readFrom = 0;
|
long readFrom = 0;
|
||||||
long realFileSize = hfs0Files[subFileNumber].getSize();
|
long realFileSize = hfs0Files[subFileNumber].getSize();
|
||||||
|
@ -157,17 +154,16 @@ public class HFS0Provider implements ISuperProvider {
|
||||||
if (realFileSize - readFrom < readPice)
|
if (realFileSize - readFrom < readPice)
|
||||||
readPice = Math.toIntExact(realFileSize - readFrom); // it's safe, I guarantee
|
readPice = Math.toIntExact(realFileSize - readFrom); // it's safe, I guarantee
|
||||||
readBuf = new byte[readPice];
|
readBuf = new byte[readPice];
|
||||||
if (bis.read(readBuf) != readPice) {
|
if (bis.read(readBuf) != readPice)
|
||||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to read requested size from file.");
|
throw new Exception("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to read requested size from file.");
|
||||||
return;
|
|
||||||
}
|
|
||||||
streamOut.write(readBuf, 0, readPice);
|
streamOut.write(readBuf, 0, readPice);
|
||||||
readFrom += readPice;
|
readFrom += readPice;
|
||||||
}
|
}
|
||||||
bis.close();
|
bis.close();
|
||||||
streamOut.close();
|
streamOut.close();
|
||||||
}
|
}
|
||||||
catch (IOException ioe){
|
catch (Exception ioe){
|
||||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to provide stream");
|
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to provide stream");
|
||||||
ioe.printStackTrace();
|
ioe.printStackTrace();
|
||||||
}
|
}
|
||||||
|
@ -176,20 +172,17 @@ public class HFS0Provider implements ISuperProvider {
|
||||||
workerThread.start();
|
workerThread.start();
|
||||||
return streamIn;
|
return streamIn;
|
||||||
}
|
}
|
||||||
|
//TODO
|
||||||
@Override
|
@Override
|
||||||
public boolean exportContent(String saveToLocation, String subFileName) throws Exception {
|
public boolean exportContent(String saveToLocation, String subFileName) throws Exception {
|
||||||
return false;
|
throw new Exception("Not implemented yet");
|
||||||
}
|
}
|
||||||
|
//TODO
|
||||||
@Override
|
@Override
|
||||||
public boolean exportContent(String saveToLocation, int subFileNumber) throws Exception {
|
public boolean exportContent(String saveToLocation, int subFileNumber) throws Exception {
|
||||||
return false;
|
throw new Exception("Not implemented yet");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sugar
|
|
||||||
* */
|
|
||||||
@Override
|
@Override
|
||||||
public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception {
|
public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception {
|
||||||
for (int i = 0; i < hfs0Files.length; i++){
|
for (int i = 0; i < hfs0Files.length; i++){
|
||||||
|
|
|
@ -76,7 +76,7 @@ public class XCIProvider{
|
||||||
xciGamecardCert = new XCIGamecardCert(gamecardCertBytes);
|
xciGamecardCert = new XCIGamecardCert(gamecardCertBytes);
|
||||||
|
|
||||||
hfs0ProviderMain = new HFS0Provider(0xf000, raf, file);
|
hfs0ProviderMain = new HFS0Provider(0xf000, raf, file);
|
||||||
if (hfs0ProviderMain.getFilesCnt() < 3){
|
if (hfs0ProviderMain.getFilesCount() < 3){
|
||||||
raf.close();
|
raf.close();
|
||||||
throw new Exception("XCI Can't read Gamecard certificate bytes.");
|
throw new Exception("XCI Can't read Gamecard certificate bytes.");
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ public class XCIProvider{
|
||||||
throw new Exception("XCI Failed file analyze for ["+file.getName()+"]\n "+ioe.getMessage());
|
throw new Exception("XCI Failed file analyze for ["+file.getName()+"]\n "+ioe.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Getters */
|
/* API */
|
||||||
public XCIGamecardHeader getGCHeader(){ return this.xciGamecardHeader; }
|
public XCIGamecardHeader getGCHeader(){ return this.xciGamecardHeader; }
|
||||||
public XCIGamecardInfo getGCInfo(){ return this.xciGamecardInfo; }
|
public XCIGamecardInfo getGCInfo(){ return this.xciGamecardInfo; }
|
||||||
public XCIGamecardCert getGCCert(){ return this.xciGamecardCert; }
|
public XCIGamecardCert getGCCert(){ return this.xciGamecardCert; }
|
||||||
|
|
|
@ -172,23 +172,14 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
|
||||||
|
|
||||||
long leftovers = realCountOfBytesToSkip % 0x200; // most likely will be 0; TODO: a lot of tests
|
long leftovers = realCountOfBytesToSkip % 0x200; // most likely will be 0; TODO: a lot of tests
|
||||||
long bytesToSkipTillRequiredBlock = realCountOfBytesToSkip - leftovers;
|
long bytesToSkipTillRequiredBlock = realCountOfBytesToSkip - leftovers;
|
||||||
long skipped = super.skip(bytesToSkipTillRequiredBlock);
|
skipLoop(bytesToSkipTillRequiredBlock);
|
||||||
if (bytesToSkipTillRequiredBlock != skipped)
|
|
||||||
throw new IOException("Can't skip bytes. To skip: " +
|
|
||||||
bytesToSkipTillRequiredBlock +
|
|
||||||
".\nActually skipped: " + skipped +
|
|
||||||
".\nLeftovers inside encrypted section: " + leftovers);
|
|
||||||
fillDecryptedCache();
|
fillDecryptedCache();
|
||||||
pseudoPos += n;
|
pseudoPos += n;
|
||||||
pointerInsideDecryptedSection = (int) leftovers;
|
pointerInsideDecryptedSection = (int) leftovers;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
log.trace("4. Pointer Inside + End Position Outside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+n)+")");
|
log.trace("4. Pointer Inside + End Position Outside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+n)+")");
|
||||||
long skipped = super.skip(realCountOfBytesToSkip);
|
skipLoop(realCountOfBytesToSkip);
|
||||||
if (realCountOfBytesToSkip != skipped)
|
|
||||||
throw new IOException("Can't skip bytes. To skip: " +
|
|
||||||
realCountOfBytesToSkip +
|
|
||||||
".\nActually skipped: " + skipped);
|
|
||||||
pseudoPos += n;
|
pseudoPos += n;
|
||||||
pointerInsideDecryptedSection = 0;
|
pointerInsideDecryptedSection = 0;
|
||||||
return n;
|
return n;
|
||||||
|
@ -217,12 +208,20 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
log.trace("6. Not encrypted ("+pseudoPos+"-"+(pseudoPos+n)+")");
|
log.trace("6. Not encrypted ("+pseudoPos+"-"+(pseudoPos+n)+")");
|
||||||
long skipped = super.skip(n);
|
skipLoop(n);
|
||||||
pseudoPos += n;
|
pseudoPos += n;
|
||||||
pointerInsideDecryptedSection = 0;
|
pointerInsideDecryptedSection = 0;
|
||||||
return skipped;
|
return n;
|
||||||
|
}
|
||||||
|
private void skipLoop(long size) throws IOException{
|
||||||
|
long mustSkip = size;
|
||||||
|
long skipped = 0;
|
||||||
|
while (mustSkip > 0){
|
||||||
|
skipped += super.skip(mustSkip);
|
||||||
|
mustSkip = size - skipped;
|
||||||
|
log.trace("Skip loop: skipped: "+skipped+"\tmustSkip "+mustSkip);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized int read() throws IOException {
|
public synchronized int read() throws IOException {
|
||||||
byte[] b = new byte[1];
|
byte[] b = new byte[1];
|
||||||
|
|
|
@ -21,7 +21,7 @@ package libKonogonka.RomFsDecrypted;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
import libKonogonka.Tools.RomFs.RomFsDecryptedProvider;
|
import libKonogonka.Tools.RomFs.RomFsProvider;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -36,7 +36,7 @@ public class RomFsDecryptedTest {
|
||||||
private static final String decryptedFileAbsolutePath = "./FilesForTests/NCAContent_0 [lv6 147456].bin";
|
private static final String decryptedFileAbsolutePath = "./FilesForTests/NCAContent_0 [lv6 147456].bin";
|
||||||
private File decryptedFile;
|
private File decryptedFile;
|
||||||
long lv6offset;
|
long lv6offset;
|
||||||
RomFsDecryptedProvider provider;
|
RomFsProvider provider;
|
||||||
|
|
||||||
@Disabled
|
@Disabled
|
||||||
@DisplayName("RomFsDecryptedProvider: tests")
|
@DisplayName("RomFsDecryptedProvider: tests")
|
||||||
|
@ -55,7 +55,7 @@ public class RomFsDecryptedTest {
|
||||||
lv6offset = Long.parseLong(decryptedFile.getName().replaceAll("(^.*lv6\\s)|(]\\.bin)", ""));
|
lv6offset = Long.parseLong(decryptedFile.getName().replaceAll("(^.*lv6\\s)|(]\\.bin)", ""));
|
||||||
}
|
}
|
||||||
void makeProvider() throws Exception{
|
void makeProvider() throws Exception{
|
||||||
provider = new RomFsDecryptedProvider(decryptedFile, lv6offset);
|
provider = new RomFsProvider(decryptedFile, lv6offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -117,7 +117,7 @@ public class RomFsEncryptedTest {
|
||||||
|
|
||||||
exportFolderContent(entry, "/tmp/brandnew");
|
exportFolderContent(entry, "/tmp/brandnew");
|
||||||
//----------------------------------------------------------------------
|
//----------------------------------------------------------------------
|
||||||
exportFolderContentLegacy(entry, "/tmp/legacy");
|
// exportFolderContentLegacy(entry, "/tmp/legacy");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void exportFolderContent(FileSystemEntry entry, String saveToLocation) throws Exception{
|
private void exportFolderContent(FileSystemEntry entry, String saveToLocation) throws Exception{
|
||||||
|
@ -195,7 +195,7 @@ public class RomFsEncryptedTest {
|
||||||
private void exportSingleFileLegacy(FileSystemEntry entry, String saveToLocation) throws Exception {
|
private void exportSingleFileLegacy(FileSystemEntry entry, String saveToLocation) throws Exception {
|
||||||
File contentFile = new File(saveToLocation + entry.getName());
|
File contentFile = new File(saveToLocation + entry.getName());
|
||||||
|
|
||||||
BufferedOutputStream extractedFileBOS = new BufferedOutputStream(new FileOutputStream(contentFile));
|
BufferedOutputStream extractedFileBOS = new BufferedOutputStream(Files.newOutputStream(contentFile.toPath()));
|
||||||
PipedInputStream pis = ncaProvider.getNCAContentProvider(1).getRomfs().getContent(entry);
|
PipedInputStream pis = ncaProvider.getNCAContentProvider(1).getRomfs().getContent(entry);
|
||||||
|
|
||||||
byte[] readBuf = new byte[0x200]; // 8mb NOTE: consider switching to 1mb 1048576
|
byte[] readBuf = new byte[0x200]; // 8mb NOTE: consider switching to 1mb 1048576
|
||||||
|
|
66
src/test/java/libKonogonka/RomFsDecrypted/XciTest.java
Normal file
66
src/test/java/libKonogonka/RomFsDecrypted/XciTest.java
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package libKonogonka.RomFsDecrypted;
|
||||||
|
|
||||||
|
import libKonogonka.Tools.XCI.XCIProvider;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
|
||||||
|
// log.fatal("Configuration File Defined To Be :: "+System.getProperty("log4j.configurationFile"));
|
||||||
|
|
||||||
|
public class XciTest {
|
||||||
|
private static final String xci_header_keyFileLocation = "./FilesForTests/xci_header_key.txt";
|
||||||
|
private static final String decryptedFileAbsolutePath = "./FilesForTests/sample.xci";
|
||||||
|
private File xciFile;
|
||||||
|
XCIProvider provider;
|
||||||
|
String xci_header_key;
|
||||||
|
|
||||||
|
@Disabled
|
||||||
|
@DisplayName("RomFsDecryptedProvider: tests")
|
||||||
|
@Test
|
||||||
|
void romFsValidation() throws Exception{
|
||||||
|
makeFile();
|
||||||
|
getXciHeaderKey();
|
||||||
|
makeProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
void getXciHeaderKey() throws Exception{
|
||||||
|
BufferedReader br = new BufferedReader(new FileReader(xci_header_keyFileLocation));
|
||||||
|
xci_header_key = br.readLine();
|
||||||
|
br.close();
|
||||||
|
|
||||||
|
if (xci_header_key == null)
|
||||||
|
throw new Exception("Unable to retrieve xci_header_key");
|
||||||
|
|
||||||
|
xci_header_key = xci_header_key.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
void makeFile(){
|
||||||
|
xciFile = new File(decryptedFileAbsolutePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void makeProvider() throws Exception{
|
||||||
|
provider = new XCIProvider(xciFile, xci_header_key);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue