Ass stream support into System2 related classes.
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Dmitry Isaenko 2023-01-14 15:07:32 +03:00
parent 045d195f91
commit 7add08c196
11 changed files with 350 additions and 129 deletions

View file

@ -4,9 +4,8 @@ import java.io.BufferedInputStream;
import java.io.File;
public interface IProducer {
BufferedInputStream produce() throws Exception;
IProducer getSuccessor(long subOffset);
IProducer getSuccessor(long subOffset) throws Exception;
boolean isEncrypted();
File getFile();
}

View file

@ -22,6 +22,7 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.HashMap;
// NOTE: Probably it would be better to make it singleton which creates along with 'library initialization'
public class KeyChainHolder {
@ -72,31 +73,11 @@ public class KeyChainHolder {
return tempKeySet;
}
public String getXci_header_key() {
return xci_header_key;
}
public String getHeader_key() {
return rawKeySet.get("header_key");
}
public HashMap<String, String> getRawKeySet() {
return rawKeySet;
}
public HashMap<String, String> getKey_area_key_application() {
return key_area_key_application;
}
public HashMap<String, String> getKey_area_key_ocean() {
return key_area_key_ocean;
}
public HashMap<String, String> getKey_area_key_system() {
return key_area_key_system;
}
public HashMap<String, String> getTitlekek() {
return titlekek;
}
public String getXci_header_key() { return xci_header_key; }
public String getHeader_key() { return rawKeySet.get("header_key"); }
public HashMap<String, String> getRawKeySet() { return rawKeySet; }
public HashMap<String, String> getKey_area_key_application() { return key_area_key_application; }
public HashMap<String, String> getKey_area_key_ocean() { return key_area_key_ocean; }
public HashMap<String, String> getKey_area_key_system() { return key_area_key_system; }
public HashMap<String, String> getTitlekek() { return titlekek; }
}

View file

