KIP1 export support
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Dmitry Isaenko 2023-01-06 13:04:23 +03:00
parent e406dd642c
commit 3262fb2360
4 changed files with 111 additions and 33 deletions

View file

@ -60,7 +60,7 @@ public class Ini1Provider {
AesCtrDecryptClassic decryptor = new AesCtrDecryptClassic(system2Header.getKey(), system2Header.getSection0Ctr()); AesCtrDecryptClassic decryptor = new AesCtrDecryptClassic(system2Header.getKey(), system2Header.getSection0Ctr());
stream = new AesCtrClassicBufferedInputStream(decryptor, stream = new AesCtrClassicBufferedInputStream(decryptor,
0x200, 0x200,
Files.size(filePath), Files.size(filePath), // size of system2
Files.newInputStream(filePath), Files.newInputStream(filePath),
Files.size(filePath)); Files.size(filePath));
@ -78,24 +78,27 @@ public class Ini1Provider {
private void collectKips() throws Exception{ private void collectKips() throws Exception{
kip1List = new ArrayList<>(); kip1List = new ArrayList<>();
long skipTillNextKip1 = 0; long skipTillNextKip1 = 0;
long kip1StartOffset = 0;
for (int i = 0; i < ini1Header.getKipNumber(); i++){ for (int i = 0; i < ini1Header.getKipNumber(); i++){
if (skipTillNextKip1 != stream.skip(skipTillNextKip1)) if (skipTillNextKip1 != stream.skip(skipTillNextKip1))
throw new Exception("Unable to skip bytes till next KIP1 header"); throw new Exception("Unable to skip bytes till next KIP1 header");
byte[] kip1bytes = new byte[0x100]; byte[] kip1bytes = new byte[0x100];
if (0x100 != stream.read(kip1bytes)) if (0x100 != stream.read(kip1bytes))
throw new Exception("Unable to read KIP1 data "); throw new Exception("Unable to read KIP1 data ");
Kip1 kip1 = new Kip1(kip1bytes); Kip1 kip1 = new Kip1(kip1bytes, kip1StartOffset);
kip1List.add(kip1); kip1List.add(kip1);
skipTillNextKip1 = kip1.getTextSegmentHeader().getSizeAsDecompressed() + skipTillNextKip1 = kip1.getTextSegmentHeader().getSizeAsDecompressed() +
kip1.getRoDataSegmentHeader().getSizeAsDecompressed() + kip1.getRoDataSegmentHeader().getSizeAsDecompressed() +
kip1.getDataSegmentHeader().getSizeAsDecompressed(); kip1.getRwDataSegmentHeader().getSizeAsDecompressed() +
kip1.getBssSegmentHeader().getSizeAsDecompressed();
kip1StartOffset = kip1.getEndOffset();
} }
} }
public Ini1Header getIni1Header() { return ini1Header; } public Ini1Header getIni1Header() { return ini1Header; }
public List<Kip1> getKip1List() { return kip1List; } public List<Kip1> getKip1List() { return kip1List; }
public boolean export(String saveTo) throws Exception{ public boolean exportIni1(String saveTo) throws Exception{
makeStream(); makeStream();
File location = new File(saveTo); File location = new File(saveTo);
location.mkdirs(); location.mkdirs();
@ -132,4 +135,45 @@ public class Ini1Provider {
} }
return true; return true;
} }
public boolean exportKip1(String saveTo, Kip1 kip1) throws Exception{
makeStream();
long startOffset = 0x10 + kip1.getStartOffset();
if (startOffset != stream.skip(startOffset))
throw new Exception("Can't seek to start position of KIP1: "+startOffset);
File location = new File(saveTo);
location.mkdirs();
try (BufferedOutputStream extractedFileBOS = new BufferedOutputStream(
Files.newOutputStream(Paths.get(saveTo+File.separator+kip1.getName()+".kip1")))){
long size = kip1.getEndOffset()-kip1.getStartOffset();
int blockSize = 0x200;
if (size < 0x200)
blockSize = (int) size;
long i = 0;
byte[] block = new byte[blockSize];
int actuallyRead;
while (true) {
if ((actuallyRead = stream.read(block)) != blockSize)
throw new Exception("Read failure. Block Size: "+blockSize+", actuallyRead: "+actuallyRead);
extractedFileBOS.write(block);
i += blockSize;
if ((i + blockSize) > size) {
blockSize = (int) (size - i);
if (blockSize == 0)
break;
block = new byte[blockSize];
}
}
}
catch (Exception e){
log.error("File export failure", e);
return false;
}
return true;
}
} }

View file

@ -42,15 +42,18 @@ public class Kip1 {
private final int threadAffinityMask; private final int threadAffinityMask;
private final SegmentHeader roDataSegmentHeader; private final SegmentHeader roDataSegmentHeader;
private final int mainThreadStackSize ; private final int mainThreadStackSize ;
private final SegmentHeader dataSegmentHeader; private final SegmentHeader rwDataSegmentHeader;
private final byte[] reserved2; private final byte[] reserved2;
private final SegmentHeader bssSegmentHeader; private final SegmentHeader bssSegmentHeader;
private final byte[] reserved3; private final byte[] reserved3;
private final KernelAccessControlProvider kernelCapabilityData; private final KernelAccessControlProvider kernelCapabilityData;
public Kip1(byte[] kip1Bytes) throws Exception{ private final long startOffset;
private final long endOffset;
public Kip1(byte[] kip1Bytes, long kip1StartOffset) throws Exception{
this.magic = new String(kip1Bytes, 0, 0x4); this.magic = new String(kip1Bytes, 0, 0x4);
this.name = new String(kip1Bytes, 0x4, 0xC); this.name = new String(kip1Bytes, 0x4, 0xC).trim();
this.programId = Arrays.copyOfRange(kip1Bytes, 0x10, 0x18); this.programId = Arrays.copyOfRange(kip1Bytes, 0x10, 0x18);
this.version = Converter.getLEint(kip1Bytes, 0x18); this.version = Converter.getLEint(kip1Bytes, 0x18);
this.mainThreadPriority = kip1Bytes[0x1c]; this.mainThreadPriority = kip1Bytes[0x1c];
@ -61,11 +64,16 @@ public class Kip1 {
this.threadAffinityMask = Converter.getLEint(kip1Bytes, 0x2c); this.threadAffinityMask = Converter.getLEint(kip1Bytes, 0x2c);
this.roDataSegmentHeader = new SegmentHeader(kip1Bytes, 0x30); this.roDataSegmentHeader = new SegmentHeader(kip1Bytes, 0x30);
this.mainThreadStackSize = Converter.getLEint(kip1Bytes, 0x3c); this.mainThreadStackSize = Converter.getLEint(kip1Bytes, 0x3c);
this.dataSegmentHeader = new SegmentHeader(kip1Bytes, 0x40); this.rwDataSegmentHeader = new SegmentHeader(kip1Bytes, 0x40);
this.reserved2 = Arrays.copyOfRange(kip1Bytes, 0x4c, 0x50); this.reserved2 = Arrays.copyOfRange(kip1Bytes, 0x4c, 0x50);
this.bssSegmentHeader = new SegmentHeader(kip1Bytes, 0x50); this.bssSegmentHeader = new SegmentHeader(kip1Bytes, 0x50);
this.reserved3 = Arrays.copyOfRange(kip1Bytes, 0x5c, 0x80); this.reserved3 = Arrays.copyOfRange(kip1Bytes, 0x5c, 0x80);
this.kernelCapabilityData = new KernelAccessControlProvider(Arrays.copyOfRange(kip1Bytes, 0x80, 0x100)); this.kernelCapabilityData = new KernelAccessControlProvider(Arrays.copyOfRange(kip1Bytes, 0x80, 0x100));
this.startOffset = kip1StartOffset;
this.endOffset = 0x100 + kip1StartOffset + textSegmentHeader.getSizeAsDecompressed() + roDataSegmentHeader.getSizeAsDecompressed() +
rwDataSegmentHeader.getSizeAsDecompressed() + bssSegmentHeader.getSizeAsDecompressed();
} }
public String getMagic() { return magic; } public String getMagic() { return magic; }
@ -80,12 +88,15 @@ public class Kip1 {
public int getThreadAffinityMask() { return threadAffinityMask; } public int getThreadAffinityMask() { return threadAffinityMask; }
public SegmentHeader getRoDataSegmentHeader() { return roDataSegmentHeader; } public SegmentHeader getRoDataSegmentHeader() { return roDataSegmentHeader; }
public int getMainThreadStackSize() { return mainThreadStackSize; } public int getMainThreadStackSize() { return mainThreadStackSize; }
public SegmentHeader getDataSegmentHeader() { return dataSegmentHeader; } public SegmentHeader getRwDataSegmentHeader() { return rwDataSegmentHeader; }
public byte[] getReserved2() { return reserved2; } public byte[] getReserved2() { return reserved2; }
public SegmentHeader getBssSegmentHeader() { return bssSegmentHeader; } public SegmentHeader getBssSegmentHeader() { return bssSegmentHeader; }
public byte[] getReserved3() { return reserved3; } public byte[] getReserved3() { return reserved3; }
public KernelAccessControlProvider getKernelCapabilityData() { return kernelCapabilityData; } public KernelAccessControlProvider getKernelCapabilityData() { return kernelCapabilityData; }
public long getStartOffset() { return startOffset; }
public long getEndOffset() { return endOffset; }
public void printDebug(){ public void printDebug(){
StringBuilder mapIoOrNormalRange = new StringBuilder(); StringBuilder mapIoOrNormalRange = new StringBuilder();
StringBuilder interruptPairs = new StringBuilder(); StringBuilder interruptPairs = new StringBuilder();
@ -115,7 +126,7 @@ public class Kip1 {
syscallMasks.append("\n"); syscallMasks.append("\n");
}); });
log.debug(" ..:: KIP1 ::..\n" + log.debug(String.format("..:: KIP1 (0x%x-0x%x) ::..%n", startOffset, endOffset) +
"Magic : " + magic + "\n" + "Magic : " + magic + "\n" +
"Name : " + name + "\n" + "Name : " + name + "\n" +
"ProgramId : " + Converter.byteArrToHexStringAsLE(programId) + "\n" + "ProgramId : " + Converter.byteArrToHexStringAsLE(programId) + "\n" +
@ -135,9 +146,9 @@ public class Kip1 {
" Size : " + RainbowDump.formatDecHexString(roDataSegmentHeader.getSizeAsDecompressed()) + "\n" + " Size : " + RainbowDump.formatDecHexString(roDataSegmentHeader.getSizeAsDecompressed()) + "\n" +
"Main thread stack size : " + RainbowDump.formatDecHexString(mainThreadStackSize) + "\n" + "Main thread stack size : " + RainbowDump.formatDecHexString(mainThreadStackSize) + "\n" +
".data segment header\n" + ".data segment header\n" +
" Segment offset : " + RainbowDump.formatDecHexString(dataSegmentHeader.getSegmentOffset()) + "\n" + " Segment offset : " + RainbowDump.formatDecHexString(rwDataSegmentHeader.getSegmentOffset()) + "\n" +
" Memory offset : " + RainbowDump.formatDecHexString(dataSegmentHeader.getMemoryOffset()) + "\n" + " Memory offset : " + RainbowDump.formatDecHexString(rwDataSegmentHeader.getMemoryOffset()) + "\n" +
" Size : " + RainbowDump.formatDecHexString(dataSegmentHeader.getSizeAsDecompressed()) + "\n" + " Size : " + RainbowDump.formatDecHexString(rwDataSegmentHeader.getSizeAsDecompressed()) + "\n" +
"Reserved 2 : " + Converter.byteArrToHexStringAsLE(reserved2) + "\n" + "Reserved 2 : " + Converter.byteArrToHexStringAsLE(reserved2) + "\n" +
".bss segment header\n" + ".bss segment header\n" +
" Segment offset : " + RainbowDump.formatDecHexString(bssSegmentHeader.getSegmentOffset()) + "\n" + " Segment offset : " + RainbowDump.formatDecHexString(bssSegmentHeader.getSegmentOffset()) + "\n" +

View file

@ -23,6 +23,7 @@ import libKonogonka.KeyChainHolder;
import libKonogonka.RainbowDump; import libKonogonka.RainbowDump;
import libKonogonka.Tools.NCA.NCAContent; import libKonogonka.Tools.NCA.NCAContent;
import libKonogonka.Tools.NCA.NCAProvider; import libKonogonka.Tools.NCA.NCAProvider;
import libKonogonka.Tools.RomFs.FileSystemEntry;
import libKonogonka.Tools.RomFs.RomFsProvider; import libKonogonka.Tools.RomFs.RomFsProvider;
import libKonogonka.ctraes.InFileStreamProducer; import libKonogonka.ctraes.InFileStreamProducer;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
@ -30,16 +31,18 @@ import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.io.BufferedInputStream; import java.io.*;
import java.io.BufferedReader; import java.nio.file.Files;
import java.io.File; import java.nio.file.Path;
import java.io.FileReader; import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
public class Package2Test { public class Package2Test {
private static final String keysFileLocation = "./FilesForTests/prod.keys"; private static final String keysFileLocation = "./FilesForTests/prod.keys";
private static final String xci_header_keyFileLocation = "./FilesForTests/xci_header_key.txt"; private static final String xci_header_keyFileLocation = "./FilesForTests/xci_header_key.txt";
private static final String ncaFileLocation = "/home/loper/Projects/tempPatchesPlayground/fw1100"; private static final String ncaFileLocation = "/home/loper/Загрузки/patchesPlayground/fw1100";
private static KeyChainHolder keyChainHolder; private static KeyChainHolder keyChainHolder;
private static NCAProvider ncaProvider; private static NCAProvider ncaProvider;
@ -59,7 +62,8 @@ public class Package2Test {
keyChainHolder = new KeyChainHolder(keysFileLocation, keyValue); keyChainHolder = new KeyChainHolder(keysFileLocation, keyValue);
File parent = new File(ncaFileLocation); File parent = new File(ncaFileLocation);
String[] dirWithFiles = parent.list((file, s) -> s.endsWith(".nca")); //String[] dirWithFiles = parent.list((file, s) -> s.endsWith(".cnmt.nca")); String[] dirWithFiles = parent.list((file, s) ->
s.endsWith(".nca") && !s.endsWith(".cnmt.nca")); //String[] dirWithFiles = parent.list((file, s) -> s.endsWith(".cnmt.nca"));
Assertions.assertNotNull(dirWithFiles); Assertions.assertNotNull(dirWithFiles);
@ -79,32 +83,50 @@ public class Package2Test {
else else
return; return;
for (int i = 0; i < 4; i++){
NCAContent content = ncaProvider.getNCAContentProvider(i);
System.out.println("NCAContent "+i+" exists = "+!(content == null));
}
//ncaProvider.getSectionBlock0().printDebug();
if (ncaProvider.getSectionBlock0().getSuperBlockIVFC() == null) if (ncaProvider.getSectionBlock0().getSuperBlockIVFC() == null)
return; return;
RomFsProvider romFsProvider = ncaProvider.getNCAContentProvider(0).getRomfs(); RomFsProvider romFsProvider = ncaProvider.getNCAContentProvider(0).getRomfs();
romFsProvider.printDebug();
romFsProvider.exportContent("./FilesForTests/"+file.getName()+"_extracted", romFsProvider.getRootEntry());
//int contentSize = (int) pfs0Provider.getHeader().getPfs0subFiles()[0].getSize(); FileSystemEntry package2Entry = romFsProvider.getRootEntry().getContent()
.stream()
.filter(fileSystemEntry -> fileSystemEntry.getName().equals("nx"))
.collect(Collectors.toList())
.get(0)
.getContent()
.stream()
.filter(fileSystemEntry -> fileSystemEntry.getName().equals("package2"))
.collect(Collectors.toList())
.get(0);
System.out.println("NAME : "+package2Entry.getName());
System.out.println("SIZE : "+package2Entry.getSize());
System.out.println("OFFSET : "+package2Entry.getOffset());
//File tempDir = new File(System.getProperty("java.io.tmpdir"));
//File tempFile = File.createTempFile("pFilename", ".tmp", tempDir);
romFsProvider.exportContent(System.getProperty("java.io.tmpdir"), package2Entry);
System.out.println(System.getProperty("java.io.tmpdir"));
// . . .
Files.delete(Paths.get(System.getProperty("java.io.tmpdir")+File.separator+"package2"));
/*
int contentSize = 0x200; int contentSize = 0x200;
InFileStreamProducer producer = romFsProvider.getStreamProducer( InFileStreamProducer producer = romFsProvider.getStreamProducer(
romFsProvider.getRootEntry() romFsProvider.getRootEntry()
.getContent().get(0) .getContent().get(0)
.getContent().get(2) .getContent().get(2)
); );
try (BufferedInputStream stream = producer.produce()){ try (BufferedInputStream stream = producer.produce()){
byte[] everythingCNMT = new byte[contentSize]; byte[] everythingCNMT = new byte[contentSize];
Assertions.assertEquals(contentSize, stream.read(everythingCNMT)); Assertions.assertEquals(contentSize, stream.read(everythingCNMT));
RainbowDump.hexDumpUTF8(everythingCNMT); RainbowDump.hexDumpUTF8(everythingCNMT);
} }
*/
} }
} }

View file

@ -24,7 +24,6 @@ import libKonogonka.Tools.other.System2.ini1.Ini1Provider;
import libKonogonka.Tools.other.System2.ini1.Kip1; import libKonogonka.Tools.other.System2.ini1.Kip1;
import libKonogonka.ctraesclassic.AesCtrDecryptClassic; import libKonogonka.ctraesclassic.AesCtrDecryptClassic;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -45,7 +44,6 @@ public class Package2UnpackedTest {
private static final String fileLocation = "/home/loper/Projects/libKonogonka/FilesForTests/6b7abe7efa17ad065b18e62d1c87a5cc.nca_extracted/ROOT/nx/package2"; private static final String fileLocation = "/home/loper/Projects/libKonogonka/FilesForTests/6b7abe7efa17ad065b18e62d1c87a5cc.nca_extracted/ROOT/nx/package2";
@Disabled
@DisplayName("Package2 unpacked test") @DisplayName("Package2 unpacked test")
@Test @Test
void discover() throws Exception{ void discover() throws Exception{
@ -93,7 +91,6 @@ public class Package2UnpackedTest {
System.out.println(entry.getKey()+" "+entry.getValue()+" "+magicString); System.out.println(entry.getKey()+" "+entry.getValue()+" "+magicString);
} }
} }
@Disabled
@DisplayName("Package2 written test") @DisplayName("Package2 written test")
@Test @Test
void implement() throws Exception{ void implement() throws Exception{
@ -110,8 +107,12 @@ public class Package2UnpackedTest {
boolean exported = provider.exportKernel("/home/loper/Projects/libKonogonka/FilesForTests/own/"); boolean exported = provider.exportKernel("/home/loper/Projects/libKonogonka/FilesForTests/own/");
System.out.println("Exported = "+exported); System.out.println("Exported = "+exported);
exported = ini1Provider.export("/home/loper/Projects/libKonogonka/FilesForTests/own/"); exported = ini1Provider.exportIni1("/home/loper/Projects/libKonogonka/FilesForTests/own/");
System.out.println("Exported INI1 = "+exported); System.out.println("Exported INI1 = "+exported);
for (Kip1 kip1 : ini1Provider.getKip1List()) {
exported = ini1Provider.exportKip1("/home/loper/Projects/libKonogonka/FilesForTests/own/KIP1s", kip1);
System.out.println("Exported KIP1s "+ kip1.getName() +" = " + exported + String.format(" Size 0x%x", Files.size(Paths.get("/home/loper/Projects/libKonogonka/FilesForTests/own/KIP1s/"+kip1.getName()+".kip1"))));
}
} }
} }