libKonogonka/src/main/java/libKonogonka/fs/PFS0/PFS0Provider.java

156 lines
6.1 KiB
Java

/*
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.fs.PFS0;
import libKonogonka.RainbowDump;
import libKonogonka.fs.ExportAble;
import libKonogonka.fs.ISuperProvider;
import libKonogonka.fs.NCA.NCASectionTableBlock.SuperBlockPFS0;
import libKonogonka.aesctr.InFileStreamProducer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.*;
import java.util.LinkedList;
public class PFS0Provider extends ExportAble implements ISuperProvider {
private final static Logger log = LogManager.getLogger(PFS0Provider.class);
private final long rawBlockDataStart;
private long offsetPositionInFile;
private final InFileStreamProducer producer;
private SuperBlockPFS0 superBlockPFS0;
private long mediaStartOffset;
private final PFS0Header header;
private LinkedList<byte[]> pfs0SHA256hashes;
public PFS0Provider(File nspFile) throws Exception{
this.producer = new InFileStreamProducer(nspFile);
this.stream = producer.produce();
this.header = new PFS0Header(stream);
this.rawBlockDataStart = 0x10L + (header.getFilesCount() * 0x18L) + header.getStringTableSize();
}
public PFS0Provider(InFileStreamProducer producer,
long offsetPositionInFile,
SuperBlockPFS0 superBlockPFS0,
long mediaStartOffset) throws Exception {
this.producer = producer;
this.offsetPositionInFile = offsetPositionInFile;
this.superBlockPFS0 = superBlockPFS0;
this.mediaStartOffset = mediaStartOffset;
this.stream = producer.produce();
long toSkip = offsetPositionInFile + superBlockPFS0.getHashTableOffset();
if (toSkip != stream.skip(toSkip))
throw new Exception("Can't skip bytes prior Hash Table offset");
collectHashes();
this.stream = producer.produce();
toSkip = offsetPositionInFile + superBlockPFS0.getPfs0offset();
if (toSkip != stream.skip(toSkip))
throw new Exception("Can't skip bytes prior PFS0 offset");
this.header = new PFS0Header(stream);
this.rawBlockDataStart = superBlockPFS0.getPfs0offset() + 0x10L + (header.getFilesCount() * 0x18L) + header.getStringTableSize();
}
private void collectHashes() throws Exception{
pfs0SHA256hashes = new LinkedList<>();
long hashTableOffset = superBlockPFS0.getHashTableOffset();
long hashTableSize = superBlockPFS0.getHashTableSize();
if (hashTableOffset > 0){
if (hashTableOffset != stream.skip(hashTableOffset))
throw new Exception("Unable to skip bytes till Hash Table Offset: "+hashTableOffset);
}
for (int i = 0; i < hashTableSize / 0x20; i++){
byte[] sectionHash = new byte[0x20];
if (0x20 != stream.read(sectionHash))
throw new Exception("Unable to read hash");
pfs0SHA256hashes.add(sectionHash);
}
}
public boolean isEncrypted() { return producer.isEncrypted(); }
public PFS0Header getHeader() {return header;}
@Override
public long getRawFileDataStart() { return rawBlockDataStart;}
@Override
public boolean exportContent(String saveToLocation, String subFileName){
PFS0subFile[] pfs0subFiles = header.getPfs0subFiles();
for (int i = 0; i < pfs0subFiles.length; i++){
if (pfs0subFiles[i].getName().equals(subFileName))
return exportContent(saveToLocation, i);
}
return false;
}
@Override
public boolean exportContent(String saveToLocation, int subFileNumber){
try {
PFS0subFile subFile = header.getPfs0subFiles()[subFileNumber];
this.stream = producer.produce();
long toSkip = subFile.getOffset() + mediaStartOffset * 0x200 + rawBlockDataStart;
return export(saveToLocation, subFile.getName(), toSkip, subFile.getSize());
}
catch (Exception e){
log.error("File export failure", e);
return false;
}
}
@Override
public InFileStreamProducer getStreamProducer(String subFileName) throws FileNotFoundException {
PFS0subFile[] pfs0subFiles = header.getPfs0subFiles();
for (int i = 0; i < pfs0subFiles.length; i++) {
if (pfs0subFiles[i].getName().equals(subFileName))
return getStreamProducer(i);
}
throw new FileNotFoundException("No file with such name exists: "+subFileName);
}
@Override
public InFileStreamProducer getStreamProducer(int subFileNumber) {
PFS0subFile subFile = header.getPfs0subFiles()[subFileNumber];
long subFileOffset = subFile.getOffset() + mediaStartOffset * 0x200 + rawBlockDataStart;
return producer.getSuccessor(subFileOffset);
}
public LinkedList<byte[]> getPfs0SHA256hashes() {
return pfs0SHA256hashes;
}
@Override
public File getFile() {
return producer.getFile();
}
public void printDebug(){
log.debug(".:: PFS0Provider ::.\n" +
"File name: " + getFile().getName() + "\n" +
"Raw block data start " + RainbowDump.formatDecHexString(rawBlockDataStart) + "\n" +
"Offset position in file " + RainbowDump.formatDecHexString(offsetPositionInFile) + "\n" +
"Media Start Offset " + RainbowDump.formatDecHexString(mediaStartOffset) + "\n"
);
header.printDebug();
}
}