293 lines
12 KiB
Java
293 lines
12 KiB
Java
/*
|
|
* Copyright 2019-2020 Dmitry Isaenko
|
|
*
|
|
* This file is part of Konogonka.
|
|
*
|
|
* Konogonka 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.
|
|
*
|
|
* Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package libKonogonka.Tools.RomFs;
|
|
|
|
import libKonogonka.ctraes.AesCtrDecryptSimple;
|
|
|
|
import java.io.File;
|
|
import java.io.PipedInputStream;
|
|
import java.io.PipedOutputStream;
|
|
import java.io.RandomAccessFile;
|
|
import java.util.Arrays;
|
|
|
|
public class RomFsEncryptedProvider implements IRomFsProvider{
|
|
|
|
private long level6Offset;
|
|
|
|
private File file;
|
|
private Level6Header header;
|
|
|
|
private FileSystemEntry rootEntry;
|
|
|
|
//--------------------------------
|
|
|
|
private long romFSoffsetPosition;
|
|
private byte[] key;
|
|
private byte[] sectionCTR;
|
|
private long mediaStartOffset;
|
|
private long mediaEndOffset;
|
|
|
|
public RomFsEncryptedProvider(long romFSoffsetPosition,
|
|
long level6Offset,
|
|
File fileWithEncPFS0,
|
|
byte[] key,
|
|
byte[] sectionCTR,
|
|
long mediaStartOffset,
|
|
long mediaEndOffset
|
|
) throws Exception{
|
|
this.file = fileWithEncPFS0;
|
|
this.level6Offset = level6Offset;
|
|
this.romFSoffsetPosition = romFSoffsetPosition;
|
|
this.key = key;
|
|
this.sectionCTR = sectionCTR;
|
|
this.mediaStartOffset = mediaStartOffset;
|
|
this.mediaEndOffset = mediaEndOffset;
|
|
|
|
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
|
long abosluteOffsetPosition = romFSoffsetPosition + (mediaStartOffset * 0x200);
|
|
raf.seek(abosluteOffsetPosition + level6Offset);
|
|
|
|
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
|
|
//Go to Level 6 header
|
|
decryptor.skipNext(level6Offset / 0x200);
|
|
|
|
// Decrypt data
|
|
byte[] encryptedBlock = new byte[0x200];
|
|
byte[] dectyptedBlock;
|
|
if (raf.read(encryptedBlock) == 0x200)
|
|
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
|
else
|
|
throw new Exception("Failed to read header header (0x200 - block)");
|
|
|
|
this.header = new Level6Header(dectyptedBlock);
|
|
|
|
header.printDebugInfo();
|
|
|
|
if (header.getDirectoryMetadataTableLength() < 0)
|
|
throw new Exception("Not supported: DirectoryMetadataTableLength < 0");
|
|
|
|
if (header.getFileMetadataTableLength() < 0)
|
|
throw new Exception("Not supported: FileMetadataTableLength < 0");
|
|
|
|
/*---------------------------------*/
|
|
|
|
// Read directories metadata
|
|
byte[] directoryMetadataTable = readMetaTable(abosluteOffsetPosition,
|
|
header.getDirectoryMetadataTableOffset(),
|
|
header.getDirectoryMetadataTableLength(),
|
|
raf);
|
|
|
|
// Read files metadata
|
|
byte[] fileMetadataTable = readMetaTable(abosluteOffsetPosition,
|
|
header.getFileMetadataTableOffset(),
|
|
header.getFileMetadataTableLength(),
|
|
raf);
|
|
|
|
rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable);
|
|
|
|
raf.close();
|
|
}
|
|
|
|
private byte[] readMetaTable(long abosluteOffsetPosition,
|
|
long metaOffset,
|
|
long metaSize,
|
|
RandomAccessFile raf) throws Exception{
|
|
byte[] encryptedBlock;
|
|
byte[] dectyptedBlock;
|
|
byte[] metadataTable = new byte[(int) metaSize];
|
|
//0
|
|
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
|
|
|
|
long startBlock = metaOffset / 0x200;
|
|
|
|
decryptor.skipNext(level6Offset / 0x200 + startBlock);
|
|
|
|
raf.seek(abosluteOffsetPosition + level6Offset + startBlock * 0x200);
|
|
|
|
//1
|
|
long ignoreBytes = metaOffset - startBlock * 0x200;
|
|
long currentPosition = 0;
|
|
|
|
if (ignoreBytes > 0) {
|
|
encryptedBlock = new byte[0x200];
|
|
if (raf.read(encryptedBlock) == 0x200) {
|
|
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
|
// If we have extra-small file that is less then a block and even more
|
|
if ((0x200 - ignoreBytes) > metaSize){
|
|
metadataTable = Arrays.copyOfRange(dectyptedBlock, (int)ignoreBytes, 0x200);
|
|
return metadataTable;
|
|
}
|
|
else {
|
|
System.arraycopy(dectyptedBlock, (int) ignoreBytes, metadataTable, 0, 0x200 - (int) ignoreBytes);
|
|
currentPosition = 0x200 - ignoreBytes;
|
|
}
|
|
}
|
|
else {
|
|
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from 1st bock for Directory Metadata Table");
|
|
}
|
|
startBlock++;
|
|
}
|
|
long endBlock = (metaSize + ignoreBytes) / 0x200 + startBlock; // <- pointing to place where any data related to this media-block ends
|
|
|
|
//2
|
|
int extraData = (int) ((endBlock - startBlock)*0x200 - (metaSize + ignoreBytes));
|
|
|
|
if (extraData < 0)
|
|
endBlock--;
|
|
//3
|
|
while ( startBlock < endBlock ) {
|
|
encryptedBlock = new byte[0x200];
|
|
if (raf.read(encryptedBlock) == 0x200) {
|
|
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
|
System.arraycopy(dectyptedBlock, 0, metadataTable, (int) currentPosition, 0x200);
|
|
}
|
|
else
|
|
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory Metadata Table");
|
|
|
|
startBlock++;
|
|
currentPosition += 0x200;
|
|
}
|
|
|
|
//4
|
|
if (extraData != 0){ // In case we didn't get what we want
|
|
encryptedBlock = new byte[0x200];
|
|
if (raf.read(encryptedBlock) == 0x200) {
|
|
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
|
System.arraycopy(dectyptedBlock, 0, metadataTable, (int) currentPosition, Math.abs(extraData));
|
|
}
|
|
else
|
|
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory Metadata Table");
|
|
}
|
|
|
|
return metadataTable;
|
|
}
|
|
|
|
@Override
|
|
public long getLevel6Offset() { return level6Offset; }
|
|
@Override
|
|
public Level6Header getHeader() { return header; }
|
|
@Override
|
|
public FileSystemEntry getRootEntry() { return rootEntry; }
|
|
@Override
|
|
public PipedInputStream getContent(FileSystemEntry entry) throws Exception{
|
|
if (entry.isDirectory())
|
|
throw new Exception("Request of the binary stream for the folder entry doesn't make sense.");
|
|
|
|
PipedOutputStream streamOut = new PipedOutputStream();
|
|
Thread workerThread;
|
|
|
|
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
|
workerThread = new Thread(() -> {
|
|
System.out.println("RomFsDecryptedProvider -> getContent(): Executing thread");
|
|
try {
|
|
|
|
byte[] encryptedBlock;
|
|
byte[] dectyptedBlock;
|
|
|
|
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
|
|
|
//0
|
|
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
|
|
|
|
long startBlock = (entry.getFileOffset() + header.getFileDataOffset()) / 0x200;
|
|
|
|
decryptor.skipNext(level6Offset / 0x200 + startBlock);
|
|
|
|
long abosluteOffsetPosition = romFSoffsetPosition + (mediaStartOffset * 0x200);
|
|
|
|
raf.seek(abosluteOffsetPosition + level6Offset + startBlock * 0x200);
|
|
|
|
//1
|
|
long ignoreBytes = (entry.getFileOffset() + header.getFileDataOffset()) - startBlock * 0x200;
|
|
|
|
if (ignoreBytes > 0) {
|
|
encryptedBlock = new byte[0x200];
|
|
if (raf.read(encryptedBlock) == 0x200) {
|
|
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
|
// If we have extra-small file that is less then a block and even more
|
|
if ((0x200 - ignoreBytes) > entry.getFileSize()){
|
|
streamOut.write(dectyptedBlock, (int)ignoreBytes, (int) entry.getFileSize()); // safe cast
|
|
raf.close();
|
|
streamOut.close();
|
|
return;
|
|
}
|
|
else {
|
|
streamOut.write(dectyptedBlock, (int) ignoreBytes, 0x200 - (int) ignoreBytes);
|
|
}
|
|
}
|
|
else {
|
|
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from 1st bock for Directory Metadata Table");
|
|
}
|
|
startBlock++;
|
|
}
|
|
long endBlock = (entry.getFileSize() + ignoreBytes) / 0x200 + startBlock; // <- pointing to place where any data related to this media-block ends
|
|
|
|
//2
|
|
int extraData = (int) ((endBlock - startBlock)*0x200 - (entry.getFileSize() + ignoreBytes));
|
|
|
|
if (extraData < 0)
|
|
endBlock--;
|
|
//3
|
|
while ( startBlock < endBlock ) {
|
|
encryptedBlock = new byte[0x200];
|
|
if (raf.read(encryptedBlock) == 0x200) {
|
|
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
|
streamOut.write(dectyptedBlock);
|
|
}
|
|
else
|
|
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory Metadata Table");
|
|
|
|
startBlock++;
|
|
}
|
|
|
|
//4
|
|
if (extraData != 0){ // In case we didn't get what we want
|
|
encryptedBlock = new byte[0x200];
|
|
if (raf.read(encryptedBlock) == 0x200) {
|
|
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
|
streamOut.write(dectyptedBlock, 0, Math.abs(extraData));
|
|
}
|
|
else
|
|
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory Metadata Table");
|
|
}
|
|
raf.close();
|
|
streamOut.close();
|
|
} catch (Exception e) {
|
|
System.out.println("RomFsDecryptedProvider -> getContent(): Unable to provide stream");
|
|
e.printStackTrace();
|
|
}
|
|
System.out.println("RomFsDecryptedProvider -> getContent(): Thread is dead");
|
|
});
|
|
workerThread.start();
|
|
return streamIn;
|
|
}
|
|
|
|
@Override
|
|
public File getFile() {
|
|
return file;
|
|
}
|
|
|
|
private void printDebug(byte[] directoryMetadataTable, byte[] fileMetadataTable){
|
|
new FolderMeta4Debug(header.getDirectoryMetadataTableLength(), directoryMetadataTable);
|
|
new FileMeta4Debug(header.getFileMetadataTableLength(), fileMetadataTable);
|
|
rootEntry.printTreeForDebug();
|
|
}
|
|
}
|