libKonogonka/src/main/java/libKonogonka/Tools/NCA/NCAContent.java

183 lines
7.5 KiB
Java
Raw Normal View History

2022-08-10 15:55:50 +03:00
/*
2022-08-10 20:20:10 +03:00
Copyright 2019-2022 Dmitry Isaenko
2022-08-10 15:55:50 +03:00
2022-08-10 20:20:10 +03:00
This file is part of libKonogonka.
2022-08-10 15:55:50 +03:00
2022-08-10 20:20:10 +03:00
libKonogonka is free software: you can redistribute it and/or modify
2022-08-10 15:55:50 +03:00
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.
2022-08-10 20:20:10 +03:00
libKonogonka is distributed in the hope that it will be useful,
2022-08-10 15:55:50 +03:00
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
2022-08-10 20:20:10 +03:00
along with libKonogonka. If not, see <https://www.gnu.org/licenses/>.
2022-08-10 15:55:50 +03:00
*/
package libKonogonka.Tools.NCA;
2022-09-05 00:39:48 +03:00
import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader;
2022-08-10 15:55:50 +03:00
import libKonogonka.Tools.PFS0.PFS0Provider;
import libKonogonka.Tools.RomFs.RomFsProvider;
2022-09-12 01:02:22 +03:00
import libKonogonka.ctraes.AesCtrBufferedInputStream;
import libKonogonka.ctraes.AesCtrDecryptForMediaBlocks;
import libKonogonka.ctraes.InFileStreamProducer;
2022-08-10 15:55:50 +03:00
import libKonogonka.exceptions.EmptySectionException;
2022-09-05 00:39:48 +03:00
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
2022-08-10 15:55:50 +03:00
import java.io.*;
2022-09-12 01:02:22 +03:00
import java.nio.file.Files;
import java.nio.file.Paths;
2022-08-10 15:55:50 +03:00
public class NCAContent {
2022-09-05 00:39:48 +03:00
private final static Logger log = LogManager.getLogger(NCAContent.class);
2022-09-05 00:39:48 +03:00
private final File file;
private final long ncaOffsetPosition;
2022-09-05 00:39:48 +03:00
private final NcaFsHeader ncaFsHeader;
private final NCAHeaderTableEntry ncaHeaderTableEntry;
private final byte[] decryptedKey;
private PFS0Provider pfs0;
private RomFsProvider romfs;
2022-08-10 15:55:50 +03:00
2022-09-12 01:02:22 +03:00
// TODO: if decryptedKey is empty, throw exception?
2022-08-10 15:55:50 +03:00
public NCAContent(File file,
long ncaOffsetPosition,
2022-09-05 00:39:48 +03:00
NcaFsHeader ncaFsHeader,
2022-08-10 15:55:50 +03:00
NCAHeaderTableEntry ncaHeaderTableEntry,
byte[] decryptedKey) throws Exception
{
this.file = file;
this.ncaOffsetPosition = ncaOffsetPosition;
2022-09-05 00:39:48 +03:00
this.ncaFsHeader = ncaFsHeader;
2022-08-10 15:55:50 +03:00
this.ncaHeaderTableEntry = ncaHeaderTableEntry;
this.decryptedKey = decryptedKey;
// If nothing to do
if (ncaHeaderTableEntry.getMediaEndOffset() == 0)
throw new EmptySectionException("Empty section");
// If it's PFS0Provider
2022-09-05 00:39:48 +03:00
if (ncaFsHeader.getSuperBlockPFS0() != null)
2022-08-10 15:55:50 +03:00
this.proceedPFS0();
2022-09-05 00:39:48 +03:00
else if (ncaFsHeader.getSuperBlockIVFC() != null)
2022-08-10 15:55:50 +03:00
this.proceedRomFs();
else
throw new Exception("NCAContent(): Not supported. PFS0 or RomFS supported only.");
}
private void proceedPFS0() throws Exception {
2022-09-05 00:39:48 +03:00
switch (ncaFsHeader.getCryptoType()){
2022-09-12 01:02:22 +03:00
case 0x01: // IF NO ENCRYPTION
proceedPFS0NotEncrypted();
2022-08-10 15:55:50 +03:00
break;
case 0x03:
2022-09-12 01:02:22 +03:00
proceedPFS0Encrypted();
2022-08-10 15:55:50 +03:00
break;
default:
2022-09-12 01:02:22 +03:00
throw new Exception("'Crypto type' not supported: "+ncaFsHeader.getCryptoType());
2022-08-10 15:55:50 +03:00
}
}
private void proceedPFS0NotEncrypted() throws Exception{
InFileStreamProducer producer = new InFileStreamProducer(file); // no need to bypass ncaOffsetPosition!
pfs0 = new PFS0Provider(producer,
makeOffsetPositionInFile(),
ncaFsHeader.getSuperBlockPFS0(),
ncaHeaderTableEntry.getMediaStartOffset());
2022-08-10 15:55:50 +03:00
}
private void proceedPFS0Encrypted() throws Exception{
pfs0 = new PFS0Provider(makeEncryptedProducer(), makeOffsetPositionInFile(), ncaFsHeader.getSuperBlockPFS0(),
ncaHeaderTableEntry.getMediaStartOffset());
2022-08-10 15:55:50 +03:00
}
private void proceedRomFs() throws Exception{
2022-09-05 00:39:48 +03:00
switch (ncaFsHeader.getCryptoType()){
2022-08-10 15:55:50 +03:00
case 0x01:
proceedRomFsNotEncrypted();
2022-08-10 15:55:50 +03:00
break;
case 0x03:
proceedRomFsEncrypted();
2022-08-10 15:55:50 +03:00
break;
default:
throw new Exception("Non-supported 'Crypto type' "+ncaFsHeader.getCryptoType());
2022-08-10 15:55:50 +03:00
}
}
2022-09-05 00:39:48 +03:00
private void proceedRomFsNotEncrypted(){ // TODO: Clarify, implement if needed
log.error("proceedRomFs() -> proceedRomFsNotEncrypted() is not implemented :(");
2022-08-10 15:55:50 +03:00
}
private void proceedRomFsEncrypted() throws Exception{
if (decryptedKey == null)
throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided.");
this.romfs = new RomFsProvider(makeEncryptedProducer(), ncaFsHeader.getSuperBlockIVFC().getLvl6Offset(),
makeOffsetPositionInFile(), ncaHeaderTableEntry.getMediaStartOffset());
2022-08-10 15:55:50 +03:00
}
public PFS0Provider getPfs0() { return pfs0; }
public RomFsProvider getRomfs() { return romfs; }
2022-08-10 15:55:50 +03:00
private InFileStreamProducer makeEncryptedProducer() throws Exception{
AesCtrDecryptForMediaBlocks decryptor = new AesCtrDecryptForMediaBlocks(decryptedKey, ncaFsHeader.getSectionCTR(),
ncaHeaderTableEntry.getMediaStartOffset() * 0x200);
return new InFileStreamProducer(file, ncaOffsetPosition, 0, decryptor,
ncaHeaderTableEntry.getMediaStartOffset(), ncaHeaderTableEntry.getMediaEndOffset());
}
private long makeOffsetPositionInFile(){
return ncaOffsetPosition + ncaHeaderTableEntry.getMediaStartOffset() * 0x200;
}
2022-08-10 15:55:50 +03:00
/**
* Export NCA content AS IS.
* Not so good for PFS0 since there are SHAs list that discourages but good for 'romfs' and things like that
* */
2022-09-12 01:02:22 +03:00
public boolean exportMediaBlock(String saveToLocation){
File location = new File(saveToLocation);
location.mkdirs();
BufferedInputStream stream;
long mediaStartOffset = ncaHeaderTableEntry.getMediaStartOffset();
long mediaEndOffset = ncaHeaderTableEntry.getMediaEndOffset();
long mediaBlocksCount = mediaEndOffset - mediaStartOffset;
try (BufferedOutputStream extractedFileBOS = new BufferedOutputStream(
Files.newOutputStream(Paths.get(saveToLocation+File.separator+file.getName()+"_MediaBlock.bin")))){
if(ncaFsHeader.getCryptoType()==0x01){
stream = new BufferedInputStream(Files.newInputStream(file.toPath()));
}
else if(ncaFsHeader.getCryptoType()==0x03) {
AesCtrDecryptForMediaBlocks decryptor = new AesCtrDecryptForMediaBlocks(decryptedKey,
2022-09-12 01:02:22 +03:00
ncaFsHeader.getSectionCTR(),
mediaStartOffset * 0x200);
stream = new AesCtrBufferedInputStream(decryptor,
ncaOffsetPosition,
mediaStartOffset,
mediaEndOffset,
Files.newInputStream(file.toPath()),
Files.size(file.toPath()));
2022-09-12 01:02:22 +03:00
}
else
throw new Exception("Crypto type not supported");
for (int i = 0; i < mediaBlocksCount; i++){
byte[] block = new byte[0x200];
if (0x200 != stream.read(block))
throw new Exception("Read failure");
extractedFileBOS.write(block);
}
stream.close();
} catch (Exception e) {
log.error("Failed to export MediaBlock", e);
return false;
2022-08-10 15:55:50 +03:00
}
2022-09-12 01:02:22 +03:00
return true;
2022-08-10 15:55:50 +03:00
}
2022-09-12 01:02:22 +03:00
2022-08-10 15:55:50 +03:00
public long getRawDataContentSize(){
return (ncaHeaderTableEntry.getMediaEndOffset() - ncaHeaderTableEntry.getMediaStartOffset()) * 0x200;
}
public String getFileName(){
return file.getName();
}
}