Extract files support for 'RomFS' content (decrypted blob)
This commit is contained in:
parent
8efe9a118c
commit
f3bcb356ea
12 changed files with 385 additions and 85 deletions
|
@ -112,13 +112,13 @@ public class NSPController implements ITabController {
|
||||||
@Override
|
@Override
|
||||||
public void analyze(File selectedFile, long offset){
|
public void analyze(File selectedFile, long offset){
|
||||||
// TODO: IMPLEMENT??
|
// TODO: IMPLEMENT??
|
||||||
return;
|
System.out.print("NOT IMPLEMENTED: NSPController -> analyze(File selectedFile, long offset)");
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void analyze(File selectedFile){
|
public void analyze(File selectedFile){
|
||||||
Task analyzer = Analyzer.analyzePFS0(selectedFile);
|
Task<PFS0Provider> analyzer = Analyzer.analyzePFS0(selectedFile);
|
||||||
analyzer.setOnSucceeded(e->{
|
analyzer.setOnSucceeded(e->{
|
||||||
PFS0Provider pfs0 = (PFS0Provider) analyzer.getValue();
|
PFS0Provider pfs0 = analyzer.getValue();
|
||||||
this.setData(pfs0, selectedFile);
|
this.setData(pfs0, selectedFile);
|
||||||
});
|
});
|
||||||
Thread workThread = new Thread(analyzer);
|
Thread workThread = new Thread(analyzer);
|
||||||
|
|
|
@ -19,8 +19,6 @@
|
||||||
package konogonka.Controllers.RFS;
|
package konogonka.Controllers.RFS;
|
||||||
|
|
||||||
import javafx.beans.InvalidationListener;
|
import javafx.beans.InvalidationListener;
|
||||||
import javafx.beans.Observable;
|
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
@ -36,15 +34,15 @@ import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.input.MouseButton;
|
import javafx.scene.input.MouseButton;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import konogonka.Controllers.IRowModel;
|
import konogonka.Tools.RomFs.FileSystemEntry;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class RFSFolderTableViewController implements Initializable {
|
public class RFSFolderTableViewController implements Initializable {
|
||||||
@FXML
|
@FXML
|
||||||
private TableView<RFSEntry> table;
|
private TableView<RFSModelEntry> table;
|
||||||
private ObservableList<RFSEntry> rowsObsLst;
|
private ObservableList<RFSModelEntry> rowsObsLst;
|
||||||
@FXML
|
@FXML
|
||||||
private HBox navigationHBox;
|
private HBox navigationHBox;
|
||||||
|
|
||||||
|
@ -59,7 +57,7 @@ public class RFSFolderTableViewController implements Initializable {
|
||||||
table.setOnKeyPressed(keyEvent -> {
|
table.setOnKeyPressed(keyEvent -> {
|
||||||
if (!rowsObsLst.isEmpty()) {
|
if (!rowsObsLst.isEmpty()) {
|
||||||
if (keyEvent.getCode() == KeyCode.SPACE) {
|
if (keyEvent.getCode() == KeyCode.SPACE) {
|
||||||
for (RFSEntry item : table.getSelectionModel().getSelectedItems()) {
|
for (RFSModelEntry item : table.getSelectionModel().getSelectedItems()) {
|
||||||
item.setMarkSelected( ! item.isMarkSelected());
|
item.setMarkSelected( ! item.isMarkSelected());
|
||||||
}
|
}
|
||||||
table.refresh();
|
table.refresh();
|
||||||
|
@ -68,11 +66,11 @@ public class RFSFolderTableViewController implements Initializable {
|
||||||
keyEvent.consume();
|
keyEvent.consume();
|
||||||
});
|
});
|
||||||
|
|
||||||
TableColumn<RFSEntry, Node> imageColumn = new TableColumn<>();
|
TableColumn<RFSModelEntry, Node> imageColumn = new TableColumn<>();
|
||||||
TableColumn<RFSEntry, String> fileNameColumn = new TableColumn<>(resourceBundle.getString("tableFileNameLbl"));
|
TableColumn<RFSModelEntry, String> fileNameColumn = new TableColumn<>(resourceBundle.getString("tableFileNameLbl"));
|
||||||
TableColumn<RFSEntry, Long> fileOffsetColumn = new TableColumn<>(resourceBundle.getString("tableOffsetLbl"));
|
TableColumn<RFSModelEntry, Long> fileOffsetColumn = new TableColumn<>(resourceBundle.getString("tableOffsetLbl"));
|
||||||
TableColumn<RFSEntry, Long> fileSizeColumn = new TableColumn<>(resourceBundle.getString("tableSizeLbl"));
|
TableColumn<RFSModelEntry, Long> fileSizeColumn = new TableColumn<>(resourceBundle.getString("tableSizeLbl"));
|
||||||
TableColumn<RFSEntry, Boolean> checkBoxColumn = new TableColumn<>(resourceBundle.getString("tableUploadLbl"));
|
TableColumn<RFSModelEntry, Boolean> checkBoxColumn = new TableColumn<>(resourceBundle.getString("tableUploadLbl"));
|
||||||
|
|
||||||
imageColumn.setEditable(false);
|
imageColumn.setEditable(false);
|
||||||
fileNameColumn.setEditable(false);
|
fileNameColumn.setEditable(false);
|
||||||
|
@ -104,7 +102,7 @@ public class RFSFolderTableViewController implements Initializable {
|
||||||
checkBoxColumn.setResizable(false);
|
checkBoxColumn.setResizable(false);
|
||||||
|
|
||||||
imageColumn.setCellValueFactory(paramFeatures -> {
|
imageColumn.setCellValueFactory(paramFeatures -> {
|
||||||
RFSEntry model = paramFeatures.getValue();
|
RFSModelEntry model = paramFeatures.getValue();
|
||||||
return new ObservableValue<Node>() {
|
return new ObservableValue<Node>() {
|
||||||
@Override
|
@Override
|
||||||
public Node getValue() {
|
public Node getValue() {
|
||||||
|
@ -130,7 +128,7 @@ public class RFSFolderTableViewController implements Initializable {
|
||||||
fileSizeColumn.setCellValueFactory(new PropertyValueFactory<>("fileSize"));
|
fileSizeColumn.setCellValueFactory(new PropertyValueFactory<>("fileSize"));
|
||||||
fileOffsetColumn.setCellValueFactory(new PropertyValueFactory<>("fileOffset"));
|
fileOffsetColumn.setCellValueFactory(new PropertyValueFactory<>("fileOffset"));
|
||||||
checkBoxColumn.setCellValueFactory(paramFeatures -> {
|
checkBoxColumn.setCellValueFactory(paramFeatures -> {
|
||||||
RFSEntry model = paramFeatures.getValue();
|
RFSModelEntry model = paramFeatures.getValue();
|
||||||
|
|
||||||
SimpleBooleanProperty booleanProperty = new SimpleBooleanProperty(model.isMarkSelected());
|
SimpleBooleanProperty booleanProperty = new SimpleBooleanProperty(model.isMarkSelected());
|
||||||
|
|
||||||
|
@ -144,30 +142,30 @@ public class RFSFolderTableViewController implements Initializable {
|
||||||
checkBoxColumn.setCellFactory(paramFeatures -> new CheckBoxTableCell<>());
|
checkBoxColumn.setCellFactory(paramFeatures -> new CheckBoxTableCell<>());
|
||||||
table.setRowFactory( // this shit is made to implement context menu. It's such a pain..
|
table.setRowFactory( // this shit is made to implement context menu. It's such a pain..
|
||||||
RFSEntryTableView -> {
|
RFSEntryTableView -> {
|
||||||
final TableRow<RFSEntry> row = new TableRow<>();
|
final TableRow<RFSModelEntry> row = new TableRow<>();
|
||||||
|
/*
|
||||||
ContextMenu contextMenu = new ContextMenu();
|
ContextMenu contextMenu = new ContextMenu();
|
||||||
/* // TODO: CHANGE TO 'Export' or something
|
// TODO: ADD'Export'?
|
||||||
MenuItem openMenuItem = new MenuItem("Open");
|
MenuItem openMenuItem = new MenuItem("Export");
|
||||||
openMenuItem.setOnAction(new EventHandler<ActionEvent>() {
|
openMenuItem.setOnAction(event -> {
|
||||||
@Override
|
RFSEntry entry = row.getItem();
|
||||||
public void handle(ActionEvent actionEvent) {
|
System.out.print("Selected: "+entry.getFileName());
|
||||||
MediatorControl.getInstance().getContoller().showContentWindow(provider, row.getItem());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
contextMenu.getItems().addAll(openMenuItem);
|
contextMenu.getItems().add(openMenuItem);
|
||||||
*/
|
|
||||||
row.setContextMenu(contextMenu);
|
row.setContextMenu(contextMenu);
|
||||||
|
|
||||||
row.contextMenuProperty().bind(
|
row.contextMenuProperty().bind(
|
||||||
Bindings.when(Bindings.isNotNull(row.itemProperty())).then(contextMenu).otherwise((ContextMenu)null)
|
Bindings.when(Bindings.isNotNull(row.itemProperty())).then(contextMenu).otherwise((ContextMenu)null)
|
||||||
);
|
);
|
||||||
// Just.. don't ask..
|
*/
|
||||||
row.setOnMouseClicked(mouseEvent -> {
|
row.setOnMouseClicked(mouseEvent -> {
|
||||||
if (!row.isEmpty() && mouseEvent.getButton() == MouseButton.PRIMARY){
|
if (!row.isEmpty() && mouseEvent.getButton() == MouseButton.PRIMARY){
|
||||||
RFSEntry thisItem = row.getItem();
|
RFSModelEntry thisItem = row.getItem();
|
||||||
thisItem.setMarkSelected(!thisItem.isMarkSelected());
|
thisItem.setMarkSelected(!thisItem.isMarkSelected());
|
||||||
table.refresh();
|
table.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
mouseEvent.consume();
|
mouseEvent.consume();
|
||||||
});
|
});
|
||||||
return row;
|
return row;
|
||||||
|
@ -180,10 +178,11 @@ public class RFSFolderTableViewController implements Initializable {
|
||||||
table.getColumns().add(fileSizeColumn);
|
table.getColumns().add(fileSizeColumn);
|
||||||
table.getColumns().add(checkBoxColumn);
|
table.getColumns().add(checkBoxColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add files when user selected them on left-hand tree
|
* Add files when user selected them on left-hand tree
|
||||||
* */
|
* */
|
||||||
public void setContent(TreeItem<RFSEntry> containerTreeItem){
|
public void setContent(TreeItem<RFSModelEntry> containerTreeItem){
|
||||||
reset();
|
reset();
|
||||||
|
|
||||||
if (containerTreeItem == null) {
|
if (containerTreeItem == null) {
|
||||||
|
@ -191,7 +190,7 @@ public class RFSFolderTableViewController implements Initializable {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (TreeItem<RFSEntry> childTreeItem : containerTreeItem.getChildren())
|
for (TreeItem<RFSModelEntry> childTreeItem : containerTreeItem.getChildren())
|
||||||
rowsObsLst.add(childTreeItem.getValue());
|
rowsObsLst.add(childTreeItem.getValue());
|
||||||
|
|
||||||
setNavigationContent(containerTreeItem);
|
setNavigationContent(containerTreeItem);
|
||||||
|
@ -199,8 +198,8 @@ public class RFSFolderTableViewController implements Initializable {
|
||||||
table.refresh();
|
table.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setNavigationContent(TreeItem<RFSEntry> childTreeItem){
|
private void setNavigationContent(TreeItem<RFSModelEntry> childTreeItem){
|
||||||
TreeItem<RFSEntry> parentTreeItem;
|
TreeItem<RFSModelEntry> parentTreeItem;
|
||||||
|
|
||||||
LinkedList<Button> content = new LinkedList<>();
|
LinkedList<Button> content = new LinkedList<>();
|
||||||
|
|
||||||
|
@ -215,15 +214,28 @@ public class RFSFolderTableViewController implements Initializable {
|
||||||
for (Button button : content)
|
for (Button button : content)
|
||||||
navigationHBox.getChildren().add(button);
|
navigationHBox.getChildren().add(button);
|
||||||
}
|
}
|
||||||
private Button createNavigationButton(TreeItem<RFSEntry> treeItem){
|
private Button createNavigationButton(TreeItem<RFSModelEntry> treeItem){
|
||||||
Button button = new Button(treeItem.getValue().getFileName());
|
Button button = new Button(treeItem.getValue().getFileName());
|
||||||
button.setOnAction(event -> setContent(treeItem));
|
button.setOnAction(event -> setContent(treeItem));
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void reset(){
|
public void reset(){
|
||||||
rowsObsLst.clear();
|
rowsObsLst.clear();
|
||||||
navigationHBox.getChildren().clear();
|
navigationHBox.getChildren().clear();
|
||||||
|
table.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FileSystemEntry> getFilesForDump(){
|
||||||
|
if (rowsObsLst.isEmpty())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
List<FileSystemEntry> fsEntries = new ArrayList<>();
|
||||||
|
|
||||||
|
for (RFSModelEntry model: rowsObsLst) {
|
||||||
|
if (model.isMarkSelected())
|
||||||
|
fsEntries.add(model.getFileSystemEntry());
|
||||||
|
}
|
||||||
|
return fsEntries;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -21,11 +21,11 @@ package konogonka.Controllers.RFS;
|
||||||
import konogonka.Controllers.IRowModel;
|
import konogonka.Controllers.IRowModel;
|
||||||
import konogonka.Tools.RomFs.FileSystemEntry;
|
import konogonka.Tools.RomFs.FileSystemEntry;
|
||||||
|
|
||||||
public class RFSEntry implements IRowModel {
|
public class RFSModelEntry implements IRowModel {
|
||||||
private FileSystemEntry fileSystemEntry;
|
private FileSystemEntry fileSystemEntry;
|
||||||
private boolean check;
|
private boolean check;
|
||||||
|
|
||||||
public RFSEntry(FileSystemEntry fileSystemEntry){
|
public RFSModelEntry(FileSystemEntry fileSystemEntry){
|
||||||
this.fileSystemEntry = fileSystemEntry;
|
this.fileSystemEntry = fileSystemEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,10 @@ public class RFSEntry implements IRowModel {
|
||||||
return fileSystemEntry.isDirectory();
|
return fileSystemEntry.isDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FileSystemEntry getFileSystemEntry() {
|
||||||
|
return fileSystemEntry;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
return fileSystemEntry.getName();
|
return fileSystemEntry.getName();
|
|
@ -18,17 +18,24 @@
|
||||||
*/
|
*/
|
||||||
package konogonka.Controllers.RFS;
|
package konogonka.Controllers.RFS;
|
||||||
|
|
||||||
|
import javafx.concurrent.Task;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
|
import konogonka.AppPreferences;
|
||||||
import konogonka.Controllers.ITabController;
|
import konogonka.Controllers.ITabController;
|
||||||
|
import konogonka.MediatorControl;
|
||||||
import konogonka.Tools.ISuperProvider;
|
import konogonka.Tools.ISuperProvider;
|
||||||
import konogonka.Tools.RomFs.FileSystemEntry;
|
import konogonka.Tools.RomFs.FileSystemEntry;
|
||||||
|
import konogonka.Tools.RomFs.IRomFsProvider;
|
||||||
import konogonka.Tools.RomFs.Level6Header;
|
import konogonka.Tools.RomFs.Level6Header;
|
||||||
import konogonka.Tools.RomFs.RomFsDecryptedProvider;
|
import konogonka.Tools.RomFs.RomFsDecryptedProvider;
|
||||||
|
import konogonka.Workers.Analyzer;
|
||||||
|
import konogonka.Workers.DumbRomFsExtractor;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.List;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
public class RomFsController implements ITabController {
|
public class RomFsController implements ITabController {
|
||||||
|
@ -57,33 +64,103 @@ public class RomFsController implements ITabController {
|
||||||
headerFileDataOffsetHexLbl;
|
headerFileDataOffsetHexLbl;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TreeView<RFSEntry> filesTreeView;
|
private TreeView<RFSModelEntry> filesTreeView;
|
||||||
|
|
||||||
private RomFsDecryptedProvider RomFsProvider;
|
private IRomFsProvider provider;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private RFSFolderTableViewController RFSTableViewController;
|
private RFSFolderTableViewController RFSTableViewController;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button extractRootBtn, extractBtn;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(URL url, ResourceBundle resourceBundle) {
|
public void initialize(URL url, ResourceBundle resourceBundle) {
|
||||||
filesTreeView.setOnMouseClicked(mouseEvent -> {
|
filesTreeView.setOnMouseClicked(mouseEvent -> {
|
||||||
TreeItem<RFSEntry> item = filesTreeView.getSelectionModel().getSelectedItem();
|
TreeItem<RFSModelEntry> item = filesTreeView.getSelectionModel().getSelectedItem();
|
||||||
if (item != null && item.getValue().isDirectory())
|
if (item != null && item.getValue().isDirectory())
|
||||||
RFSTableViewController.setContent(item);
|
RFSTableViewController.setContent(item);
|
||||||
mouseEvent.consume();
|
mouseEvent.consume();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
extractRootBtn.setOnAction(event -> extractRootBtn());
|
||||||
public void analyze(File file) {
|
extractBtn.setOnAction(event -> extractSelectedBtn());
|
||||||
this.analyze(file, 0);
|
}
|
||||||
|
private void extractRootBtn(){
|
||||||
|
File dir = new File(AppPreferences.getInstance().getExtractFilesDir()+File.separator+ provider.getFile().getName()+" extracted");
|
||||||
|
try {
|
||||||
|
dir.mkdir();
|
||||||
|
}
|
||||||
|
catch (SecurityException se){
|
||||||
|
MediatorControl.getInstance().getContoller().logArea.setText("Can't create dir to store files.");
|
||||||
|
}
|
||||||
|
if (!dir.exists())
|
||||||
|
return;
|
||||||
|
|
||||||
|
extractBtn.setDisable(true);
|
||||||
|
extractRootBtn.setDisable(true);
|
||||||
|
|
||||||
|
DumbRomFsExtractor extractor = new DumbRomFsExtractor(provider, provider.getRootEntry(), dir.getAbsolutePath()+File.separator);
|
||||||
|
extractor.setOnSucceeded(e->{
|
||||||
|
extractBtn.setDisable(false);
|
||||||
|
extractRootBtn.setDisable(false);
|
||||||
|
});
|
||||||
|
Thread workThread = new Thread(extractor);
|
||||||
|
workThread.setDaemon(true);
|
||||||
|
workThread.start();
|
||||||
|
}
|
||||||
|
private void extractSelectedBtn(){
|
||||||
|
List<FileSystemEntry> fsEntries = RFSTableViewController.getFilesForDump();
|
||||||
|
|
||||||
|
if (fsEntries == null || fsEntries.isEmpty() || provider == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
File dir = new File(AppPreferences.getInstance().getExtractFilesDir()+File.separator+ provider.getFile().getName()+" extracted");
|
||||||
|
try {
|
||||||
|
dir.mkdir();
|
||||||
|
}
|
||||||
|
catch (SecurityException se){
|
||||||
|
MediatorControl.getInstance().getContoller().logArea.setText("Can't create dir to store files.");
|
||||||
|
}
|
||||||
|
if (!dir.exists())
|
||||||
|
return;
|
||||||
|
|
||||||
|
extractBtn.setDisable(true);
|
||||||
|
extractRootBtn.setDisable(true);
|
||||||
|
|
||||||
|
DumbRomFsExtractor extractor = new DumbRomFsExtractor(provider, fsEntries, dir.getAbsolutePath()+File.separator);
|
||||||
|
extractor.setOnSucceeded(e->{
|
||||||
|
extractBtn.setDisable(false);
|
||||||
|
extractRootBtn.setDisable(false);
|
||||||
|
});
|
||||||
|
Thread workThread = new Thread(extractor);
|
||||||
|
workThread.setDaemon(true);
|
||||||
|
workThread.start();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void analyze(File file, long offset) {
|
public void analyze(File file, long offset) {
|
||||||
|
// TODO: IMPLEMENT?
|
||||||
|
System.out.print("NOT IMPLEMENTED: RomFsController -> analyze(File selectedFile, long offset)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void analyze(File file) {
|
||||||
|
Task<RomFsDecryptedProvider> analyzer = Analyzer.analyzeRomFS(file);
|
||||||
|
analyzer.setOnSucceeded(e->{
|
||||||
|
RomFsDecryptedProvider provider = analyzer.getValue();
|
||||||
|
this.setData(provider);
|
||||||
|
});
|
||||||
|
Thread workThread = new Thread(analyzer);
|
||||||
|
workThread.setDaemon(true);
|
||||||
|
workThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(RomFsDecryptedProvider provider) {
|
||||||
try {
|
try {
|
||||||
this.RomFsProvider = new RomFsDecryptedProvider(file);
|
this.provider = provider;
|
||||||
Level6Header header = RomFsProvider.getHeader();
|
Level6Header header = provider.getHeader();
|
||||||
long tempValue;
|
long tempValue;
|
||||||
tempValue = header.getHeaderLength();
|
tempValue = header.getHeaderLength();
|
||||||
headerHeaderLengthLbl.setText(Long.toString(tempValue));
|
headerHeaderLengthLbl.setText(Long.toString(tempValue));
|
||||||
|
@ -116,23 +193,25 @@ public class RomFsController implements ITabController {
|
||||||
headerFileDataOffsetLbl.setText(Long.toString(tempValue));
|
headerFileDataOffsetLbl.setText(Long.toString(tempValue));
|
||||||
headerFileDataOffsetHexLbl.setText(getHexString(tempValue));
|
headerFileDataOffsetHexLbl.setText(getHexString(tempValue));
|
||||||
|
|
||||||
TreeItem<RFSEntry> rootItem = getTreeFolderItem(RomFsProvider.getRootEntry());
|
TreeItem<RFSModelEntry> rootItem = getTreeFolderItem(provider.getRootEntry());
|
||||||
|
|
||||||
filesTreeView.setRoot(rootItem);
|
filesTreeView.setRoot(rootItem);
|
||||||
|
|
||||||
RFSTableViewController.setContent(rootItem);
|
RFSTableViewController.setContent(rootItem);
|
||||||
|
|
||||||
|
extractBtn.setDisable(false);
|
||||||
|
extractRootBtn.setDisable(false);
|
||||||
}
|
}
|
||||||
catch (Exception e){ // TODO: FIX
|
catch (Exception e){ // TODO: FIX?
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TreeItem<RFSEntry> getTreeFolderItem(FileSystemEntry childEntry){
|
private TreeItem<RFSModelEntry> getTreeFolderItem(FileSystemEntry childEntry){
|
||||||
TreeItem<RFSEntry> entryTreeItem = new TreeItem<>(new RFSEntry(childEntry), getFolderImage());
|
TreeItem<RFSModelEntry> entryTreeItem = new TreeItem<>(new RFSModelEntry(childEntry), getFolderImage());
|
||||||
for (FileSystemEntry entry : childEntry.getContent()){
|
for (FileSystemEntry entry : childEntry.getContent()){
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory())
|
||||||
entryTreeItem.getChildren().add(getTreeFolderItem(entry));
|
entryTreeItem.getChildren().add(getTreeFolderItem(entry));
|
||||||
}
|
|
||||||
else
|
else
|
||||||
entryTreeItem.getChildren().add( getTreeFileItem(entry) );;
|
entryTreeItem.getChildren().add( getTreeFileItem(entry) );;
|
||||||
}
|
}
|
||||||
|
@ -140,13 +219,13 @@ public class RomFsController implements ITabController {
|
||||||
|
|
||||||
return entryTreeItem;
|
return entryTreeItem;
|
||||||
}
|
}
|
||||||
private TreeItem<RFSEntry> getTreeFileItem(FileSystemEntry childEntry) {
|
private TreeItem<RFSModelEntry> getTreeFileItem(FileSystemEntry childEntry) {
|
||||||
return new TreeItem<>(new RFSEntry(childEntry), getFileImage());
|
return new TreeItem<>(new RFSModelEntry(childEntry), getFileImage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void analyze(ISuperProvider parentProvider, int fileNo) throws Exception {
|
public void analyze(ISuperProvider parentProvider, int fileNo) throws Exception {
|
||||||
throw new Exception("NOT IMPLEMENTED: analyze(ISuperProvider parentProvider, int fileNo)");
|
throw new Exception("NOT SUPPORTED FOR 'RomFS' Controller: analyze(ISuperProvider parentProvider, int fileNo)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -175,6 +254,8 @@ public class RomFsController implements ITabController {
|
||||||
|
|
||||||
filesTreeView.setRoot(null);
|
filesTreeView.setRoot(null);
|
||||||
RFSTableViewController.reset();
|
RFSTableViewController.reset();
|
||||||
|
extractBtn.setDisable(true);
|
||||||
|
extractRootBtn.setDisable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Region getFolderImage(){
|
private Region getFolderImage(){
|
||||||
|
|
|
@ -45,7 +45,7 @@ public class FileSystemEntry {
|
||||||
this.directoryFlag = true;
|
this.directoryFlag = true;
|
||||||
DirectoryMetaData rootDirectoryMetaData = new DirectoryMetaData();
|
DirectoryMetaData rootDirectoryMetaData = new DirectoryMetaData();
|
||||||
if (rootDirectoryMetaData.dirName.isEmpty())
|
if (rootDirectoryMetaData.dirName.isEmpty())
|
||||||
this.name = "/";
|
this.name = "ROOT";
|
||||||
else
|
else
|
||||||
this.name = rootDirectoryMetaData.dirName;
|
this.name = rootDirectoryMetaData.dirName;
|
||||||
if (rootDirectoryMetaData.parentDirectoryOffset != 0)
|
if (rootDirectoryMetaData.parentDirectoryOffset != 0)
|
||||||
|
|
30
src/main/java/konogonka/Tools/RomFs/IRomFsProvider.java
Normal file
30
src/main/java/konogonka/Tools/RomFs/IRomFsProvider.java
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019-2020 Dmitry Isaenko
|
||||||
|
*
|
||||||
|
* This file is part of Konogonka.
|
||||||
|
*
|
||||||
|
* Konogonka is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Konogonka is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package konogonka.Tools.RomFs;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.PipedInputStream;
|
||||||
|
|
||||||
|
public interface IRomFsProvider {
|
||||||
|
Level6Header getHeader();
|
||||||
|
FileSystemEntry getRootEntry();
|
||||||
|
PipedInputStream getContent(FileSystemEntry entry) throws Exception;
|
||||||
|
File getFile();
|
||||||
|
}
|
|
@ -19,28 +19,21 @@
|
||||||
|
|
||||||
package konogonka.Tools.RomFs;
|
package konogonka.Tools.RomFs;
|
||||||
|
|
||||||
import konogonka.Tools.ISuperProvider;
|
import java.io.*;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
public class RomFsDecryptedProvider implements IRomFsProvider{
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.PipedInputStream;
|
|
||||||
|
|
||||||
import static konogonka.RainbowDump.formatDecHexString;
|
|
||||||
|
|
||||||
public class RomFsDecryptedProvider implements ISuperProvider {
|
|
||||||
|
|
||||||
private static final long LEVEL_6_DEFAULT_OFFSET = 0x14000;
|
private static final long LEVEL_6_DEFAULT_OFFSET = 0x14000;
|
||||||
|
|
||||||
private File decryptedFSImage;
|
private File file;
|
||||||
private Level6Header header;
|
private Level6Header header;
|
||||||
|
|
||||||
private FileSystemEntry rootEntry;
|
private FileSystemEntry rootEntry;
|
||||||
|
|
||||||
public RomFsDecryptedProvider(File decryptedFSImage) throws Exception{ // TODO: add default setup AND using meta-data headers from NCA RomFs section (?)
|
public RomFsDecryptedProvider(File decryptedFsImageFile) throws Exception{ // TODO: add default setup AND using meta-data headers from NCA RomFs section (?)
|
||||||
this.decryptedFSImage = decryptedFSImage;
|
this.file = decryptedFsImageFile;
|
||||||
|
|
||||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(decryptedFSImage));
|
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(decryptedFsImageFile));
|
||||||
|
|
||||||
skipBytes(bis, LEVEL_6_DEFAULT_OFFSET);
|
skipBytes(bis, LEVEL_6_DEFAULT_OFFSET);
|
||||||
|
|
||||||
|
@ -103,28 +96,59 @@ public class RomFsDecryptedProvider implements ISuperProvider {
|
||||||
mustSkip = size - skipped;
|
mustSkip = size - skipped;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
public Level6Header getHeader() { return header; }
|
public Level6Header getHeader() { return header; }
|
||||||
|
@Override
|
||||||
public FileSystemEntry getRootEntry() { return rootEntry; }
|
public FileSystemEntry getRootEntry() { return rootEntry; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception {
|
public PipedInputStream getContent(FileSystemEntry entry) throws Exception{
|
||||||
return null;
|
if (entry.isDirectory())
|
||||||
}
|
throw new Exception("Request of the binary stream for the folder entry doesn't make sense.");
|
||||||
|
|
||||||
@Override
|
PipedOutputStream streamOut = new PipedOutputStream();
|
||||||
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception {
|
Thread workerThread;
|
||||||
throw new Exception("RomFsDecryptedProvider -> getProviderSubFilePipedInpStream(): Get files by number is not supported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||||
|
|
||||||
|
workerThread = new Thread(() -> {
|
||||||
|
System.out.println("RomFsDecryptedProvider -> getContent(): Executing thread");
|
||||||
|
try {
|
||||||
|
long subFileRealPosition = LEVEL_6_DEFAULT_OFFSET + header.getFileDataOffset() + entry.getFileOffset();
|
||||||
|
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||||
|
skipBytes(bis, subFileRealPosition);
|
||||||
|
|
||||||
|
int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576
|
||||||
|
|
||||||
|
long readFrom = 0;
|
||||||
|
long realFileSize = entry.getFileSize();
|
||||||
|
|
||||||
|
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("RomFsDecryptedProvider -> getContent(): Unable to read requested size from file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
streamOut.write(readBuf);
|
||||||
|
readFrom += readPice;
|
||||||
|
}
|
||||||
|
bis.close();
|
||||||
|
streamOut.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("RomFsDecryptedProvider -> getContent(): Unable to provide stream");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
System.out.println("RomFsDecryptedProvider -> getContent(): Thread is dead");
|
||||||
|
});
|
||||||
|
workerThread.start();
|
||||||
|
return streamIn;
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public File getFile() {
|
public File getFile() {
|
||||||
return decryptedFSImage;
|
return file;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getRawFileDataStart() {
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void printDebug(byte[] directoryMetadataTable, byte[] fileMetadataTable){
|
private void printDebug(byte[] directoryMetadataTable, byte[] fileMetadataTable){
|
||||||
|
|
|
@ -25,6 +25,7 @@ import konogonka.Tools.ISuperProvider;
|
||||||
import konogonka.Tools.NCA.NCAProvider;
|
import konogonka.Tools.NCA.NCAProvider;
|
||||||
import konogonka.Tools.NPDM.NPDMProvider;
|
import konogonka.Tools.NPDM.NPDMProvider;
|
||||||
import konogonka.Tools.PFS0.PFS0Provider;
|
import konogonka.Tools.PFS0.PFS0Provider;
|
||||||
|
import konogonka.Tools.RomFs.RomFsDecryptedProvider;
|
||||||
import konogonka.Tools.TIK.TIKProvider;
|
import konogonka.Tools.TIK.TIKProvider;
|
||||||
import konogonka.Tools.XCI.XCIProvider;
|
import konogonka.Tools.XCI.XCIProvider;
|
||||||
|
|
||||||
|
@ -159,4 +160,23 @@ public class Analyzer {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Task<RomFsDecryptedProvider> analyzeRomFS(File file){
|
||||||
|
LogPrinter logPrinter = new LogPrinter();
|
||||||
|
return new Task<RomFsDecryptedProvider>() {
|
||||||
|
@Override
|
||||||
|
protected RomFsDecryptedProvider call() {
|
||||||
|
logPrinter.print("\tStart chain: RomFS", EMsgType.INFO);
|
||||||
|
try {
|
||||||
|
return new RomFsDecryptedProvider(file);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logPrinter.print(e.getMessage(), EMsgType.FAIL);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
logPrinter.print("\tEnd chain: RomFS", EMsgType.INFO);
|
||||||
|
logPrinter.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ public class DumbNCA3ContentExtractor extends Task<Void> {
|
||||||
readBuf = new byte[0x200];
|
readBuf = new byte[0x200];
|
||||||
//*** PROGRESS BAR DECORCATIONS START
|
//*** PROGRESS BAR DECORCATIONS START
|
||||||
progressHandleFRead += readSize;
|
progressHandleFRead += readSize;
|
||||||
System.out.println(readSize);
|
//System.out.println(readSize);
|
||||||
try {
|
try {
|
||||||
logPrinter.updateProgress((progressHandleFRead)/(progressHandleFSize/100.0) / 100.0);
|
logPrinter.updateProgress((progressHandleFRead)/(progressHandleFSize/100.0) / 100.0);
|
||||||
}catch (InterruptedException ignore){}
|
}catch (InterruptedException ignore){}
|
||||||
|
|
109
src/main/java/konogonka/Workers/DumbRomFsExtractor.java
Normal file
109
src/main/java/konogonka/Workers/DumbRomFsExtractor.java
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
package konogonka.Workers;
|
||||||
|
|
||||||
|
import javafx.concurrent.Task;
|
||||||
|
import konogonka.ModelControllers.EMsgType;
|
||||||
|
import konogonka.ModelControllers.LogPrinter;
|
||||||
|
import konogonka.Tools.RomFs.FileSystemEntry;
|
||||||
|
import konogonka.Tools.RomFs.IRomFsProvider;
|
||||||
|
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.PipedInputStream;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class DumbRomFsExtractor extends Task<Void> {
|
||||||
|
|
||||||
|
private IRomFsProvider provider;
|
||||||
|
private FileSystemEntry entry;
|
||||||
|
private List<FileSystemEntry> entries;
|
||||||
|
private LogPrinter logPrinter;
|
||||||
|
private String filesDestPath;
|
||||||
|
|
||||||
|
public DumbRomFsExtractor(IRomFsProvider provider, List<FileSystemEntry> entries, String filesDestPath){
|
||||||
|
this.provider = provider;
|
||||||
|
this.entries = entries;
|
||||||
|
this.filesDestPath = filesDestPath;
|
||||||
|
this.logPrinter = new LogPrinter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DumbRomFsExtractor(IRomFsProvider provider, FileSystemEntry entry, String filesDestPath){
|
||||||
|
this.provider = provider;
|
||||||
|
this.entry = entry;
|
||||||
|
this.filesDestPath = filesDestPath;
|
||||||
|
this.logPrinter = new LogPrinter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void call() {
|
||||||
|
try {
|
||||||
|
if (this.entries == null){
|
||||||
|
logPrinter.print("\tStart dummy extracting from 'RomFs' image: \n"+filesDestPath+entry.getName(), EMsgType.INFO);
|
||||||
|
if (entry.isFile())
|
||||||
|
exportSingleFile(entry, filesDestPath);
|
||||||
|
else
|
||||||
|
exportFolderContent(entry, filesDestPath);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logPrinter.print("\tStart dummy extracting from 'RomFs' image: \n"+filesDestPath+"...", EMsgType.INFO);
|
||||||
|
for (FileSystemEntry e : entries){
|
||||||
|
if (e.isFile())
|
||||||
|
exportSingleFile(e, filesDestPath);
|
||||||
|
else
|
||||||
|
exportFolderContent(e, filesDestPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception ioe) {
|
||||||
|
logPrinter.print("\tDummy extracting from 'RomFs' image issue\n\t" + ioe.getMessage(), EMsgType.INFO);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
logPrinter.print("\tEnd dummy extracting from 'RomFs' image extracting", EMsgType.INFO);
|
||||||
|
logPrinter.close();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exportSingleFile(FileSystemEntry entry, String saveToLocation) throws Exception{
|
||||||
|
File contentFile = new File(saveToLocation + entry.getName());
|
||||||
|
|
||||||
|
BufferedOutputStream extractedFileBOS = new BufferedOutputStream(new FileOutputStream(contentFile));
|
||||||
|
PipedInputStream pis = provider.getContent(entry);
|
||||||
|
|
||||||
|
byte[] readBuf = new byte[0x200]; // 8mb NOTE: consider switching to 1mb 1048576
|
||||||
|
int readSize;
|
||||||
|
//*** PROGRESS BAR VARS START
|
||||||
|
long progressHandleFSize = entry.getFileSize();
|
||||||
|
int progressHandleFRead = 0;
|
||||||
|
//*** PROGRESS BAR VARS END
|
||||||
|
while ((readSize = pis.read(readBuf)) > -1) {
|
||||||
|
extractedFileBOS.write(readBuf, 0, readSize);
|
||||||
|
readBuf = new byte[0x200];
|
||||||
|
//*** PROGRESS BAR DECORCATIONS START
|
||||||
|
progressHandleFRead += readSize;
|
||||||
|
//System.out.println(readSize);
|
||||||
|
try {
|
||||||
|
logPrinter.updateProgress((progressHandleFRead)/(progressHandleFSize/100.0) / 100.0);
|
||||||
|
}catch (InterruptedException ignore){}
|
||||||
|
//*** PROGRESS BAR DECORCATIONS END
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
logPrinter.updateProgress(1.0);
|
||||||
|
}
|
||||||
|
catch (InterruptedException ignored){}
|
||||||
|
|
||||||
|
extractedFileBOS.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exportFolderContent(FileSystemEntry entry, String saveToLocation) throws Exception{
|
||||||
|
File contentFile = new File(saveToLocation + entry.getName());
|
||||||
|
contentFile.mkdirs();
|
||||||
|
String currentDirPath = saveToLocation + entry.getName() + File.separator;
|
||||||
|
for (FileSystemEntry fse : entry.getContent()){
|
||||||
|
if (fse.isDirectory())
|
||||||
|
exportFolderContent(fse, currentDirPath);
|
||||||
|
else
|
||||||
|
exportSingleFile(fse, currentDirPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,7 +62,7 @@ public class Extractor extends Task<Void> {
|
||||||
readBuf = new byte[0x800000];
|
readBuf = new byte[0x800000];
|
||||||
//*** PROGRESS BAR DECORCATIONS START
|
//*** PROGRESS BAR DECORCATIONS START
|
||||||
progressHandleFRead += readSize;
|
progressHandleFRead += readSize;
|
||||||
System.out.println(readSize);
|
//System.out.println(readSize);
|
||||||
try {
|
try {
|
||||||
logPrinter.updateProgress((progressHandleFRead)/(progressHandleFSize/100.0) / 100.0);
|
logPrinter.updateProgress((progressHandleFRead)/(progressHandleFSize/100.0) / 100.0);
|
||||||
}catch (InterruptedException ie){
|
}catch (InterruptedException ie){
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.SplitPane?>
|
<?import javafx.scene.control.SplitPane?>
|
||||||
<?import javafx.scene.control.TitledPane?>
|
<?import javafx.scene.control.TitledPane?>
|
||||||
|
@ -8,8 +9,10 @@
|
||||||
<?import javafx.scene.layout.AnchorPane?>
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
<?import javafx.scene.layout.ColumnConstraints?>
|
<?import javafx.scene.layout.ColumnConstraints?>
|
||||||
<?import javafx.scene.layout.GridPane?>
|
<?import javafx.scene.layout.GridPane?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
<?import javafx.scene.layout.RowConstraints?>
|
<?import javafx.scene.layout.RowConstraints?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.shape.SVGPath?>
|
||||||
<?import javafx.scene.text.Font?>
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
<VBox 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 spacing="5.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="konogonka.Controllers.RFS.RomFsController">
|
||||||
|
@ -359,6 +362,23 @@
|
||||||
</items>
|
</items>
|
||||||
|
|
||||||
</SplitPane>
|
</SplitPane>
|
||||||
|
<HBox alignment="TOP_CENTER" spacing="5.0">
|
||||||
|
<children>
|
||||||
|
<Button fx:id="extractRootBtn" disable="true" mnemonicParsing="false" text="Extract ROOT">
|
||||||
|
<graphic>
|
||||||
|
<SVGPath content="M2,10.96C1.5,10.68 1.35,10.07 1.63,9.59L3.13,7C3.24,6.8 3.41,6.66 3.6,6.58L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.66,6.72 20.82,6.88 20.91,7.08L22.36,9.6C22.64,10.08 22.47,10.69 22,10.96L21,11.54V16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V10.96C2.7,11.13 2.32,11.14 2,10.96M12,4.15V4.15L12,10.85V10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V12.69L14,15.59C13.67,15.77 13.3,15.76 13,15.6V19.29L19,15.91M13.85,13.36L20.13,9.73L19.55,8.72L13.27,12.35L13.85,13.36Z" />
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
<Button fx:id="extractBtn" disable="true" mnemonicParsing="false" text="Extract selected">
|
||||||
|
<graphic>
|
||||||
|
<SVGPath content="M2,10.96C1.5,10.68 1.35,10.07 1.63,9.59L3.13,7C3.24,6.8 3.41,6.66 3.6,6.58L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.66,6.72 20.82,6.88 20.91,7.08L22.36,9.6C22.64,10.08 22.47,10.69 22,10.96L21,11.54V16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V10.96C2.7,11.13 2.32,11.14 2,10.96M12,4.15V4.15L12,10.85V10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V12.69L14,15.59C13.67,15.77 13.3,15.76 13,15.6V19.29L19,15.91M13.85,13.36L20.13,9.73L19.55,8.72L13.27,12.35L13.85,13.36Z" />
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</HBox>
|
||||||
</children>
|
</children>
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
|
Loading…
Reference in a new issue