Adjust added classes to application domain standards; add interface for KIP1 extraction
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
e47d977779
commit
ce16e19985
13 changed files with 509 additions and 123 deletions
12
src/main/java/libKonogonka/IProducer.java
Normal file
12
src/main/java/libKonogonka/IProducer.java
Normal file
|
@ -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();
|
||||
}
|
|
@ -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" +
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Kip1> kip1List;
|
||||
private List<KIP1Provider> 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<Kip1> getKip1List() { return kip1List; }
|
||||
public List<KIP1Provider> 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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" +
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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;}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(){
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue