Compare commits

...

3 commits

Author SHA1 Message Date
Dmitry Isaenko
b6ab72e7fd add screenshots; update README.md 2020-05-02 18:27:55 +03:00
Dmitry Isaenko
270033b3f9 RomFs support 2020-04-29 18:07:30 +03:00
Dmitry Isaenko
416e5280a6 RomFsEncryptedProvider complete 2020-04-29 17:35:34 +03:00
16 changed files with 110 additions and 35 deletions

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

BIN
screenshots/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
screenshots/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

BIN
screenshots/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
screenshots/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

BIN
screenshots/6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

BIN
screenshots/7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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.");
return; 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;
}
else {
streamOut.write(dectyptedBlock, (int) ignoreBytes, 0x200 - (int) ignoreBytes);
}
} }
streamOut.write(readBuf); else {
readFrom += readPice; throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from 1st bock for Directory Metadata Table");
}
startBlock++;
} }
bis.close(); 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;

View 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;

View file

@ -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();

View file

@ -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>