Compare commits
3 commits
8225f0fcba
...
b6ab72e7fd
Author | SHA1 | Date | |
---|---|---|---|
|
b6ab72e7fd | ||
|
270033b3f9 | ||
|
416e5280a6 |
|
@ -6,6 +6,12 @@ Deep WIP multi-tool to work with NS-specific files / filesystem images.
|
||||||
|
|
||||||
[GNU General Public License v3+](https://github.com/developersu/konogonka/blob/master/LICENSE)
|
[GNU General Public License v3+](https://github.com/developersu/konogonka/blob/master/LICENSE)
|
||||||
|
|
||||||
|
<img src="screenshots/1.png" alt="drawing" width="250"/> <img src="screenshots/2.png" alt="drawing" width="250"/> <img src="screenshots/3.png" alt="drawing" width="250"/>
|
||||||
|
|
||||||
|
<img src="screenshots/4.png" alt="drawing" width="250"/> <img src="screenshots/5.png" alt="drawing" width="250"/> <img src="screenshots/6.png" alt="drawing" width="250"/>
|
||||||
|
|
||||||
|
<img src="screenshots/7.png" alt="drawing" width="250"/>
|
||||||
|
|
||||||
### Used libraries & resources
|
### Used libraries & resources
|
||||||
* [Bouncy Castle](https://www.bouncycastle.org/) for Java.
|
* [Bouncy Castle](https://www.bouncycastle.org/) for Java.
|
||||||
* [Java-XTS-AES](https://github.com/horrorho/Java-XTS-AES) by horrorho with minimal changes.
|
* [Java-XTS-AES](https://github.com/horrorho/Java-XTS-AES) by horrorho with minimal changes.
|
||||||
|
@ -45,6 +51,6 @@ JRE/JDK 8u60 or higher.
|
||||||
* [ ] CERT support
|
* [ ] CERT support
|
||||||
* [ ] CNMT support
|
* [ ] CNMT support
|
||||||
* [ ] NSO support
|
* [ ] NSO support
|
||||||
* [ ] RomFS deep-dive
|
* [x] RomFS
|
||||||
* [ ] LogPrinter to singleton implementation.
|
* [ ] LogPrinter to singleton implementation.
|
||||||
* [x] 'Save to folder' option
|
* [x] 'Save to folder' option
|
BIN
screenshots/1.png
Normal file
After Width: | Height: | Size: 234 KiB |
BIN
screenshots/2.png
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
screenshots/3.png
Normal file
After Width: | Height: | Size: 132 KiB |
BIN
screenshots/4.png
Normal file
After Width: | Height: | Size: 99 KiB |
BIN
screenshots/5.png
Normal file
After Width: | Height: | Size: 168 KiB |
BIN
screenshots/6.png
Normal file
After Width: | Height: | Size: 114 KiB |
BIN
screenshots/7.png
Normal file
After Width: | Height: | Size: 146 KiB |
|
@ -110,7 +110,7 @@ public class MainController implements Initializable {
|
||||||
fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
|
fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
|
||||||
|
|
||||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NS files",
|
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NS files",
|
||||||
"*.nsp", "*.nsz", "*.xci", "*.nca", "*.tik", "*.xml", "*.npdm", "*.romfs"));
|
"*.nsp", "*.nsz", "*.xci", "*.nca", "*.tik", "*.xml", "*.npdm", "*.bin"));
|
||||||
|
|
||||||
this.selectedFile = fileChooser.showOpenDialog(analyzeBtn.getScene().getWindow());
|
this.selectedFile = fileChooser.showOpenDialog(analyzeBtn.getScene().getWindow());
|
||||||
|
|
||||||
|
@ -156,7 +156,7 @@ public class MainController implements Initializable {
|
||||||
case "npdm":
|
case "npdm":
|
||||||
tabPane.getSelectionModel().select(5);
|
tabPane.getSelectionModel().select(5);
|
||||||
break;
|
break;
|
||||||
case "romfs":
|
case "bin":
|
||||||
tabPane.getSelectionModel().select(6);
|
tabPane.getSelectionModel().select(6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,7 +185,7 @@ public class MainController implements Initializable {
|
||||||
case "npdm":
|
case "npdm":
|
||||||
NPDMTabController.analyze(selectedFile);
|
NPDMTabController.analyze(selectedFile);
|
||||||
break;
|
break;
|
||||||
case "romfs":
|
case "bin":
|
||||||
RFSTabController.analyze(selectedFile);
|
RFSTabController.analyze(selectedFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,7 +198,7 @@ public class MainController implements Initializable {
|
||||||
case "tik":
|
case "tik":
|
||||||
case "xml":
|
case "xml":
|
||||||
case "npdm":
|
case "npdm":
|
||||||
case "romfs":
|
case "bin":
|
||||||
return false;
|
return false;
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -147,7 +147,15 @@ public class RomFsController implements ITabController {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void analyze(File file) {
|
public void analyze(File file) {
|
||||||
Task<RomFsDecryptedProvider> analyzer = Analyzer.analyzeRomFS(file);
|
long lv6offset = -1;
|
||||||
|
try{
|
||||||
|
System.out.println(file.getName().replaceAll("(^.*lv6\\s)|(]\\.bin)", ""));
|
||||||
|
lv6offset = Long.parseLong(file.getName().replaceAll("(^.*lv6\\s)|(]\\.bin)", ""));
|
||||||
|
}
|
||||||
|
catch (Exception e){
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
Task<RomFsDecryptedProvider> analyzer = Analyzer.analyzeRomFS(file, lv6offset);
|
||||||
analyzer.setOnSucceeded(e->{
|
analyzer.setOnSucceeded(e->{
|
||||||
RomFsDecryptedProvider provider = analyzer.getValue();
|
RomFsDecryptedProvider provider = analyzer.getValue();
|
||||||
this.setData(provider);
|
this.setData(provider);
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.io.File;
|
||||||
import java.io.PipedInputStream;
|
import java.io.PipedInputStream;
|
||||||
|
|
||||||
public interface IRomFsProvider {
|
public interface IRomFsProvider {
|
||||||
|
long getLevel6Offset();
|
||||||
Level6Header getHeader();
|
Level6Header getHeader();
|
||||||
FileSystemEntry getRootEntry();
|
FileSystemEntry getRootEntry();
|
||||||
PipedInputStream getContent(FileSystemEntry entry) throws Exception;
|
PipedInputStream getContent(FileSystemEntry entry) throws Exception;
|
||||||
|
|
|
@ -23,20 +23,24 @@ import java.io.*;
|
||||||
|
|
||||||
public class RomFsDecryptedProvider implements IRomFsProvider{
|
public class RomFsDecryptedProvider implements IRomFsProvider{
|
||||||
|
|
||||||
private static final long LEVEL_6_DEFAULT_OFFSET = 0x14000; // TODO: FIX incorrect
|
private long level6Offset;
|
||||||
|
|
||||||
private File file;
|
private File file;
|
||||||
private Level6Header header;
|
private Level6Header header;
|
||||||
|
|
||||||
private FileSystemEntry rootEntry;
|
private FileSystemEntry rootEntry;
|
||||||
// TODO: FIX. LEVEL 6 OFFSET MUST be provided
|
|
||||||
|
|
||||||
public RomFsDecryptedProvider(File decryptedFsImageFile) throws Exception{ // TODO: add default setup AND using meta-data headers from NCA RomFs section (?)
|
public RomFsDecryptedProvider(File decryptedFsImageFile, long level6Offset) throws Exception{
|
||||||
|
if (level6Offset < 0)
|
||||||
|
throw new Exception("Incorrect Level 6 Offset");
|
||||||
|
|
||||||
this.file = decryptedFsImageFile;
|
this.file = decryptedFsImageFile;
|
||||||
|
|
||||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(decryptedFsImageFile));
|
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(decryptedFsImageFile));
|
||||||
|
|
||||||
skipBytes(bis, LEVEL_6_DEFAULT_OFFSET);
|
this.level6Offset = level6Offset;
|
||||||
|
|
||||||
|
skipBytes(bis, level6Offset);
|
||||||
|
|
||||||
byte[] rawDataChunk = new byte[0x50];
|
byte[] rawDataChunk = new byte[0x50];
|
||||||
|
|
||||||
|
@ -98,6 +102,8 @@ public class RomFsDecryptedProvider implements IRomFsProvider{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
|
public long getLevel6Offset() { return level6Offset; }
|
||||||
|
@Override
|
||||||
public Level6Header getHeader() { return header; }
|
public Level6Header getHeader() { return header; }
|
||||||
@Override
|
@Override
|
||||||
public FileSystemEntry getRootEntry() { return rootEntry; }
|
public FileSystemEntry getRootEntry() { return rootEntry; }
|
||||||
|
@ -114,7 +120,7 @@ public class RomFsDecryptedProvider implements IRomFsProvider{
|
||||||
workerThread = new Thread(() -> {
|
workerThread = new Thread(() -> {
|
||||||
System.out.println("RomFsDecryptedProvider -> getContent(): Executing thread");
|
System.out.println("RomFsDecryptedProvider -> getContent(): Executing thread");
|
||||||
try {
|
try {
|
||||||
long subFileRealPosition = LEVEL_6_DEFAULT_OFFSET + header.getFileDataOffset() + entry.getFileOffset();
|
long subFileRealPosition = level6Offset + header.getFileDataOffset() + entry.getFileOffset();
|
||||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||||
skipBytes(bis, subFileRealPosition);
|
skipBytes(bis, subFileRealPosition);
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ import java.util.Arrays;
|
||||||
|
|
||||||
public class RomFsEncryptedProvider implements IRomFsProvider{
|
public class RomFsEncryptedProvider implements IRomFsProvider{
|
||||||
|
|
||||||
private static long level6Offset;
|
private long level6Offset;
|
||||||
|
|
||||||
private File file;
|
private File file;
|
||||||
private Level6Header header;
|
private Level6Header header;
|
||||||
|
@ -176,6 +176,8 @@ public class RomFsEncryptedProvider implements IRomFsProvider{
|
||||||
return metadataTable;
|
return metadataTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLevel6Offset() { return level6Offset; }
|
||||||
@Override
|
@Override
|
||||||
public Level6Header getHeader() { return header; }
|
public Level6Header getHeader() { return header; }
|
||||||
@Override
|
@Override
|
||||||
|
@ -189,33 +191,80 @@ public class RomFsEncryptedProvider implements IRomFsProvider{
|
||||||
Thread workerThread;
|
Thread workerThread;
|
||||||
|
|
||||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||||
|
|
||||||
workerThread = new Thread(() -> {
|
workerThread = new Thread(() -> {
|
||||||
System.out.println("RomFsDecryptedProvider -> getContent(): Executing thread");
|
System.out.println("RomFsDecryptedProvider -> getContent(): Executing thread");
|
||||||
try {
|
try {
|
||||||
long subFileRealPosition = level6Offset + header.getFileDataOffset() + entry.getFileOffset();
|
|
||||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
|
||||||
bis.skip(subFileRealPosition);
|
|
||||||
|
|
||||||
int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576
|
byte[] encryptedBlock;
|
||||||
|
byte[] dectyptedBlock;
|
||||||
|
|
||||||
long readFrom = 0;
|
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||||
long realFileSize = entry.getFileSize();
|
|
||||||
|
|
||||||
byte[] readBuf;
|
//0
|
||||||
|
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
|
||||||
|
|
||||||
while (readFrom < realFileSize) {
|
long startBlock = (entry.getFileOffset() + header.getFileDataOffset()) / 0x200;
|
||||||
if (realFileSize - readFrom < readPice)
|
|
||||||
readPice = Math.toIntExact(realFileSize - readFrom); // it's safe, I guarantee
|
decryptor.skipNext(level6Offset / 0x200 + startBlock);
|
||||||
readBuf = new byte[readPice];
|
|
||||||
if (bis.read(readBuf) != readPice) {
|
long abosluteOffsetPosition = romFSoffsetPosition + (mediaStartOffset * 0x200);
|
||||||
System.out.println("RomFsDecryptedProvider -> getContent(): Unable to read requested size from file.");
|
|
||||||
|
raf.seek(abosluteOffsetPosition + level6Offset + startBlock * 0x200);
|
||||||
|
|
||||||
|
//1
|
||||||
|
long ignoreBytes = (entry.getFileOffset() + header.getFileDataOffset()) - startBlock * 0x200;
|
||||||
|
|
||||||
|
if (ignoreBytes > 0) {
|
||||||
|
encryptedBlock = new byte[0x200];
|
||||||
|
if (raf.read(encryptedBlock) == 0x200) {
|
||||||
|
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||||
|
// If we have extra-small file that is less then a block and even more
|
||||||
|
if ((0x200 - ignoreBytes) > entry.getFileSize()){
|
||||||
|
streamOut.write(dectyptedBlock, (int)ignoreBytes, (int) entry.getFileSize()); // safe cast
|
||||||
|
raf.close();
|
||||||
|
streamOut.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
streamOut.write(readBuf);
|
else {
|
||||||
readFrom += readPice;
|
streamOut.write(dectyptedBlock, (int) ignoreBytes, 0x200 - (int) ignoreBytes);
|
||||||
}
|
}
|
||||||
bis.close();
|
}
|
||||||
|
else {
|
||||||
|
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from 1st bock for Directory Metadata Table");
|
||||||
|
}
|
||||||
|
startBlock++;
|
||||||
|
}
|
||||||
|
long endBlock = (entry.getFileSize() + ignoreBytes) / 0x200 + startBlock; // <- pointing to place where any data related to this media-block ends
|
||||||
|
|
||||||
|
//2
|
||||||
|
int extraData = (int) ((endBlock - startBlock)*0x200 - (entry.getFileSize() + ignoreBytes));
|
||||||
|
|
||||||
|
if (extraData < 0)
|
||||||
|
endBlock--;
|
||||||
|
//3
|
||||||
|
while ( startBlock < endBlock ) {
|
||||||
|
encryptedBlock = new byte[0x200];
|
||||||
|
if (raf.read(encryptedBlock) == 0x200) {
|
||||||
|
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||||
|
streamOut.write(dectyptedBlock);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory Metadata Table");
|
||||||
|
|
||||||
|
startBlock++;
|
||||||
|
}
|
||||||
|
|
||||||
|
//4
|
||||||
|
if (extraData != 0){ // In case we didn't get what we want
|
||||||
|
encryptedBlock = new byte[0x200];
|
||||||
|
if (raf.read(encryptedBlock) == 0x200) {
|
||||||
|
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||||
|
streamOut.write(dectyptedBlock, 0, Math.abs(extraData));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory Metadata Table");
|
||||||
|
}
|
||||||
|
raf.close();
|
||||||
streamOut.close();
|
streamOut.close();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println("RomFsDecryptedProvider -> getContent(): Unable to provide stream");
|
System.out.println("RomFsDecryptedProvider -> getContent(): Unable to provide stream");
|
||||||
|
@ -226,6 +275,7 @@ public class RomFsEncryptedProvider implements IRomFsProvider{
|
||||||
workerThread.start();
|
workerThread.start();
|
||||||
return streamIn;
|
return streamIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File getFile() {
|
public File getFile() {
|
||||||
return file;
|
return file;
|
||||||
|
|
|
@ -161,14 +161,14 @@ public class Analyzer {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task<RomFsDecryptedProvider> analyzeRomFS(File file){
|
public static Task<RomFsDecryptedProvider> analyzeRomFS(File file, long lv6offset){
|
||||||
LogPrinter logPrinter = new LogPrinter();
|
LogPrinter logPrinter = new LogPrinter();
|
||||||
return new Task<RomFsDecryptedProvider>() {
|
return new Task<RomFsDecryptedProvider>() {
|
||||||
@Override
|
@Override
|
||||||
protected RomFsDecryptedProvider call() {
|
protected RomFsDecryptedProvider call() {
|
||||||
logPrinter.print("\tStart chain: RomFS", EMsgType.INFO);
|
logPrinter.print("\tStart chain: RomFS", EMsgType.INFO);
|
||||||
try {
|
try {
|
||||||
return new RomFsDecryptedProvider(file);
|
return new RomFsDecryptedProvider(file, lv6offset);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logPrinter.print(e.getMessage(), EMsgType.FAIL);
|
logPrinter.print(e.getMessage(), EMsgType.FAIL);
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -26,8 +26,12 @@ public class DumbNCA3ContentExtractor extends Task<Void> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Void call() {
|
protected Void call() {
|
||||||
logPrinter.print("\tStart dummy extracting: \n"+filesDestPath+"NCAContent_"+ncaNumberInFile+".bin", EMsgType.INFO);
|
String lv6mark = "";
|
||||||
File contentFile = new File(filesDestPath + "NCAContent_"+ncaNumberInFile+".bin");
|
if (ncaContent.getRomfs() != null){
|
||||||
|
lv6mark = " [lv6 "+ncaContent.getRomfs().getLevel6Offset()+"]";
|
||||||
|
}
|
||||||
|
logPrinter.print("\tStart dummy extracting: \n"+filesDestPath+"NCAContent_"+ncaNumberInFile+lv6mark+".bin", EMsgType.INFO);
|
||||||
|
File contentFile = new File(filesDestPath + "NCAContent_"+ncaNumberInFile+lv6mark+".bin");
|
||||||
try {
|
try {
|
||||||
BufferedOutputStream extractedFileBOS = new BufferedOutputStream(new FileOutputStream(contentFile));
|
BufferedOutputStream extractedFileBOS = new BufferedOutputStream(new FileOutputStream(contentFile));
|
||||||
PipedInputStream pis = ncaContent.getRawDataContentPipedInpStream();
|
PipedInputStream pis = ncaContent.getRawDataContentPipedInpStream();
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<?import javafx.scene.shape.SVGPath?>
|
<?import javafx.scene.shape.SVGPath?>
|
||||||
<?import javafx.scene.text.Font?>
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
<VBox minHeight="600.0" spacing="5.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="konogonka.Controllers.RFS.RomFsController">
|
<VBox minHeight="750.0" spacing="5.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="konogonka.Controllers.RFS.RomFsController">
|
||||||
<children>
|
<children>
|
||||||
<TitledPane animated="false" expanded="false" text="Header (Level 6)">
|
<TitledPane animated="false" expanded="false" text="Header (Level 6)">
|
||||||
<content>
|
<content>
|
||||||
|
|