From 3262fb2360c3f14d51c6c33c0f5a988ce5d0e6f2 Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko Date: Fri, 6 Jan 2023 13:04:23 +0300 Subject: [PATCH] KIP1 export support --- .../other/System2/ini1/Ini1Provider.java | 52 ++++++++++++++++-- .../Tools/other/System2/ini1/Kip1.java | 29 ++++++---- .../RomFsDecrypted/Package2Test.java | 54 +++++++++++++------ .../RomFsDecrypted/Package2UnpackedTest.java | 9 ++-- 4 files changed, 111 insertions(+), 33 deletions(-) diff --git a/src/main/java/libKonogonka/Tools/other/System2/ini1/Ini1Provider.java b/src/main/java/libKonogonka/Tools/other/System2/ini1/Ini1Provider.java index 717c828..31241a2 100644 --- a/src/main/java/libKonogonka/Tools/other/System2/ini1/Ini1Provider.java +++ b/src/main/java/libKonogonka/Tools/other/System2/ini1/Ini1Provider.java @@ -60,7 +60,7 @@ public class Ini1Provider { AesCtrDecryptClassic decryptor = new AesCtrDecryptClassic(system2Header.getKey(), system2Header.getSection0Ctr()); stream = new AesCtrClassicBufferedInputStream(decryptor, 0x200, - Files.size(filePath), + Files.size(filePath), // size of system2 Files.newInputStream(filePath), Files.size(filePath)); @@ -78,24 +78,27 @@ public class Ini1Provider { private void collectKips() throws Exception{ kip1List = new ArrayList<>(); long skipTillNextKip1 = 0; + long kip1StartOffset = 0; for (int i = 0; i < ini1Header.getKipNumber(); i++){ if (skipTillNextKip1 != stream.skip(skipTillNextKip1)) throw new Exception("Unable to skip bytes till next KIP1 header"); byte[] kip1bytes = new byte[0x100]; if (0x100 != stream.read(kip1bytes)) throw new Exception("Unable to read KIP1 data "); - Kip1 kip1 = new Kip1(kip1bytes); + Kip1 kip1 = new Kip1(kip1bytes, kip1StartOffset); kip1List.add(kip1); skipTillNextKip1 = kip1.getTextSegmentHeader().getSizeAsDecompressed() + kip1.getRoDataSegmentHeader().getSizeAsDecompressed() + - kip1.getDataSegmentHeader().getSizeAsDecompressed(); + kip1.getRwDataSegmentHeader().getSizeAsDecompressed() + + kip1.getBssSegmentHeader().getSizeAsDecompressed(); + kip1StartOffset = kip1.getEndOffset(); } } public Ini1Header getIni1Header() { return ini1Header; } public List getKip1List() { return kip1List; } - public boolean export(String saveTo) throws Exception{ + public boolean exportIni1(String saveTo) throws Exception{ makeStream(); File location = new File(saveTo); location.mkdirs(); @@ -132,4 +135,45 @@ public class Ini1Provider { } 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; + } } diff --git a/src/main/java/libKonogonka/Tools/other/System2/ini1/Kip1.java b/src/main/java/libKonogonka/Tools/other/System2/ini1/Kip1.java index f431f9f..2bb0b2f 100644 --- a/src/main/java/libKonogonka/Tools/other/System2/ini1/Kip1.java +++ b/src/main/java/libKonogonka/Tools/other/System2/ini1/Kip1.java @@ -42,15 +42,18 @@ public class Kip1 { private final int threadAffinityMask; private final SegmentHeader roDataSegmentHeader; private final int mainThreadStackSize ; - private final SegmentHeader dataSegmentHeader; + private final SegmentHeader rwDataSegmentHeader; private final byte[] reserved2; private final SegmentHeader bssSegmentHeader; private final byte[] reserved3; 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.name = new String(kip1Bytes, 0x4, 0xC); + this.name = new String(kip1Bytes, 0x4, 0xC).trim(); this.programId = Arrays.copyOfRange(kip1Bytes, 0x10, 0x18); this.version = Converter.getLEint(kip1Bytes, 0x18); this.mainThreadPriority = kip1Bytes[0x1c]; @@ -61,11 +64,16 @@ public class Kip1 { this.threadAffinityMask = Converter.getLEint(kip1Bytes, 0x2c); this.roDataSegmentHeader = new SegmentHeader(kip1Bytes, 0x30); 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.bssSegmentHeader = new SegmentHeader(kip1Bytes, 0x50); this.reserved3 = Arrays.copyOfRange(kip1Bytes, 0x5c, 0x80); 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; } @@ -80,12 +88,15 @@ public class Kip1 { public int getThreadAffinityMask() { return threadAffinityMask; } public SegmentHeader getRoDataSegmentHeader() { return roDataSegmentHeader; } public int getMainThreadStackSize() { return mainThreadStackSize; } - public SegmentHeader getDataSegmentHeader() { return dataSegmentHeader; } + public SegmentHeader getRwDataSegmentHeader() { return rwDataSegmentHeader; } public byte[] getReserved2() { return reserved2; } public SegmentHeader getBssSegmentHeader() { return bssSegmentHeader; } public byte[] getReserved3() { return reserved3; } public KernelAccessControlProvider getKernelCapabilityData() { return kernelCapabilityData; } + public long getStartOffset() { return startOffset; } + public long getEndOffset() { return endOffset; } + public void printDebug(){ StringBuilder mapIoOrNormalRange = new StringBuilder(); StringBuilder interruptPairs = new StringBuilder(); @@ -115,7 +126,7 @@ public class Kip1 { syscallMasks.append("\n"); }); - log.debug(" ..:: KIP1 ::..\n" + + log.debug(String.format("..:: KIP1 (0x%x-0x%x) ::..%n", startOffset, endOffset) + "Magic : " + magic + "\n" + "Name : " + name + "\n" + "ProgramId : " + Converter.byteArrToHexStringAsLE(programId) + "\n" + @@ -135,9 +146,9 @@ public class Kip1 { " Size : " + RainbowDump.formatDecHexString(roDataSegmentHeader.getSizeAsDecompressed()) + "\n" + "Main thread stack size : " + RainbowDump.formatDecHexString(mainThreadStackSize) + "\n" + ".data segment header\n" + - " Segment offset : " + RainbowDump.formatDecHexString(dataSegmentHeader.getSegmentOffset()) + "\n" + - " Memory offset : " + RainbowDump.formatDecHexString(dataSegmentHeader.getMemoryOffset()) + "\n" + - " Size : " + RainbowDump.formatDecHexString(dataSegmentHeader.getSizeAsDecompressed()) + "\n" + + " Segment offset : " + RainbowDump.formatDecHexString(rwDataSegmentHeader.getSegmentOffset()) + "\n" + + " Memory offset : " + RainbowDump.formatDecHexString(rwDataSegmentHeader.getMemoryOffset()) + "\n" + + " Size : " + RainbowDump.formatDecHexString(rwDataSegmentHeader.getSizeAsDecompressed()) + "\n" + "Reserved 2 : " + Converter.byteArrToHexStringAsLE(reserved2) + "\n" + ".bss segment header\n" + " Segment offset : " + RainbowDump.formatDecHexString(bssSegmentHeader.getSegmentOffset()) + "\n" + diff --git a/src/test/java/libKonogonka/RomFsDecrypted/Package2Test.java b/src/test/java/libKonogonka/RomFsDecrypted/Package2Test.java index 805e932..8a1128b 100644 --- a/src/test/java/libKonogonka/RomFsDecrypted/Package2Test.java +++ b/src/test/java/libKonogonka/RomFsDecrypted/Package2Test.java @@ -23,6 +23,7 @@ import libKonogonka.KeyChainHolder; import libKonogonka.RainbowDump; import libKonogonka.Tools.NCA.NCAContent; import libKonogonka.Tools.NCA.NCAProvider; +import libKonogonka.Tools.RomFs.FileSystemEntry; import libKonogonka.Tools.RomFs.RomFsProvider; import libKonogonka.ctraes.InFileStreamProducer; 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.Test; -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; public class Package2Test { private static final String keysFileLocation = "./FilesForTests/prod.keys"; 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 NCAProvider ncaProvider; @@ -59,7 +62,8 @@ public class Package2Test { keyChainHolder = new KeyChainHolder(keysFileLocation, keyValue); 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); @@ -79,32 +83,50 @@ public class Package2Test { else 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) return; 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; InFileStreamProducer producer = romFsProvider.getStreamProducer( romFsProvider.getRootEntry() .getContent().get(0) .getContent().get(2) ); + try (BufferedInputStream stream = producer.produce()){ byte[] everythingCNMT = new byte[contentSize]; Assertions.assertEquals(contentSize, stream.read(everythingCNMT)); RainbowDump.hexDumpUTF8(everythingCNMT); } - + */ } } diff --git a/src/test/java/libKonogonka/RomFsDecrypted/Package2UnpackedTest.java b/src/test/java/libKonogonka/RomFsDecrypted/Package2UnpackedTest.java index ec6536a..15cd531 100644 --- a/src/test/java/libKonogonka/RomFsDecrypted/Package2UnpackedTest.java +++ b/src/test/java/libKonogonka/RomFsDecrypted/Package2UnpackedTest.java @@ -24,7 +24,6 @@ import libKonogonka.Tools.other.System2.ini1.Ini1Provider; import libKonogonka.Tools.other.System2.ini1.Kip1; import libKonogonka.ctraesclassic.AesCtrDecryptClassic; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; 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"; - @Disabled @DisplayName("Package2 unpacked test") @Test void discover() throws Exception{ @@ -93,7 +91,6 @@ public class Package2UnpackedTest { System.out.println(entry.getKey()+" "+entry.getValue()+" "+magicString); } } - @Disabled @DisplayName("Package2 written test") @Test void implement() throws Exception{ @@ -110,8 +107,12 @@ public class Package2UnpackedTest { boolean exported = provider.exportKernel("/home/loper/Projects/libKonogonka/FilesForTests/own/"); 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); + 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")))); + } } }