@ -79,7 +79,7 @@ public class RomFsProvider extends ExportAble {
exportSingleFile(entry, saveToLocation);
}
catch (Exception e){
log.error("File export failure", e);
log.error(getFile().getName()+" export failure ", e);
return false;
}
return true;
@ -98,10 +98,15 @@ public class RomFsProvider extends ExportAble {
}
private void exportSingleFile(FileSystemEntry entry, String saveToLocation) throws Exception {
try {
stream = producer.produce();
long skipBytes = entry.getOffset() + mediaStartOffset * 0x200 + level6Header.getFileDataOffset() + level6Offset;
export(saveToLocation, entry.getName(), skipBytes, entry.getSize());
}
catch (Exception e){
throw new Exception(entry.getName()+": "+e.getLocalizedMessage(), e);
}
}
public InFileStreamProducer getStreamProducer(FileSystemEntry entry) throws Exception{
if (entry.isDirectory())

View file

@ -115,6 +115,9 @@ public class System2Header {
sha256overEncryptedSection1 = Arrays.copyOfRange(decodedHeaderBytes, 0xa0, 0xc0);
sha256overEncryptedSection2 = Arrays.copyOfRange(decodedHeaderBytes, 0xc0, 0xe0);
sha256overEncryptedSection3 = Arrays.copyOfRange(decodedHeaderBytes, 0xe0, 0x100);
if (packageSize != 0x200 + section0size)
log.error("'Package size' doesn't match 'Header Size' + 'Section 0 size'!");
}
public byte[] getHeaderCtr() { return headerCtr; }

View file

@ -21,11 +21,9 @@ package libKonogonka.Tools.other.System2;
import libKonogonka.KeyChainHolder;
import libKonogonka.Tools.ExportAble;
import libKonogonka.Tools.other.System2.ini1.Ini1Provider;
import libKonogonka.ctraesclassic.AesCtrClassicBufferedInputStream;
import libKonogonka.ctraesclassic.AesCtrDecryptClassic;
import libKonogonka.ctraesclassic.AesCtrStream;
import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.ctraesclassic.InFileStreamClassicProducer;
import javax.crypto.CipherInputStream;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.file.Files;
@ -39,16 +37,30 @@ public class System2Provider extends ExportAble {
private KernelMap kernelMap;
private Ini1Provider ini1Provider;
private final String pathToFile;
private final KeyChainHolder keyChainHolder;
private InFileStreamClassicProducer producer;
public System2Provider(String pathToFile, KeyChainHolder keyChainHolder) throws Exception{
this.pathToFile = pathToFile;
this.keyChainHolder = keyChainHolder;
this.stream = new BufferedInputStream(Files.newInputStream(Paths.get(pathToFile)));
Path filePath = Paths.get(pathToFile);
this.stream = new BufferedInputStream(Files.newInputStream(filePath));
readSignatures();
readHeader();
this.stream.close();
createProducerOfFile(filePath);
findIni1KernelMap();
this.stream.close();
}
public System2Provider(InFileStreamProducer producer, KeyChainHolder keyChainHolder) throws Exception{
this.keyChainHolder = keyChainHolder;
this.stream = producer.produce();
readSignatures();
readHeader();
this.stream.close();
createProducerOfStream(producer);
findIni1KernelMap();
this.stream.close();
}
@ -66,23 +78,40 @@ public class System2Provider extends ExportAble {
this.header = new System2Header(headerBytes, keyChainHolder.getRawKeySet());
}
private void createProducerOfFile(Path filePath) throws Exception{
this.producer = new InFileStreamClassicProducer(filePath,
0,
0x200,
header.getPackageSize(),
header.getKey(),
header.getSection0Ctr());
this.stream = producer.produce();
}
private void createProducerOfStream(InFileStreamProducer parentProducer) throws Exception{
producer = new InFileStreamClassicProducer(parentProducer,
0,
0x200,
header.getPackageSize(),
header.getKey(),
header.getSection0Ctr(),
header.getPackageSize());
this.stream = producer.produce();
}
private void findIni1KernelMap() throws Exception{
try (InputStream fis = Files.newInputStream(Paths.get(pathToFile))){
// Encrypted section comes next
long toSkip = 0x200;
if (toSkip != fis.skip(toSkip))
throw new Exception("Unable to skip offset: " + toSkip);
if (0x200 != stream.skip(0x200))
throw new Exception("Unable to skip offset of 0x200");
ByteBuffer byteBuffer = ByteBuffer.allocate(0x1000);
try (CipherInputStream cipherInputStream = AesCtrStream.getStream(header.getKey(), header.getSection0Ctr(), fis);) {
for (int j = 0; j < 8; j++) {
byte[] block = new byte[0x200];
int actuallyRead;
if ((actuallyRead = cipherInputStream.read(block)) != 0x200)
if ((actuallyRead = stream.read(block)) != 0x200)
throw new Exception("Read failure " + actuallyRead);
byteBuffer.put(block);
}
}
byte[] searchField = byteBuffer.array();
for (int i = 0; i < 1024; i += 4) {
@ -92,17 +121,9 @@ public class System2Provider extends ExportAble {
}
throw new Exception("Kernel map not found");
}
}
public boolean exportKernel(String saveTo) throws Exception{
Path filePath = Paths.get(pathToFile);
AesCtrDecryptClassic decryptor = new AesCtrDecryptClassic(header.getKey(), header.getSection0Ctr());
stream = new AesCtrClassicBufferedInputStream(decryptor,
0x200,
Files.size(filePath), // size of system2
Files.newInputStream(filePath),
Files.size(filePath));
stream = producer.produce();
return export(saveTo, "Kernel.bin", 0x200, header.getSection0size());
}
@ -111,7 +132,8 @@ public class System2Provider extends ExportAble {
public KernelMap getKernelMap() { return kernelMap; }
public Ini1Provider getIni1Provider() throws Exception{
if (ini1Provider == null)
ini1Provider = new Ini1Provider(header, pathToFile, kernelMap);
ini1Provider = new Ini1Provider(
producer.getSuccessor(0x200 + kernelMap.getIni1Offset(), true));
return ini1Provider;
}
}

View file

@ -19,7 +19,6 @@
package libKonogonka.Tools.other.System2.ini1;
import libKonogonka.Tools.ExportAble;
import libKonogonka.Tools.other.System2.KernelMap;
import libKonogonka.Tools.other.System2.System2Header;
import libKonogonka.ctraesclassic.InFileStreamClassicProducer;
@ -35,17 +34,26 @@ public class Ini1Provider extends ExportAble {
private final InFileStreamClassicProducer producer;
public Ini1Provider(System2Header system2Header, String pathToFile, KernelMap kernelMap) throws Exception{
public Ini1Provider(InFileStreamClassicProducer producer) throws Exception{
this.producer = producer;
this.stream = producer.produce();
makeHeader();
collectKips();
this.stream.close();
}
public Ini1Provider(System2Header system2Header, String pathToFile, int kernelMapIni1Offset) throws Exception{
Path filePath = Paths.get(pathToFile);
this.producer = new InFileStreamClassicProducer(filePath,
0x200 + kernelMap.getIni1Offset(),
0x200 + kernelMapIni1Offset,
0x200,
Files.size(filePath), // size of system2
system2Header.getKey(),
system2Header.getSection0Ctr());
stream = producer.produce();
this.stream = producer.produce();
makeHeader();
collectKips();
this.stream.close();
}
private void makeHeader() throws Exception{

View file

@ -54,23 +54,25 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
private int pointerInsideDecryptedSection;
@Override
public synchronized int read(byte[] b) throws IOException{
int bytesToRead = b.length;
public int read(byte[] b, int off, int len) throws IOException {
if (off != 0 || len != b.length)
throw new IOException("Not supported");
if (isPointerInsideEncryptedSection()){
int bytesFromFirstBlock = 0x200 - pointerInsideDecryptedSection;
if (bytesFromFirstBlock > bytesToRead){
if (bytesFromFirstBlock > len){
log.trace("1.2. Pointer Inside + End Position Inside (Decrypted) Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesToRead);
System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, len);
pseudoPos += bytesToRead;
pointerInsideDecryptedSection += bytesToRead;
pseudoPos += len;
pointerInsideDecryptedSection += len;
return b.length;
}
if (isEndPositionInsideEncryptedSection(b.length)) {
log.trace("1.1. Pointer Inside + End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
int middleBlocksCount = (bytesToRead - bytesFromFirstBlock) / 0x200;
int bytesFromLastBlock = (bytesToRead - bytesFromFirstBlock) % 0x200;
int middleBlocksCount = (len - bytesFromFirstBlock) / 0x200;
int bytesFromLastBlock = (len - bytesFromFirstBlock) % 0x200;
//1
System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesFromFirstBlock);
//2
@ -79,17 +81,17 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
System.arraycopy(decryptedBytes, 0, b, bytesFromFirstBlock+i*0x200, 0x200);
}
//3
if(fileSize > (pseudoPos+bytesToRead)) {
if(fileSize > (pseudoPos+ len)) {
fillDecryptedCache();
System.arraycopy(decryptedBytes, 0, b, bytesFromFirstBlock + middleBlocksCount * 0x200, bytesFromLastBlock);
}
pseudoPos += bytesToRead;
pseudoPos += len;
pointerInsideDecryptedSection = bytesFromLastBlock;
return b.length;
}
log.trace("1. Pointer Inside + End Position Outside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
int middleBlocksCount = (int) ((mediaOffsetPositionEnd - (pseudoPos+bytesFromFirstBlock)) / 0x200);
int bytesFromEnd = bytesToRead - bytesFromFirstBlock - middleBlocksCount * 0x200;
int bytesFromEnd = len - bytesFromFirstBlock - middleBlocksCount * 0x200;
//1
System.arraycopy(decryptedBytes, pointerInsideDecryptedSection, b, 0, bytesFromFirstBlock);
//2
@ -100,15 +102,15 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
}
//3 // TODO: if it's zero?
System.arraycopy(readChunk(bytesFromEnd), 0, b, bytesFromFirstBlock+middleBlocksCount*0x200, bytesFromEnd);
pseudoPos += bytesToRead;
pseudoPos += len;
pointerInsideDecryptedSection = 0;
return b.length;
}
if (isEndPositionInsideEncryptedSection(bytesToRead)) {
if (isEndPositionInsideEncryptedSection(len)) {
log.trace("2. End Position Inside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
int bytesTillEncrypted = (int) (mediaOffsetPositionStart - pseudoPos);
int fullEncryptedBlocks = (bytesToRead - bytesTillEncrypted) / 0x200;
int incompleteEncryptedBytes = (bytesToRead - bytesTillEncrypted) % 0x200;
int fullEncryptedBlocks = (len - bytesTillEncrypted) / 0x200;
int incompleteEncryptedBytes = (len - bytesTillEncrypted) % 0x200;
System.arraycopy(readChunk(bytesTillEncrypted), 0, b, 0, bytesTillEncrypted);
//2
for (int i = 0; i < fullEncryptedBlocks; i++) {
@ -118,12 +120,12 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
//3
fillDecryptedCache();
System.arraycopy(decryptedBytes, 0, b, bytesTillEncrypted+fullEncryptedBlocks*0x200, incompleteEncryptedBytes);
pseudoPos += bytesToRead;
pseudoPos += len;
pointerInsideDecryptedSection = incompleteEncryptedBytes;
return b.length;
}
log.trace("3. Not encrypted ("+pseudoPos+"-"+(pseudoPos+b.length)+")");
pseudoPos += bytesToRead;
pseudoPos += len;
pointerInsideDecryptedSection = 0;
return super.read(b);
}
@ -146,10 +148,9 @@ public class AesCtrBufferedInputStream extends BufferedInputStream {
}
private byte[] readChunk(int bytes) throws IOException{
byte[] chunkBytes = new byte[bytes];
long actuallyRead = super.read(chunkBytes);
long actuallyRead = super.read(chunkBytes, 0, bytes);
if (actuallyRead != bytes)
throw new IOException("Can't read. Need block of "+ bytes +" while only " +
actuallyRead + " bytes.");
throw new IOException("Can't read: " + actuallyRead + "/"+ bytes);
return chunkBytes;
}

View file

@ -138,8 +138,7 @@ public class AesCtrClassicBufferedInputStream extends BufferedInputStream {
byte[] chunkBytes = new byte[bytes];
long actuallyRead = super.read(chunkBytes);
if (actuallyRead != bytes)
throw new IOException("Can't read. Need block of "+ bytes +" while only " +
actuallyRead + " bytes.");
throw new IOException("Can't read. "+ bytes +"/" + actuallyRead);
return chunkBytes;
}

View file

@ -27,6 +27,7 @@ import javax.crypto.spec.SecretKeySpec;
import java.io.InputStream;
import java.security.Security;
@Deprecated
public class AesCtrStream {
private static boolean BCinitialized = false;

View file

@ -19,21 +19,36 @@
package libKonogonka.ctraesclassic;
import libKonogonka.IProducer;
import libKonogonka.RainbowDump;
import libKonogonka.ctraes.InFileStreamProducer;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FilterInputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class InFileStreamClassicProducer implements IProducer {
private boolean encrypted;
private final Path filePath;
private Path filePath;
private InFileStreamProducer parentProducer;
private long offset;
private long encryptedStartOffset;
private long encryptedEndOffset;
private AesCtrDecryptClassic decryptor;
private final long fileSize;
/** Reference AES-CTR stream producer.
* @param filePath Path to file-container
* @param offset Offset to skip (since file beginning).
* @param encryptedStartOffset Offset since file beginning where encrypted section starts
* @param encryptedEndOffset Offset since file beginning where encrypted section ends
* @param key AES-CTR Key
* @param iv CTR / IV (counter)
*/
public InFileStreamClassicProducer(Path filePath,
long offset,
long encryptedStartOffset,
@ -46,43 +61,94 @@ public class InFileStreamClassicProducer implements IProducer {
this.encryptedStartOffset = encryptedStartOffset;
this.encryptedEndOffset = encryptedEndOffset;
this.decryptor = new AesCtrDecryptClassic(key, iv);
this.fileSize = Files.size(filePath);
}
public InFileStreamClassicProducer(Path filePath,
private InFileStreamClassicProducer(Path filePath,
long offset,
long encryptedStartOffset,
long encryptedEndOffset, //Files.size(filePath)
AesCtrDecryptClassic decryptor){
AesCtrDecryptClassic decryptor) throws Exception{
this.encrypted = true;
this.filePath = filePath;
this.offset = offset;
this.encryptedStartOffset = encryptedStartOffset;
this.encryptedEndOffset = encryptedEndOffset;
this.decryptor = decryptor;
this.fileSize = Files.size(filePath);
}
public InFileStreamClassicProducer(Path filePath){
/** Stream producer for non-encrypted files.
* @param filePath Path to file-container
* */
public InFileStreamClassicProducer(Path filePath) throws Exception{
this.filePath = filePath;
this.fileSize = Files.size(filePath);
}
public InFileStreamClassicProducer(Path filePath, long offset){
/** Stream producer for non-encrypted files.
* @param filePath Path to file-container
* @param offset Offset to skip (since file beginning).
* */
public InFileStreamClassicProducer(Path filePath, long offset) throws Exception{
this.filePath = filePath;
this.offset = offset;
this.fileSize = Files.size(filePath);
}
/** Reference AES-CTR stream producer that utilizes InFileStreamProducer instead of file.
* @param parentProducer Producer of the stream
* @param offset Offset to skip at parent stream
* @param encryptedStartOffset Offset since parent stream start at stream where encrypted section starts
* @param encryptedEndOffset Offset since parent stream start at stream where encrypted section ends
* @param key AES-CTR Key
* @param iv CTR / IV (counter)
*/
public InFileStreamClassicProducer(InFileStreamProducer parentProducer,
long offset,
long encryptedStartOffset,
long encryptedEndOffset,
String key,
byte[] iv,
long fileSize) throws Exception{
this.parentProducer = parentProducer;
this.encrypted = true;
this.offset = offset;
this.encryptedStartOffset = encryptedStartOffset;
this.encryptedEndOffset = encryptedEndOffset;
this.decryptor = new AesCtrDecryptClassic(key, iv);
this.fileSize = fileSize;
}
private InFileStreamClassicProducer(InFileStreamProducer parentProducer,
long offset,
long encryptedStartOffset,
long encryptedEndOffset,
AesCtrDecryptClassic decryptor,
long fileSize){
this.parentProducer = parentProducer;
this.encrypted = true;
this.offset = offset;
this.encryptedStartOffset = encryptedStartOffset;
this.encryptedEndOffset = encryptedEndOffset;
this.decryptor = decryptor;
this.fileSize = fileSize;
}
@Override
public BufferedInputStream produce() throws Exception{
if (encrypted)
return produceAesCtr();
else
return produceNotEncrypted();
}
private BufferedInputStream produceAesCtr() throws Exception{
decryptor.reset();
AesCtrClassicBufferedInputStream stream = new AesCtrClassicBufferedInputStream(decryptor,
encryptedStartOffset,
encryptedEndOffset,
Files.newInputStream(filePath),
Files.size(filePath));
InputStream is;
if (filePath == null)
is = parentProducer.produce();
else
is = Files.newInputStream(filePath);
AesCtrClassicBufferedInputStream stream = new AesCtrClassicBufferedInputStream(
decryptor, encryptedStartOffset, encryptedEndOffset, is, fileSize);
if (offset != stream.skip(offset))
throw new Exception("Unable to skip offset: "+offset);
@ -91,18 +157,29 @@ public class InFileStreamClassicProducer implements IProducer {
}
private BufferedInputStream produceNotEncrypted() throws Exception{
BufferedInputStream stream = new BufferedInputStream(Files.newInputStream(filePath));
BufferedInputStream stream;
if (filePath == null)
stream = new BufferedInputStream(parentProducer.produce());
else
stream = new BufferedInputStream(Files.newInputStream(filePath));
if (offset != stream.skip(offset))
throw new Exception("Unable to skip offset: "+offset);
return stream;
}
@Override
public InFileStreamClassicProducer getSuccessor(long offset){
if (encrypted)
return new InFileStreamClassicProducer(filePath, offset, encryptedStartOffset, encryptedEndOffset, decryptor);
public InFileStreamClassicProducer getSuccessor(long offset) throws Exception{
if (! encrypted)
return new InFileStreamClassicProducer(filePath, offset);
if (filePath == null)
return new InFileStreamClassicProducer(parentProducer, offset, encryptedStartOffset, encryptedEndOffset, decryptor, fileSize);
return new InFileStreamClassicProducer(filePath, offset, encryptedStartOffset, encryptedEndOffset, decryptor);
}
public InFileStreamClassicProducer getSuccessor(long offset, boolean incrementExisting){
public InFileStreamClassicProducer getSuccessor(long offset, boolean incrementExisting) throws Exception{
if (incrementExisting)
return getSuccessor(this.offset + offset);
return getSuccessor(offset);
@ -113,9 +190,15 @@ public class InFileStreamClassicProducer implements IProducer {
return encrypted;
}
@Override
public File getFile(){ return filePath.toFile(); }
public File getFile(){
if (filePath == null)
return parentProducer.getFile();
return filePath.toFile();
}
@Override
public String toString(){
if (filePath == null)
return parentProducer.getFile().getAbsolutePath();
return filePath.toString();
}
}

View file

@ -18,10 +18,15 @@
*/
package libKonogonka.RomFsDecrypted;
import libKonogonka.Converter;
import libKonogonka.KeyChainHolder;
import libKonogonka.Tools.NCA.NCAProvider;
import libKonogonka.Tools.RomFs.FileSystemEntry;
import libKonogonka.Tools.RomFs.RomFsProvider;
import libKonogonka.Tools.other.System2.System2Provider;
import libKonogonka.Tools.other.System2.ini1.Ini1Provider;
import libKonogonka.Tools.other.System2.ini1.KIP1Provider;
import libKonogonka.ctraes.InFileStreamProducer;
import libKonogonka.ctraesclassic.AesCtrDecryptClassic;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
@ -32,9 +37,8 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
public class Package2UnpackedTest {
@ -44,6 +48,8 @@ public class Package2UnpackedTest {
private static final String fileLocation = "/home/loper/Projects/libKonogonka/FilesForTests/6b7abe7efa17ad065b18e62d1c87a5cc.nca_extracted/ROOT/nx/package2";
final String pathToFirmware = "/home/loper/Загрузки/patchesPlayground/nintendo-switch-global-firmwares/Firmware 14.1.0";
@DisplayName("Package2 unpacked test")
@Test
void discover() throws Exception{
@ -118,17 +124,130 @@ public class Package2UnpackedTest {
@DisplayName("KIP1 unpack test")
@Test
void unpackKip1() throws Exception{
void unpackKip1FromNca() throws Exception{
keyChainHolder = new KeyChainHolder(keysFileLocation, null);
System2Provider provider = new System2Provider(fileLocation, keyChainHolder);
// ------------------------------------------------------------------------------------------------------------
File firmware = new File(pathToFirmware);
if (! firmware.exists())
throw new Exception("Firmware directory does not exist " + pathToFirmware);
String[] fileNamesArray = firmware.list((File directory, String file) -> ( ! file.endsWith(".cnmt.nca") && file.endsWith(".nca")));
List<String> ncaFilesList = Arrays.asList(Objects.requireNonNull(fileNamesArray));
if (ncaFilesList.size() == 0)
throw new Exception("No NCA files found in firmware folder");
List<NCAProvider> ncaProviders = new ArrayList<>();
for (String ncaFileName : fileNamesArray){
File nca = new File(firmware.getAbsolutePath()+File.separator+ncaFileName);
NCAProvider provider = new NCAProvider(nca, keyChainHolder.getRawKeySet());
ncaProviders.add(provider);
}
// ------------------------------------------------------------------------------------------------------------
NCAProvider system2FatNcaProvider = null;
NCAProvider system2ExFatNcaProvider = null;
for (NCAProvider ncaProvider : ncaProviders) {
String titleId = Converter.byteArrToHexStringAsLE(ncaProvider.getTitleId());
if (titleId.equals("0100000000000819"))
system2FatNcaProvider = ncaProvider;
if (titleId.equals("010000000000081b"))
system2ExFatNcaProvider = ncaProvider;
}
System.out.println("FAT " + (system2FatNcaProvider == null ? "NOT FOUND": "FOUND"));
System.out.println("ExFAT " + (system2ExFatNcaProvider == null ? "NOT FOUND": "FOUND"));
RomFsProvider romFsExFatProvider = null;
FileSystemEntry exFatPackage2Content = null;
InFileStreamProducer producerExFat = null;
if (system2ExFatNcaProvider != null){
romFsExFatProvider = system2ExFatNcaProvider.getNCAContentProvider(0).getRomfs();
exFatPackage2Content = romFsExFatProvider.getRootEntry().getContent()
.stream()
.filter(e -> e.getName().equals("nx"))
.collect(Collectors.toList())
.get(0)
.getContent()
.stream()
.filter(e -> e.getName().equals("package2"))
.collect(Collectors.toList())
.get(0);
producerExFat = romFsExFatProvider.getStreamProducer(exFatPackage2Content);
system2ExFatNcaProvider.getNCAContentProvider(0).getRomfs().getRootEntry().printTreeForDebug();
romFsExFatProvider.exportContent("/tmp/exported_ExFat", exFatPackage2Content);
System2Provider provider = new System2Provider(producerExFat, keyChainHolder);
provider.getKernelMap().printDebug();
Ini1Provider ini1Provider = provider.getIni1Provider();
KIP1Provider fsProvider = null;
for (KIP1Provider kip1Provider : ini1Provider.getKip1List())
if (kip1Provider.getHeader().getName().startsWith("FS"))
kip1Provider.printDebug();
fsProvider = kip1Provider;
for (KIP1Provider kip1Provider : ini1Provider.getKip1List()) {
if (kip1Provider.getHeader().getName().startsWith("FS"))
kip1Provider.exportAsDecompressed("/tmp");
if (fsProvider != null) {
fsProvider.printDebug();
fsProvider.exportAsDecompressed("/tmp/FAT_kip1");
}
else
System.out.println("FS KIP1 NOT FOUND");
}
RomFsProvider romFsFatProvider = null;
FileSystemEntry fatPackage2Content = null;
InFileStreamProducer producerFat;
if (system2FatNcaProvider != null){
romFsFatProvider = system2FatNcaProvider.getNCAContentProvider(0).getRomfs();
fatPackage2Content = romFsFatProvider.getRootEntry().getContent()
.stream()
.filter(e -> e.getName().equals("nx"))
.collect(Collectors.toList())
.get(0)
.getContent()
.stream()
.filter(e -> e.getName().equals("package2"))
.collect(Collectors.toList())
.get(0);
producerFat = romFsFatProvider.getStreamProducer(fatPackage2Content);
System2Provider provider = new System2Provider(producerFat, keyChainHolder);
provider.getKernelMap().printDebug();
Ini1Provider ini1Provider = provider.getIni1Provider();
KIP1Provider fsProvider = null;
for (KIP1Provider kip1Provider : ini1Provider.getKip1List())
if (kip1Provider.getHeader().getName().startsWith("FS"))
fsProvider = kip1Provider;
if (fsProvider != null) {
fsProvider.printDebug();
fsProvider.exportAsDecompressed("/tmp/FAT_kip1");
}
else
System.out.println("FS KIP1 NOT FOUND");
}
}
@DisplayName("KIP1 unpack test")
@Test
void unpackKip1() throws Exception{
System2Provider provider = new System2Provider(fileLocation, keyChainHolder);
provider.getKernelMap().printDebug();
Ini1Provider ini1Provider = provider.getIni1Provider();
KIP1Provider fsProvider = null;
for (KIP1Provider kip1Provider : ini1Provider.getKip1List())
if (kip1Provider.getHeader().getName().startsWith("FS"))
fsProvider = kip1Provider;
if (fsProvider != null) {
fsProvider.printDebug();
fsProvider.exportAsDecompressed("/tmp");
}
else
System.out.println("FS KIP1 NOT FOUND");
}
}