From ce16e1998599860ecd7c14c29aabb979ec00f76f Mon Sep 17 00:00:00 2001 From: Dmitry Isaenko Date: Sun, 8 Jan 2023 00:00:47 +0300 Subject: [PATCH] Adjust added classes to application domain standards; add interface for KIP1 extraction --- src/main/java/libKonogonka/IProducer.java | 12 ++ .../libKonogonka/Tools/NSO/NSO0Header.java | 6 +- .../libKonogonka/Tools/NSO/NSO0Unpacker.java | 16 +- .../libKonogonka/Tools/NSO/SegmentHeader.java | 5 +- .../other/System2/ini1/Ini1Provider.java | 62 +++---- .../ini1/{Kip1.java => KIP1Header.java} | 82 ++++------ .../other/System2/ini1/KIP1Provider.java | 82 ++++++++++ .../Tools/other/System2/ini1/KIP1Raw.java | 47 ++++++ .../other/System2/ini1/Kip1Unpacker.java | 151 ++++++++++++++++++ .../ctraes/InFileStreamProducer.java | 12 +- .../ctraesclassic/AesCtrDecryptClassic.java | 17 +- .../InFileStreamClassicProducer.java | 121 ++++++++++++++ .../RomFsDecrypted/Package2UnpackedTest.java | 19 +-- 13 files changed, 509 insertions(+), 123 deletions(-) create mode 100644 src/main/java/libKonogonka/IProducer.java rename src/main/java/libKonogonka/Tools/other/System2/ini1/{Kip1.java => KIP1Header.java} (81%) create mode 100644 src/main/java/libKonogonka/Tools/other/System2/ini1/KIP1Provider.java create mode 100644 src/main/java/libKonogonka/Tools/other/System2/ini1/KIP1Raw.java create mode 100644 src/main/java/libKonogonka/Tools/other/System2/ini1/Kip1Unpacker.java create mode 100644 src/main/java/libKonogonka/ctraesclassic/InFileStreamClassicProducer.java diff --git a/src/main/java/libKonogonka/IProducer.java b/src/main/java/libKonogonka/IProducer.java new file mode 100644 index 0000000..eb162a3 --- /dev/null +++ b/src/main/java/libKonogonka/IProducer.java @@ -0,0 +1,12 @@ +package libKonogonka; + +import java.io.BufferedInputStream; +import java.io.File; + +public interface IProducer { + + BufferedInputStream produce() throws Exception; + IProducer getSuccessor(long subOffset); + boolean isEncrypted(); + File getFile(); +} diff --git a/src/main/java/libKonogonka/Tools/NSO/NSO0Header.java b/src/main/java/libKonogonka/Tools/NSO/NSO0Header.java index b0eef23..753906d 100644 --- a/src/main/java/libKonogonka/Tools/NSO/NSO0Header.java +++ b/src/main/java/libKonogonka/Tools/NSO/NSO0Header.java @@ -133,19 +133,19 @@ public class NSO0Header { "SegmentHeader for .text\n" + " |- File Offset - - - - - - - - - - - - - - "+ RainbowDump.formatDecHexString(textSegmentHeader.getSegmentOffset()) + "\n" + " |- Memory Offset - - - - - - - - - - - - - "+ RainbowDump.formatDecHexString(textSegmentHeader.getMemoryOffset()) + "\n" + - " |- Size As Decompressed - - - - - - - - - "+ RainbowDump.formatDecHexString(textSegmentHeader.getSizeAsDecompressed()) + "\n" + + " |- Size As Decompressed - - - - - - - - - "+ RainbowDump.formatDecHexString(textSegmentHeader.getSize()) + "\n" + "ModuleNameOffset (calculated by sizeof(header)) " + RainbowDump.formatDecHexString(moduleNameOffset) + "\n" + " +++\n"+ "SegmentHeader for .rodata\n" + " |- File Offset - - - - - - - - - - - - - - " + RainbowDump.formatDecHexString(rodataSegmentHeader.getSegmentOffset()) + "\n" + " |- Memory Offset - - - - - - - - - - - - - " + RainbowDump.formatDecHexString(rodataSegmentHeader.getMemoryOffset()) + "\n" + - " |- Size As Decompressed - - - - - - - - - " + RainbowDump.formatDecHexString(rodataSegmentHeader.getSizeAsDecompressed()) + "\n" + + " |- Size As Decompressed - - - - - - - - - " + RainbowDump.formatDecHexString(rodataSegmentHeader.getSize()) + "\n" + "ModuleNameSize " + RainbowDump.formatDecHexString(moduleNameSize) + "\n" + " +++\n"+ "SegmentHeader for .data\n" + " |- File Offset - - - - - - - - - - - - - - " + RainbowDump.formatDecHexString(dataSegmentHeader.getSegmentOffset()) + "\n" + " |- Memory Offset - - - - - - - - - - - - - " + RainbowDump.formatDecHexString(dataSegmentHeader.getMemoryOffset()) + "\n" + - " |- Size As Decompressed - - - - - - - - - " + RainbowDump.formatDecHexString(dataSegmentHeader.getSizeAsDecompressed()) + "\n" + + " |- Size As Decompressed - - - - - - - - - " + RainbowDump.formatDecHexString(dataSegmentHeader.getSize()) + "\n" + " .bss Size " + RainbowDump.formatDecHexString(bssSize) + "\n" + // Block Started by Symbol "Module ID (aka Build ID) " + Converter.byteArrToHexStringAsLE(moduleId) + "\n" + " .text Size (compressed) " + RainbowDump.formatDecHexString(textCompressedSize) + "\n" + diff --git a/src/main/java/libKonogonka/Tools/NSO/NSO0Unpacker.java b/src/main/java/libKonogonka/Tools/NSO/NSO0Unpacker.java index 7f0a872..a26b625 100644 --- a/src/main/java/libKonogonka/Tools/NSO/NSO0Unpacker.java +++ b/src/main/java/libKonogonka/Tools/NSO/NSO0Unpacker.java @@ -98,7 +98,7 @@ class NSO0Unpacker { private byte[] decompressSection(SegmentHeader segmentHeader, int compressedSectionSize) throws Exception{ try (BufferedInputStream stream = producer.produce()) { - int sectionDecompressedSize = segmentHeader.getSizeAsDecompressed(); + int sectionDecompressedSize = segmentHeader.getSize(); byte[] compressed = new byte[compressedSectionSize]; if (segmentHeader.getSegmentOffset() != stream.skip(segmentHeader.getSegmentOffset())) @@ -121,7 +121,7 @@ class NSO0Unpacker { private byte[] duplicateSection(SegmentHeader segmentHeader) throws Exception{ try (BufferedInputStream stream = producer.produce()) { - int size = segmentHeader.getSizeAsDecompressed(); + int size = segmentHeader.getSize(); byte[] sectionContent = new byte[size]; if (segmentHeader.getSegmentOffset() != stream.skip(segmentHeader.getSegmentOffset())) @@ -183,20 +183,20 @@ class NSO0Unpacker { .putInt(nso0Header.getFlags() & 0b111000) .putInt(textFileOffsetNew) .putInt(nso0Header.getTextSegmentHeader().getMemoryOffset()) - .putInt(nso0Header.getTextSegmentHeader().getSizeAsDecompressed()) + .putInt(nso0Header.getTextSegmentHeader().getSize()) .putInt(0x100) .putInt(rodataFileOffsetNew) .putInt(nso0Header.getRodataSegmentHeader().getMemoryOffset()) - .putInt(nso0Header.getRodataSegmentHeader().getSizeAsDecompressed()) + .putInt(nso0Header.getRodataSegmentHeader().getSize()) .putInt(0) .putInt(dataFileOffsetNew) .putInt(nso0Header.getDataSegmentHeader().getMemoryOffset()) - .putInt(nso0Header.getDataSegmentHeader().getSizeAsDecompressed()) + .putInt(nso0Header.getDataSegmentHeader().getSize()) .putInt(nso0Header.getBssSize()) .put(nso0Header.getModuleId()) - .putInt(nso0Header.getTextSegmentHeader().getSizeAsDecompressed()) - .putInt(nso0Header.getRodataSegmentHeader().getSizeAsDecompressed()) - .putInt(nso0Header.getDataSegmentHeader().getSizeAsDecompressed()) + .putInt(nso0Header.getTextSegmentHeader().getSize()) + .putInt(nso0Header.getRodataSegmentHeader().getSize()) + .putInt(nso0Header.getDataSegmentHeader().getSize()) .put(nso0Header.getBottomReserved()) .putInt(nso0Header.get_api_infoRelative().getOffset()) .putInt(nso0Header.get_api_infoRelative().getSize()) diff --git a/src/main/java/libKonogonka/Tools/NSO/SegmentHeader.java b/src/main/java/libKonogonka/Tools/NSO/SegmentHeader.java index 24dd92a..058fa5c 100644 --- a/src/main/java/libKonogonka/Tools/NSO/SegmentHeader.java +++ b/src/main/java/libKonogonka/Tools/NSO/SegmentHeader.java @@ -43,9 +43,10 @@ public class SegmentHeader { return memoryOffset; } /** - * @return Size of compressed if used in KIP1 + * SegmentHeader used in both NSO0 and KIP1 structures + * @return Size as decompressed if used in NSO0; size of compressed if used in KIP1. * */ - public int getSizeAsDecompressed() { + public int getSize() { return sizeAsDecompressed; } } 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 7474e8b..0f31a9f 100644 --- a/src/main/java/libKonogonka/Tools/other/System2/ini1/Ini1Provider.java +++ b/src/main/java/libKonogonka/Tools/other/System2/ini1/Ini1Provider.java @@ -21,10 +21,7 @@ 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.AesCtrClassicBufferedInputStream; -import libKonogonka.ctraesclassic.AesCtrDecryptClassic; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import libKonogonka.ctraesclassic.InFileStreamClassicProducer; import java.nio.file.Files; import java.nio.file.Path; @@ -33,34 +30,22 @@ import java.util.ArrayList; import java.util.List; public class Ini1Provider extends ExportAble { - private final System2Header system2Header; - private final String pathToFile; - private final KernelMap kernelMap; private Ini1Header ini1Header; - private List kip1List; + private List kip1List; + + private final InFileStreamClassicProducer producer; public Ini1Provider(System2Header system2Header, String pathToFile, KernelMap kernelMap) throws Exception{ - this.system2Header = system2Header; - this.pathToFile = pathToFile; - this.kernelMap = kernelMap; - - makeStream(); - makeHeader(); - collectKips(); - } - - private void makeStream() throws Exception{ Path filePath = Paths.get(pathToFile); - long toSkip = 0x200 + kernelMap.getIni1Offset(); - AesCtrDecryptClassic decryptor = new AesCtrDecryptClassic(system2Header.getKey(), system2Header.getSection0Ctr()); - stream = new AesCtrClassicBufferedInputStream(decryptor, + this.producer = new InFileStreamClassicProducer(filePath, + 0x200 + kernelMap.getIni1Offset(), 0x200, Files.size(filePath), // size of system2 - Files.newInputStream(filePath), - Files.size(filePath)); - - if (toSkip != stream.skip(toSkip)) - throw new Exception("Unable to skip offset: "+toSkip); + system2Header.getKey(), + system2Header.getSection0Ctr()); + stream = producer.produce(); + makeHeader(); + collectKips(); } private void makeHeader() throws Exception{ @@ -80,29 +65,22 @@ public class Ini1Provider extends ExportAble { byte[] kip1bytes = new byte[0x100]; if (0x100 != stream.read(kip1bytes)) throw new Exception("Unable to read KIP1 data "); - Kip1 kip1 = new Kip1(kip1bytes, kip1StartOffset); + KIP1Provider kip1 = new KIP1Provider(kip1bytes, kip1StartOffset, producer.getSuccessor(0x10, true)); kip1List.add(kip1); - skipTillNextKip1 = kip1.getTextSegmentHeader().getSizeAsDecompressed() + - kip1.getRoDataSegmentHeader().getSizeAsDecompressed() + - kip1.getRwDataSegmentHeader().getSizeAsDecompressed() + - kip1.getBssSegmentHeader().getSizeAsDecompressed(); + KIP1Header kip1Header = kip1.getHeader(); + skipTillNextKip1 = kip1Header.getTextSegmentHeader().getSize() + + kip1Header.getRoDataSegmentHeader().getSize() + + kip1Header.getRwDataSegmentHeader().getSize() + + kip1Header.getBssSegmentHeader().getSize(); kip1StartOffset = kip1.getEndOffset(); } } public Ini1Header getIni1Header() { return ini1Header; } - public List getKip1List() { return kip1List; } + public List getKip1List() { return kip1List; } - public boolean exportIni1(String saveTo) throws Exception{ - makeStream(); + public boolean export(String saveTo) throws Exception{ + stream = producer.produce(); return export(saveTo, "INI1.bin", 0, ini1Header.getSize()); } - - public boolean exportKip1(String saveTo, Kip1 kip1) throws Exception{ - makeStream(); - return export(saveTo, - kip1.getName()+".kip1", - 0x10 + kip1.getStartOffset(), - kip1.getEndOffset()-kip1.getStartOffset()); - } } diff --git a/src/main/java/libKonogonka/Tools/other/System2/ini1/Kip1.java b/src/main/java/libKonogonka/Tools/other/System2/ini1/KIP1Header.java similarity index 81% rename from src/main/java/libKonogonka/Tools/other/System2/ini1/Kip1.java rename to src/main/java/libKonogonka/Tools/other/System2/ini1/KIP1Header.java index 8ae867e..622c592 100644 --- a/src/main/java/libKonogonka/Tools/other/System2/ini1/Kip1.java +++ b/src/main/java/libKonogonka/Tools/other/System2/ini1/KIP1Header.java @@ -25,49 +25,30 @@ import libKonogonka.Tools.NSO.SegmentHeader; 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.Arrays; -public class Kip1 { - private final static Logger log = LogManager.getLogger(Kip1.class); +public class KIP1Header { + private final static Logger log = LogManager.getLogger(KIP1Header.class); - private String magic; - private String name; - private byte[] programId; - private int version; - private byte mainThreadPriority; - private byte mainThreadCoreNumber; - private byte reserved1; - private byte flags; - private SegmentHeader textSegmentHeader; - private int threadAffinityMask; - private SegmentHeader roDataSegmentHeader; - private int mainThreadStackSize ; - private SegmentHeader rwDataSegmentHeader; - private byte[] reserved2; - private SegmentHeader bssSegmentHeader; - private byte[] reserved3; - private KernelAccessControlProvider kernelCapabilityData; - - private long startOffset; - private long endOffset; - - public Kip1(String fileLocation) throws Exception{ - try (BufferedInputStream stream = new BufferedInputStream(Files.newInputStream(Paths.get(fileLocation)));) { - byte[] kip1HeaderBytes = new byte[0x100]; - if (0x100 != stream.read(kip1HeaderBytes)) - throw new Exception("Unable to read KIP1 file header"); - makeHeader(kip1HeaderBytes, 0); - } - } + private final String magic; + private final String name; + private final byte[] programId; + private final int version; + private final byte mainThreadPriority; + private final byte mainThreadCoreNumber; + private final byte reserved1; + private final byte flags; + private final SegmentHeader textSegmentHeader; + private final int threadAffinityMask; + private final SegmentHeader roDataSegmentHeader; + private final int mainThreadStackSize; + private final SegmentHeader rwDataSegmentHeader; + private final byte[] reserved2; + private final SegmentHeader bssSegmentHeader; + private final byte[] reserved3; + private final KernelAccessControlProvider kernelCapabilityData; - public Kip1(byte[] kip1HeaderBytes, long kip1StartOffset) throws Exception{ - makeHeader(kip1HeaderBytes, kip1StartOffset); - } - - private void makeHeader(byte[] kip1HeaderBytes, long kip1StartOffset) throws Exception{ + public KIP1Header(byte[] kip1HeaderBytes) throws Exception{ this.magic = new String(kip1HeaderBytes, 0, 0x4); this.name = new String(kip1HeaderBytes, 0x4, 0xC).trim(); this.programId = Arrays.copyOfRange(kip1HeaderBytes, 0x10, 0x18); @@ -85,10 +66,6 @@ public class Kip1 { this.bssSegmentHeader = new SegmentHeader(kip1HeaderBytes, 0x50); this.reserved3 = Arrays.copyOfRange(kip1HeaderBytes, 0x5c, 0x80); this.kernelCapabilityData = new KernelAccessControlProvider(Arrays.copyOfRange(kip1HeaderBytes, 0x80, 0x100)); - - this.startOffset = kip1StartOffset; - this.endOffset = 0x100 + kip1StartOffset + textSegmentHeader.getSizeAsDecompressed() + roDataSegmentHeader.getSizeAsDecompressed() + - rwDataSegmentHeader.getSizeAsDecompressed() + bssSegmentHeader.getSizeAsDecompressed(); } public String getMagic() { return magic; } @@ -99,6 +76,12 @@ public class Kip1 { public byte getMainThreadCoreNumber() { return mainThreadCoreNumber; } public byte getReserved1() { return reserved1; } public byte getFlags() { return flags; } + public boolean isTextCompressFlag(){ return (flags & 1) == 1; } + public boolean isRoDataCompressFlag(){ return (flags >> 1 & 1) == 1; } + public boolean isRwDataCompressFlag(){ return (flags >> 2 & 1) == 1; } + public boolean is64BitInstruction(){ return (flags >> 3 & 1) == 1; } + public boolean isAddressSpace64Bit(){ return (flags >> 4 & 1) == 1; } + public boolean isUseSecureMemory(){ return (flags >> 5 & 1) == 1; } public SegmentHeader getTextSegmentHeader() { return textSegmentHeader; } public int getThreadAffinityMask() { return threadAffinityMask; } public SegmentHeader getRoDataSegmentHeader() { return roDataSegmentHeader; } @@ -109,9 +92,6 @@ public class Kip1 { 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(); @@ -141,7 +121,7 @@ public class Kip1 { syscallMasks.append("\n"); }); - log.debug(String.format("..:: KIP1 (0x%x-0x%x) ::..%n", startOffset, endOffset) + + log.debug("..:: KIP1 ::..\n" + "Magic : " + magic + "\n" + "Name : " + name + "\n" + "ProgramId : " + Converter.byteArrToHexStringAsLE(programId) + "\n" + @@ -159,22 +139,22 @@ public class Kip1 { ".text segment header\n" + " Segment offset : " + RainbowDump.formatDecHexString(textSegmentHeader.getSegmentOffset()) + "\n" + " Memory offset : " + RainbowDump.formatDecHexString(textSegmentHeader.getMemoryOffset()) + "\n" + - " Size : " + RainbowDump.formatDecHexString(textSegmentHeader.getSizeAsDecompressed()) + "\n" + + " Size : " + RainbowDump.formatDecHexString(textSegmentHeader.getSize()) + "\n" + "Thread affinity mask : " + RainbowDump.formatDecHexString(threadAffinityMask) + "\n" + ".ro segment header\n" + " Segment offset : " + RainbowDump.formatDecHexString(roDataSegmentHeader.getSegmentOffset()) + "\n" + " Memory offset : " + RainbowDump.formatDecHexString(roDataSegmentHeader.getMemoryOffset()) + "\n" + - " Size : " + RainbowDump.formatDecHexString(roDataSegmentHeader.getSizeAsDecompressed()) + "\n" + + " Size : " + RainbowDump.formatDecHexString(roDataSegmentHeader.getSize()) + "\n" + "Main thread stack size : " + RainbowDump.formatDecHexString(mainThreadStackSize) + "\n" + ".rw segment header\n" + " Segment offset : " + RainbowDump.formatDecHexString(rwDataSegmentHeader.getSegmentOffset()) + "\n" + " Memory offset : " + RainbowDump.formatDecHexString(rwDataSegmentHeader.getMemoryOffset()) + "\n" + - " Size : " + RainbowDump.formatDecHexString(rwDataSegmentHeader.getSizeAsDecompressed()) + "\n" + + " Size : " + RainbowDump.formatDecHexString(rwDataSegmentHeader.getSize()) + "\n" + "Reserved 2 : " + Converter.byteArrToHexStringAsLE(reserved2) + "\n" + ".bss segment header\n" + " Segment offset : " + RainbowDump.formatDecHexString(bssSegmentHeader.getSegmentOffset()) + "\n" + " Memory offset : " + RainbowDump.formatDecHexString(bssSegmentHeader.getMemoryOffset()) + "\n" + - " Size : " + RainbowDump.formatDecHexString(bssSegmentHeader.getSizeAsDecompressed()) + "\n" + + " Size : " + RainbowDump.formatDecHexString(bssSegmentHeader.getSize()) + "\n" + "Reserved 3 : " + Converter.byteArrToHexStringAsLE(reserved3) + "\n" + "Kernel capability data\n" + " Kernel flags available? : " + kernelCapabilityData.isKernelFlagsAvailable() + "\n" + diff --git a/src/main/java/libKonogonka/Tools/other/System2/ini1/KIP1Provider.java b/src/main/java/libKonogonka/Tools/other/System2/ini1/KIP1Provider.java new file mode 100644 index 0000000..42d5702 --- /dev/null +++ b/src/main/java/libKonogonka/Tools/other/System2/ini1/KIP1Provider.java @@ -0,0 +1,82 @@ +/* + Copyright 2019-2023 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 . + */ +package libKonogonka.Tools.other.System2.ini1; + +import libKonogonka.Tools.ExportAble; +import libKonogonka.ctraesclassic.InFileStreamClassicProducer; + +import java.nio.file.Paths; + +public class KIP1Provider extends ExportAble { + + private KIP1Header header; + private final InFileStreamClassicProducer producer; + + private long startOffset; + private long endOffset; + + public KIP1Provider(String fileLocation) throws Exception{ + this.producer = new InFileStreamClassicProducer(Paths.get(fileLocation)); + this.stream = producer.produce(); + byte[] kip1HeaderBytes = new byte[0x100]; + if (0x100 != stream.read(kip1HeaderBytes)) + throw new Exception("Unable to read KIP1 file header"); + + makeHeader(kip1HeaderBytes); + calculateOffsets(0); + } + + public KIP1Provider(byte[] kip1HeaderBytes, long kip1StartOffset, InFileStreamClassicProducer producer) throws Exception{ + makeHeader(kip1HeaderBytes); + calculateOffsets(kip1StartOffset); + this.producer = producer; + this.stream = producer.produce(); + } + + private void makeHeader(byte[] kip1HeaderBytes) throws Exception{ + this.header = new KIP1Header(kip1HeaderBytes); + } + private void calculateOffsets(long kip1StartOffset){ + this.startOffset = kip1StartOffset; + this.endOffset = 0x100 + kip1StartOffset + + header.getTextSegmentHeader().getSize() + header.getRoDataSegmentHeader().getSize() + + header.getRwDataSegmentHeader().getSize() + header.getBssSegmentHeader().getSize(); + } + + public KIP1Header getHeader() { return header; } + + public long getStartOffset() { return startOffset; } + public long getEndOffset() { return endOffset; } + + public boolean export(String saveTo) throws Exception{ + stream = producer.produce(); + return export(saveTo, header.getName()+".kip1", startOffset, endOffset - startOffset); + } + public boolean exportAsDecompressed(String saveToLocation) throws Exception{ + return Kip1Unpacker.unpack(header, producer, saveToLocation); + } + + public KIP1Raw getAsDecompressed() throws Exception{ + return Kip1Unpacker.getNSO0Raw(header, producer); + } + + public void printDebug(){ + header.printDebug(); + } +} diff --git a/src/main/java/libKonogonka/Tools/other/System2/ini1/KIP1Raw.java b/src/main/java/libKonogonka/Tools/other/System2/ini1/KIP1Raw.java new file mode 100644 index 0000000..258aca9 --- /dev/null +++ b/src/main/java/libKonogonka/Tools/other/System2/ini1/KIP1Raw.java @@ -0,0 +1,47 @@ +/* + Copyright 2019-2023 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 . + */ +package libKonogonka.Tools.other.System2.ini1; + +public class KIP1Raw { + private KIP1Header headerObject; + private final byte[] header; + private final byte[] _textDecompressedSection; + private final byte[] _rodataDecompressedSection; + private final byte[] _dataDecompressedSection; + + KIP1Raw(byte[] header, + byte[] _textDecompressedSection, + byte[] _rodataDecompressedSection, + byte[] _dataDecompressedSection){ + this.header = header; + this._textDecompressedSection = _textDecompressedSection; + this. _rodataDecompressedSection = _rodataDecompressedSection; + this._dataDecompressedSection = _dataDecompressedSection; + try { + this.headerObject = new KIP1Header(header); + } + catch (Exception e){ e.printStackTrace(); } + } + + public KIP1Header getHeader() { return headerObject; } + public byte[] getHeaderRaw() {return header;} + public byte[] getTextRaw() {return _textDecompressedSection;} + public byte[] getRodataRaw() {return _rodataDecompressedSection;} + public byte[] getDataRaw() {return _dataDecompressedSection;} +} diff --git a/src/main/java/libKonogonka/Tools/other/System2/ini1/Kip1Unpacker.java b/src/main/java/libKonogonka/Tools/other/System2/ini1/Kip1Unpacker.java new file mode 100644 index 0000000..6666932 --- /dev/null +++ b/src/main/java/libKonogonka/Tools/other/System2/ini1/Kip1Unpacker.java @@ -0,0 +1,151 @@ +/* + Copyright 2019-2023 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 . + */ +package libKonogonka.Tools.other.System2.ini1; + +import libKonogonka.Tools.NSO.SegmentHeader; +import libKonogonka.ctraesclassic.InFileStreamClassicProducer; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; + +public class Kip1Unpacker { + private static final String DECOMPRESSED_FILE_POSTFIX = "_decompressed"; + + private final KIP1Header kip1Header; + private final InFileStreamClassicProducer producer; + private byte[] header; + private byte[] _textDecompressedSection; + private byte[] _roDataDecompressedSection; + private byte[] _rwDataDecompressedSection; + private int textFileOffsetNew; + private int roDataFileOffsetNew; + private int rwDataFileOffsetNew; + + private Kip1Unpacker(KIP1Header kip1Header, InFileStreamClassicProducer producer) throws Exception{ + this.kip1Header = kip1Header; + this.producer = producer; + + decompressSections(); + makeHeader(); + } + + static boolean unpack(KIP1Header kip1Header, InFileStreamClassicProducer producer, String saveToLocation) throws Exception{ + if (! kip1Header.isTextCompressFlag() && ! kip1Header.isRoDataCompressFlag() && ! kip1Header.isRwDataCompressFlag()) + throw new Exception("This file is not compressed. Use 'export(location)' method instead."); + Kip1Unpacker instance = new Kip1Unpacker(kip1Header, producer); + + instance.writeFile(saveToLocation); + return true; + } + + static KIP1Raw getNSO0Raw(KIP1Header kip1Header, InFileStreamClassicProducer producer) throws Exception{ + Kip1Unpacker instance = new Kip1Unpacker(kip1Header, producer); + + return new KIP1Raw(instance.header, + instance._textDecompressedSection, + instance._roDataDecompressedSection, + instance._rwDataDecompressedSection); + } + + private void decompressSections() throws Exception{ + decompressTextSection(); + decompressRodataSection(); + decompressDataSection(); + } + private void decompressTextSection() throws Exception{ + + if (kip1Header.isTextCompressFlag()) + _textDecompressedSection = decompressSection(kip1Header.getTextSegmentHeader(), kip1Header.getTextSegmentHeader().getSize()); + else + _textDecompressedSection = duplicateSection(kip1Header.getTextSegmentHeader()); + } + private void decompressRodataSection() throws Exception{ + if (kip1Header.isRoDataCompressFlag()) + _roDataDecompressedSection = decompressSection(kip1Header.getRoDataSegmentHeader(), kip1Header.getRoDataSegmentHeader().getSize()); + else + _roDataDecompressedSection = duplicateSection(kip1Header.getRoDataSegmentHeader()); + } + private void decompressDataSection() throws Exception{ + if (kip1Header.isRwDataCompressFlag()) + _rwDataDecompressedSection = decompressSection(kip1Header.getRwDataSegmentHeader(), kip1Header.getRwDataSegmentHeader().getSize()); + else + _rwDataDecompressedSection = duplicateSection(kip1Header.getRwDataSegmentHeader()); + } + + private byte[] decompressSection(SegmentHeader segmentHeader, int compressedSectionSize) throws Exception{ + // TODO + return new byte[1]; + } + + private byte[] duplicateSection(SegmentHeader segmentHeader) throws Exception{ + try (BufferedInputStream stream = producer.produce()) { + int size = segmentHeader.getSize(); + + byte[] sectionContent = new byte[size]; + if (segmentHeader.getSegmentOffset() != stream.skip(segmentHeader.getSegmentOffset())) + throw new Exception("Failed to skip " + segmentHeader.getSegmentOffset() + " bytes till section"); + + if (size != stream.read(sectionContent)) + throw new Exception("Failed to read entire section"); + + return sectionContent; + } + } + + private void makeHeader() throws Exception{ + try (BufferedInputStream stream = producer.produce()) { + byte[] headerBytes = new byte[0x100]; + + if (0x100 != stream.read(headerBytes)) + throw new Exception("Unable to read initial 0x100 bytes needed for export."); + //TODO + //textFileOffsetNew = kip1Header.getTextSegmentHeader().getMemoryOffset()+0x100; + //roDataFileOffsetNew = kip1Header.getRoDataSegmentHeader().getMemoryOffset()+0x100; + //rwDataFileOffsetNew = kip1Header.getRwDataSegmentHeader().getMemoryOffset()+0x100; + + ByteBuffer resultingHeader = ByteBuffer.allocate(0x100).order(ByteOrder.LITTLE_ENDIAN); + resultingHeader.put("KIP1".getBytes(StandardCharsets.US_ASCII)); + //.putInt(kip1Header.getVersion()) + //.put(kip1Header.getUpperReserved()) + + + header = resultingHeader.array(); + } + } + + private void writeFile(String saveToLocation) throws Exception{ + File location = new File(saveToLocation); + location.mkdirs(); + + try (RandomAccessFile raf = new RandomAccessFile( + saveToLocation+File.separator+kip1Header.getName()+DECOMPRESSED_FILE_POSTFIX+".kip1", "rw")){ + raf.write(header); + raf.seek(textFileOffsetNew); + raf.write(_textDecompressedSection); + raf.seek(roDataFileOffsetNew); + raf.write(_roDataDecompressedSection); + raf.seek(roDataFileOffsetNew); + raf.write(_rwDataDecompressedSection); + } + } +} diff --git a/src/main/java/libKonogonka/ctraes/InFileStreamProducer.java b/src/main/java/libKonogonka/ctraes/InFileStreamProducer.java index b4767ea..cff3f6c 100644 --- a/src/main/java/libKonogonka/ctraes/InFileStreamProducer.java +++ b/src/main/java/libKonogonka/ctraes/InFileStreamProducer.java @@ -18,11 +18,13 @@ */ package libKonogonka.ctraes; +import libKonogonka.IProducer; + import java.io.BufferedInputStream; import java.io.File; import java.nio.file.Files; -public class InFileStreamProducer { +public class InFileStreamProducer implements IProducer { private boolean encrypted; private final File file; @@ -57,7 +59,7 @@ public class InFileStreamProducer { this.mediaStartOffset = mediaStartOffset; this.mediaEndOffset = mediaEndOffset; } - + @Override public BufferedInputStream produce() throws Exception{ if (encrypted) return produceAesCtr(); @@ -80,12 +82,12 @@ public class InFileStreamProducer { skipBytesTillBeginning(stream, subOffset); return stream; } - + @Override public InFileStreamProducer getSuccessor(long subOffset){ this.subOffset = subOffset; return new InFileStreamProducer(file, initialOffset, subOffset, decryptor, mediaStartOffset, mediaEndOffset); } - + @Override public boolean isEncrypted() { return encrypted; } @@ -98,7 +100,7 @@ public class InFileStreamProducer { mustSkip = size - skipped; } } - + @Override public File getFile(){ return file; } @Override public String toString(){ diff --git a/src/main/java/libKonogonka/ctraesclassic/AesCtrDecryptClassic.java b/src/main/java/libKonogonka/ctraesclassic/AesCtrDecryptClassic.java index 50d0730..0c9b751 100644 --- a/src/main/java/libKonogonka/ctraesclassic/AesCtrDecryptClassic.java +++ b/src/main/java/libKonogonka/ctraesclassic/AesCtrDecryptClassic.java @@ -59,9 +59,7 @@ public class AesCtrDecryptClassic { * @param blocks - how many blocks from encrypted section start should be skipped. Block size = 0x200 * */ public void resetAndSkip(long blocks) throws Exception{ - cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC"); - IvParameterSpec iv = new IvParameterSpec(calculateCtr(blocks * 0x200)); - cipher.init(Cipher.DECRYPT_MODE, key, iv); + reset(calculateCtr(blocks * 0x200)); } private byte[] calculateCtr(long offset){ BigInteger ctr = new BigInteger(ivArray); @@ -69,6 +67,19 @@ public class AesCtrDecryptClassic { return ctr.add(updateTo).toByteArray(); } + /** + * Initializes cipher again using initial IV + * */ + public void reset() throws Exception{ + reset(ivArray.clone()); + } + + private void reset(byte[] updatedIvArray) throws Exception{ + cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC"); + IvParameterSpec iv = new IvParameterSpec(updatedIvArray); + cipher.init(Cipher.DECRYPT_MODE, key, iv); + } + private byte[] hexStrToByteArray(String s) { int len = s.length(); byte[] data = new byte[len / 2]; diff --git a/src/main/java/libKonogonka/ctraesclassic/InFileStreamClassicProducer.java b/src/main/java/libKonogonka/ctraesclassic/InFileStreamClassicProducer.java new file mode 100644 index 0000000..342af11 --- /dev/null +++ b/src/main/java/libKonogonka/ctraesclassic/InFileStreamClassicProducer.java @@ -0,0 +1,121 @@ +/* + Copyright 2019-2023 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 . + */ +package libKonogonka.ctraesclassic; + +import libKonogonka.IProducer; + +import java.io.BufferedInputStream; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; + +public class InFileStreamClassicProducer implements IProducer { + private boolean encrypted; + + private final Path filePath; + private long offset; + private long encryptedStartOffset; + private long encryptedEndOffset; + private AesCtrDecryptClassic decryptor; + + public InFileStreamClassicProducer(Path filePath, + long offset, + long encryptedStartOffset, + long encryptedEndOffset, //Files.size(filePath) + String key, + byte[] iv) throws Exception{ + this.encrypted = true; + this.filePath = filePath; + this.offset = offset; + this.encryptedStartOffset = encryptedStartOffset; + this.encryptedEndOffset = encryptedEndOffset; + this.decryptor = new AesCtrDecryptClassic(key, iv); + } + public InFileStreamClassicProducer(Path filePath, + long offset, + long encryptedStartOffset, + long encryptedEndOffset, //Files.size(filePath) + AesCtrDecryptClassic decryptor){ + this.encrypted = true; + this.filePath = filePath; + this.offset = offset; + this.encryptedStartOffset = encryptedStartOffset; + this.encryptedEndOffset = encryptedEndOffset; + this.decryptor = decryptor; + } + + public InFileStreamClassicProducer(Path filePath){ + this.filePath = filePath; + } + public InFileStreamClassicProducer(Path filePath, long offset){ + this.filePath = filePath; + this.offset = offset; + } + + @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)); + + if (offset != stream.skip(offset)) + throw new Exception("Unable to skip offset: "+offset); + + return stream; + } + + private BufferedInputStream produceNotEncrypted() throws Exception{ + BufferedInputStream 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); + return new InFileStreamClassicProducer(filePath, offset); + } + public InFileStreamClassicProducer getSuccessor(long offset, boolean incrementExisting){ + if (incrementExisting) + return getSuccessor(this.offset + offset); + return getSuccessor(offset); + } + + @Override + public boolean isEncrypted() { + return encrypted; + } + @Override + public File getFile(){ return filePath.toFile(); } + @Override + public String toString(){ + return filePath.toString(); + } +} diff --git a/src/test/java/libKonogonka/RomFsDecrypted/Package2UnpackedTest.java b/src/test/java/libKonogonka/RomFsDecrypted/Package2UnpackedTest.java index 7a29ffb..ada88c8 100644 --- a/src/test/java/libKonogonka/RomFsDecrypted/Package2UnpackedTest.java +++ b/src/test/java/libKonogonka/RomFsDecrypted/Package2UnpackedTest.java @@ -21,7 +21,7 @@ package libKonogonka.RomFsDecrypted; import libKonogonka.KeyChainHolder; import libKonogonka.Tools.other.System2.System2Provider; import libKonogonka.Tools.other.System2.ini1.Ini1Provider; -import libKonogonka.Tools.other.System2.ini1.Kip1; +import libKonogonka.Tools.other.System2.ini1.KIP1Provider; import libKonogonka.ctraesclassic.AesCtrDecryptClassic; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; @@ -101,23 +101,24 @@ public class Package2UnpackedTest { provider.getKernelMap().printDebug(); Ini1Provider ini1Provider = provider.getIni1Provider(); ini1Provider.getIni1Header().printDebug(); - for (Kip1 kip1 : ini1Provider.getKip1List()) - kip1.printDebug(); + for (KIP1Provider kip1Provider : ini1Provider.getKip1List()) + kip1Provider.printDebug(); boolean exported = provider.exportKernel("/home/loper/Projects/libKonogonka/FilesForTests/own/"); System.out.println("Exported = "+exported); - exported = ini1Provider.exportIni1("/home/loper/Projects/libKonogonka/FilesForTests/own/"); + exported = ini1Provider.export("/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")))); + for (KIP1Provider kip1Provider : ini1Provider.getKip1List()) { + exported = kip1Provider.export("/home/loper/Projects/libKonogonka/FilesForTests/own/KIP1s"); + System.out.println("Exported KIP1s "+ kip1Provider.getHeader().getName() +" = " + exported + + String.format(" Size 0x%x", Files.size(Paths.get("/home/loper/Projects/libKonogonka/FilesForTests/own/KIP1s/"+ kip1Provider.getHeader().getName()+".kip1")))); } } @DisplayName("KIP1 read reference") @Test void checkReference() throws Exception{ - Kip1 kip1 = new Kip1("/home/loper/Projects/libKonogonka/FilesForTests/"); - kip1.printDebug(); + KIP1Provider kip1Provider = new KIP1Provider("/home/loper/Projects/libKonogonka/FilesForTests/FS.kip1-fat.dec"); + kip1Provider.printDebug(); } }