158 lines
6.2 KiB
Java
158 lines
6.2 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.Tools.PFS0;
|
|
|
|
import libKonogonka.RainbowDump;
|
|
import libKonogonka.Tools.ExportAble;
|
|
import libKonogonka.Tools.ISuperProvider;
|
|
import libKonogonka.Tools.NCA.NCASectionTableBlock.SuperBlockPFS0;
|
|
import libKonogonka.ctraes.InFileStreamProducer;
|
|
import org.apache.logging.log4j.LogManager;
|
|
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 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();
|
|
}
|
|
} |