NCA table/contoller/provider updates
This commit is contained in:
parent
643f09fd9e
commit
4e1bacd4fc
20 changed files with 450 additions and 302 deletions
|
@ -14,7 +14,8 @@ Deep WIP multi-tool to work with XCI/NSP/NCA/NRO(?) files
|
|||
|
||||
### Information taken from
|
||||
* Switch brew wiki
|
||||
* roothorick, shchmue, He and others advices and notes. Thanks!
|
||||
* Original ScriesM software
|
||||
* Thanks to roothorick, shchmue, He and others for their advices, notes and examples
|
||||
|
||||
### System requirements
|
||||
|
||||
|
|
70
src/main/java/konogonka/Child/ChildWindow.java
Normal file
70
src/main/java/konogonka/Child/ChildWindow.java
Normal file
|
@ -0,0 +1,70 @@
|
|||
package konogonka.Child;
|
||||
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Stage;
|
||||
import konogonka.Controllers.IRowModel;
|
||||
import konogonka.Controllers.NCA.NCAController;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public class ChildWindow {
|
||||
public ChildWindow(ISuperProvider provider, IRowModel model) throws IOException{
|
||||
Stage stageSettings = new Stage();
|
||||
|
||||
stageSettings.setMinWidth(570);
|
||||
stageSettings.setMinHeight(500);
|
||||
|
||||
FXMLLoader loaderSettings;
|
||||
|
||||
if (model.getFileName().endsWith(".nca")){
|
||||
loaderSettings = new FXMLLoader(getClass().getResource("/FXML/NCA/NCATab.fxml"));
|
||||
}
|
||||
else if(model.getFileName().endsWith(".cert")){
|
||||
// TODO: IMPLEMENT
|
||||
return;
|
||||
}
|
||||
else if(model.getFileName().endsWith(".tik")){
|
||||
// TODO: IMPLEMENT
|
||||
return;
|
||||
}
|
||||
else if(model.getFileName().endsWith(".xml")){
|
||||
// TODO: IMPLEMENT
|
||||
return;
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
Locale userLocale = new Locale(Locale.getDefault().getISO3Language());
|
||||
ResourceBundle resourceBundle = ResourceBundle.getBundle("locale", userLocale);
|
||||
loaderSettings.setResources(resourceBundle);
|
||||
Parent parentAbout = loaderSettings.load();
|
||||
|
||||
|
||||
// TODO: REFACTOR
|
||||
if (model.getFileName().endsWith(".nca")){
|
||||
NCAController ncaController = loaderSettings.<NCAController>getController();
|
||||
ncaController.analyze(provider.getFile(), provider.getRawFileDataStart()+model.getFileOffset());
|
||||
}
|
||||
|
||||
|
||||
stageSettings.setTitle(model.getFileName());
|
||||
stageSettings.getIcons().addAll(
|
||||
new Image(getClass().getResourceAsStream("/res/app_icon32x32.png")),
|
||||
new Image(getClass().getResourceAsStream("/res/app_icon48x48.png")),
|
||||
new Image(getClass().getResourceAsStream("/res/app_icon64x64.png")),
|
||||
new Image(getClass().getResourceAsStream("/res/app_icon128x128.png"))
|
||||
);
|
||||
Scene settingsScene = new Scene(parentAbout, 800, 800);
|
||||
settingsScene.getStylesheets().add("/res/app_light.css");
|
||||
stageSettings.setScene(settingsScene);
|
||||
stageSettings.setMinWidth(550.0);
|
||||
stageSettings.setMinHeight(550.0);
|
||||
stageSettings.show();
|
||||
}
|
||||
}
|
12
src/main/java/konogonka/Controllers/ITabController.java
Normal file
12
src/main/java/konogonka/Controllers/ITabController.java
Normal file
|
@ -0,0 +1,12 @@
|
|||
package konogonka.Controllers;
|
||||
|
||||
import javafx.fxml.Initializable;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface ITabController extends Initializable {
|
||||
void analyze(File file);
|
||||
void analyze(ISuperProvider provider, int subFileNumber);
|
||||
void resetTab();
|
||||
}
|
|
@ -6,11 +6,13 @@ import javafx.scene.control.*;
|
|||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import konogonka.AppPreferences;
|
||||
import konogonka.Child.ChildWindow;
|
||||
import konogonka.Controllers.NCA.NCAController;
|
||||
import konogonka.Controllers.NSP.NSPController;
|
||||
import konogonka.Controllers.XCI.XCIController;
|
||||
import konogonka.MediatorControl;
|
||||
import konogonka.Settings.SettingsWindow;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
|
@ -115,6 +117,14 @@ public class MainController implements Initializable {
|
|||
else
|
||||
splitPane.getItems().add(logPane);
|
||||
}
|
||||
public void showContentWindow(ISuperProvider provider, IRowModel model){
|
||||
try{
|
||||
new ChildWindow(provider, model);
|
||||
}
|
||||
catch (IOException e){
|
||||
logArea.appendText("\nUnable to create windows for "+model.getFileName()+"\n"+e.getMessage());
|
||||
}
|
||||
|
||||
};
|
||||
public void exit(){ AppPreferences.getInstance().setRecentPath(previouslyOpenedPath); }
|
||||
}
|
|
@ -4,7 +4,8 @@ import javafx.fxml.FXML;
|
|||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import konogonka.AppPreferences;
|
||||
import konogonka.Controllers.TabController;
|
||||
import konogonka.Controllers.ITabController;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import konogonka.Tools.NCA.NCAContentPFS0;
|
||||
import konogonka.Tools.NCA.NCAProvider;
|
||||
import konogonka.Workers.AnalyzerNCA;
|
||||
|
@ -16,7 +17,7 @@ import java.util.ResourceBundle;
|
|||
|
||||
import static konogonka.LoperConverter.byteArrToHexString;
|
||||
|
||||
public class NCAController implements TabController {
|
||||
public class NCAController implements ITabController {
|
||||
|
||||
private File selectedFile;
|
||||
@FXML
|
||||
|
@ -46,6 +47,7 @@ public class NCAController implements TabController {
|
|||
keyIndexLbl,
|
||||
ncaSizeLbl,
|
||||
titleIdLbl,
|
||||
contentIndexLbl,
|
||||
sdkVersionLbl,
|
||||
cryptoType2Lbl,
|
||||
ticketLbl;
|
||||
|
@ -70,9 +72,12 @@ public class NCAController implements TabController {
|
|||
public void initialize(URL url, ResourceBundle resourceBundle) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void analyze(File file) {
|
||||
public void analyze(ISuperProvider provider, int subFileNumber){
|
||||
// TODO
|
||||
}
|
||||
|
||||
public void analyze(File file, long offset) {
|
||||
this.selectedFile = file;
|
||||
HashMap<String, String> keysMap = new HashMap<>();
|
||||
keysMap.put("header_key", AppPreferences.getInstance().getHeaderKey());
|
||||
|
@ -91,7 +96,7 @@ public class NCAController implements TabController {
|
|||
keysMap.put(pair[0], pair[1]);
|
||||
}
|
||||
|
||||
AnalyzerNCA analyzerNCA = new AnalyzerNCA(file, keysMap);
|
||||
AnalyzerNCA analyzerNCA = new AnalyzerNCA(file, keysMap, offset);
|
||||
analyzerNCA.setOnSucceeded(e->{
|
||||
populateFields(analyzerNCA.getValue());
|
||||
});
|
||||
|
@ -100,6 +105,11 @@ public class NCAController implements TabController {
|
|||
workThread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void analyze(File file) {
|
||||
analyze(file, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetTab() {
|
||||
// Header
|
||||
|
@ -115,6 +125,7 @@ public class NCAController implements TabController {
|
|||
sdkVersionLbl.setText("-");
|
||||
cryptoType2Lbl.setText("-");
|
||||
ticketLbl.setText("-");
|
||||
contentIndexLbl.setText("-");
|
||||
sha256section1TF.setText("-");
|
||||
sha256section2TF.setText("-");
|
||||
sha256section3TF.setText("-");
|
||||
|
@ -156,6 +167,7 @@ public class NCAController implements TabController {
|
|||
keyIndexLbl.setText(Byte.toString(ncaProvider.getKeyIndex()));
|
||||
ncaSizeLbl.setText(Long.toString(ncaProvider.getNcaSize()));
|
||||
titleIdLbl.setText(byteArrToHexString(ncaProvider.getTitleId()));
|
||||
contentIndexLbl.setText(byteArrToHexString(ncaProvider.getContentIndx())); //
|
||||
sdkVersionLbl.setText(ncaProvider.getSdkVersion()[3]
|
||||
+"."+ncaProvider.getSdkVersion()[2]
|
||||
+"."+ncaProvider.getSdkVersion()[1]
|
||||
|
|
|
@ -8,7 +8,6 @@ import javafx.scene.layout.VBox;
|
|||
import konogonka.Controllers.NSP.NSPController;
|
||||
import konogonka.LoperConverter;
|
||||
import konogonka.Tools.PFS0.IPFS0Provider;
|
||||
import konogonka.Tools.PFS0.PFS0Provider;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedList;
|
||||
|
|
|
@ -4,7 +4,7 @@ import javafx.fxml.FXML;
|
|||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import konogonka.Controllers.IRowModel;
|
||||
import konogonka.Controllers.TabController;
|
||||
import konogonka.Controllers.ITabController;
|
||||
import konogonka.MediatorControl;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import konogonka.Tools.PFS0.IPFS0Provider;
|
||||
|
@ -19,7 +19,7 @@ import java.util.ResourceBundle;
|
|||
|
||||
import static konogonka.LoperConverter.byteArrToHexString;
|
||||
|
||||
public class NSPController implements TabController {
|
||||
public class NSPController implements ITabController {
|
||||
|
||||
@FXML
|
||||
private Button extractBtn;
|
||||
|
@ -92,6 +92,11 @@ public class NSPController implements TabController {
|
|||
* Start analyze NSP
|
||||
* */
|
||||
@Override
|
||||
public void analyze(ISuperProvider provider, int subFileNumber){
|
||||
// TODO: IMPLEMENT
|
||||
return;
|
||||
}
|
||||
@Override
|
||||
public void analyze(File selectedFile){
|
||||
this.selectedFile = selectedFile;
|
||||
AnalyzerNSP analyzerNSP = new AnalyzerNSP(selectedFile);
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package konogonka.Controllers.NSP;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
|
@ -17,6 +19,7 @@ import javafx.scene.input.MouseButton;
|
|||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.util.Callback;
|
||||
import konogonka.Controllers.IRowModel;
|
||||
import konogonka.MediatorControl;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import konogonka.Tools.PFS0.IPFS0Provider;
|
||||
|
||||
|
@ -117,15 +120,30 @@ public class Pfs0TableViewController implements Initializable {
|
|||
uploadColumn.setCellFactory(new Callback<TableColumn<Pfs0RowModel, Boolean>, TableCell<Pfs0RowModel, Boolean>>() {
|
||||
@Override
|
||||
public TableCell<Pfs0RowModel, Boolean> call(TableColumn<Pfs0RowModel, Boolean> paramFeatures) {
|
||||
CheckBoxTableCell<Pfs0RowModel, Boolean> cell = new CheckBoxTableCell<>();
|
||||
return cell;
|
||||
return new CheckBoxTableCell<>();
|
||||
}
|
||||
});
|
||||
table.setRowFactory( // this shit is made to implement context menu. It's such a pain..
|
||||
new Callback<TableView<Pfs0RowModel>, TableRow<Pfs0RowModel>>() {
|
||||
@Override
|
||||
public TableRow<Pfs0RowModel> call(TableView<Pfs0RowModel> nslRowModelTableView) {
|
||||
public TableRow<Pfs0RowModel> call(TableView<Pfs0RowModel> Pfs0RowModelTableView) {
|
||||
final TableRow<Pfs0RowModel> row = new TableRow<>();
|
||||
ContextMenu contextMenu = new ContextMenu();
|
||||
|
||||
MenuItem openMenuItem = new MenuItem("Open");
|
||||
openMenuItem.setOnAction(new EventHandler<ActionEvent>() {
|
||||
@Override
|
||||
public void handle(ActionEvent actionEvent) {
|
||||
MediatorControl.getInstance().getContoller().showContentWindow(provider, row.getItem()); // TODO: change to something better
|
||||
}
|
||||
});
|
||||
|
||||
contextMenu.getItems().addAll(openMenuItem);
|
||||
|
||||
row.setContextMenu(contextMenu);
|
||||
row.contextMenuProperty().bind(
|
||||
Bindings.when(Bindings.isNotNull(row.itemProperty())).then(contextMenu).otherwise((ContextMenu)null)
|
||||
);
|
||||
row.setOnMouseClicked(new EventHandler<MouseEvent>() { // Just.. don't ask..
|
||||
@Override
|
||||
public void handle(MouseEvent mouseEvent) {
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
package konogonka.Controllers;
|
||||
|
||||
import javafx.fxml.Initializable;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface TabController extends Initializable {
|
||||
void analyze(File file);
|
||||
void resetTab();
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
package konogonka.Controllers.XCI;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import konogonka.AppPreferences;
|
||||
import konogonka.Controllers.TabController;
|
||||
import konogonka.Controllers.ITabController;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import konogonka.Tools.XCI.XCIProvider;
|
||||
import konogonka.Workers.AnalyzerXCI;
|
||||
|
||||
|
@ -15,7 +15,7 @@ import java.util.ResourceBundle;
|
|||
|
||||
import static konogonka.LoperConverter.byteArrToHexString;
|
||||
|
||||
public class XCIController implements TabController {
|
||||
public class XCIController implements ITabController {
|
||||
|
||||
/* Header */
|
||||
@FXML
|
||||
|
@ -90,6 +90,11 @@ public class XCIController implements TabController {
|
|||
* Start analyze XCI
|
||||
* */
|
||||
@Override
|
||||
public void analyze(ISuperProvider provider, int subFileNumber){
|
||||
// TODO: IMPLEMENT
|
||||
return;
|
||||
}
|
||||
@Override
|
||||
public void analyze(File selectedFile){
|
||||
HFSBlockController.setSelectedFile(selectedFile);
|
||||
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
package konogonka.Tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PipedInputStream;
|
||||
|
||||
public interface ISuperProvider {
|
||||
PipedInputStream getProviderSubFilePipedInpStream(String subFileName);
|
||||
PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber);
|
||||
PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception;
|
||||
PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception;
|
||||
|
||||
File getFile();
|
||||
long getRawFileDataStart();
|
||||
}
|
||||
|
|
|
@ -71,12 +71,12 @@ public class NCAContentPFS0 {
|
|||
|
||||
private class CryptoSection03{
|
||||
|
||||
CryptoSection03(File file, long offsetPosition, byte[] decryptedKey, NCASectionBlock ncaSectionBlock, long mediaStartOffset, long mediaEndOffset) throws Exception{
|
||||
CryptoSection03(File file, long offsetPosition, byte[] decryptedKey, NCASectionBlock ncaSectionBlock, long mediaStartBlocksOffset, long mediaEndBlocksOffset) throws Exception{
|
||||
/*//--------------------------------------------------------------------------------------------------
|
||||
System.out.println("Media start location: " + mediaStartOffset);
|
||||
System.out.println("Media end location: " + mediaEndOffset);
|
||||
System.out.println("Media size : " + (mediaEndOffset-mediaStartOffset));
|
||||
System.out.println("Media act. location: " + (offsetPosition + (mediaStartOffset * 0x200)));
|
||||
System.out.println("Media start location: " + mediaStartBlocksOffset);
|
||||
System.out.println("Media end location: " + mediaEndBlocksOffset);
|
||||
System.out.println("Media size : " + (mediaEndBlocksOffset-mediaStartBlocksOffset));
|
||||
System.out.println("Media act. location: " + (offsetPosition + (mediaStartBlocksOffset * 0x200)));
|
||||
System.out.println("SHA256 hash tbl size: " + ncaSectionBlock.getSuperBlockPFS0().getHashTableSize());
|
||||
System.out.println("SHA256 hash tbl offs: " + ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset());
|
||||
System.out.println("PFS0 Offs: " + ncaSectionBlock.getSuperBlockPFS0().getPfs0offset());
|
||||
|
@ -89,14 +89,14 @@ public class NCAContentPFS0 {
|
|||
throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided.");
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
long abosluteOffsetPosition = offsetPosition + (mediaStartOffset * 0x200);
|
||||
long abosluteOffsetPosition = offsetPosition + (mediaStartBlocksOffset * 0x200);
|
||||
raf.seek(abosluteOffsetPosition);
|
||||
|
||||
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey, ncaSectionBlock.getSectionCTR(), mediaStartOffset * 0x200);
|
||||
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey, ncaSectionBlock.getSectionCTR(), mediaStartBlocksOffset * 0x200);
|
||||
|
||||
byte[] encryptedBlock;
|
||||
byte[] dectyptedBlock;
|
||||
long mediaBlockSize = mediaEndOffset - mediaStartOffset;
|
||||
long mediaBlocksSize = mediaEndBlocksOffset - mediaStartBlocksOffset;
|
||||
// Prepare thread to parse encrypted data
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
PipedInputStream streamInp = new PipedInputStream(streamOut);
|
||||
|
@ -110,12 +110,12 @@ public class NCAContentPFS0 {
|
|||
file,
|
||||
decryptedKey,
|
||||
ncaSectionBlock.getSectionCTR(),
|
||||
mediaStartOffset,
|
||||
mediaEndOffset
|
||||
mediaStartBlocksOffset,
|
||||
mediaEndBlocksOffset
|
||||
));
|
||||
pThread.start();
|
||||
// Decrypt data
|
||||
for (int i = 0; i < mediaBlockSize; i++){
|
||||
for (int i = 0; i < mediaBlocksSize; i++){
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (raf.read(encryptedBlock) != -1){
|
||||
//dectyptedBlock = aesCtr.decrypt(encryptedBlock);
|
||||
|
@ -139,9 +139,9 @@ public class NCAContentPFS0 {
|
|||
|
||||
raf = new RandomAccessFile(file, "r");
|
||||
raf.seek(abosluteOffsetPosition);
|
||||
decryptor = new AesCtrDecryptSimple(decryptedKey, ncaSectionBlock.getSectionCTR(), mediaStartOffset * 0x200);
|
||||
decryptor = new AesCtrDecryptSimple(decryptedKey, ncaSectionBlock.getSectionCTR(), mediaStartBlocksOffset * 0x200);
|
||||
|
||||
for (int i = 0; i < mediaBlockSize; i++){
|
||||
for (int i = 0; i < mediaBlocksSize; i++){
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (raf.read(encryptedBlock) != -1){
|
||||
//dectyptedBlock = aesCtr.decrypt(encryptedBlock);
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package konogonka.Tools.NCA;
|
||||
|
||||
import konogonka.LoperConverter;
|
||||
import konogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock;
|
||||
import konogonka.xtsaes.XTSAESCipher;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
@ -30,6 +29,7 @@ public class NCAProvider {
|
|||
private byte keyIndex; // application/ocean/system (kaek index?)
|
||||
private long ncaSize; // Size of this NCA (bytes)
|
||||
private byte[] titleId;
|
||||
private byte[] contentIndx;
|
||||
private byte[] sdkVersion; // version ver_revision.ver_micro.vev_minor.ver_major
|
||||
private byte cryptoType2; // keyblob index. Considering as number within application/ocean/system
|
||||
private byte[] rightsId;
|
||||
|
@ -139,7 +139,8 @@ public class NCAProvider {
|
|||
cryptoType1 = decryptedData[0x206];
|
||||
keyIndex = decryptedData[0x207];
|
||||
ncaSize = getLElong(decryptedData, 0x208);
|
||||
titleId = Arrays.copyOfRange(decryptedData, 0x210, 0x21C); // 0x218 ?
|
||||
titleId = Arrays.copyOfRange(decryptedData, 0x210, 0x218);
|
||||
contentIndx = Arrays.copyOfRange(decryptedData, 0x218, 0x21C);
|
||||
sdkVersion = Arrays.copyOfRange(decryptedData, 0x21c, 0x220);
|
||||
cryptoType2 = decryptedData[0x220];
|
||||
rightsId = Arrays.copyOfRange(decryptedData, 0x230, 0x240);
|
||||
|
@ -214,6 +215,7 @@ public class NCAProvider {
|
|||
public byte getKeyIndex() { return keyIndex; }
|
||||
public long getNcaSize() { return ncaSize; }
|
||||
public byte[] getTitleId() { return titleId; }
|
||||
public byte[] getContentIndx() { return contentIndx; }
|
||||
public byte[] getSdkVersion() { return sdkVersion; }
|
||||
public byte getCryptoType2() { return cryptoType2; }
|
||||
public byte[] getRightsId() { return rightsId; }
|
||||
|
@ -273,7 +275,7 @@ public class NCAProvider {
|
|||
}
|
||||
switch (sectionNumber) {
|
||||
case 0:
|
||||
return new NCAContentPFS0(file, offset, sectionBlock0, tableEntry0, key); // TODO: remove decryptedKey2
|
||||
return new NCAContentPFS0(file, offset, sectionBlock0, tableEntry0, key); // TODO: remove decryptedKey2 ?
|
||||
case 1:
|
||||
return new NCAContentPFS0(file, offset, sectionBlock1, tableEntry1, key);
|
||||
case 2:
|
||||
|
|
|
@ -9,6 +9,5 @@ public interface IPFS0Provider extends ISuperProvider {
|
|||
int getStringTableSize();
|
||||
byte[] getPadding();
|
||||
|
||||
long getRawFileDataStart();
|
||||
PFS0subFile[] getPfs0subFiles();
|
||||
}
|
||||
|
|
|
@ -25,8 +25,8 @@ public class PFS0EncryptedProvider implements IPFS0Provider{
|
|||
private File file;
|
||||
private byte[] key;
|
||||
private byte[] sectionCTR;
|
||||
private long mediaStartOffset;
|
||||
private long mediaEndOffset;
|
||||
private long mediaStartOffset; // In 512-blocks
|
||||
private long mediaEndOffset; // In 512-blocks
|
||||
|
||||
public PFS0EncryptedProvider(PipedInputStream pipedInputStream, long pfs0offsetPosition,
|
||||
long offsetPositionInFile,
|
||||
|
@ -44,7 +44,7 @@ public class PFS0EncryptedProvider implements IPFS0Provider{
|
|||
this.mediaStartOffset = mediaStartOffset;
|
||||
this.mediaEndOffset = mediaEndOffset;
|
||||
// pfs0offsetPosition is a position relative to Media block. Lets add pfs0 'header's' bytes count and get raw data start position in media block
|
||||
rawFileDataStart = -1; // Set -1 for PFS0EncryptedProvider
|
||||
rawFileDataStart = -1; // Set -1 for PFS0EncryptedProvider
|
||||
// Detect raw data start position using next var
|
||||
rawBlockDataStart = pfs0offsetPosition;
|
||||
|
||||
|
@ -145,145 +145,137 @@ public class PFS0EncryptedProvider implements IPFS0Provider{
|
|||
public long getRawFileDataStart() { return rawFileDataStart; }
|
||||
@Override
|
||||
public PFS0subFile[] getPfs0subFiles() { return pfs0subFiles; }
|
||||
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) {
|
||||
public File getFile(){ return file; }
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception {
|
||||
if (subFileNumber >= pfs0subFiles.length) {
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Requested sub file doesn't exists");
|
||||
return null;
|
||||
throw new Exception("PFS0Provider -> getPfs0subFilePipedInpStream(): Requested sub file doesn't exists");
|
||||
}
|
||||
/*/------------------------------------------------------------------
|
||||
System.out.println("Raw Block Data Start (PFS0 Start): " + rawBlockDataStart);
|
||||
System.out.println("Skipped blocks: " + rawBlockDataStart/0x200); // aesCtrDecryptSimple.skip(THIS)
|
||||
System.out.println("Skip bytes: " + (rawBlockDataStart-(rawBlockDataStart/0x200)*0x200)); // write to stream after skiping THIS
|
||||
*///-----------------------------------------------------------------
|
||||
|
||||
Thread workerThread;
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
|
||||
try{
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
workerThread = new Thread(() -> {
|
||||
System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Executing thread");
|
||||
try {
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
// Let's store what we're about to skip
|
||||
int skipBytes = (int) (offsetPositionInFile + (mediaStartOffset * 0x200));
|
||||
// Check if skip was successful
|
||||
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
workerThread = new Thread(() -> {
|
||||
System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Executing thread");
|
||||
try {
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
// Let's store what we're about to skip
|
||||
int skipBytes = (int) (offsetPositionInFile + (mediaStartOffset * 0x200));
|
||||
// Check if skip was successful
|
||||
if (bis.skip(skipBytes) != skipBytes) {
|
||||
System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Failed to skip range "+skipBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
AesCtrDecryptSimple aesCtrDecryptSimple = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
|
||||
|
||||
byte[] encryptedBlock;
|
||||
byte[] dectyptedBlock;
|
||||
|
||||
//----------------------------- Pre-set: skip non-necessary data --------------------------------
|
||||
|
||||
long startBlock = (rawBlockDataStart + pfs0subFiles[subFileNumber].getOffset()) / 0x200; // <- pointing to place where actual data starts
|
||||
|
||||
if (startBlock > 0) {
|
||||
aesCtrDecryptSimple.skipNext(startBlock);
|
||||
skipBytes = (int)(startBlock * 0x200);
|
||||
if (bis.skip(skipBytes) != skipBytes) {
|
||||
System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Failed to skip range "+skipBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
AesCtrDecryptSimple aesCtrDecryptSimple = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
|
||||
|
||||
byte[] encryptedBlock;
|
||||
byte[] dectyptedBlock;
|
||||
|
||||
//----------------------------- Pre-set: skip non-necessary data --------------------------------
|
||||
|
||||
long startBlock = (rawBlockDataStart + pfs0subFiles[subFileNumber].getOffset()) / 0x200; // <- pointing to place where actual data starts
|
||||
|
||||
if (startBlock > 0) {
|
||||
aesCtrDecryptSimple.skipNext(startBlock);
|
||||
skipBytes = (int)(startBlock * 0x200);
|
||||
if (bis.skip(skipBytes) != skipBytes) {
|
||||
System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Failed to skip range "+skipBytes);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------- Step 1: get starting bytes from the end of the junk block --------------------------------
|
||||
|
||||
// Since our data could be located in position with some offset from the decrypted block, let's skip bytes left. Considering the case when data is not aligned to block
|
||||
skipBytes = (int) ( (rawBlockDataStart + pfs0subFiles[subFileNumber].getOffset()) - startBlock * 0x200); // <- How much bytes shall we skip to reach requested data start of sub-file
|
||||
|
||||
if (skipBytes > 0) {
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (bis.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock);
|
||||
// If we have extra-small file that is less then a block and even more
|
||||
if ((0x200 - skipBytes) > pfs0subFiles[subFileNumber].getSize()){
|
||||
streamOut.write(dectyptedBlock, skipBytes, (int) pfs0subFiles[subFileNumber].getSize()); // safe cast
|
||||
return;
|
||||
}
|
||||
else
|
||||
streamOut.write(dectyptedBlock, skipBytes, 0x200 - skipBytes);
|
||||
}
|
||||
else {
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from 1st bock");
|
||||
return;
|
||||
}
|
||||
startBlock++;
|
||||
}
|
||||
|
||||
long endBlock = pfs0subFiles[subFileNumber].getSize() / 0x200 + startBlock; // <- pointing to place where any data related to this media-block ends
|
||||
|
||||
//----------------------------- Step 2: Detect if we have junk data on the end of the final block --------------------------------
|
||||
int extraData = (int)(rawBlockDataStart+pfs0subFiles[subFileNumber].getOffset()+pfs0subFiles[subFileNumber].getSize() - (endBlock*0x200)); // safe cast
|
||||
if (extraData < 0){
|
||||
endBlock--;
|
||||
}
|
||||
//----------------------------- Step 3: Read main part of data --------------------------------
|
||||
// Here we're reading main amount of bytes. We can read only less bytes.
|
||||
while ( startBlock < endBlock) {
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (bis.read(encryptedBlock) == 0x200) {
|
||||
//dectyptedBlock = aesCtr.decrypt(encryptedBlock);
|
||||
dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock);
|
||||
// Writing decrypted data to pipe
|
||||
streamOut.write(dectyptedBlock);
|
||||
}
|
||||
else {
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from bock");
|
||||
return;
|
||||
}
|
||||
startBlock++;
|
||||
}
|
||||
//----------------------------- Step 4: Read what's left --------------------------------
|
||||
// Now we have to find out if data overlaps to one more extra block
|
||||
if (extraData > 0){ // In case we didn't get what we want
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (bis.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock);
|
||||
streamOut.write(dectyptedBlock, 0, extraData);
|
||||
}
|
||||
else {
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from 1st bock");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (extraData < 0){ // In case we can get more than we need
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (bis.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock);
|
||||
streamOut.write(dectyptedBlock, 0, 0x200 + extraData);
|
||||
}
|
||||
else {
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from 1st bock");
|
||||
return;
|
||||
}
|
||||
}
|
||||
bis.close();
|
||||
streamOut.close();
|
||||
}
|
||||
catch (Exception e){
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): "+e.getMessage());
|
||||
e.printStackTrace();
|
||||
|
||||
//----------------------------- Step 1: get starting bytes from the end of the junk block --------------------------------
|
||||
|
||||
// Since our data could be located in position with some offset from the decrypted block, let's skip bytes left. Considering the case when data is not aligned to block
|
||||
skipBytes = (int) ( (rawBlockDataStart + pfs0subFiles[subFileNumber].getOffset()) - startBlock * 0x200); // <- How much bytes shall we skip to reach requested data start of sub-file
|
||||
|
||||
if (skipBytes > 0) {
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (bis.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock);
|
||||
// If we have extra-small file that is less then a block and even more
|
||||
if ((0x200 - skipBytes) > pfs0subFiles[subFileNumber].getSize()){
|
||||
streamOut.write(dectyptedBlock, skipBytes, (int) pfs0subFiles[subFileNumber].getSize()); // safe cast
|
||||
return;
|
||||
}
|
||||
else
|
||||
streamOut.write(dectyptedBlock, skipBytes, 0x200 - skipBytes);
|
||||
}
|
||||
else {
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from 1st bock");
|
||||
return;
|
||||
}
|
||||
startBlock++;
|
||||
}
|
||||
System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Thread died");
|
||||
|
||||
long endBlock = pfs0subFiles[subFileNumber].getSize() / 0x200 + startBlock; // <- pointing to place where any data related to this media-block ends
|
||||
|
||||
//----------------------------- Step 2: Detect if we have junk data on the end of the final block --------------------------------
|
||||
int extraData = (int)(rawBlockDataStart+pfs0subFiles[subFileNumber].getOffset()+pfs0subFiles[subFileNumber].getSize() - (endBlock*0x200)); // safe cast
|
||||
if (extraData < 0){
|
||||
endBlock--;
|
||||
}
|
||||
//----------------------------- Step 3: Read main part of data --------------------------------
|
||||
// Here we're reading main amount of bytes. We can read only less bytes.
|
||||
while ( startBlock < endBlock) {
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (bis.read(encryptedBlock) == 0x200) {
|
||||
//dectyptedBlock = aesCtr.decrypt(encryptedBlock);
|
||||
dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock);
|
||||
// Writing decrypted data to pipe
|
||||
streamOut.write(dectyptedBlock);
|
||||
}
|
||||
else {
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from bock");
|
||||
return;
|
||||
}
|
||||
startBlock++;
|
||||
}
|
||||
//----------------------------- Step 4: Read what's left --------------------------------
|
||||
// Now we have to find out if data overlaps to one more extra block
|
||||
if (extraData > 0){ // In case we didn't get what we want
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (bis.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock);
|
||||
streamOut.write(dectyptedBlock, 0, extraData);
|
||||
}
|
||||
else {
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from 1st bock");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (extraData < 0){ // In case we can get more than we need
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (bis.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock);
|
||||
streamOut.write(dectyptedBlock, 0, 0x200 + extraData);
|
||||
}
|
||||
else {
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from 1st bock");
|
||||
return;
|
||||
}
|
||||
}
|
||||
bis.close();
|
||||
streamOut.close();
|
||||
}
|
||||
catch (Exception e){
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): "+e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Thread died");
|
||||
|
||||
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
catch (IOException ioe){
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Unable to provide stream");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) {
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception{
|
||||
for (int i = 0; i < pfs0subFiles.length; i++){
|
||||
if (pfs0subFiles[i].getName().equals(subFileName))
|
||||
return getProviderSubFilePipedInpStream(i);
|
||||
|
|
|
@ -110,68 +110,61 @@ public class PFS0Provider implements IPFS0Provider{
|
|||
@Override
|
||||
public PFS0subFile[] getPfs0subFiles() { return pfs0subFiles; }
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber){ // TODO: Throw exceptions?
|
||||
public File getFile(){ return file; }
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception{ // TODO: Throw exceptions?
|
||||
if (subFileNumber >= pfs0subFiles.length) {
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Requested sub file doesn't exists");
|
||||
return null;
|
||||
throw new Exception("PFS0Provider -> getPfs0subFilePipedInpStream(): Requested sub file doesn't exists");
|
||||
}
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
Thread workerThread;
|
||||
try{
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
|
||||
workerThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Executing thread");
|
||||
try {
|
||||
long subFileRealPosition = rawFileDataStart + pfs0subFiles[subFileNumber].getOffset();
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
if (bis.skip(subFileRealPosition) != subFileRealPosition) {
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Unable to skip requested offset");
|
||||
return;
|
||||
}
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
|
||||
int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576
|
||||
|
||||
long readFrom = 0;
|
||||
long realFileSize = pfs0subFiles[subFileNumber].getSize();
|
||||
|
||||
byte[] readBuf;
|
||||
|
||||
while (readFrom < realFileSize) {
|
||||
if (realFileSize - readFrom < readPice)
|
||||
readPice = Math.toIntExact(realFileSize - readFrom); // it's safe, I guarantee
|
||||
readBuf = new byte[readPice];
|
||||
if (bis.read(readBuf) != readPice) {
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Unable to read requested size from file.");
|
||||
return;
|
||||
}
|
||||
streamOut.write(readBuf);
|
||||
readFrom += readPice;
|
||||
}
|
||||
bis.close();
|
||||
streamOut.close();
|
||||
} catch (IOException ioe) {
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Unable to provide stream");
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Thread died");
|
||||
workerThread = new Thread(() -> {
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Executing thread");
|
||||
try {
|
||||
long subFileRealPosition = rawFileDataStart + pfs0subFiles[subFileNumber].getOffset();
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
if (bis.skip(subFileRealPosition) != subFileRealPosition) {
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Unable to skip requested offset");
|
||||
return;
|
||||
}
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
catch (IOException ioe){
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Unable to provide stream");
|
||||
return null;
|
||||
}
|
||||
|
||||
int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576
|
||||
|
||||
long readFrom = 0;
|
||||
long realFileSize = pfs0subFiles[subFileNumber].getSize();
|
||||
|
||||
byte[] readBuf;
|
||||
|
||||
while (readFrom < realFileSize) {
|
||||
if (realFileSize - readFrom < readPice)
|
||||
readPice = Math.toIntExact(realFileSize - readFrom); // it's safe, I guarantee
|
||||
readBuf = new byte[readPice];
|
||||
if (bis.read(readBuf) != readPice) {
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Unable to read requested size from file.");
|
||||
return;
|
||||
}
|
||||
streamOut.write(readBuf);
|
||||
readFrom += readPice;
|
||||
}
|
||||
bis.close();
|
||||
streamOut.close();
|
||||
} catch (IOException ioe) {
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Unable to provide stream");
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Thread died");
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
/**
|
||||
* Some sugar
|
||||
* */
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(String subFileName){
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception {
|
||||
for (int i = 0; i < pfs0subFiles.length; i++){
|
||||
if (pfs0subFiles[i].getName().equals(subFileName))
|
||||
return getProviderSubFilePipedInpStream(i);
|
||||
|
|
|
@ -105,71 +105,65 @@ public class HFS0Provider implements ISuperProvider {
|
|||
public int getFilesCnt() { return filesCnt; }
|
||||
public boolean isPaddingHfs0() { return paddingHfs0; }
|
||||
public int getStringTableSize() { return stringTableSize; }
|
||||
|
||||
@Override
|
||||
public long getRawFileDataStart() { return rawFileDataStart; }
|
||||
public HFS0File[] getHfs0Files() { return hfs0Files; }
|
||||
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber){
|
||||
public File getFile(){ return file; }
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception{
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
Thread workerThread;
|
||||
if (subFileNumber >= hfs0Files.length) {
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Requested sub file doesn't exists");
|
||||
return null;
|
||||
throw new Exception("HFS0Provider -> getHfs0FilePipedInpStream(): Requested sub file doesn't exists");
|
||||
}
|
||||
try{
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
|
||||
workerThread = new Thread(() -> {
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Executing thread");
|
||||
try{
|
||||
long subFileRealPosition = rawFileDataStart + hfs0Files[subFileNumber].getOffset();
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
if (bis.skip(subFileRealPosition) != subFileRealPosition) {
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to skip requested offset");
|
||||
workerThread = new Thread(() -> {
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Executing thread");
|
||||
try{
|
||||
long subFileRealPosition = rawFileDataStart + hfs0Files[subFileNumber].getOffset();
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
if (bis.skip(subFileRealPosition) != subFileRealPosition) {
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to skip requested offset");
|
||||
return;
|
||||
}
|
||||
|
||||
int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576
|
||||
|
||||
long readFrom = 0;
|
||||
long realFileSize = hfs0Files[subFileNumber].getSize();
|
||||
|
||||
byte[] readBuf;
|
||||
|
||||
while (readFrom < realFileSize){
|
||||
if (realFileSize - readFrom < readPice)
|
||||
readPice = Math.toIntExact(realFileSize - readFrom); // it's safe, I guarantee
|
||||
readBuf = new byte[readPice];
|
||||
if (bis.read(readBuf) != readPice) {
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to read requested size from file.");
|
||||
return;
|
||||
}
|
||||
|
||||
int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576
|
||||
|
||||
long readFrom = 0;
|
||||
long realFileSize = hfs0Files[subFileNumber].getSize();
|
||||
|
||||
byte[] readBuf;
|
||||
|
||||
while (readFrom < realFileSize){
|
||||
if (realFileSize - readFrom < readPice)
|
||||
readPice = Math.toIntExact(realFileSize - readFrom); // it's safe, I guarantee
|
||||
readBuf = new byte[readPice];
|
||||
if (bis.read(readBuf) != readPice) {
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to read requested size from file.");
|
||||
return;
|
||||
}
|
||||
streamOut.write(readBuf, 0, readPice);
|
||||
readFrom += readPice;
|
||||
}
|
||||
bis.close();
|
||||
streamOut.close();
|
||||
streamOut.write(readBuf, 0, readPice);
|
||||
readFrom += readPice;
|
||||
}
|
||||
catch (IOException ioe){
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to provide stream");
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Thread died");
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
catch (IOException ioe){
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to provide stream");
|
||||
return null;
|
||||
}
|
||||
bis.close();
|
||||
streamOut.close();
|
||||
}
|
||||
catch (IOException ioe){
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to provide stream");
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Thread died");
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
/**
|
||||
* Sugar
|
||||
* */
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(String subFileName){
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception {
|
||||
for (int i = 0; i < hfs0Files.length; i++){
|
||||
if (hfs0Files[i].getName().equals(subFileName))
|
||||
return getProviderSubFilePipedInpStream(i);
|
||||
|
|
|
@ -3,6 +3,7 @@ package konogonka.Workers;
|
|||
import javafx.concurrent.Task;
|
||||
import konogonka.ModelControllers.EMsgType;
|
||||
import konogonka.ModelControllers.LogPrinter;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import konogonka.Tools.NCA.NCAProvider;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -11,11 +12,18 @@ import java.util.HashMap;
|
|||
public class AnalyzerNCA extends Task<NCAProvider> {
|
||||
|
||||
private File file;
|
||||
private long offset;
|
||||
private LogPrinter logPrinter;
|
||||
private HashMap<String, String> keysMap;
|
||||
|
||||
|
||||
public AnalyzerNCA(File file, HashMap<String, String> keysMap){
|
||||
this(file, keysMap, 0);
|
||||
}
|
||||
|
||||
public AnalyzerNCA(File file, HashMap<String, String> keysMap, long offset){
|
||||
this.file = file;
|
||||
this.offset = offset;
|
||||
this.logPrinter = new LogPrinter();
|
||||
this.keysMap = keysMap;
|
||||
}
|
||||
|
@ -27,7 +35,7 @@ public class AnalyzerNCA extends Task<NCAProvider> {
|
|||
NCAProvider ncaProvider;
|
||||
|
||||
try {
|
||||
ncaProvider = new NCAProvider(file, keysMap);
|
||||
ncaProvider = new NCAProvider(file, keysMap, offset);
|
||||
}catch (Exception e){
|
||||
logPrinter.print(e.getMessage(), EMsgType.FAIL);
|
||||
ncaProvider = null;
|
||||
|
|
|
@ -22,9 +22,6 @@ public class NspXciExtractor extends Task<Void> {
|
|||
this.models = models;
|
||||
this.filesDestPath = filesDestPath;
|
||||
this.logPrinter = new LogPrinter();
|
||||
for (IRowModel model : models) {
|
||||
System.out.println(model.getFileName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -60,8 +57,8 @@ public class NspXciExtractor extends Task<Void> {
|
|||
getException().printStackTrace(); // TODO: Do something with this
|
||||
}
|
||||
extractedFileBOS.close();
|
||||
} catch (IOException ioe) {
|
||||
logPrinter.print("\tRead/Write error\n\t" + ioe.getMessage(), EMsgType.INFO);
|
||||
} catch (Exception ioe) {
|
||||
logPrinter.print("\tExtracting issue\n\t" + ioe.getMessage(), EMsgType.INFO);
|
||||
return null;
|
||||
} finally {
|
||||
logPrinter.print("\tEnd extracting", EMsgType.INFO);
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
<RowConstraints maxHeight="30.0" minHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="30.0" minHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="30.0" minHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="30.0" minHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="30.0" minHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="30.0" minHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="30.0" minHeight="30.0" vgrow="SOMETIMES" />
|
||||
|
@ -72,7 +73,7 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.rowIndex="13">
|
||||
<AnchorPane styleClass="customGrid" GridPane.rowIndex="14">
|
||||
<children>
|
||||
<Label text="0x240" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -108,7 +109,7 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.rowIndex="11">
|
||||
<AnchorPane styleClass="customGrid" GridPane.rowIndex="12">
|
||||
<children>
|
||||
<Label text="0x220" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -135,7 +136,7 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.rowIndex="10">
|
||||
<AnchorPane styleClass="customGrid" GridPane.rowIndex="11">
|
||||
<children>
|
||||
<Label text="0x21C" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -144,7 +145,7 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.rowIndex="12">
|
||||
<AnchorPane styleClass="customGrid" GridPane.rowIndex="13">
|
||||
<children>
|
||||
<Label text="0x230" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -162,7 +163,7 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.rowIndex="14">
|
||||
<AnchorPane styleClass="customGrid" GridPane.rowIndex="15">
|
||||
<children>
|
||||
<Label text="0x280" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -171,7 +172,7 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.rowIndex="15">
|
||||
<AnchorPane styleClass="customGrid" GridPane.rowIndex="16">
|
||||
<children>
|
||||
<Label text="0x300" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -243,7 +244,7 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="1" GridPane.rowIndex="15">
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="1" GridPane.rowIndex="16">
|
||||
<children>
|
||||
<Label text="0x10*0x4(0x40)" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -252,7 +253,7 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="1" GridPane.rowIndex="14">
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="1" GridPane.rowIndex="15">
|
||||
<children>
|
||||
<Label text="0x20*0x4(0x80)" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -261,7 +262,7 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="1" GridPane.rowIndex="13">
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="1" GridPane.rowIndex="14">
|
||||
<children>
|
||||
<Label text="0x10*0x4(0x40)" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -324,7 +325,7 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="1" GridPane.rowIndex="10">
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="1" GridPane.rowIndex="11">
|
||||
<children>
|
||||
<Label text="0x4" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -333,7 +334,7 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="1" GridPane.rowIndex="11">
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="1" GridPane.rowIndex="12">
|
||||
<children>
|
||||
<Label text="0x1" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -342,7 +343,7 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="1" GridPane.rowIndex="12">
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="1" GridPane.rowIndex="13">
|
||||
<children>
|
||||
<Label text="0x10" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -362,7 +363,7 @@
|
|||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="2" GridPane.rowIndex="6">
|
||||
<children>
|
||||
<Label text="Crypto Type. Only used stating with 3.0.0. Normally 0. 2 = Crypto supported starting with 3.0.0." AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<Label text="Old Key Generation. Only used stating with 3.0.0. Normally 0. 2 = Crypto supported starting with 3.0.0." AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
<Insets left="5.0" right="5.0" />
|
||||
</padding>
|
||||
|
@ -387,7 +388,7 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="2" GridPane.rowIndex="10">
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="2" GridPane.rowIndex="11">
|
||||
<children>
|
||||
<Label text="sdk_version. byte0 usually 0? Known minimum-value(0x000B0000). Calc: byte3.byte2.byte1.byte0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -396,16 +397,16 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="2" GridPane.rowIndex="11">
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="2" GridPane.rowIndex="12">
|
||||
<children>
|
||||
<Label text="Crypto-Type2. Selects which crypto-sysver to use. 0x3 = 3.0.1, 0x4 = 4.0.0, 0x5 = 5.0.0." AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<Label text="Key Generation. Selects which crypto-sysver to use. 0x3 = 3.0.1, 0x4 = 4.0.0, 0x5 = 5.0.0." AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
<Insets left="5.0" right="5.0" />
|
||||
</padding>
|
||||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="2" GridPane.rowIndex="12">
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="2" GridPane.rowIndex="13">
|
||||
<children>
|
||||
<Label text="Rights ID (title.keys key-name to decrypt)" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -414,7 +415,7 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="2" GridPane.columnSpan="2" GridPane.rowIndex="13">
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="2" GridPane.columnSpan="2" GridPane.rowIndex="14">
|
||||
<children>
|
||||
<VBox spacing="5.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<children>
|
||||
|
@ -437,7 +438,7 @@
|
|||
</VBox>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="2" GridPane.rowIndex="14">
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="2" GridPane.rowIndex="15">
|
||||
<children>
|
||||
<Label text="Table of SHA256 hashes, over each 0x200-byte Section Header Block." AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -446,7 +447,7 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="2" GridPane.rowIndex="15">
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="2" GridPane.rowIndex="16">
|
||||
<children>
|
||||
<Label text="Key area" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -518,7 +519,7 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="3" GridPane.rowIndex="10">
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="3" GridPane.rowIndex="11">
|
||||
<children>
|
||||
<Label fx:id="sdkVersionLbl" text="-" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -527,7 +528,7 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="3" GridPane.rowIndex="11">
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="3" GridPane.rowIndex="12">
|
||||
<children>
|
||||
<Label fx:id="cryptoType2Lbl" text="-" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -536,7 +537,7 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="3" GridPane.rowIndex="12">
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="3" GridPane.rowIndex="13">
|
||||
<children>
|
||||
<Label fx:id="ticketLbl" text="-" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -564,7 +565,7 @@
|
|||
<TextField fx:id="rsa2048twoTF" editable="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="3" GridPane.rowIndex="14">
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="3" GridPane.rowIndex="15">
|
||||
<children>
|
||||
<VBox spacing="3.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<children>
|
||||
|
@ -579,7 +580,7 @@
|
|||
</VBox>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="3" GridPane.rowIndex="15">
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="3" GridPane.rowIndex="16">
|
||||
<children>
|
||||
<VBox spacing="3.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
|
@ -648,6 +649,42 @@
|
|||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.rowIndex="10">
|
||||
<children>
|
||||
<Label text="0x218" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
<Insets left="5.0" right="5.0" />
|
||||
</padding>
|
||||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="1" GridPane.rowIndex="10">
|
||||
<children>
|
||||
<Label text="0x4" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
<Insets left="5.0" right="5.0" />
|
||||
</padding>
|
||||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="2" GridPane.rowIndex="10">
|
||||
<children>
|
||||
<Label text="Content Index" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
<Insets left="5.0" right="5.0" />
|
||||
</padding>
|
||||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
<AnchorPane styleClass="customGrid" GridPane.columnIndex="3" GridPane.rowIndex="10">
|
||||
<children>
|
||||
<Label fx:id="contentIndexLbl" text="-" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<padding>
|
||||
<Insets left="5.0" right="5.0" />
|
||||
</padding>
|
||||
</Label>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</children>
|
||||
</GridPane>
|
||||
</children>
|
||||
|
|
Loading…
Reference in a new issue