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
|
||||
|
||||
[![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
|
||||
|
||||
[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
|
||||
* [Bouncy Castle](https://www.bouncycastle.org/) for Java.
|
||||
|
|
|
@ -36,14 +36,62 @@ public class RainbowDump {
|
|||
|
||||
|
||||
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)
|
||||
return;
|
||||
System.out.print(ANSI_BLUE);
|
||||
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);
|
||||
for (byte b: byteArray)
|
||||
System.out.print(String.format("%02x ", b));
|
||||
System.out.printf("%02x ", b);
|
||||
System.out.println();
|
||||
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.PFS0Provider;
|
||||
import libKonogonka.Tools.RomFs.IRomFsProvider;
|
||||
import libKonogonka.Tools.RomFs.RomFsEncryptedProvider;
|
||||
import libKonogonka.Tools.RomFs.RomFsProvider;
|
||||
import libKonogonka.ctraes.AesCtrBufferedInputStream;
|
||||
import libKonogonka.ctraes.AesCtrDecryptSimple;
|
||||
import libKonogonka.exceptions.EmptySectionException;
|
||||
|
@ -32,7 +32,6 @@ import org.apache.logging.log4j.Logger;
|
|||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class NCAContent {
|
||||
private final static Logger log = LogManager.getLogger(NCAContent.class);
|
||||
|
@ -43,7 +42,6 @@ public class NCAContent {
|
|||
private final NCAHeaderTableEntry ncaHeaderTableEntry;
|
||||
private final byte[] decryptedKey;
|
||||
|
||||
private LinkedList<byte[]> Pfs0SHA256hashes;
|
||||
private IPFS0Provider pfs0;
|
||||
private IRomFsProvider romfs;
|
||||
|
||||
|
@ -59,8 +57,6 @@ public class NCAContent {
|
|||
this.ncaFsHeader = ncaFsHeader;
|
||||
this.ncaHeaderTableEntry = ncaHeaderTableEntry;
|
||||
this.decryptedKey = decryptedKey;
|
||||
System.out.println("NCAContent pfs0offsetPosition: "+ncaOffsetPosition);
|
||||
Pfs0SHA256hashes = new LinkedList<>();
|
||||
// If nothing to do
|
||||
if (ncaHeaderTableEntry.getMediaEndOffset() == 0)
|
||||
throw new EmptySectionException("Empty section");
|
||||
|
@ -91,7 +87,6 @@ public class NCAContent {
|
|||
ncaFsHeader.getSuperBlockPFS0(),
|
||||
ncaHeaderTableEntry.getMediaStartOffset(),
|
||||
ncaHeaderTableEntry.getMediaEndOffset());
|
||||
Pfs0SHA256hashes = pfs0.getPfs0SHA256hashes();
|
||||
}
|
||||
|
||||
private void proceedPFS0Encrypted() throws Exception{
|
||||
|
@ -103,16 +98,15 @@ public class NCAContent {
|
|||
decryptor,
|
||||
ncaHeaderTableEntry.getMediaStartOffset(),
|
||||
ncaHeaderTableEntry.getMediaEndOffset());
|
||||
Pfs0SHA256hashes = pfs0.getPfs0SHA256hashes();
|
||||
}
|
||||
|
||||
private void proceedRomFs() throws Exception{
|
||||
switch (ncaFsHeader.getCryptoType()){
|
||||
case 0x01:
|
||||
proceedRomFsNotEncrypted(); // IF NO ENCRYPTION
|
||||
proceedRomFsNotEncrypted();
|
||||
break;
|
||||
case 0x03:
|
||||
proceedRomFsEncrypted(); // If encrypted regular [ 0x03 ]
|
||||
proceedRomFsEncrypted();
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Non-supported 'Crypto type'");
|
||||
|
@ -125,7 +119,7 @@ public class NCAContent {
|
|||
if (decryptedKey == null)
|
||||
throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided.");
|
||||
|
||||
this.romfs = new RomFsEncryptedProvider(
|
||||
this.romfs = new RomFsProvider(
|
||||
ncaFsHeader.getSuperBlockIVFC().getLvl6Offset(),
|
||||
file,
|
||||
ncaOffsetPosition,
|
||||
|
|
|
@ -29,8 +29,6 @@ import javax.crypto.Cipher;
|
|||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
||||
|
|
|
@ -33,8 +33,7 @@ import java.nio.file.Paths;
|
|||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import static libKonogonka.Converter.getLEint;
|
||||
import static libKonogonka.Converter.getLElong;
|
||||
import static libKonogonka.Converter.*;
|
||||
|
||||
public class PFS0Provider implements IPFS0Provider{
|
||||
private final static Logger log = LogManager.getLogger(PFS0Provider.class);
|
||||
|
@ -295,11 +294,68 @@ public class PFS0Provider implements IPFS0Provider{
|
|||
return true;
|
||||
}
|
||||
|
||||
//TODO: REMOVE
|
||||
/**
|
||||
* @deprecated
|
||||
* */
|
||||
@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
|
||||
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() {
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.apache.logging.log4j.Logger;
|
|||
* This class stores information contained in Level 6 Header of the RomFS image
|
||||
* ------------------------------------
|
||||
* | Header Length (usually 0x50) |
|
||||
* | Directory Hash Table Offset | Not used by this library | '<< 32' to get real offset: see implementation
|
||||
* | Directory Hash Table Offset | Not used by this library
|
||||
* | Directory Hash Table Length | Not used by this library
|
||||
* | Directory Metadata Table Offset |
|
||||
* | Directory Metadata Table Length |
|
||||
|
@ -42,7 +42,7 @@ public class Level6Header {
|
|||
private final static Logger log = LogManager.getLogger(Level6Header.class);
|
||||
|
||||
private final long headerLength;
|
||||
private long directoryHashTableOffset;
|
||||
private final long directoryHashTableOffset;
|
||||
private final long directoryHashTableLength;
|
||||
private final long directoryMetadataTableOffset;
|
||||
private final long directoryMetadataTableLength;
|
||||
|
@ -61,7 +61,7 @@ public class Level6Header {
|
|||
throw new Exception("Level 6 Header section is too small");
|
||||
headerLength = getNext();
|
||||
directoryHashTableOffset = getNext();
|
||||
directoryHashTableOffset <<= 32;
|
||||
//directoryHashTableOffset <<= 32;
|
||||
directoryHashTableLength = getNext();
|
||||
directoryMetadataTableOffset = getNext();
|
||||
directoryMetadataTableLength = getNext();
|
||||
|
@ -73,7 +73,7 @@ public class Level6Header {
|
|||
}
|
||||
|
||||
private long getNext(){
|
||||
final long result = Converter.getLEint(headerBytes, _cursor);
|
||||
final long result = Converter.getLElongOfInt(headerBytes, _cursor);
|
||||
_cursor += 0x8;
|
||||
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
|
||||
* along with libKonogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package libKonogonka.Tools.RomFs;
|
||||
|
||||
import libKonogonka.Tools.RomFs.view.DirectoryMetaTablePlainView;
|
||||
|
@ -27,62 +26,70 @@ import java.io.File;
|
|||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
|
||||
public class RomFsEncryptedProvider implements IRomFsProvider{
|
||||
public class RomFsProvider implements IRomFsProvider{
|
||||
private final File file;
|
||||
private final long level6Offset;
|
||||
private final Level6Header level6Header;
|
||||
private final FileSystemEntry rootEntry;
|
||||
|
||||
private final byte[] key; // Used @ createDecryptor only
|
||||
private final byte[] sectionCTR; // Used @ createDecryptor only
|
||||
private final long mediaStartOffset; // Used @ createDecryptor only
|
||||
private final long absoluteOffsetPosition;
|
||||
|
||||
//private long mediaEndOffset; // We know this, but actually never use
|
||||
|
||||
private long ncaOffsetPosition;
|
||||
private byte[] key; // Used @ createDecryptor only
|
||||
private byte[] sectionCTR; // Used @ createDecryptor only
|
||||
private long mediaStartOffset; // Used @ createDecryptor only
|
||||
private long mediaEndOffset;
|
||||
// Used only for debug
|
||||
private final byte[] directoryMetadataTable;
|
||||
private final byte[] fileMetadataTable;
|
||||
|
||||
public RomFsEncryptedProvider(long level6Offset,
|
||||
File encryptedFsImageFile,
|
||||
long romFsOffsetPosition,
|
||||
byte[] key,
|
||||
byte[] sectionCTR,
|
||||
long mediaStartOffset
|
||||
) throws Exception{
|
||||
this(level6Offset, encryptedFsImageFile, romFsOffsetPosition, key, sectionCTR, mediaStartOffset, -1);
|
||||
private final boolean encryptedAesCtr;
|
||||
|
||||
public RomFsProvider(File decryptedFsImageFile, long level6offset) throws Exception{
|
||||
RomFsConstruct construct = new RomFsConstruct(decryptedFsImageFile, level6offset);
|
||||
this.file = decryptedFsImageFile;
|
||||
this.level6Offset = level6offset;
|
||||
this.level6Header = construct.getHeader();
|
||||
this.rootEntry = construct.getRootEntry();
|
||||
|
||||
this.directoryMetadataTable = construct.getDirectoryMetadataTable();
|
||||
this.fileMetadataTable = construct.getFileMetadataTable();
|
||||
|
||||
this.encryptedAesCtr = false;
|
||||
}
|
||||
|
||||
public RomFsEncryptedProvider(long level6Offset,
|
||||
File encryptedFsImageFile,
|
||||
long romFsOffsetPosition,
|
||||
byte[] key,
|
||||
byte[] sectionCTR,
|
||||
long mediaStartOffset,
|
||||
long mediaEndOffset
|
||||
public RomFsProvider(long level6Offset,
|
||||
File encryptedFsImageFile,
|
||||
long ncaOffsetPosition,
|
||||
byte[] key,
|
||||
byte[] sectionCTR,
|
||||
long mediaStartOffset,
|
||||
long mediaEndOffset
|
||||
) throws Exception{
|
||||
this.key = key;
|
||||
this.sectionCTR = sectionCTR;
|
||||
this.mediaStartOffset = mediaStartOffset;
|
||||
this.mediaEndOffset = mediaEndOffset;
|
||||
this.ncaOffsetPosition = ncaOffsetPosition;
|
||||
|
||||
RomFsEncryptedConstruct construct = new RomFsEncryptedConstruct(encryptedFsImageFile,
|
||||
romFsOffsetPosition,
|
||||
RomFsConstruct construct = new RomFsConstruct(encryptedFsImageFile,
|
||||
ncaOffsetPosition,
|
||||
level6Offset,
|
||||
createDecryptor(),
|
||||
mediaStartOffset);
|
||||
mediaStartOffset,
|
||||
mediaEndOffset);
|
||||
this.file = encryptedFsImageFile;
|
||||
this.level6Offset = level6Offset;
|
||||
this.level6Header = construct.getHeader();
|
||||
this.rootEntry = construct.getRootEntry();
|
||||
this.absoluteOffsetPosition = romFsOffsetPosition + (mediaStartOffset * 0x200);
|
||||
|
||||
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
|
||||
|
@ -92,7 +99,12 @@ public class RomFsEncryptedProvider implements IRomFsProvider{
|
|||
@Override
|
||||
public FileSystemEntry getRootEntry() { return rootEntry; }
|
||||
@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())
|
||||
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 internalFileSize = entry.getSize();
|
||||
|
||||
Thread contentRetrievingThread = new Thread(new RomFsEncryptedContentRetrieve(
|
||||
Thread contentRetrievingThread = new Thread(new RomFsContentRetrieve(
|
||||
file,
|
||||
streamOut,
|
||||
absoluteOffsetPosition,
|
||||
createDecryptor(),
|
||||
internalFileOffset,
|
||||
internalFileSize,
|
||||
level6Header.getFileDataOffset(),
|
||||
level6Offset,
|
||||
level6Header.getFileDataOffset()
|
||||
));
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void printDebug(){
|
||||
level6Header.printDebugInfo();
|
|
@ -22,6 +22,7 @@ import libKonogonka.Tools.ISuperProvider;
|
|||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static libKonogonka.Converter.*;
|
||||
|
@ -30,59 +31,54 @@ import static libKonogonka.Converter.*;
|
|||
* HFS0
|
||||
* */
|
||||
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 int filesCnt;
|
||||
private boolean paddingHfs0;
|
||||
private int stringTableSize;
|
||||
private long rawFileDataStart;
|
||||
private final HFS0File[] hfs0Files;
|
||||
|
||||
private HFS0File[] hfs0Files;
|
||||
|
||||
private File file;
|
||||
private final File file;
|
||||
|
||||
HFS0Provider(long hfsOffsetPosition, RandomAccessFile raf, File file) throws Exception{
|
||||
this.file = file; // Will be used @ getHfs0FilePipedInpStream. It's a bad implementation.
|
||||
byte[] hfs0bytes = new byte[16];
|
||||
try{
|
||||
raf.seek(hfsOffsetPosition);
|
||||
if (raf.read(hfs0bytes) != 16){
|
||||
throw new Exception("Read HFS0 structure failure. Can't read first 16 bytes on requested offset.");
|
||||
}
|
||||
|
||||
raf.seek(hfsOffsetPosition);
|
||||
if (raf.read(hfs0bytes) != 16){
|
||||
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];
|
||||
long[] sizeHfs0files = new long[filesCnt];
|
||||
int[] hashedRegionSizeHfs0Files = new int[filesCnt];
|
||||
boolean[] paddingHfs0Files = new boolean[filesCnt];
|
||||
byte[][] SHA256HashHfs0Files = new byte[filesCnt][];
|
||||
int[] strTableOffsets = new int[filesCnt];
|
||||
// TODO: IF NOT EMPTY TABLE: add validation
|
||||
|
||||
long[] offsetSubFile = new long[filesCount];
|
||||
long[] sizeSubFile = new long[filesCount];
|
||||
int[] hashedRegionSubFile = new int[filesCount];
|
||||
boolean[] paddingSubFile = new boolean[filesCount];
|
||||
byte[][] SHA256HashSubFile = new byte[filesCount][];
|
||||
int[] stringTableOffsetSubFile = new int[filesCount];
|
||||
|
||||
try {
|
||||
// Populate meta information regarding each file inside (?) HFS0
|
||||
byte[] metaInfoBytes = new byte[64];
|
||||
for (int i=0; i < filesCnt; i++){
|
||||
if (raf.read(metaInfoBytes) != 64) {
|
||||
for (int i = 0; i < filesCount; i++){
|
||||
if (raf.read(metaInfoBytes) != 64)
|
||||
throw new Exception("Read HFS0 File Entry Table failure for file # "+i);
|
||||
}
|
||||
offsetHfs0files[i] = getLElong(metaInfoBytes, 0);
|
||||
sizeHfs0files[i] = getLElong(metaInfoBytes, 8);
|
||||
hashedRegionSizeHfs0Files[i] = getLEint(metaInfoBytes, 20);
|
||||
paddingHfs0Files[i] = Arrays.equals(Arrays.copyOfRange(metaInfoBytes, 24, 32), new byte[8]);
|
||||
SHA256HashHfs0Files[i] = Arrays.copyOfRange(metaInfoBytes, 32, 64);
|
||||
offsetSubFile[i] = getLElong(metaInfoBytes, 0);
|
||||
sizeSubFile[i] = getLElong(metaInfoBytes, 8);
|
||||
hashedRegionSubFile[i] = getLEint(metaInfoBytes, 20);
|
||||
paddingSubFile[i] = Arrays.equals(Arrays.copyOfRange(metaInfoBytes, 24, 32), new byte[8]);
|
||||
SHA256HashSubFile[i] = Arrays.copyOfRange(metaInfoBytes, 32, 64);
|
||||
|
||||
strTableOffsets[i] = getLEint(metaInfoBytes, 16);
|
||||
stringTableOffsetSubFile[i] = getLEint(metaInfoBytes, 16);
|
||||
}
|
||||
// Define location of actual data for this HFS0
|
||||
rawFileDataStart = raf.getFilePointer()+stringTableSize;
|
||||
|
@ -92,24 +88,24 @@ public class HFS0Provider implements ISuperProvider {
|
|||
if (raf.read(stringTbl) != 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
|
||||
for (int i=0; i < filesCnt; i++){
|
||||
for (int i = 0; i < filesCount; i++){
|
||||
int j = 0;
|
||||
while (stringTbl[strTableOffsets[i]+j] != (byte)0x00)
|
||||
while (stringTbl[stringTableOffsetSubFile[i]+j] != (byte)0x00)
|
||||
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
|
||||
for (int i=0; i < filesCnt; i++){
|
||||
for (int i = 0; i < filesCount; i++){
|
||||
hfs0Files[i] = new HFS0File(
|
||||
namesHfs0files[i],
|
||||
offsetHfs0files[i],
|
||||
sizeHfs0files[i],
|
||||
hashedRegionSizeHfs0Files[i],
|
||||
paddingHfs0Files[i],
|
||||
SHA256HashHfs0Files[i]
|
||||
namesSubFile[i],
|
||||
offsetSubFile[i],
|
||||
sizeSubFile[i],
|
||||
hashedRegionSubFile[i],
|
||||
paddingSubFile[i],
|
||||
SHA256HashSubFile[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -118,15 +114,18 @@ public class HFS0Provider implements ISuperProvider {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean isMagicHFS0() { return magicHFS0; }
|
||||
public int getFilesCnt() { return filesCnt; }
|
||||
public boolean isPaddingHfs0() { return paddingHfs0; }
|
||||
public String getMagic() { return magic; }
|
||||
public int getFilesCount() { return filesCount; }
|
||||
public byte[] getPadding() { return padding; }
|
||||
public int getStringTableSize() { return stringTableSize; }
|
||||
@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();
|
||||
|
@ -140,13 +139,11 @@ public class HFS0Provider implements ISuperProvider {
|
|||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Executing thread");
|
||||
try{
|
||||
long subFileRealPosition = rawFileDataStart + hfs0Files[subFileNumber].getOffset();
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
if (bis.skip(subFileRealPosition) != subFileRealPosition) {
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to skip requested offset");
|
||||
return;
|
||||
}
|
||||
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 = 8388608; // 8mb NOTE: consider switching to 1mb 1048576
|
||||
int readPice = 0x800000; // 8mb NOTE: consider switching to 1mb 1048576
|
||||
|
||||
long readFrom = 0;
|
||||
long realFileSize = hfs0Files[subFileNumber].getSize();
|
||||
|
@ -157,17 +154,16 @@ public class HFS0Provider implements ISuperProvider {
|
|||
if (realFileSize - readFrom < readPice)
|
||||
readPice = Math.toIntExact(realFileSize - readFrom); // it's safe, I guarantee
|
||||
readBuf = new byte[readPice];
|
||||
if (bis.read(readBuf) != readPice) {
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to read requested size from file.");
|
||||
return;
|
||||
}
|
||||
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 (IOException ioe){
|
||||
catch (Exception ioe){
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to provide stream");
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
|
@ -176,20 +172,17 @@ public class HFS0Provider implements ISuperProvider {
|
|||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
|
||||
//TODO
|
||||
@Override
|
||||
public boolean exportContent(String saveToLocation, String subFileName) throws Exception {
|
||||
return false;
|
||||
throw new Exception("Not implemented yet");
|
||||
}
|
||||
|
||||
//TODO
|
||||
@Override
|
||||
public boolean exportContent(String saveToLocation, int subFileNumber) throws Exception {
|
||||
return false;
|
||||
throw new Exception("Not implemented yet");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sugar
|
||||
* */
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception {
|
||||
for (int i = 0; i < hfs0Files.length; i++){
|
||||
|
|
|
@ -76,7 +76,7 @@ public class XCIProvider{
|
|||
xciGamecardCert = new XCIGamecardCert(gamecardCertBytes);
|
||||
|
||||
hfs0ProviderMain = new HFS0Provider(0xf000, raf, file);
|
||||
if (hfs0ProviderMain.getFilesCnt() < 3){
|
||||
if (hfs0ProviderMain.getFilesCount() < 3){
|
||||
raf.close();
|
||||
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());
|
||||
}
|
||||
}
|
||||
/* Getters */
|
||||
/* API */
|
||||
public XCIGamecardHeader getGCHeader(){ return this.xciGamecardHeader; }
|
||||
public XCIGamecardInfo getGCInfo(){ return this.xciGamecardInfo; }
|
||||
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 bytesToSkipTillRequiredBlock = realCountOfBytesToSkip - leftovers;
|
||||
long skipped = super.skip(bytesToSkipTillRequiredBlock);
|
||||
if (bytesToSkipTillRequiredBlock != skipped)
|
||||
throw new IOException("Can't skip bytes. To skip: " +
|
||||
bytesToSkipTillRequiredBlock +
|
||||
".\nActually skipped: " + skipped +
|
||||
".\nLeftovers inside encrypted section: " + leftovers);
|
||||
skipLoop(bytesToSkipTillRequiredBlock);
|
||||
fillDecryptedCache();
|
||||
pseudoPos += n;
|
||||
pointerInsideDecryptedSection = (int) leftovers;
|
||||
return n;
|
||||
}
|
||||
log.trace("4. Pointer Inside + End Position Outside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+n)+")");
|
||||
long skipped = super.skip(realCountOfBytesToSkip);
|
||||
if (realCountOfBytesToSkip != skipped)
|
||||
throw new IOException("Can't skip bytes. To skip: " +
|
||||
realCountOfBytesToSkip +
|
||||
".\nActually skipped: " + skipped);
|
||||
skipLoop(realCountOfBytesToSkip);
|
||||
pseudoPos += n;
|
||||
pointerInsideDecryptedSection = 0;
|
||||
return n;
|
||||
|
@ -217,12 +208,20 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
|
|||
return n;
|
||||
}
|
||||
log.trace("6. Not encrypted ("+pseudoPos+"-"+(pseudoPos+n)+")");
|
||||
long skipped = super.skip(n);
|
||||
skipLoop(n);
|
||||
pseudoPos += n;
|
||||
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
|
||||
public synchronized int read() throws IOException {
|
||||
byte[] b = new byte[1];
|
||||
|
|
|
@ -21,7 +21,7 @@ package libKonogonka.RomFsDecrypted;
|
|||
import java.io.File;
|
||||
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.DisplayName;
|
||||
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 File decryptedFile;
|
||||
long lv6offset;
|
||||
RomFsDecryptedProvider provider;
|
||||
RomFsProvider provider;
|
||||
|
||||
@Disabled
|
||||
@DisplayName("RomFsDecryptedProvider: tests")
|
||||
|
@ -55,7 +55,7 @@ public class RomFsDecryptedTest {
|
|||
lv6offset = Long.parseLong(decryptedFile.getName().replaceAll("(^.*lv6\\s)|(]\\.bin)", ""));
|
||||
}
|
||||
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");
|
||||
//----------------------------------------------------------------------
|
||||
exportFolderContentLegacy(entry, "/tmp/legacy");
|
||||
// exportFolderContentLegacy(entry, "/tmp/legacy");
|
||||
}
|
||||
|
||||
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 {
|
||||
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);
|
||||
|
||||
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